| Trees | Indices | Help |
|
|---|
|
|
1 #==================================================
2 # GNUmed SANE/TWAIN scanner classes
3 #==================================================
4 # $Source: /cvsroot/gnumed/gnumed/gnumed/client/pycommon/gmScanBackend.py,v $
5 # $Id: gmScanBackend.py,v 1.56 2009/12/21 15:02:51 ncq Exp $
6 __version__ = "$Revision: 1.56 $"
7 __license__ = "GPL"
8 __author__ = """Sebastian Hilbert <Sebastian.Hilbert@gmx.net>, Karsten Hilbert <Karsten.Hilbert@gmx.net>"""
9
10 #==================================================
11 # stdlib
12 import sys, os.path, os, string, time, shutil, codecs, glob, locale, errno, stat, logging
13
14
15 # 3rd party
16 #import Image
17
18
19 # GNUmed
20 if __name__ == '__main__':
21 sys.path.insert(0, '../../')
22 from Gnumed.pycommon import gmShellAPI, gmTools, gmI18N
23
24
25 _log = logging.getLogger('gm.scanning')
26 _log.info(__version__)
27
28 _twain_module = None
29 _sane_module = None
30
31 use_XSane = True
32 #=======================================================
33 # TWAIN handling
34 #=======================================================
36 global _twain_module
37 if _twain_module is None:
38 try:
39 import twain
40 _twain_module = twain
41 except ImportError:
42 _log.exception('cannot import TWAIN module (WinTWAIN.py)')
43 raise
44 _log.info("TWAIN version: %s" % _twain_module.Version())
45 #=======================================================
47
48 # FIXME: we need to handle this exception in the right place: <class 'twain.excTWCC_SUCCESS'>
49
51 _twain_import_module()
52
53 self.__calling_window = calling_window
54 self.__src_manager = None
55 self.__scanner = None
56 self.__done_transferring_image = False
57
58 self.__register_event_handlers()
59 #---------------------------------------------------
60 # external API
61 #---------------------------------------------------
63 if filename is None:
64 filename = gmTools.get_unique_filename(prefix='gmScannedObj-', suffix='.bmp', tmp_dir=tmpdir)
65 else:
66 tmp, ext = os.path.splitext(filename)
67 if ext != '.bmp':
68 filename = filename + '.bmp'
69
70 self.__filename = os.path.abspath(os.path.expanduser(filename))
71
72 if not self.__init_scanner():
73 raise OSError(-1, 'cannot init TWAIN scanner device')
74
75 self.__done_transferring_image = False
76 self.__scanner.RequestAcquire(True)
77
78 return [self.__filename]
79 #---------------------------------------------------
82 #---------------------------------------------------
84 # close() is called after acquire_pages*() so if we destroy the source
85 # before TWAIN is done we hang it, an RequestAcquire() only *requests*
86 # a scan, we would have to wait for process_xfer to finisch before
87 # destroying the source, and even then it might destroy state in the
88 # non-Python TWAIN subsystem
89 #**********************************
90 # if we do this TWAIN does not work
91 #**********************************
92 # if self.__scanner is not None:
93 # self.__scanner.destroy()
94
95 # if self.__src_manager is not None:
96 # self.__src_manager.destroy()
97
98 # del self.__scanner
99 # del self.__src_manager
100 return
101 #---------------------------------------------------
102 # internal helpers
103 #---------------------------------------------------
105 if self.__scanner is not None:
106 return True
107
108 self.__init_src_manager()
109 if self.__src_manager is None:
110 return False
111
112 # TWAIN will notify us when the image is scanned
113 self.__src_manager.SetCallback(self._twain_event_callback)
114
115 # no arg == show "select source" dialog
116 self.__scanner = self.__src_manager.OpenSource()
117 if self.__scanner is None:
118 _log.error("user canceled scan source selection dialog")
119 return False
120
121 _log.info("TWAIN data source: %s" % self.__scanner.GetSourceName())
122 _log.debug("TWAIN data source config: %s" % str(self.__scanner.GetIdentity()))
123
124 return True
125 #---------------------------------------------------
127 # open scanner manager
128 if self.__src_manager is not None:
129 return
130
131 # clean up scanner driver since we will initialize the source manager
132 # if self.__scanner is not None:
133 # self.__scanner.destroy() # this probably should not be done here
134 # del self.__scanner # try to sneak this back in later
135 # self.__scanner = None # this really should work
136
137 # TWAIN talks to us via MS-Windows message queues
138 # so we need to pass it a handle to ourselves,
139 # the following fails with "attempt to create Pseudo Window failed",
140 # I assume because the TWAIN vendors want to sabotage rebranding their GUI
141 # self.__src_manager = _twain_module.SourceManager(self.__calling_window.GetHandle(), ProductName = 'GNUmed - The EMR that never sleeps.')
142 self.__src_manager = _twain_module.SourceManager(self.__calling_window.GetHandle())
143
144 _log.info("TWAIN source manager config: %s" % str(self.__src_manager.GetIdentity()))
145 #---------------------------------------------------
146 # TWAIN callback handling
147 #---------------------------------------------------
149 self.__twain_event_handlers = {
150 _twain_module.MSG_XFERREADY: self._twain_handle_transfer_in_memory,
151 _twain_module.MSG_CLOSEDSREQ: self._twain_close_datasource,
152 _twain_module.MSG_CLOSEDSOK: self._twain_save_state,
153 _twain_module.MSG_DEVICEEVENT: self._twain_handle_src_event
154 }
155 #---------------------------------------------------
157 _log.debug('notification of TWAIN event <%s>' % str(twain_event))
158 self.__twain_event_handlers[twain_event]()
159 self.__scanner = None
160 return
161 #---------------------------------------------------
163 _log.info("being asked to close data source")
164 #---------------------------------------------------
166 _log.info("being asked to save application state")
167 #---------------------------------------------------
169 _log.info("being asked to handle device specific event")
170 #---------------------------------------------------
172
173 # FIXME: handle several images
174
175 _log.debug('receiving image from TWAIN source')
176 _log.debug('image info: %s' % self.__scanner.GetImageInfo())
177 _log.debug('image layout: %s' % str(self.__scanner.GetImageLayout()))
178
179 # get image from source
180 (external_data_handle, more_images_pending) = self.__scanner.XferImageNatively()
181 try:
182 # convert DIB to standard bitmap file (always .bmp)
183 _twain_module.DIBToBMFile(external_data_handle, self.__filename)
184 finally:
185 _twain_module.GlobalHandleFree(external_data_handle)
186 _log.debug('%s pending images' % more_images_pending)
187
188 # hide the scanner user interface again
189 # self.__scanner.HideUI() # needed ?
190 # self.__scanner = None # not sure why this should be needed, simple_wx does it, though
191
192 self.__done_transferring_image = True
193 #---------------------------------------------------
195
196 # the docs say this is not required to be implemented
197 # therefor we can't use it by default :-(
198 # UNTESTED !!!!
199
200 _log.debug('receiving image from TWAIN source')
201 _log.debug('image info: %s' % self.__scanner.GetImageInfo())
202 _log.debug('image layout: %s' % self.__scanner.GetImageLayout())
203
204 self.__scanner.SetXferFileName(self.__filename) # FIXME: allow format
205
206 more_images_pending = self.__scanner.XferImageByFile()
207 _log.debug('%s pending images' % more_images_pending)
208
209 # hide the scanner user interface again
210 self.__scanner.HideUI()
211 # self.__scanner = None
212
213 return
214 #=======================================================
215 # SANE handling
216 #=======================================================
218 global _sane_module
219 if _sane_module is None:
220 try:
221 import sane
222 except ImportError:
223 _log.exception('cannot import SANE module')
224 raise
225 _sane_module = sane
226 try:
227 init_result = _sane_module.init()
228 except:
229 _log.exception('cannot init SANE module')
230 raise
231 _log.info("SANE version: %s" % str(init_result))
232 _log.debug('SANE device list: %s' % str(_sane_module.get_devices()))
233 #=======================================================
235
236 # for testing uncomment "test" backend in /etc/sane/dll.conf
237
238 _src_manager = None
239
241 _sane_import_module()
242
243 # FIXME: need to test against devs[x][0]
244 # devs = _sane_module.get_devices()
245 # if device not in devs:
246 # _log.error("device [%s] not found in list of devices detected by SANE" % device)
247 # _log.error(str(devs))
248 # raise gmExceptions.ConstructorError, msg
249
250 self.__device = device
251 _log.info('using SANE device [%s]' % self.__device)
252
253 self.__init_scanner()
254 #---------------------------------------------------
256 self.__scanner = _sane_module.open(self.__device)
257
258 _log.debug('opened SANE device: %s' % str(self.__scanner))
259 _log.debug('SANE device config: %s' % str(self.__scanner.get_parameters()))
260 _log.debug('SANE device opts : %s' % str(self.__scanner.optlist))
261 _log.debug('SANE device opts : %s' % str(self.__scanner.get_options()))
262
263 return True
264 #---------------------------------------------------
266 self.__scanner.close()
267 #---------------------------------------------------
269 if filename is None:
270 filename = gmTools.get_unique_filename(prefix='gmScannedObj-', suffix='.bmp', tmp_dir=tmpdir)
271 else:
272 tmp, ext = os.path.splitext(filename)
273 if ext != '.bmp':
274 filename = filename + '.bmp'
275
276 filename = os.path.abspath(os.path.expanduser(filename))
277
278 if delay is not None:
279 time.sleep(delay)
280 _log.debug('some sane backends report device_busy if we advance too fast. delay set to %s sec' % delay)
281
282 _log.debug('Trying to get image from scanner into [%s] !' % filename)
283 self.__scanner.start()
284 img = self.__scanner.snap()
285 img.save(filename)
286
287 return [filename]
288 #---------------------------------------------------
291 #---------------------------------------------------
292 # def dummy(self):
293 # pass
294 # # supposedly there is a method *.close() but it does not
295 # # seem to work, therefore I put in the following line (else
296 # # it reports a busy sane-device on the second and consecutive runs)
297 # try:
298 # # by default use the first device
299 # # FIXME: room for improvement - option
300 # self.__scanner = _sane_module.open(_sane_module.get_devices()[0][0])
301 # except:
302 # _log.exception('cannot open SANE scanner')
303 # return False
304 #
305 # # Set scan parameters
306 # # FIXME: get those from config file
307 # #self.__scannercontrast=170 ; self.__scannerbrightness=150 ; self.__scannerwhite_level=190
308 # #self.__scannerdepth=6
309 # #self.__scannerbr_x = 412.0
310 # #self.__scannerbr_y = 583.0
311
312 #==================================================
313 # XSane handling
314 #==================================================
316
317 _filetype = '.png' # FIXME: configurable, TIFF ?
318 _xsanerc = os.path.expanduser(os.path.join('~', '.sane', 'xsane', 'xsane.rc'))
319 #_xsanerc_backup = os.path.expanduser(os.path.join('~', '.sane', 'xsane', 'xsane.rc.gnumed.bak'))
320 _xsanerc_gnumed = os.path.expanduser(os.path.join('~', '.gnumed', 'gnumed-xsanerc.conf'))
321 _xsanerc_backup = os.path.expanduser(os.path.join('~', '.gnumed', 'gnumed-xsanerc.conf.bak'))
322
323 #----------------------------------------------
325 # while not strictly necessary it is good to fail early
326 # this will tell us fairly safely whether XSane is properly installed
327 try:
328 open(cXSaneScanner._xsanerc, 'r').close()
329 except IOError:
330 msg = (
331 'XSane not properly installed for this user:\n\n'
332 ' [%s] not found\n\n'
333 'Start XSane once before using it with GNUmed.'
334 ) % cXSaneScanner._xsanerc
335 raise ImportError(msg)
336 # if not os.access(cXSaneScanner._xsanerc, os.R_OK):
337 # raise ImportError('XSane not properly installed for this user, no write access for [%s]' % cXSaneScanner._xsanerc)
338
339 self.device_settings_file = None
340 self.default_device = None
341 #----------------------------------------------
344 #----------------------------------------------
346 """Call XSane.
347
348 <filename> name part must have format name-001.ext>
349 """
350 if filename is None:
351 filename = gmTools.get_unique_filename (
352 prefix = 'gmScannedObj-',
353 suffix = cXSaneScanner._filetype,
354 tmp_dir = tmpdir
355 )
356 name, ext = os.path.splitext(filename)
357 filename = '%s-001%s' % (name, cXSaneScanner._filetype)
358
359 filename = os.path.abspath(os.path.expanduser(filename))
360 path, name = os.path.split(filename)
361
362 self.__prepare_xsanerc(tmpdir=path)
363
364 cmd = 'xsane --no-mode-selection --save --force-filename "%s" --xsane-rc "%s" %s %s' % (
365 filename,
366 cXSaneScanner._xsanerc_gnumed,
367 gmTools.coalesce(self.device_settings_file, '', '--device-settings %s'),
368 gmTools.coalesce(self.default_device, '')
369 )
370 normal_exit = gmShellAPI.run_command_in_shell(command = cmd, blocking = True)
371
372 if normal_exit:
373 flist = glob.glob(filename.replace('001', '*'))
374 flist.sort()
375 return flist
376
377 raise ImportError('error starting XSane as [%s]' % cmd)
378 #---------------------------------------------------
381 #----------------------------------------------
382 # internal API
383 #----------------------------------------------
385
386 try:
387 open(cXSaneScanner._xsanerc_gnumed, 'r+b').close()
388 except IOError:
389 _log.info('creating [%s] from [%s]', cXSaneScanner._xsanerc_gnumed, cXSaneScanner._xsanerc)
390 shutil.copyfile(cXSaneScanner._xsanerc, cXSaneScanner._xsanerc_gnumed)
391
392 shutil.move(cXSaneScanner._xsanerc_gnumed, cXSaneScanner._xsanerc_backup)
393
394 # our closest bet, might contain umlauts
395 enc = gmI18N.get_encoding()
396 fread = codecs.open(cXSaneScanner._xsanerc_backup, mode = "rU", encoding = enc)
397 fwrite = codecs.open(cXSaneScanner._xsanerc_gnumed, mode = "w", encoding = enc)
398
399 val_dict = {
400 'filetype': cXSaneScanner._filetype,
401 'tmp-path': tmpdir,
402 'working-directory': tmpdir,
403 'skip-existing-numbers': '1',
404 'filename-counter-step': '1',
405 'filename-counter-len': '3'
406 }
407
408 for idx, line in enumerate(fread):
409 line = line.replace(fread.newlines, '')
410
411 if idx % 2 == 0: # even lines are keys
412 key = line.strip('"')
413 fwrite.write('"%s"%s' % (key, fread.newlines))
414 else: # odd lines are corresponding values
415 try:
416 value = val_dict[key]
417 except KeyError:
418 value = line
419 fwrite.write('%s%s' % (value, fread.newlines))
420
421 fwrite.flush()
422 fwrite.close()
423 fread.close()
424
425 #os.chmod(cXSaneScanner._xsanerc, stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
426
427 return True
428 #----------------------------------------------
429 # def __restore_xsanerc(self):
430 # shutil.copy2(cXSaneScanner._xsanerc_backup, cXSaneScanner._xsanerc)
431 # #os.chmod(cXSaneScanner._xsanerc, stat.S_IWUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH)
432 #==================================================
434 try:
435 _twain_import_module()
436 # TWAIN does not support get_devices():
437 # devices can only be selected from within TWAIN itself
438 return None
439 except ImportError:
440 pass
441
442 if use_XSane:
443 # neither does XSane
444 return None
445
446 _sane_import_module()
447 return _sane_module.get_devices()
448 #-----------------------------------------------------
449 -def acquire_pages_into_files(device=None, delay=None, filename=None, tmpdir=None, calling_window=None, xsane_device_settings=None):
450 """Connect to a scanner and return the scanned pages as a file list.
451
452 returns:
453 - list of filenames: names of scanned pages, may be []
454 - None: unable to connect to scanner
455 """
456 try:
457 scanner = cTwainScanner(calling_window=calling_window)
458 _log.debug('using TWAIN')
459 except ImportError:
460 if use_XSane:
461 _log.debug('using XSane')
462 scanner = cXSaneScanner()
463 scanner.device_settings_file = xsane_device_settings
464 scanner.default_device = device
465 else:
466 _log.debug('using SANE directly')
467 scanner = cSaneScanner(device=device)
468
469 _log.debug('requested filename: [%s]' % filename)
470 fnames = scanner.acquire_pages_into_files(filename=filename, delay=delay, tmpdir=tmpdir)
471 scanner.close()
472 _log.debug('acquired pages into files: %s' % str(fnames))
473
474 return fnames
475 #==================================================
476 # main
477 #==================================================
478 if __name__ == '__main__':
479
480 if len(sys.argv) > 1 and sys.argv[1] == u'test':
481
482 logging.basicConfig(level=logging.DEBUG)
483
484 print "devices:"
485 print get_devices()
486
487 sys.exit()
488
489 setups = [
490 {'dev': 'test:0', 'file': 'x1-test0-1-0001'},
491 {'dev': 'test:1', 'file': 'x2-test1-1-0001.bmp'},
492 {'dev': 'test:0', 'file': 'x3-test0-2-0001.bmp-ccc'}
493 ]
494
495 idx = 1
496 for setup in setups:
497 print "scanning page #%s from device [%s]" % (idx, setup['dev'])
498 idx += 1
499 fnames = acquire_pages_into_files(device = setup['dev'], filename = setup['file'], delay = (idx*5))
500 if fnames is False:
501 print "error, cannot acquire page"
502 else:
503 print " image files:", fnames
504
505 #==================================================
506 # $Log: gmScanBackend.py,v $
507 # Revision 1.56 2009/12/21 15:02:51 ncq
508 # - cleanup
509 #
510 # Revision 1.55 2009/02/18 13:45:25 ncq
511 # - get_unique_filename API change
512 #
513 # Revision 1.54 2008/07/10 11:20:03 ncq
514 # - use --xsane-rc when calling XSane
515 #
516 # Revision 1.53 2008/04/11 12:24:01 ncq
517 # - better handle missing XSane when TWAIN is missing, too
518 #
519 # Revision 1.52 2007/12/12 16:17:15 ncq
520 # - better logger names
521 #
522 # Revision 1.51 2007/12/11 15:38:29 ncq
523 # - no more gmLog2
524 #
525 # Revision 1.50 2007/12/11 14:33:48 ncq
526 # - use standard logging module
527 #
528 # Revision 1.49 2007/09/10 20:27:40 ncq
529 # - eventually find out how xsane handles counter filenames
530 #
531 # Revision 1.48 2007/08/29 14:33:38 ncq
532 # - a bit more readability
533 #
534 # Revision 1.47 2007/08/08 21:26:39 ncq
535 # - tiny improvements re file extension in XSane scanner driver
536 #
537 # Revision 1.46 2007/07/17 13:40:31 ncq
538 # - PIL currently not used so don't load it
539 #
540 # Revision 1.45 2007/07/13 09:49:10 ncq
541 # - add missing ","
542 #
543 # Revision 1.44 2007/07/11 21:06:01 ncq
544 # - use gmTools.get_unique_filename()
545 #
546 # Revision 1.43 2007/07/10 20:37:56 ncq
547 # - properly delete the tempfile so XSane won't complain
548 #
549 # Revision 1.42 2007/07/09 12:38:38 ncq
550 # - cleanup
551 #
552 # Revision 1.41 2007/06/10 10:19:24 ncq
553 # - use exceptions for error reporting
554 #
555 # Revision 1.40 2007/06/05 14:58:16 ncq
556 # - better support missing XSane, thereby enabling better error reporting
557 #
558 # Revision 1.39 2007/05/08 11:14:34 ncq
559 # - cleanup
560 #
561 # Revision 1.38 2007/04/01 15:27:09 ncq
562 # - safely get_encoding()
563 #
564 # Revision 1.37 2007/02/17 18:18:09 ncq
565 # - support pre-setting device and device-settings-file with XSane
566 #
567 # Revision 1.36 2007/02/15 12:03:27 ncq
568 # - really support numbered multi-file scans with XSane
569 #
570 # Revision 1.35 2007/01/29 11:59:34 ncq
571 # - improve comment
572 #
573 # Revision 1.34 2007/01/19 14:06:17 ncq
574 # - do not attempt to handle several scans at once
575 #
576 # Revision 1.33 2007/01/19 13:37:39 ncq
577 # - cannot wait on semaphore as it blocks the TWAIN GUI
578 #
579 # Revision 1.32 2007/01/19 13:29:35 ncq
580 # - try semaphore on TWAIN scanning to detect finish
581 #
582 # Revision 1.31 2007/01/19 12:43:39 ncq
583 # - properly return False if initing scanner was cancelled
584 #
585 # Revision 1.30 2007/01/19 10:55:56 ncq
586 # - clean up
587 #
588 # Revision 1.29 2007/01/18 19:33:09 ncq
589 # - fix typo
590 #
591 # Revision 1.28 2007/01/18 18:43:07 ncq
592 # - added print "" for debugging
593 #
594 # Revision 1.27 2007/01/18 17:58:34 ncq
595 # - explicitely wait for TWAIN memory scan transfer to finish
596 #
597 # Revision 1.26 2007/01/18 17:14:54 ncq
598 # - closer to twain example code
599 #
600 # Revision 1.25 2007/01/18 14:41:29 ncq
601 # - use ShowModal in RequestAcquire()
602 #
603 # Revision 1.24 2007/01/18 13:27:16 ncq
604 # - try to comply more closely with TWAIN sample wx app
605 #
606 # Revision 1.23 2007/01/18 13:03:25 ncq
607 # - no ProductName
608 #
609 # Revision 1.22 2007/01/18 12:34:01 ncq
610 # - must init self.__scanner/self.__src_manager
611 #
612 # Revision 1.21 2007/01/18 12:08:56 ncq
613 # - try to once again fix/improve TWAIN scanning
614 #
615 # Revision 1.20 2006/12/27 16:42:53 ncq
616 # - cleanup
617 # - add XSane interface in cXSaneScanner as worked out by Kai Schmidt
618 # - acquire_pages_into_files() now returns a list
619 # - fix test suite
620 #
621 # Revision 1.19 2006/09/02 21:11:59 ncq
622 # - improved test suite
623 #
624 # Revision 1.18 2006/08/29 18:41:58 ncq
625 # - improve test suite
626 #
627 # Revision 1.17 2006/08/29 18:33:02 ncq
628 # - forward port TWAIN fixes from 0.2 branch
629 #
630 # Revision 1.16 2006/05/14 20:42:20 ncq
631 # - properly handle get_devices()
632 #
633 # Revision 1.15 2006/05/13 23:42:13 shilbert
634 # - getting there, TWAIN now lets me take more than one image in one session
635 #
636 # Revision 1.14 2006/05/13 23:18:11 shilbert
637 # - fix more TWAIN issues
638 #
639 # Revision 1.13 2006/05/13 21:36:15 shilbert
640 # - fixed some TWAIN rleated issues
641 #
642 # Revision 1.12 2006/01/17 19:45:32 ncq
643 # - close scanner when done
644 # - cleanup
645 #
646 # Revision 1.11 2006/01/16 19:42:18 ncq
647 # - improve unit test
648 #
649 # Revision 1.10 2006/01/16 19:41:29 ncq
650 # - properly init sane now
651 #
652 # Revision 1.9 2006/01/16 19:35:41 ncq
653 # - can only get sane device list after init()
654 #
655 # Revision 1.8 2006/01/16 19:27:26 ncq
656 # - cleaner layout
657 # - report_devices() -> get_devices()
658 #
659 # Revision 1.7 2006/01/15 14:00:28 ncq
660 # - reconvert spaces to tabs
661 #
662 # Revision 1.6 2006/01/15 13:16:06 shilbert
663 # - support for multiple scanners was added
664 #
665 # Revision 1.5 2006/01/15 10:04:37 shilbert
666 # - scanner device has not been passed on to the acquire_image function - fixed
667 #
668 # Revision 1.4 2005/11/27 13:05:45 ncq
669 # - used calling_window in the wrong place ...
670 #
671 # Revision 1.3 2005/11/27 10:38:46 ncq
672 # - use secure creation of file names when not given
673 #
674 # Revision 1.2 2005/11/27 08:48:45 ncq
675 # - some old cruft removed
676 # - example code useful being kept around for now commented out
677 #
678 # Revision 1.1 2005/11/26 16:53:43 shilbert
679 # - moved here from Archive
680 # - needed by gmScanIdxMedDocs plugin
681 #
682 # Revision 1.7 2005/11/09 11:30:21 ncq
683 # - activate sane test scanner
684 #
685
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Feb 9 04:02:07 2010 | http://epydoc.sourceforge.net |