Algorithm transparency

SourceAFIS » Algorithm » Transparency

Algorithm transparency in SourceAFIS is an API that lets applications capture all intermediate data structures that are created during feature extraction and matching, including images in various stages of filtering, extracted minutiae and ridges, pairing tree constructed by matcher, and score breakdown. Learning basics of how the algorithm works is recommended before using transparency API.

Why?

The simple API exposed by SourceAFIS is generally an advantage, because the job of SourceAFIS is to abstract away details of fingerprint matching. There are however occasions when seeing what the algorithm is doing is beneficial. Specifically, algorithm transparency can be used to:

Transparency data is also used internally to ensure consistency of language ports of SourceAFIS and to perform basic sanity checks in unit tests.

Algorithm transparency is rare in commercial matchers, becasue it weakens intellectual property protection. SourceAFIS, being fully opensource, doesn't need to hide anything from you. Algorithm transparency is its unique advantage.

Standard API data

Transparency data does not include data that is available in some other way. Specifically, it excludes the original fingerprint image, source string during deserialization, the resulting template (which is documented elsewhere), and the final score.

Full log

The simplest way to obtain transparency data is to ask SourceAFIS for a transparency ZIP file. The following example logs data from feature extraction, matcher initialization, and a single match.

FingerprintTemplate candidate = ...;
byte[] probeImage = ...;

try (
    OutputStream stream = new FileOutputStream("transparency.zip");
    FingerprintTransparency transparency = FingerprintTransparency.zip(stream)
) {
    FingerprintTemplate probe = new FingerprintTemplate(
        new FingerprintImage()
            .dpi(500)
            .create(probeImage));
    new FingerprintMatcher()
        .index(probe)
        .match(candidate);
}

See: FingerprintTransparency.zip() (Java)

This will create file transparency.zip populated by numerous JSON and binary data files. You can customize what gets logged by adding or removing method calls inside the try block.

Zip contents

List of files in the generated transparency.zip is shown below. Files named pairing and score appear multiple times, each containing different minutia pairing. Some of these repeats have been omitted from the list. Click on any file group to learn about its format.

001-version.json
002-decoded-image.json
003-decoded-image.dat
004-scaled-image.json
005-scaled-image.dat
006-block-map.json
007-histogram.json
008-histogram.dat
009-smoothed-histogram.json
010-smoothed-histogram.dat
011-clipped-contrast.json
012-clipped-contrast.dat
013-absolute-contrast-mask.json
014-absolute-contrast-mask.dat
015-relative-contrast-mask.json
016-relative-contrast-mask.dat
017-combined-mask.json
018-combined-mask.dat
019-filtered-mask.json
020-filtered-mask.dat
021-equalized-image.json
022-equalized-image.dat
023-pixelwise-orientation.json
024-pixelwise-orientation.dat
025-block-orientation.json
026-block-orientation.dat
027-smoothed-orientation.json
028-smoothed-orientation.dat
029-parallel-smoothing.json
030-parallel-smoothing.dat
031-orthogonal-smoothing.json
032-orthogonal-smoothing.dat
033-binarized-image.json
034-binarized-image.dat
035-filtered-binary-image.json
036-filtered-binary-image.dat
037-pixel-mask.json
038-pixel-mask.dat
039-inner-mask.json
040-inner-mask.dat
041-ridges-binarized-skeleton.json
042-ridges-binarized-skeleton.dat
043-ridges-thinned-skeleton.json
044-ridges-thinned-skeleton.dat
045-ridges-traced-skeleton.json
046-ridges-traced-skeleton.dat
047-ridges-removed-dots.json
048-ridges-removed-dots.dat
049-ridges-removed-pores.json
050-ridges-removed-pores.dat
051-ridges-removed-gaps.json
052-ridges-removed-gaps.dat
053-ridges-removed-tails.json
054-ridges-removed-tails.dat
055-ridges-removed-fragments.json
056-ridges-removed-fragments.dat
057-valleys-binarized-skeleton.json
058-valleys-binarized-skeleton.dat
059-valleys-thinned-skeleton.json
060-valleys-thinned-skeleton.dat
061-valleys-traced-skeleton.json
062-valleys-traced-skeleton.dat
063-valleys-removed-dots.json
064-valleys-removed-dots.dat
065-valleys-removed-pores.json
066-valleys-removed-pores.dat
067-valleys-removed-gaps.json
068-valleys-removed-gaps.dat
069-valleys-removed-tails.json
070-valleys-removed-tails.dat
071-valleys-removed-fragments.json
072-valleys-removed-fragments.dat
073-skeleton-minutiae.json
074-inner-minutiae.json
075-removed-minutia-clouds.json
076-top-minutiae.json
077-shuffled-minutiae.json
078-edge-table.json
079-edge-hash.dat
080-root-pairs.json
081-pairing.json
082-score.json
083-pairing.json
084-score.json
... skipped 96 files ...
181-pairing.json
182-score.json
183-pairing.json
184-score.json
185-best-match.json

Selective logging

ZIP file might be easy to generate and examine by hand, but most use cases for transparency API require programmatic access to data. While you could save the ZIP file and then load parts of it, it is easier and more performant to define your own FingerprintTransparency implementation like in this example.

// extend class FingerprintTransparency and define capture() method
class TransparencyContents extends FingerprintTransparency {
    @Override protected void capture(String keyword, Map<String, Supplier<byte[]>> data) {
        for (String suffix : data.keySet()) {
            byte[] content = data.get(suffix).get();
            out.printf("%,9d B  %-6s %s\n", content.length, suffix, keyword);
        }
    }
}

FingerprintTemplate candidate = ...;
byte[] probeImage = ...;

// use the newly defined logging class to capture data
try (TransparencyContents transparency = new TransparencyContents()) {
    FingerprintTemplate probe = new FingerprintTemplate(
        new FingerprintImage()
            .dpi(500)
            .create(probeImage));
    new FingerprintMatcher()
        .index(probe)
        .match(candidate);
}

See: FingerprintTransparency.capture(String,Map) (Java)

This should result in the following output. You can see the data is more structured. While this example just reports data length, it does load all serialized data into the content variable, ready to be parsed. Data is passed to the logger in the exact order in which file entries appear in the ZIP file above.

24 B  .json  version
1,160,896 B  .dat   decoded-image
      161 B  .json  decoded-image
1,160,896 B  .dat   scaled-image
      161 B  .json  scaled-image
    1,518 B  .json  block-map
  665,600 B  .dat   histogram
          .   .     .
          .   .     .
          .   .     .
   13,514 B  .json  pairing
      590 B  .json  score
       87 B  .json  pairing
      555 B  .json  score
   17,579 B  .json  pairing
      593 B  .json  score
       17 B  .json  best-match

If you don't pull content from the Supplier, it will never get constructed. This lets you access algorithm transparency data with minimal overhead, picking only data you actually need.

Deserialization data

Above examples capture the primary workflow of feature extraction and matching. It is also possible to collect transparency data from template deserialization.

FingerprintTemplate template = ...;
byte[] serialized = template.toByteArray();

try (
    OutputStream stream = new FileOutputStream("transparency-deserialized.zip");
    FingerprintTransparency transparency = FingerprintTransparency.zip(stream)
) {
    new FingerprintTemplate(serialized);
}

The above code will produce archive transparency-deserialized.zip with the following contents.

This ZIP archive doesn't include the template itself, because serialized templates can be examined directly by following template format documentation.