| Home | Trees | Indices | Help |
|
|---|
|
|
1 #==================================================
2 # GNUmed SANE/TWAIN scanner classes
3 #==================================================
4 __license__ = "GPL v2 or later"
5 __author__ = """Sebastian Hilbert <Sebastian.Hilbert@gmx.net>, Karsten Hilbert <Karsten.Hilbert@gmx.net>"""
6
7
8 # stdlib
9 import sys
10 import os.path
11 import os
12 import time
13 import shutil
14 import codecs
15 import glob
16 import logging
17 #import stat
18
19
20 # GNUmed
21 if __name__ == '__main__':
22 sys.path.insert(0, '../../')
23 from Gnumed.pycommon import gmShellAPI
24 from Gnumed.pycommon import gmTools
25 from Gnumed.pycommon import gmI18N
26 from Gnumed.pycommon import gmLog2
27
28
29 _log = logging.getLogger('gm.scanning')
30
31 _twain_module = None
32 _sane_module = None
33
34 use_XSane = True
35 #=======================================================
36 # TWAIN handling
37 #=======================================================
39 global _twain_module
40 if _twain_module is None:
41 try:
42 import twain
43 _twain_module = twain
44 except ImportError:
45 _log.exception('cannot import TWAIN module (WinTWAIN.py)')
46 raise
47 _log.info("TWAIN version: %s" % _twain_module.Version())
48 #=======================================================
50
51 # http://twainmodule.sourceforge.net/docs/index.html
52
53 # FIXME: we need to handle this exception in the right place: <class 'twain.excTWCC_SUCCESS'>
54
56 _twain_import_module()
57
58 self.__calling_window = calling_window
59 self.__src_manager = None
60 self.__scanner = None
61 self.__done_transferring_image = False
62
63 self.__register_event_handlers()
64 #---------------------------------------------------
65 # external API
66 #---------------------------------------------------
68 if filename is None:
69 filename = gmTools.get_unique_filename(prefix = 'gmScannedObj-', suffix = '.bmp')
70 else:
71 tmp, ext = os.path.splitext(filename)
72 if ext != '.bmp':
73 filename = filename + '.bmp'
74
75 self.__filename = os.path.abspath(os.path.expanduser(filename))
76
77 if not self.__init_scanner():
78 raise OSError(-1, 'cannot init TWAIN scanner device')
79
80 self.__done_transferring_image = False
81 self.__scanner.RequestAcquire(True)
82
83 return [self.__filename]
84 #---------------------------------------------------
87 #---------------------------------------------------
89 # close() is called after acquire_pages*() so if we destroy the source
90 # before TWAIN is done we hang it, an RequestAcquire() only *requests*
91 # a scan, we would have to wait for process_xfer to finisch before
92 # destroying the source, and even then it might destroy state in the
93 # non-Python TWAIN subsystem
94 #**********************************
95 # if we do this TWAIN does not work
96 #**********************************
97 # if self.__scanner is not None:
98 # self.__scanner.destroy()
99
100 # if self.__src_manager is not None:
101 # self.__src_manager.destroy()
102
103 # del self.__scanner
104 # del self.__src_manager
105 return
106 #---------------------------------------------------
107 # internal helpers
108 #---------------------------------------------------
110 if self.__scanner is not None:
111 return True
112
113 self.__init_src_manager()
114 if self.__src_manager is None:
115 return False
116
117 # TWAIN will notify us when the image is scanned
118 self.__src_manager.SetCallback(self._twain_event_callback)
119
120 # no arg == show "select source" dialog
121 try:
122 self.__scanner = self.__src_manager.OpenSource()
123 except _twain_module.excDSOpenFailed:
124 _log.exception('cannot open TWAIN data source (image capture device)')
125 gmLog2.log_stack_trace()
126 return False
127
128 if self.__scanner is None:
129 _log.error("user canceled scan source selection dialog")
130 return False
131
132 _log.info("TWAIN data source: %s" % self.__scanner.GetSourceName())
133 _log.debug("TWAIN data source config: %s" % str(self.__scanner.GetIdentity()))
134
135 return True
136 #---------------------------------------------------
138
139 if self.__src_manager is not None:
140 return
141
142 # clean up scanner driver since we will initialize the source manager
143 # if self.__scanner is not None:
144 # self.__scanner.destroy() # this probably should not be done here
145 # del self.__scanner # try to sneak this back in later
146 # self.__scanner = None # this really should work
147
148 # TWAIN talks to us via MS-Windows message queues
149 # so we need to pass it a handle to ourselves,
150 # the following fails with "attempt to create Pseudo Window failed",
151 # I assume because the TWAIN vendors want to sabotage rebranding their GUI
152 # self.__src_manager = _twain_module.SourceManager(self.__calling_window.GetHandle(), ProductName = 'GNUmed - The EMR that never sleeps.')
153 try:
154 self.__src_manager = _twain_module.SourceManager(self.__calling_window.GetHandle())
155
156 except _twain_module.excSMLoadFileFailed:
157 _log.exception('failed to load TWAIN_32.DLL')
158 return
159
160 except _twain_module.excSMGetProcAddressFailed:
161 _log.exception('failed to jump into TWAIN_32.DLL')
162 return
163
164 except _twain_module.excSMOpenFailed:
165 _log.exception('failed to open Source Manager')
166 return
167
168 _log.info("TWAIN source manager config: %s" % str(self.__src_manager.GetIdentity()))
169 #---------------------------------------------------
170 # TWAIN callback handling
171 #---------------------------------------------------
173 self.__twain_event_handlers = {
174 _twain_module.MSG_XFERREADY: self._twain_handle_transfer_in_memory,
175 _twain_module.MSG_CLOSEDSREQ: self._twain_close_datasource,
176 _twain_module.MSG_CLOSEDSOK: self._twain_save_state,
177 _twain_module.MSG_DEVICEEVENT: self._twain_handle_src_event
178 }
179 #---------------------------------------------------
181 _log.debug('notification of TWAIN event <%s>' % str(twain_event))
182 self.__twain_event_handlers[twain_event]()
183 self.__scanner = None
184 return
185 #---------------------------------------------------
187 _log.info("being asked to close data source")
188 #---------------------------------------------------
190 _log.info("being asked to save application state")
191 #---------------------------------------------------
193 _log.info("being asked to handle device specific event")
194 #---------------------------------------------------
196
197 # FIXME: handle several images
198
199 _log.debug('receiving image from TWAIN source')
200 _log.debug('image info: %s' % self.__scanner.GetImageInfo())
201 _log.debug('image layout: %s' % str(self.__scanner.GetImageLayout()))
202
203 # get image from source
204 (external_data_handle, more_images_pending) = self.__scanner.XferImageNatively()
205 try:
206 # convert DIB to standard bitmap file (always .bmp)
207 _twain_module.DIBToBMFile(external_data_handle, self.__filename)
208 finally:
209 _twain_module.GlobalHandleFree(external_data_handle)
210 _log.debug('%s pending images' % more_images_pending)
211
212 # hide the scanner user interface again
213 # self.__scanner.HideUI() # needed ?
214 # self.__scanner = None # not sure why this should be needed, simple_wx does it, though
215
216 self.__done_transferring_image = True
217 #---------------------------------------------------
219
220 # the docs say this is not required to be implemented
221 # therefor we can't use it by default :-(
222 # UNTESTED !!!!
223
224 _log.debug('receiving image from TWAIN source')
225 _log.debug('image info: %s' % self.__scanner.GetImageInfo())
226 _log.debug('image layout: %s' % self.__scanner.GetImageLayout())
227
228 self.__scanner.SetXferFileName(self.__filename) # FIXME: allow format
229
230 more_images_pending = self.__scanner.XferImageByFile()
231 _log.debug('%s pending images' % more_images_pending)
232
233 # hide the scanner user interface again
234 self.__scanner.HideUI()
235 # self.__scanner = None
236
237 return
238 #=======================================================
239 # SANE handling
240 #=======================================================
242 global _sane_module
243 if _sane_module is None:
244 try:
245 import sane
246 except ImportError:
247 _log.exception('cannot import SANE module')
248 raise
249 _sane_module = sane
250 try:
251 init_result = _sane_module.init()
252 except:
253 _log.exception('cannot init SANE module')
254 raise
255 _log.info("SANE version: %s" % str(init_result))
256 _log.debug('SANE device list: %s' % str(_sane_module.get_devices()))
257 #=======================================================
259
260 # for testing uncomment "test" backend in /etc/sane/dll.conf
261
262 _src_manager = None
263
265 _sane_import_module()
266
267 # FIXME: need to test against devs[x][0]
268 # devs = _sane_module.get_devices()
269 # if device not in devs:
270 # _log.error("device [%s] not found in list of devices detected by SANE" % device)
271 # _log.error(str(devs))
272 # raise gmExceptions.ConstructorError, msg
273
274 self.__device = device
275 _log.info('using SANE device [%s]' % self.__device)
276
277 self.__init_scanner()
278 #---------------------------------------------------
280 self.__scanner = _sane_module.open(self.__device)
281
282 _log.debug('opened SANE device: %s' % str(self.__scanner))
283 _log.debug('SANE device config: %s' % str(self.__scanner.get_parameters()))
284 _log.debug('SANE device opts : %s' % str(self.__scanner.optlist))
285 _log.debug('SANE device opts : %s' % str(self.__scanner.get_options()))
286
287 return True
288 #---------------------------------------------------
290 self.__scanner.close()
291 #---------------------------------------------------
293 if filename is None:
294 filename = gmTools.get_unique_filename(prefix='gmScannedObj-', suffix='.bmp')
295 else:
296 tmp, ext = os.path.splitext(filename)
297 if ext != '.bmp':
298 filename = filename + '.bmp'
299
300 filename = os.path.abspath(os.path.expanduser(filename))
301
302 if delay is not None:
303 time.sleep(delay)
304 _log.debug('some sane backends report device_busy if we advance too fast. delay set to %s sec' % delay)
305
306 _log.debug('Trying to get image from scanner into [%s] !' % filename)
307 self.__scanner.start()
308 img = self.__scanner.snap()
309 img.save(filename)
310
311 return [filename]
312 #---------------------------------------------------
315 #---------------------------------------------------
316 # def dummy(self):
317 # pass
318 # # supposedly there is a method *.close() but it does not
319 # # seem to work, therefore I put in the following line (else
320 # # it reports a busy sane-device on the second and consecutive runs)
321 # try:
322 # # by default use the first device
323 # # FIXME: room for improvement - option
324 # self.__scanner = _sane_module.open(_sane_module.get_devices()[0][0])
325 # except:
326 # _log.exception('cannot open SANE scanner')
327 # return False
328 #
329 # # Set scan parameters
330 # # FIXME: get those from config file
331 # #self.__scannercontrast=170 ; self.__scannerbrightness=150 ; self.__scannerwhite_level=190
332 # #self.__scannerdepth=6
333 # #self.__scannerbr_x = 412.0
334 # #self.__scannerbr_y = 583.0
335
336 #==================================================
337 # XSane handling
338 #==================================================
340
341 _FILETYPE = u'.png'
342
343 #----------------------------------------------
345 # while not strictly necessary it is good to fail early
346 # this will tell us fairly safely whether XSane is properly installed
347 self._stock_xsanerc = os.path.expanduser(os.path.join('~', '.sane', 'xsane', 'xsane.rc'))
348 try:
349 open(self._stock_xsanerc, 'r').close()
350 except IOError:
351 msg = (
352 'XSane not properly installed for this user:\n\n'
353 ' [%s] not found\n\n'
354 'Start XSane once before using it with GNUmed.'
355 ) % self._stock_xsanerc
356 raise ImportError(msg)
357
358 # make sure we've got a custom xsanerc for
359 # the user to modify manually
360 self._gm_custom_xsanerc = os.path.expanduser(os.path.join('~', '.gnumed', 'gm-xsanerc.conf'))
361 try:
362 open(self._gm_custom_xsanerc, 'r+b').close()
363 except IOError:
364 _log.info('creating [%s] from [%s]', self._gm_custom_xsanerc, self._stock_xsanerc)
365 shutil.copyfile(self._stock_xsanerc, self._gm_custom_xsanerc)
366
367 self.device_settings_file = None
368 self.default_device = None
369 #----------------------------------------------
372 #----------------------------------------------
374 """Call XSane.
375
376 <filename> name part must have format name-001.ext>
377 """
378 if filename is None:
379 filename = gmTools.get_unique_filename(prefix = 'gm-scan-')
380
381 name, ext = os.path.splitext(filename)
382 filename = '%s-001%s' % (name, cXSaneScanner._FILETYPE)
383 filename = os.path.abspath(os.path.expanduser(filename))
384
385 cmd = 'xsane --no-mode-selection --save --force-filename "%s" --xsane-rc "%s" %s %s' % (
386 filename,
387 self.__get_session_xsanerc(),
388 gmTools.coalesce(self.device_settings_file, '', '--device-settings %s'),
389 gmTools.coalesce(self.default_device, '')
390 )
391 normal_exit = gmShellAPI.run_command_in_shell(command = cmd, blocking = True)
392
393 if normal_exit:
394 flist = glob.glob(filename.replace('001', '*'))
395 flist.sort()
396 return flist
397
398 raise OSError(-1, 'error running XSane as [%s]' % cmd)
399 #---------------------------------------------------
402 #----------------------------------------------
403 # internal API
404 #----------------------------------------------
406
407 # create an xsanerc for this session
408 session_xsanerc = gmTools.get_unique_filename (
409 prefix = 'gm-session_xsanerc-',
410 suffix = '.conf'
411 )
412 _log.debug('GNUmed -> XSane session xsanerc: %s', session_xsanerc)
413
414 # our closest bet, might contain umlauts
415 enc = gmI18N.get_encoding()
416 fread = codecs.open(self._gm_custom_xsanerc, mode = "rU", encoding = enc)
417 fwrite = codecs.open(session_xsanerc, mode = "w", encoding = enc)
418
419 paths = gmTools.gmPaths()
420 val_dict = {
421 u'tmp-path': paths.tmp_dir,
422 u'working-directory': paths.tmp_dir,
423 u'filename': u'<--force-filename>',
424 u'filetype': cXSaneScanner._FILETYPE,
425 u'skip-existing-numbers': u'1',
426 u'filename-counter-step': u'1',
427 u'filename-counter-len': u'3'
428 }
429
430 for idx, line in enumerate(fread):
431 line = line.replace(u'\n', u'')
432 line = line.replace(u'\r', u'')
433
434 if idx % 2 == 0: # even lines are keys
435 curr_key = line.strip(u'"')
436 fwrite.write(u'"%s"\n' % curr_key)
437 else: # odd lines are corresponding values
438 try:
439 value = val_dict[curr_key]
440 _log.debug('replaced [%s] with [%s]', curr_key, val_dict[curr_key])
441 except KeyError:
442 value = line
443 fwrite.write(u'%s\n' % value)
444
445 fwrite.flush()
446 fwrite.close()
447 fread.close()
448
449 return session_xsanerc
450 #==================================================
452 try:
453 _twain_import_module()
454 # TWAIN does not support get_devices():
455 # devices can only be selected from within TWAIN itself
456 return None
457 except ImportError:
458 pass
459
460 if use_XSane:
461 # neither does XSane
462 return None
463
464 _sane_import_module()
465 return _sane_module.get_devices()
466 #-----------------------------------------------------
467 -def acquire_pages_into_files(device=None, delay=None, filename=None, calling_window=None, xsane_device_settings=None):
468 """Connect to a scanner and return the scanned pages as a file list.
469
470 returns:
471 - list of filenames: names of scanned pages, may be []
472 - None: unable to connect to scanner
473 """
474 try:
475 scanner = cTwainScanner(calling_window=calling_window)
476 _log.debug('using TWAIN')
477 except ImportError:
478 if use_XSane:
479 _log.debug('using XSane')
480 scanner = cXSaneScanner()
481 scanner.device_settings_file = xsane_device_settings
482 scanner.default_device = device
483 else:
484 _log.debug('using SANE directly')
485 scanner = cSaneScanner(device=device)
486
487 _log.debug('requested filename: [%s]' % filename)
488 fnames = scanner.acquire_pages_into_files(filename=filename, delay=delay)
489 scanner.close()
490 _log.debug('acquired pages into files: %s' % str(fnames))
491
492 return fnames
493 #==================================================
494 # main
495 #==================================================
496 if __name__ == '__main__':
497
498 if len(sys.argv) > 1 and sys.argv[1] == u'test':
499
500 logging.basicConfig(level=logging.DEBUG)
501
502 print "devices:"
503 print get_devices()
504
505 sys.exit()
506
507 setups = [
508 {'dev': 'test:0', 'file': 'x1-test0-1-0001'},
509 {'dev': 'test:1', 'file': 'x2-test1-1-0001.bmp'},
510 {'dev': 'test:0', 'file': 'x3-test0-2-0001.bmp-ccc'}
511 ]
512
513 idx = 1
514 for setup in setups:
515 print "scanning page #%s from device [%s]" % (idx, setup['dev'])
516 idx += 1
517 fnames = acquire_pages_into_files(device = setup['dev'], filename = setup['file'], delay = (idx*5))
518 if fnames is False:
519 print "error, cannot acquire page"
520 else:
521 print " image files:", fnames
522
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Oct 5 03:57:00 2013 | http://epydoc.sourceforge.net |