[Image-SIG] Sane: Problems in setting color mode

abel deuring a.deuring@satzbau-gmbh.de
Fri, 20 Sep 2002 17:34:38 +0200


Thomas Renard wrote:
> Hi there,
> 
> I created some very simple script for scanning all pictures of a film 
> some time ago.
> 
> With the newest version of PIL (Imaging-1.1.3) I am not able to set the 
> color mode of my Epson 1240U which worked with older versions of PIL.
> The following I coded:
> 
> import sane
> 
> sane.init()
> 
> scanner=sane.open('epson:/dev/usb/scanner0')
> scanner.mode='Color'
> 
> ...
> 
> But this will create a
> 
> Traceback (most recent call last):
>  File "./filmscan.py", line 46, in ?
>    do_scan(filename, key[:1])
>  File "./filmscan.py", line 19, in do_scan
>    scanner.mode='Color'
>  File "/usr/lib/python2.2/site-packages/PIL/sane.py", line 46, in 
> __setattr__
>    self.last_opt = dev.set_option(opt.index, value)
> _sane.error: Invalid argument

Thomas,

For some reason, the backend did not accept the value "Color" for the 
option "mode". Depending on the backend, you might get an explanation, 
if you set the environment variable SANE_DEBUG_<backendname> (e.g., 
SANE_DEBUG_EPSON) to 255.

But I'm afraid that you won't get very far anyway with the Sane module 
from PIL 1.1.3. This is the source code of the read loop in SaneDev_Snap 
from this version:

SaneDev_snap(self, args)
         SaneDevObject *self;
         PyObject *args;
{
   SANE_Status st;
   SANE_Byte buffer[8192];  /* XXX how big should the buffer be? */
   SANE_Int len;
   Imaging im;
   SANE_Parameters p;
   char *format="unknown format";
   int px, py;
   long L;

   if (!PyArg_ParseTuple(args, "i", &L))
     return NULL;
   im=(Imaging)L;

   st=SANE_STATUS_GOOD; px=py=0;
   while (st!=SANE_STATUS_EOF)
     {
       st=sane_read(self->h, buffer, 8192, &len);
       if (st && (st!=SANE_STATUS_EOF)) return PySane_Error(st);
       if (st==SANE_STATUS_GOOD)
         {
           if (p.format==SANE_FRAME_RGB)
             {
             }
           else
             {
               /* Handle some sort of 8-bit code */
               /* XXX Optimize */
               int i;
               for (i=0; i<len && py <im->ysize; i++)
                 {
                   im->image8[py][px]=buffer[i];
                   if (++px >= (int) im->xsize)
                     px = 0, py++;
                 }
             }
         }
     }

   Py_INCREF(Py_None);
   return Py_None;
}

As you can see, data is not copied, if you have the format 
SANE_FRAME_RGB, which you probably use (unless you have a three pass 
scanner). And the data from three pass scans is not properly written to 
the image object with this code.

Three other bugs in this code:

1. 'SANE_Parameters p' is not initialized (a call to sane_get_parameters 
is missing), hence 'switch (p.format)' uses an arbtrary value;
2. the scan finishes, sane_cancel must be called. (try for example the - 
relatively new - test backend on this code. Without a call to 
sane_cancel after sane_start / sane_read; calls of sane_control_option 
will return errors. The requierement to call sane_cancel is also 
mentioned in the Sane API documentation);
3. If the image aqusistion requires more than one call of sane_read,
SaneDev_snap will return an error:

         st=SANE_STATUS_GOOD; px=py=0;
         while (st!=SANE_STATUS_EOF)
           {
             st=sane_read(self->h, buffer, 8192, &len);
             if (st && (st!=SANE_STATUS_EOF)) return PySane_Error(st);

The first sane_read call will return SANE_STATUS_GOOD, so the last line 
above will raise an error... Seeing this code gave me the impression 
that the person working on this file left for a coffee break, but never 
came back from this break ;)


Other issues:

1. hand scanners are not supported (i.e., the situation that you don't 
know the image dimensions before the end of the scan). Support would be 
a bit tricky with the concept of SaneDev_snap to write the scan data 
into an already existing Image object -- but SaneDev_snap (or 
sane.snap()) should at least raise an appropriate error, if 
sane_get_parameters returns "handscanner-like" values.

2. Terminating the read loop only if the last sane_read call returns 
SANE_STATUS_EOF is a bit risky. If the Sane backend announces the wrong 
image size in sane_get_parameters (ok, that should not happen -- but you 
never know ;), the statement "im->image8[py][px]=buffer[i];" may write 
beyond the end of the memory allocated for the image.

3. SaneDev_snap simply assumes that the backend returns 8 bit data -- 
but backends may also return 1 bit or 16 bit data.

4. (this one not about SaneDev_snap): Backends can change the 
capabilities attribute of an option (example: most backends set the bit 
"SANE_CAP_INACTIVE" for the threshold value, if the scanmode is not 
"lineart", and unset this bit, if the frontend sets the scan mode to 
"lineart".). But sane.py never updates these values in the SaneDev.opt 
dictionary, so the "if not _sane.OPTION_IS_ACTIVE(opt.cap)" test in 
SaneDev.__getattr__ and SaneDev.__setattr__ can use the wrong values...

I'm working on most of this stuff; I hope that I'll have something 
useable during the next weekend.

A question to those of you who are more familiar with Python C modules: 
While playing with the sane module, I got frequently segfaults; it 
turned out that I could fix them by replacing the PyMemDEL call in 
SaneDev_dealloc with PyObjectDEL calls. Is this replacement correct?

Abel