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