Fingerprint Format - cims-bioko/cims-prints GitHub Wiki

The following describes the format of the fingerprint templates returned from the scanning activity. This may prove useful for inter-operating with other fingerprint identification systems.

General

If scanning directly from an ODK form, fingerprint scans are returned as hex-encoded strings containing extracted fingerprint templates rather than the scanned fingerprint imagery. The extracted templates are ISO 19794-2 template format. They are hex-encoded so they can safely be embedded in an ODK or CommCare form without further encoding or escaping.

Details

As stated above, the extracted templates are ISO 19794-2 template format. In hex-encoded form, they should always start with the following preamble:

464D520020323000...

This preamble corresponds to following 8-bytes, after hex-decoding, in the raw ISO 19794-2 finger minutiae record (FMR) format:

46 4D 52 00: FMR<0>
20 32 30 00:  20<0>

These two values are the record header (FMR\0) and the version ( 20\0) as outlined in section 6 of this NIST MINEX Match-on-card Evaluation:

# Field Size (bits) Valid Values Notes
1 Format identifier 32 "FMR" Record header
2 Standard version 32 " XX" XX => 20
3 Record length, in bytes 32 24 to 2^32
4 Capture Equipment Certification 4 ? ?
5 Capture device type ID 12 ? vendor code
6 Image size, x dimension (pixels) 16
7 Image size, y dimension (pixels) 16
8 Resolution, x dimension (pixels/cm) 16
9 Resolution, y dimension (pixels/cm) 16
10 Number of finger views 8 1
11 Reserved byte 8 00
12 Finger position 8 0 to 10 19794-2 Table 2
13 View number 4 0 Only one view
14 Impression type 4 0 to 3 or 8 19794-2 Table 3
15 Finger Quality 8 0 to 100 19794-2 Table 3
16 Number of minutiae 8 [0,128]
17 X location (0.1 m) 8 [0,255] repeat rec 16 times
18 Y location (0.1 m) 8 [0,255] repeat rec 16 times
19 Minutiae type 2 repeat rec 16 times
20 Minutiae angle (5.625 deg ) 6 [0,63] repeat rec 16 times
21 Extended data block length 16 0 0x0000 = No private area
...Extended data block

An example of parsing this format can be seen in SourceAFIS version 2.2.0's FingerprintTemplate.java, line 149:

...
private FingerprintMinutia[] parseIso(byte[] iso, OptionalDouble dpi) {
    try {
        DataInput in = new DataInputStream(new ByteArrayInputStream(iso));
        // 4B magic header "FMR\0"
        // 4B version " 20\0"
        // 4B template length in bytes (should be 28 + 6 * count + 2 + extra-data)
        // 2B junk
        in.skipBytes(14);
        // image size
        int width = in.readUnsignedShort();
        int height = in.readUnsignedShort();
        context.log("iso-size", new Cell(width, height));
        // pixels per cm X and Y, assuming 500dpi
        int xPixelsPerCM = in.readShort();
        int yPixelsPerCM = in.readShort();
        double dpiX = xPixelsPerCM * 255 / 100.0;
        double dpiY = yPixelsPerCM * 255 / 100.0;
        context.log("iso-dpi", new Point(dpiX, dpiY));
        if (dpi.isPresent()) {
            dpiX = dpi.getAsDouble();
            dpiY = dpi.getAsDouble();
        }
        // 1B number of fingerprints in the template (assuming 1)
        // 1B junk
        // 1B finger position
        // 1B junk
        // 1B fingerprint quality
        in.skipBytes(5);
        // minutia count
        int count = in.readUnsignedByte();
        List<FingerprintMinutia> minutiae = new ArrayList<>();
        for (int i = 0; i < count; ++i) {
            // X position, upper two bits are type
            int packedX = in.readUnsignedShort();
            // Y position, upper two bits ignored
            int packedY = in.readUnsignedShort();
            // angle, 0..255 equivalent to 0..2pi
            int angle = in.readUnsignedByte();
            // 1B minutia quality
            in.skipBytes(1);
            // type: 01 ending, 10 bifurcation, 00 other (treated as ending)
            int type = (packedX >> 14) & 0x3;
            int x = packedX & 0x3fff;
            int y = packedY & 0x3fff;
            if (Math.abs(dpiX - 500) > context.dpiTolerance)
                x = (int)Math.round(x / dpiX * 500);
            if (Math.abs(dpiY - 500) > context.dpiTolerance)
                y = (int)Math.round(y / dpiY * 500);
            FingerprintMinutia minutia = new FingerprintMinutia(
                new Cell(x, y),
                angle * Angle.PI2 / 256.0,
                type == 2 ? MinutiaType.BIFURCATION : MinutiaType.ENDING);
            minutiae.add(minutia);
        }
        // extra data length
        int extra = in.readUnsignedShort();
        // variable-length extra data section
        in.skipBytes(extra);
        FingerprintMinutia[] result = minutiae.stream().toArray(FingerprintMinutia[]::new);
        context.log("iso-minutiae", result);
        return result;
    } catch (IOException e) {
        throw new IllegalArgumentException("Invalid ISO 19794-2 template", e);
    }
}
...