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);
}
}
...