1. ISO 19794-4 Images (Print)

1.1. Reading

The open() method sets the following info properties

version
Fixed version (020)
nb_representation
The number of representations, i.e. the number of frames
nb_position
The number of position, i.e. the different finger ranks present in the frames
certification_flag
A flag indicating if the certification blocks are included or not

In addition, each frame has the following additional attributes:

header

The representation header (specific to each frame), containing:

  • capture_datetime
  • capture_device_technology_id
  • capture_device_vendor_id
  • capture_device_type_id
  • quality_records: a list of quality records (score, algo_vendor_id, algo_id)
  • certification_records: a list of certification records (authority_id, scheme_id)
  • position: the finger/plam position as a text
  • number: the image number
  • scale_units: the scale unit as a text
  • horizontal_scan_sampling_rate
  • vertical_scan_sampling_rate
  • horizontal_image_sampling_rate
  • vertical_image_sampling_rate
  • image_compression_algo: the compression algo as a text
  • impression_type: the impression type as text

When reading an image the fields position, scale_units, image_compression_algo and impression_type are converted to readable text.

1.2. Writing

The save() method can take the following keyword arguments:

save_all
If true, Pillow will save all frames of the image to a multirepresentation file.
append_images
A list of images to append as additional frames. Each of the images in the list can be a single or multiframe image.

1.3. Usage

First, let’s create a sample image looking like a fingerprint:

>>> from PIL import Image, ImageDraw
>>> sample = Image.new("L",(200,300),255)
>>> draw = ImageDraw.Draw(sample)
>>> for i in range(20,100,10):
...     for n in range(5):
...         draw.ellipse( (i+n,i+n,200-i-n,300-i-n),outline=0)

To build a single frame image, we first need a representation header. This can be built from a list of key/value.

>>> import datetime
>>> header = dict(
...     capture_datetime = datetime.datetime.now(),
...     capture_device_technology_id=b'\x00',          # unknown
...     capture_device_vendor_id=b'\xab\xcd',
...     capture_device_type_id=b'\x12\x34',
...     quality_records=[],
...     certification_records=[],
...     position='LEFT_INDEX_FINGER',
...     number=1,
...     scale_units='PPI',
...     horizontal_scan_sampling_rate=500,
...     vertical_scan_sampling_rate=500,
...     horizontal_image_sampling_rate=500,
...     vertical_image_sampling_rate=500,
...     image_compression_algo='RAW',
...     impression_type='LIVESCAN_ROLLED'
... )

Header must be defined on the image for the save operation to work correctly, but a minimal header is also possible (default values will be provided)

>>> sample.header = dict(image_compression_algo='RAW')
>>> buffer = io.BytesIO()
>>> sample.save(buffer,"FIR")

Using a fully defined header:

>>> sample.header = header
>>> buffer = io.BytesIO()
>>> sample.save(buffer,"FIR")
>>> print(buffer.getvalue()[0:3])
b'FIR'
>>> print(buffer.getvalue()[4:7])
b'020'

Multi-frames image is generated with the save_all option:

>>> buffer_multi = io.BytesIO()
>>> sample.save(buffer_multi,"FIR",save_all=True,append_images=[sample])

To read an image, just use the standard open function:

>>> nsample = Image.open(buffer_multi)
>>> nsample.info['nb_representation']
2
>>> nsample.info['nb_position']
1
>>> nsample.seek(1)

To specify the compression algorithm:

>>> nsample = Image.open(buffer_multi)
>>> buffer = io.BytesIO()
>>> nsample.header['image_compression_algo'] ='JPEG'
>>> nsample.seek(1)
>>> nsample.header['image_compression_algo'] ='JPEG'
>>> nsample.save(buffer,"FIR",save_all=True)

Jpeg2000 is also supported (see https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#jpeg-2000 for the prerequisites), use JPEG2000_LOSSY

>>> buffer = io.BytesIO()
>>> sample.header['image_compression_algo'] ='JPEG2000_LOSSY'
>>> sample.save(buffer,"FIR")

or JPEG2000_LOSSLESS

>>> buffer = io.BytesIO()
>>> sample.header['image_compression_algo'] ='JPEG2000_LOSSLESS'
>>> sample.save(buffer,"FIR")