Skip to main content

pad_train

k-NN presentation attack detection (PAD) for finger vein images. The package answers one question: can a PAD system that is trained on synthetic attack samples reliably tell real attacks from bona fide samples?

It returns APCER, BPCER and ACER as a pandas.DataFrame.

Method

Three sample types are involved:

Type Meaning Source
(1) bona fide ("real") Data_FV_Spoofing_WS2025_26
(2) real presentation attack ("spoof") Data_FV_Spoofing_WS2025_26
(3) synthetic presentation attack (diffusion) AE_Res-diff, VAE-diff

Each dataset (PLUS, IDIAP, SCUT) is evaluated with a five-fold cross-validation. The folds partition the subjects, so no person is in training and test at the same time. Only type (1) samples that have a type (2) counterpart are used (balanced sets). Two protocols run on identical folds:

protocol Training (4/5 of the subjects) Test (remaining 1/5)
baseline (1) vs. (2) (1) vs. (2)
synthetic (1) vs. (3) (1) vs. (2)

The classifier is a k-nearest-neighbour classifier on top of an exchangeable feature extractor. The reported metrics follow ISO/IEC 30107-3:

  • APCER – share of attacks classified as bona fide
  • BPCER – share of bona fide samples classified as attack
  • ACER – mean of the two

Details worth knowing:

  • The synthetic samples carry no subject identity, so they cannot be split by subject. By default each fold draws as many synthetic samples as it has bona fide training samples (balance_synthetic=True), which keeps the training set the same size as in the baseline. Synthetic samples are never part of a test set.
  • "Has a counterpart" is decided per file name for PLUS and IDIAP (real and spoof captures share their names) and per subject for SCUT.
  • The metrics are computed per fold and then averaged.

Installation

From PyPI:

pip install pad-train

Directly from the repository, over SSH or HTTPS. The repository is private, so either way you need access to it:

# SSH (needs an SSH key registered with GitHub)
pip install "git+ssh://git@github.com/Kneidl18/PAD_train.git"          # latest main
pip install "git+ssh://git@github.com/Kneidl18/PAD_train.git@1.0.0"    # a release

# HTTPS (uses your git credential helper, or asks for user name and access token)
pip install "git+https://github.com/Kneidl18/PAD_train.git"            # latest main
pip install "git+https://github.com/Kneidl18/PAD_train.git@1.0.0"      # a release

For local development:

git clone git@github.com:Kneidl18/PAD_train.git
cd PAD_train
conda env create -f environment.yml    # installs the package in editable mode
conda activate pad_train

Data

The three archives (about 3 GB in total) are downloaded on demand into the data root and removed after extraction:

<data root>/
├── Data_FV_Spoofing_WS2025_26/   # types (1) and (2)
├── AE_Res-diff/                  # type (3), residual auto encoder
└── VAE-diff/                     # type (3), variational auto encoder

The data root defaults to ./data (relative to the working directory). Change it in Python, through the environment, or on the command line:

from pathlib import Path
import pad_train

pad_train.settings.data_root = Path("/mnt/datasets/finger_vein")
export PAD_TRAIN_DATA_ROOT=/mnt/datasets/finger_vein
pad-train --data-root /mnt/datasets/finger_vein download

All paths, URLs and dataset layouts live in src/pad_train/config.py.

Fetching happens in one of two ways:

  • Explicitly: pad-train download (or pad_train.download_data()).
  • Implicitly: load_data() and evaluate() download whatever is missing and simply use what is already there. Pass download=False to forbid it.

pip cannot run code at install time for wheels, so there is no pip install --download-datasets. Run pad-train download after installing.

Usage

import pad_train

results = pad_train.evaluate(extractors=["fourier", "haralick"])
print(results)
dataset extractor  protocol train_attack   APCER   BPCER    ACER
   PLUS   fourier  baseline        spoof  ...
   PLUS   fourier synthetic       AE_Res  ...
   PLUS   fourier synthetic          VAE  ...
   ...

train_attack names the attack samples used for training: spoof for the baseline, otherwise the synthetic source. All rates are fractions in [0, 1].

Useful arguments of evaluate():

Argument Default Meaning
datasets all e.g. ["PLUS"]
extractors ("fourier",) registered names or callables
synthetic all e.g. ["VAE"]
protocols both "baseline" and/or "synthetic"
n_neighbors per dataset k of the k-NN (PLUS 6, IDIAP 1, SCUT 1)
balance_synthetic True sub-sample the synthetic training samples
per_fold False one row per fold instead of the mean
download True fetch missing data

Lower-level building blocks: load_data() returns the images of one dataset, evaluate_dataset() evaluates images, cross_validate() evaluates precomputed feature matrices.

