Remove get_ prefix from public method names
[pyfprint.git] / pyfprint / pyfprint.py
blobe3ee714f10335c4d23f463ad0da1ed9a7bb57eb0
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 # """Enumeration of the different fingers, used when using Fprint.save_to_disk()."""
50 Fingers = dict(
52 LEFT_THUMB = pyf.LEFT_THUMB,
53 LEFT_INDEX = pyf.LEFT_INDEX,
54 LEFT_MIDDLE = pyf.LEFT_MIDDLE,
55 LEFT_RING = pyf.LEFT_RING,
56 LEFT_LITTLE = pyf.LEFT_LITTLE,
57 RIGHT_THUMB = pyf.RIGHT_THUMB,
58 RIGHT_INDEX = pyf.RIGHT_INDEX,
59 RIGHT_MIDDLE = pyf.RIGHT_MIDDLE,
60 RIGHT_RING = pyf.RIGHT_RING,
61 RIGHT_LITTLE = pyf.RIGHT_LITTLE
64 class Device:
65 """Provides access to a fingerprint reading device. Don't construct this
66 directly, use discover_devices() instead."""
67 def __init__(self, dev_ptr = None, dscv_ptr = None, DscvList = None):
68 """For internal use only."""
69 self.dev = dev_ptr
70 self.dscv = dscv_ptr
71 self.DscvList = DscvList
72 if dscv_ptr and DscvList == None:
73 raise "Programming error? Device contructed with dscv without DscvList."
75 def close(self):
76 """Closes the device. No more methods, except open(), may be called after this."""
77 if self.dev:
78 pyf.fp_dev_close(self.dev)
79 self.dev = None
81 def open(self):
82 """Connects to the device."""
83 if self.dev:
84 raise "Device already open"
85 self.dev = pyf.fp_dev_open(self.dscv)
86 if not self.dev:
87 raise "device open failed"
89 def driver(self):
90 """
91 Return a Driver instance.
93 open() is not required before this method.
94 """
95 if self.dev:
96 return Driver(pyf.fp_dev_get_driver(self.dev))
97 if self.dscv:
98 return Driver(pyf.fp_dscv_dev_get_driver(self.dscv))
100 def devtype(self):
102 Return an integer representing the type of device.
104 open() is not required before this method.
106 if self.dev:
107 return pyf.fp_dev_get_devtype(self.dev)
108 if self.dscv:
109 return pyf.fp_dscv_dev_get_devtype(self.dev)
111 def nr_enroll_stages(self):
113 Return how many times enroll_finger needs to be called
114 before the finger is successfully enrolled.
116 if self.dev:
117 return pyf.fp_dev_get_nr_enroll_stages(self.dev)
118 raise "Device not open"
120 def is_compatible(self, fprint):
122 Checks whether the passed fprint is compatible with the device.
124 open() is not required before this method.
126 if self.dev:
127 if fprint.data_ptr:
128 return pyf.fp_dev_supports_print_data(self.dev, fprint.data_ptr) == 1
129 if fprint.dscv_ptr:
130 return pyf.fp_dev_supports_dscv_print(self.dev, fprint.dscv_ptr) == 1
131 raise "No print found"
132 if self.dscv:
133 if fprint.data_ptr:
134 return pyf.fp_dscv_dev_supports_print_data(self.dscv, fprint.data_ptr) == 1
135 if fprint.dscv_ptr:
136 return pyf.fp_dscv_dev_supports_dscv_print(self.dscv, fprint.dscv_ptr) == 1
137 raise "No print found"
138 raise "No device found"
140 def supports_imaging(self):
141 """If true, the device can return an image of the finger."""
142 if self.dev:
143 return pyf.fp_dev_supports_imaging(self.dev) == 1
144 raise "Device not open"
146 def img_width(self):
147 """Return the width of the images scanned by the device, in pixels."""
148 if self.dev:
149 return pyf.fp_dev_get_img_width(self.dev)
150 raise "Device not open"
152 def img_height(self):
153 """Return the height of the images scanned by the device, in pixels."""
154 if self.dev:
155 return pyf.fp_dev_get_img_height(self.dev)
156 raise "Device not open"
158 def capture_image(self, wait_for_finger):
160 Captures an image from the device. Return None if imaging isn't supported.
161 wait_for_finger controls if the device should wait for a finger to be placed
162 on the sensor before image capture.
164 if not self.dev:
165 raise "Device not open"
167 if not self.supports_imaging():
168 return None
170 unconditional = 1
171 if wait_for_finger == True:
172 unconditional = 0
174 (r, img) = pyf.pyfp_dev_img_capture(self.dev, unconditional)
175 img = Image(img)
176 if r != 0:
177 raise "image_capture failed. error: %i" % r
178 return img
180 def enroll_finger(self):
181 """FIXME: docstring, error handling"""
182 if not self.dev:
183 raise "Device not open"
184 (r, fprint, img) = pyf.pyfp_enroll_finger_img(self.dev)
185 img = Image(img)
186 if r < 0:
187 raise "Internal I/O error while enrolling: %i" % i
188 if r == pyf.FP_ENROLL_COMPLETE:
189 _dbg("enroll complete")
190 return (Fprint(data_ptr = fprint), img)
191 if r == pyf.FP_ENROLL_FAIL:
192 print "Failed. Enrollmet process reset."
193 if r == pyf.FP_ENROLL_PASS:
194 _dbg("enroll PASS")
195 return (None, img)
196 if r == pyf.FP_ENROLL_RETRY:
197 _dbg("enroll RETRY")
198 pass
199 if r == pyf.FP_ENROLL_RETRY_TOO_SHORT:
200 _dbg("enroll RETRY_SHORT")
201 pass
202 if r == pyf.FP_ENROLL_RETRY_CENTER_FINGER:
203 _dbg("enroll RETRY_CENTER")
204 pass
205 if r == pyf.FP_ENROLL_RETRY_REMOVE_FINGER:
206 _dbg("enroll RETRY_REMOVE")
207 pass
208 return ("xxx", None)
210 def verify_finger(self, fprint):
212 Compare the finger on the device with the supplied Fprint.
213 Return true if the finger and the Fprint matches.
215 if not self.dev:
216 raise "Device not open"
217 (r, img) = pyf.pyfp_verify_finger_img(self.dev, fprint._get_print_data_ptr())
218 img = Image(img)
219 if r < 0:
220 raise "verify error: %i" % r
221 if r == pyf.FP_VERIFY_NO_MATCH:
222 return (False, img)
223 if r == pyf.FP_VERIFY_MATCH:
224 return (True, img)
225 if r == pyf.FP_VERIFY_RETRY:
226 pass
227 if r == pyf.FP_VERIFY_RETRY_TOO_SHORT:
228 pass
229 if r == pyf.FP_VERIFY_RETRY_CENTER_FINGER:
230 pass
231 if r == pyf.FP_VERIFY_RETRY_REMOVE_FINGER:
232 pass
233 return (None, None)
235 def supports_identification(self):
236 """Return True if the device supports the identify_finger method."""
237 if not self.dev:
238 raise "Device not open"
239 return pyf.fp_dev_supports_identification(self.dev) == 1
241 def identify_finger(self, fprints):
243 FIXME: error handling
245 Match the finger on the reader against a list of Fprints.
247 Return a tuple: (list_offset, Fprint, Image) if a match is found,
248 (None, None, Image) otherwise.
250 Image is None if the device doesn't support imaging.
253 if not self.dev:
254 raise "Device not open"
255 gallery = pyf.pyfp_print_data_array(len(fprints))
256 for x in fprints:
257 if not self.is_compatible(x):
258 raise "can't verify uncompatible print"
259 gallery.append(x._get_print_data_ptr())
260 (r, offset, img) = pyf.pyfp_identify_finger_img(self.dev, gallery.list)
261 img = Image(img)
262 if r < 0:
263 raise "identification error"
264 if r == pyf.FP_VERIFY_NO_MATCH:
265 return (None, None, img)
266 if r == pyf.FP_VERIFY_MATCH:
267 return (offset, fprints[offset], img)
268 if r == pyf.FP_VERIFY_RETRY:
269 pass
270 if r == pyf.FP_VERIFY_RETRY_TOO_SHORT:
271 pass
272 if r == pyf.FP_VERIFY_RETRY_CENTER_FINGER:
273 pass
274 if r == pyf.FP_VERIFY_RETRY_REMOVE_FINGER:
275 pass
276 return None
278 def load_print_from_disk(self, finger):
280 Load a stored fingerprint from the users home directory.
282 - finger should be a value from Fingers.
284 Return a Fprint.
286 if not self.dev:
287 raise "Device not open"
288 (r, print_ptr) = pyf.fp_print_data_load(self.dev, finger)
289 if r != 0:
290 raise "could not load print from disk"
291 return Fprint(data_ptr = print_ptr)
293 def delete_stored_finger(self, finger):
295 Delete a fingerprint stored in the users home directory
297 - finger should be a value from Fingers.
299 if not self.dev:
300 raise "Device not open"
301 r = pyf.fp_print_data_delete(self.dev, finger)
302 if r != 0:
303 raise "delete failed"
305 class Minutia(pyf.fp_minutia):
306 """A single point of interest in a fingerprint."""
307 def __init__(self, minutia_ptr, img):
308 self.img = img
309 self.ptr = minutia_ptr
310 pyf.fp_minutia.__init__(self, minutia_ptr)
312 class Image:
313 """An image returned from the fingerprint reader."""
314 def __init__(self, img_ptr, bin = False):
315 """Private method."""
316 self._img = img_ptr
317 self._bin = bin
318 self._std = False
319 self._minutiae = None
321 def __del__(self):
322 if self._img:
323 pyf.fp_img_free(self._img)
325 def height(self):
326 """The height of the image in pixels."""
327 return pyf.fp_img_get_height(self._img)
328 def width(self):
329 """The width of the image in pixels."""
330 return pyf.fp_img_get_width(self._img)
332 def data(self):
334 Return a string containing one byte per pixel, representing a grayscale image.
336 return pyf.pyfp_img_get_data(self._img)
338 def rgb_data(self):
340 Return a string containing three bytes per pixel, representing a gray RGB image.
342 return pyf.pyfp_img_get_rgb_data(self._img)
344 def save_to_file(self, path):
345 r = pyf.fp_img_save_to_file(self._img, path)
346 if r != 0:
347 raise "Save failed"
349 def standardize(self):
350 pyf.fp_img_standardize(self._img)
351 self.std = True
353 def binarize(self):
354 if self._bin:
355 return
356 if not self._std:
357 self.standardize()
358 i = pyf.fp_img_binarize(self._img)
359 if i == None:
360 raise "Binarize failed"
361 return Image(img_ptr = i, bin = True)
363 def minutiae(self):
364 if self._minutiae:
365 return self._minutiae
366 if self._bin:
367 raise "Cannot find minutiae in binarized image"
368 if not self._std:
369 self.standardize()
370 (min_list, nr) = pyf.fp_img_get_minutiae(self._img)
371 l = []
372 for n in range(nr):
373 l.append(Minutia(img = self, minutia_ptr = pyf.pyfp_deref_minutiae(min_list, n)))
374 self._minutiae = l
375 return l
377 class Driver:
378 """Provides access to some information about a libfprint driver."""
379 def __init__(self, swig_drv_ptr):
380 """Private."""
381 self.drv = swig_drv_ptr
383 def __del__(self):
384 #FIXME: free drv?
385 pass
387 def name(self):
388 """Return the driver name."""
389 return pyf.fp_driver_get_name(self.drv)
391 def full_name(self):
392 """A longer, more desciptive version of the driver name."""
393 return pyf.fp_driver_get_full_name(self.drv)
395 def driver_id(self):
396 """Return an integer uniqly identifying the driver."""
397 return pyf.fp_driver_get_driver_id(self.drv)
399 class Fprint:
400 def __init__(self, serial_data = None, data_ptr = None, dscv_ptr = None, DscvList = None):
402 The only parameter that should be used is serial_data, which
403 should be data previously aquired from data(), in the form of a string.
404 All other parameters are for internal use only.
406 # data_ptr is a SWIG pointer to a struct pf_print_data
407 # dscv_ptr is a SWIG pointer to a struct pf_dscv_print
408 # DscvList is a class instance used to free the allocated pf_dscv_print's
409 # with pf_dscv_prints_free when they're all unused.
410 # serial_data is a string as returned by data()
412 self.data_ptr = data_ptr
413 self.dscv_ptr = dscv_ptr
414 self.DscvList = DscvList
416 if serial_data:
417 self.data_ptr = pyf.fp_print_data_from_data(serial_data)
418 return
420 if dscv_ptr != None and DscvList == None:
421 raise "Programming error: Fprint constructed with dscv_prt with DscvList == None"
423 def __del__(self):
424 if self.data_ptr:
425 pyf.fp_print_data_free(self.data_ptr)
426 # The dscv_ptr is freed when all the dscv prints have been garbage collected
428 def _get_print_data_ptr(self):
429 if not self.data_ptr:
430 self._data_from_dscv()
431 return self.data_ptr
433 def driver_id(self):
434 """Return an integer identifing the driver used to scan this print."""
435 if self.data_ptr:
436 return pyf.fp_print_data_get_driver_id(self.data_ptr)
437 elif self.dscv_ptr:
438 return pyf.fp_dscv_print_get_driver_id(self.dscv_ptr)
439 raise "no print"
441 def devtype(self):
442 """Return an integer representing the type of device used to scan this print."""
443 if self.data_ptr:
444 return pyf.fp_print_data_get_devtype(self.data_ptr)
445 elif self.dscv_ptr:
446 return pyf.fp_dscv_print_get_devtype(self.dscv_ptr)
447 raise "no print"
449 def finger(self):
451 If the Fprint was returned from discover_prints(), return
452 the Finger the Fprint represents. Otherwise raise an exception.
454 if not self.dscv_ptr:
455 raise "finger() needs a discovered print"
456 return pyf.fp_dscv_print_get_finger(self.dscv_ptr)
458 def delete_from_disk(self):
460 If the Fprint was returned from discover_prints(), delete it
461 from the users home directory. Otherwise raise an exception.
463 if not self.dscv_ptr:
464 raise "delete needs a discovered print"
465 return pyf.fp_dscv_print_delete(self.dscv_ptr)
467 def save_to_disk(self, finger):
468 """Save the print to the users home directory.
470 - finger is a member of Fingers, indicating which
471 finger this is.
473 r = pyf.fp_print_data_save(self.data_ptr, finger)
474 if r != 0:
475 raise "save failed"
477 def _data_from_dscv(self):
478 if self.data_ptr:
479 return
480 if not self.dscv_ptr:
481 raise "no print"
482 (r, ptr) = pyf.fp_print_data_from_dscv_print(self.dscv_ptr)
483 if r != 0:
484 raise "print data from dscv failed"
485 self.data_ptr = ptr
487 def data(self):
489 Return a serialized dataset representing the fprint.
490 This data could be stored away, and later passed to the
491 contructor of Fprint.
493 if not self.data_ptr:
494 raise "no print"
495 s = pyf.pyfp_print_get_data(self.data_ptr)
496 if not len(s):
497 raise "serialization failed"
498 return s
500 class DiscoveredPrints(list):
502 A list of stored fingerprints available from the users
503 home directory.
505 def __init__(self, dscv_devs_list):
506 self.ptr = dscv_devs_list
507 i = 0
508 while True:
509 x = pyf.pyfp_deref_dscv_print_ptr(dscv_devs_list, i)
510 if x == None:
511 break
512 self.append(Fprint(dscv_ptr = x, DscvList = self))
513 i = i + 1
514 def __del__(self):
515 pyf.pf_dscv_prints_free(self.ptr)
517 def discover_prints():
518 """Look for fingerprints in the users home directory ;)"""
519 if not _init_ok:
520 fp_init()
522 prints = pyf.fp_discover_prints()
524 if not prints:
525 print "Print discovery failed"
526 return DiscoveredPrints(prints)
529 class DiscoveredDevices(list):
530 """A list of available devices."""
531 def __init__(self, dscv_devs_list):
532 self.swig_list_ptr = dscv_devs_list
533 i = 0
534 while True:
535 x = pyf.pyfp_deref_dscv_dev_ptr(dscv_devs_list, i)
536 if x == None:
537 break
538 self.append(Device(dscv_ptr = x, DscvList = self))
539 i = i + 1
541 def __del__(self):
542 pyf.fp_dscv_devs_free(self.swig_list_ptr)
544 def find_compatible(self, fprint):
546 Return a Device that is compatible with the fprint,
547 or None if no compatible device is found.
549 for n in self:
550 if n.is_compatible(fprint):
551 return n
552 return None
554 def discover_devices():
555 """Return a list of available devices."""
556 if not _init_ok:
557 fp_init()
559 devs = pyf.fp_discover_devs()
561 if not devs:
562 raise "Device discovery failed"
563 return DiscoveredDevices(devs)