Add some docstrings.
[pyfprint.git] / pyfprint / pyfprint.py
blobb3ae5fb67cadfc2b8f972c84e4b06ef5515eda54
1 # encoding=utf-8
2 ############################################################################
3 # Copyright (C) 2008 by Lukas Sandström #
4 # luksan@gmail.com #
5 # #
6 # This program is free software; you can redistribute it and/or modify #
7 # it under the terms of the GNU General Public License as published by #
8 # the Free Software Foundation; either version 2 of the License, or #
9 # (at your option) any later version. #
10 # #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
15 # #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program; if not, write to the #
18 # Free Software Foundation, Inc., #
19 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #
20 ############################################################################
22 import pyfprint_swig as pyf
24 # TODO:
25 # exceptions, especially for RETRY_* errors
26 # constants for fingers
27 # tests
28 # documentation
29 # for x in y => map ?
30 # Image(img) for devices which don't support imaging? Is img NULL?
32 _init_ok = False
34 def _dbg(*arg):
35 #print arg
36 pass
38 def fp_init():
39 """Call this before doing anything else."""
40 _init_ok = (pyf.fp_init() == 0)
41 if not _init_ok:
42 raise "fprint initialization failed."
44 def fp_exit():
45 """pyfprint can't be used after this is called."""
46 pyf.fp_exit()
47 _init_ok = False
49 Fingers = dict(
50 """Enumeration of the different fingers, used when using Fprint.save_to_disk()."""
51 LEFT_THUMB = pyf.LEFT_THUMB,
52 LEFT_INDEX = pyf.LEFT_INDEX,
53 LEFT_MIDDLE = pyf.LEFT_MIDDLE,
54 LEFT_RING = pyf.LEFT_RING,
55 LEFT_LITTLE = pyf.LEFT_LITTLE,
56 RIGHT_THUMB = pyf.RIGHT_THUMB,
57 RIGHT_INDEX = pyf.RIGHT_INDEX,
58 RIGHT_MIDDLE = pyf.RIGHT_MIDDLE,
59 RIGHT_RING = pyf.RIGHT_RING,
60 RIGHT_LITTLE = pyf.RIGHT_LITTLE
63 class Device:
64 """Provides access to a fingerprint reading device. Don't construct this
65 directly, use discover_devices() instead."""
66 def __init__(self, dev_ptr = None, dscv_ptr = None, DscvList = None):
67 """For internal use only."""
68 self.dev = dev_ptr
69 self.dscv = dscv_ptr
70 self.DscvList = DscvList
71 if dscv_ptr and DscvList == None:
72 raise "Programming error? Device contructed with dscv without DscvList."
74 def close(self):
75 """Closes the device. No more methods, except open(), may be called after this."""
76 if self.dev:
77 pyf.fp_dev_close(self.dev)
78 self.dev = None
80 def open(self):
81 """Connects to the device."""
82 if self.dev:
83 raise "Device already open"
84 self.dev = pyf.fp_dev_open(self.dscv)
85 if not self.dev:
86 raise "device open failed"
88 def get_driver(self):
89 """
90 Return a Driver instance.
92 open() is not required before this method.
93 """
94 if self.dev:
95 return Driver(pyf.fp_dev_get_driver(self.dev))
96 if self.dscv:
97 return Driver(pyf.fp_dscv_dev_get_driver(self.dscv))
99 def get_devtype(self):
101 Return an integer representing the type of device.
103 open() is not required before this method.
105 if self.dev:
106 return pyf.fp_dev_get_devtype(self.dev)
107 if self.dscv:
108 return pyf.fp_dscv_dev_get_devtype(self.dev)
110 def get_nr_enroll_stages(self):
112 Return how many times enroll_finger needs to be called
113 before the finger is successfully enrolled.
115 if self.dev:
116 return pyf.fp_dev_get_nr_enroll_stages(self.dev)
117 raise "Device not open"
119 def is_compatible(self, fprint):
121 Checks whether the passed fprint is compatible with the device.
123 open() is not required before this method.
125 if self.dev:
126 if fprint.data_ptr:
127 return pyf.fp_dev_supports_print_data(self.dev, fprint.data_ptr) == 1
128 if fprint.dscv_ptr:
129 return pyf.fp_dev_supports_dscv_print(self.dev, fprint.dscv_ptr) == 1
130 raise "No print found"
131 if self.dscv:
132 if fprint.data_ptr:
133 return pyf.fp_dscv_dev_supports_print_data(self.dscv, fprint.data_ptr) == 1
134 if fprint.dscv_ptr:
135 return pyf.fp_dscv_dev_supports_dscv_print(self.dscv, fprint.dscv_ptr) == 1
136 raise "No print found"
137 raise "No device found"
139 def get_supports_imaging(self):
140 """If true, the device can return an image of the finger."""
141 if self.dev:
142 return pyf.fp_dev_supports_imaging(self.dev) == 1
143 raise "Device not open"
145 def get_img_width(self):
146 """Return the width of the images scanned by the device, in pixels."""
147 if self.dev:
148 return pyf.fp_dev_get_img_width(self.dev)
149 raise "Device not open"
151 def get_img_height(self):
152 """Return the height of the images scanned by the device, in pixels."""
153 if self.dev:
154 return pyf.fp_dev_get_img_height(self.dev)
155 raise "Device not open"
157 def capture_image(self, wait_for_finger):
158 """FIXME: check that the dev supports imaging, or check -ENOTSUP"""
159 if not self.dev:
160 raise "Device not open"
162 unconditional = 1
163 if wait_for_finger == True:
164 unconditional = 0
166 (r, img) = pyf.pyfp_dev_img_capture(self.dev, unconditional)
167 if r != 0:
168 raise "image_capture failed. error: " + r
169 return Image(img)
171 def enroll_finger(self):
172 """FIXME: docstring, error handling"""
173 if not self.dev:
174 raise "Device not open"
175 (r, fprint, img) = pyf.pyfp_enroll_finger_img(self.dev)
176 if r < 0:
177 raise "Internal I/O error while enrolling"
178 img = Image(img)
179 if r == pyf.FP_ENROLL_COMPLETE:
180 _dbg("enroll complete")
181 return (Fprint(data_ptr = fprint), img)
182 if r == pyf.FP_ENROLL_FAIL:
183 print "Failed. Enrollmet process reset."
184 if r == pyf.FP_ENROLL_PASS:
185 _dbg("enroll PASS")
186 return (None, img)
187 if r == pyf.FP_ENROLL_RETRY:
188 _dbg("enroll RETRY")
189 pass
190 if r == pyf.FP_ENROLL_RETRY_TOO_SHORT:
191 _dbg("enroll RETRY_SHORT")
192 pass
193 if r == pyf.FP_ENROLL_RETRY_CENTER_FINGER:
194 _dbg("enroll RETRY_CENTER")
195 pass
196 if r == pyf.FP_ENROLL_RETRY_REMOVE_FINGER:
197 _dbg("enroll RETRY_REMOVE")
198 pass
199 return ("xxx", None)
201 def verify_finger(self, fprint):
203 Compare the finger on the device with the supplied Fprint.
204 Return true if the finger and the Fprint matches.
206 if not self.dev:
207 raise "Device not open"
208 (r, img) = pyf.pyfp_verify_finger_img(self.dev, fprint._get_print_data_ptr())
209 if r < 0:
210 raise "verify error"
211 img = Image(img)
212 if r == pyf.FP_VERIFY_NO_MATCH:
213 return (False, img)
214 if r == pyf.FP_VERIFY_MATCH:
215 return (True, img)
216 if r == pyf.FP_VERIFY_RETRY:
217 pass
218 if r == pyf.FP_VERIFY_RETRY_TOO_SHORT:
219 pass
220 if r == pyf.FP_VERIFY_RETRY_CENTER_FINGER:
221 pass
222 if r == pyf.FP_VERIFY_RETRY_REMOVE_FINGER:
223 pass
224 return (None, None)
226 def supports_identification(self):
227 """Return True if the device supports the identify_finger method."""
228 if not self.dev:
229 raise "Device not open"
230 return pyf.fp_dev_supports_identification(self.dev) == 1
232 def identify_finger(self, fprints):
234 FIXME: error handling
236 Match the finger on the reader against a list of Fprints.
238 Return a tuple: (list_offset, Fprint, Image) if a match is found,
239 (None, None, Image) otherwise.
241 Image is None if the device doesn't support imaging.
244 if not self.dev:
245 raise "Device not open"
246 gallery = pyf.pyfp_print_data_array(len(fprints))
247 for x in fprints:
248 if not self.is_compatible(x):
249 raise "can't verify uncompatible print"
250 gallery.append(x._get_print_data_ptr())
251 (r, offset, img) = pyf.pyfp_identify_finger_img(self.dev, gallery.list)
252 if r < 0:
253 raise "identification error"
254 img = Image(img)
255 if r == pyf.FP_VERIFY_NO_MATCH:
256 return (None, None, img)
257 if r == pyf.FP_VERIFY_MATCH:
258 return (offset, fprints[offset], img)
259 if r == pyf.FP_VERIFY_RETRY:
260 pass
261 if r == pyf.FP_VERIFY_RETRY_TOO_SHORT:
262 pass
263 if r == pyf.FP_VERIFY_RETRY_CENTER_FINGER:
264 pass
265 if r == pyf.FP_VERIFY_RETRY_REMOVE_FINGER:
266 pass
267 return None
269 def load_print_from_disk(self, finger):
271 Load a stored fingerprint from the users home directory.
273 - finger should be a value from Fingers.
275 Return a Fprint.
277 if not self.dev:
278 raise "Device not open"
279 (r, print_ptr) = pyf.fp_print_data_load(self.dev, finger)
280 if r != 0:
281 raise "could not load print from disk"
282 return Fprint(data_ptr = print_ptr)
284 def delete_stored_finger(self, finger):
286 Delete a fingerprint stored in the users home directory
288 - finger should be a value from Fingers.
290 if not self.dev:
291 raise "Device not open"
292 r = pyf.fp_print_data_delete(self.dev, finger)
293 if r != 0:
294 raise "delete failed"
296 class Minutia(pyf.fp_minutia):
297 """A single point of interest in a fingerprint."""
298 def __init__(self, minutia_ptr, img):
299 self.img = img
300 self.ptr = minutia_ptr
301 pyf.fp_minutia.__init__(self, minutia_ptr)
303 class Image:
304 """An image returned from the fingerprint reader."""
305 def __init__(self, img_ptr, bin = False):
306 """Private method."""
307 self.img = img_ptr
308 self.bin = bin
309 self.std = False
310 self.minutiae = None
312 def __del__(self):
313 if self.img:
314 pyf.fp_img_free(self.img)
316 def get_height(self):
317 """The height of the image in pixels."""
318 return pyf.fp_img_get_height(self.img)
319 def get_width(self):
320 """The width of the image in pixels."""
321 return pyf.fp_img_get_width(self.img)
323 def get_data(self):
325 FIXME: vector?
326 Return a vector containing one byte per pixel, representing a grayscale image.
328 return pyf.pyfp_img_get_data(self.img)
330 def get_rgb_data(self):
332 FIXME: vector?
333 Return a vector containing three bytes per pixel, representing a gray RGB image.
335 return pyf.pyfp_img_get_rgb_data(self.img)
337 def save_to_file(self, path):
338 r = pyf.fp_img_save_to_file(self.img, path)
339 if r != 0:
340 raise "Save failed"
342 def standardize(self):
343 pyf.fp_img_standardize(self.img)
344 self.std = True
346 def binarize(self):
347 if self.bin:
348 return
349 if not self.std:
350 self.standardize()
351 i = pyf.fp_img_binarize(self.img)
352 if i == None:
353 raise "Binarize failed"
354 return Image(img_ptr = i, bin = True)
356 def get_minutiae(self):
357 if self.minutiae:
358 return self.minutiae
359 if self.bin:
360 raise "Cannot find minutiae in binarized image"
361 if not self.std:
362 self.standardize()
363 (min_list, nr) = pyf.fp_img_get_minutiae(self.img)
364 l = []
365 for n in range(nr):
366 l.append(Minutia(img = self, minutia_ptr = pyf.pyfp_deref_minutiae(min_list, n)))
367 self.minutiae = l
368 return l
370 class Driver:
371 """Provides access to some information about a libfprint driver."""
372 def __init__(self, swig_drv_ptr):
373 """Private."""
374 self.drv = swig_drv_ptr
376 def __del__(self):
377 #FIXME: free drv?
378 pass
380 def get_name(self):
381 """Return the driver name."""
382 return pyf.fp_driver_get_name(self.drv)
384 def get_full_name(self):
385 """A longer, more desciptive version of the driver name."""
386 return pyf.fp_driver_get_full_name(self.drv)
388 def get_driver_id(self):
389 """Return an integer uniqly identifying the driver."""
390 return pyf.fp_driver_get_driver_id(self.drv)
392 class Fprint:
393 def __init__(self, serial_data = None, data_ptr = None, dscv_ptr = None, DscvList = None):
395 The only parameter that should be used is serial_data, which
396 should be data previously aquired from get_data(), in the form of a string.
397 All other parameters are for internal use only.
399 # data_ptr is a SWIG pointer to a struct pf_print_data
400 # dscv_ptr is a SWIG pointer to a struct pf_dscv_print
401 # DscvList is a class instance used to free the allocated pf_dscv_print's
402 # with pf_dscv_prints_free when they're all unused.
403 # serial_data is a string as returned by get_data()
405 self.data_ptr = data_ptr
406 self.dscv_ptr = dscv_ptr
407 self.DscvList = DscvList
409 if serial_data:
410 self.data_ptr = pyf.fp_print_data_from_data(serial_data)
411 return
413 if dscv_ptr != None and DscvList == None:
414 raise "Programming error: Fprint constructed with dscv_prt with DscvList == None"
416 def __del__(self):
417 if self.data_ptr:
418 pyf.fp_print_data_free(self.data_ptr)
419 # The dscv_ptr is freed when all the dscv prints have been garbage collected
421 def _get_print_data_ptr(self):
422 if not self.data_ptr:
423 self._data_from_dscv()
424 return self.data_ptr
426 def get_driver_id(self):
427 """Return an integer identifing the driver used to scan this print."""
428 if self.data_ptr:
429 return pyf.fp_print_data_get_driver_id(self.data_ptr)
430 elif self.dscv_ptr:
431 return pyf.fp_dscv_print_get_driver_id(self.dscv_ptr)
432 raise "no print"
434 def get_devtype(self):
435 """Return an integer representing the type of device used to scan this print."""
436 if self.data_ptr:
437 return pyf.fp_print_data_get_devtype(self.data_ptr)
438 elif self.dscv_ptr:
439 return pyf.fp_dscv_print_get_devtype(self.dscv_ptr)
440 raise "no print"
442 def get_finger(self):
444 If the Fprint was returned from discover_prints(), return
445 the Finger the Fprint represents. Otherwise raise an exception.
447 if not self.dscv_ptr:
448 raise "get_finger needs a discovered print"
449 return pyf.fp_dscv_print_get_finger(self.dscv_ptr)
451 def delete_from_disk(self):
453 If the Fprint was returned from discover_prints(), delete it
454 from the users home directory. Otherwise raise an exception.
456 if not self.dscv_ptr:
457 raise "delete needs a discovered print"
458 return pyf.fp_dscv_print_delete(self.dscv_ptr)
460 def save_to_disk(self, finger):
461 """Save the print to the users home directory.
463 - finger is a member of Fingers, indicating which
464 finger this is.
466 r = pyf.fp_print_data_save(self.data_ptr, finger)
467 if r != 0:
468 raise "save failed"
470 def _data_from_dscv(self):
471 if self.data_ptr:
472 return
473 if not self.dscv_ptr:
474 raise "no print"
475 (r, ptr) = pyf.fp_print_data_from_dscv_print(self.dscv_ptr)
476 if r != 0:
477 raise "print data from dscv failed"
478 self.data_ptr = ptr
480 def get_data(self):
482 Return a serialized dataset representing the fprint.
483 This data could be stored away, and later passed to the
484 contructor of Fprint.
486 if not self.data_ptr:
487 raise "no print"
488 s = pyf.pyfp_print_get_data(self.data_ptr)
489 if not len(s):
490 raise "serialization failed"
491 return s
493 class DiscoveredPrints(list):
495 A list of stored fingerprints available from the users
496 home directory.
498 def __init__(self, dscv_devs_list):
499 self.ptr = dscv_devs_list
500 i = 0
501 while True:
502 x = pyf.pyfp_deref_dscv_print_ptr(dscv_devs_list, i)
503 if x == None:
504 break
505 self.append(Fprint(dscv_ptr = x, DscvList = self))
506 i = i + 1
507 def __del__(self):
508 pyf.pf_dscv_prints_free(self.ptr)
510 def discover_prints():
511 """Look for fingerprints in the users home directory ;)"""
512 if not _init_ok:
513 fp_init()
515 prints = pyf.fp_discover_prints()
517 if not prints:
518 print "Print discovery failed"
519 return DiscoveredPrints(prints)
522 class DiscoveredDevices(list):
523 """A list of available devices."""
524 def __init__(self, dscv_devs_list):
525 self.swig_list_ptr = dscv_devs_list
526 i = 0
527 while True:
528 x = pyf.pyfp_deref_dscv_dev_ptr(dscv_devs_list, i)
529 if x == None:
530 break
531 self.append(Device(dscv_ptr = x, DscvList = self))
532 i = i + 1
534 def __del__(self):
535 pyf.fp_dscv_devs_free(self.swig_list_ptr)
537 def find_compatible(self, fprint):
539 Return a Device that is compatible with the fprint,
540 or None if no compatible device is found.
542 for n in self:
543 if n.is_compatible(fprint):
544 return n
545 return None
547 def discover_devices():
548 """Return a list of available devices."""
549 if not _init_ok:
550 fp_init()
552 devs = pyf.fp_discover_devs()
554 if not devs:
555 raise "Device discovery failed"
556 return DiscoveredDevices(devs)