The same from the command line:

pad-train evaluate --datasets PLUS IDIAP --extractors fourier haralick --output results.csv
pad-train evaluate --help

Feature extractors

A feature extractor is any callable that maps a grayscale image (2-D uint8 array) to a 1-D feature vector whose length does not depend on the image size.

Name Description
fourier log energy of the magnitude spectrum in radial bands, thirds per dataset
haralick 13 Haralick texture statistics, averaged over the four directions

Fourier settings

fourier uses only the magnitude of the spectrum; the phase is discarded. The 30 bands are grouped into thirds (1 = low, 2 = middle, 3 = high frequencies), and each dataset uses its own selection:

Dataset fourier_thirds Bands
PLUS (1, 2, 3) 0–29
IDIAP (1, 2) 0–19
SCUT (1,) 0–9

evaluate() and the command line apply these automatically. To change them, edit fourier_thirds in _default_datasets() in src/pad_train/config.py, or override them at run time:

pad_train.settings.datasets["SCUT"].fourier_thirds = (1, 2)

Adding an extractor

Adding one takes a single decorator, in this package (src/pad_train/features/) or in your own code:

import numpy as np
import pad_train


@pad_train.register_extractor("mean_std")
def mean_std(image):
    return np.array([image.mean(), image.std()])


# a parametrised variant of a built-in one: the 10 lowest frequency bands
pad_train.register_extractor("fourier_low", pad_train.make_fourier_extractor(bands=range(10)))

pad_train.evaluate(extractors=["fourier", "fourier_low", "mean_std"])

Additional synthetic sources

Any folder with the layout <path>/<dataset>/spoof/samples/*.png can be used as a source of type (3) samples, for example post-processed versions of the provided ones:

from pathlib import Path
from pad_train import DataSource, evaluate, settings

settings.synthetic_sources["VAE_cleaned"] = DataSource("VAE_cleaned", Path("/data/VAE_cleaned"))
evaluate(synthetic=["VAE", "VAE_cleaned"])

The dataset folder names are PLUS_matched, IDIAP and SCUT.

Development

ruff format .     # formatting
ruff check .      # linting (annotations and docstrings included)
mypy              # static type checking
pytest            # tests; they generate their own toy data, no download needed

Releasing

  1. Set the new version in pyproject.toml, merge it to main.
  2. Publish a GitHub release on main whose tag is exactly that version (e.g. 1.0.0).

The Publish to PyPI workflow then checks, builds and uploads the package.

Contributing

Contributions are welcome through pull requests on GitHub:

  1. Fork the repository, or create a branch if you have write access, and branch off dev.
  2. Make your change. New feature extractors go into src/pad_train/features/ and need a test.
  3. Run ruff format ., ruff check ., mypy and pytest; all four must pass.
  4. Open a pull request against dev and describe what changed and why.

main is protected: it only receives changes from dev through a pull request, and releases are tags on main. Bugs and ideas can be reported as issues.

Authors

The Haralick feature extraction (src/pad_train/features/haralick.py) is based on code by Chumakov, Demir and Waclawek.

Acknowledgements

The datasets used by this package are provided by Andreas Uhl (Department of Artificial Intelligence and Human Interfaces, University of Salzburg): the bona fide and presentation attack samples of PLUS, IDIAP and SCUT as well as the diffusion-based synthetic samples. The evaluation protocol follows the tasks he set in his courses on image processing and multimedia security. Thank you for making the data and the topic available.

Metadata

Release files for pad-train 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pad-train 1.0.1
File Size Uploaded
pad_train-1.0.1.tar.gz 23.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pad-train 1.0.1
File Interpreter ABI Platform
pad_train-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 45.1 kB

Release files / pad_train-1.0.1.tar.gz

Download URL pad_train-1.0.1.tar.gz
Size 23.6 kB
Tags Source
SHA-256 checksum
How to use checksums
66adef09f6c00b70fe3d41815944a82d676f7d92e0118edf356193c6caa1d437
BLAKE2b-256 checksum
How to use checksums
9f7636ef9e6eb2fe059b51ee530326d8a4ecfb8c1ab84e65c28ff2096be68d13
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release files / pad_train-1.0.1-py3-none-any.whl

Download URL pad_train-1.0.1-py3-none-any.whl
Size 21.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9020106cbaa4ab6acb90437371c82587af540ac1c5c4cad7f18dc45806ff60b6
BLAKE2b-256 checksum
How to use checksums
f70bfe8cea67d1a0a393fbeb145dacbadb063388a7829c37f93549f0ea62de7c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release history Release notifications | RSS feed

1.0.2

2 release files

This release

1.0.1 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page