Metadata-Version: 2.5
Name: sigorbit-trainer
Version: 0.2.3
Summary: Offline, auditable training workflows for SigOrbit signature encoders
Project-URL: Homepage, https://github.com/jordi-murgo/sigorbit-trainer
Project-URL: Repository, https://github.com/jordi-murgo/sigorbit-trainer
Project-URL: Issues, https://github.com/jordi-murgo/sigorbit-trainer/issues
Project-URL: Documentation, https://github.com/jordi-murgo/sigorbit-trainer/tree/main/docs
Author-email: Jordi Murgó <jordi.murgo@gmail.com>
Maintainer-email: Jordi Murgó <jordi.murgo@gft.com>
License-Expression: MIT
License-File: LICENSE
Keywords: biometrics,pytorch,rotation-invariance,signature,training
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Requires-Dist: numpy<3,>=2.2
Requires-Dist: pillow<13,>=12.3
Requires-Dist: pydantic<3,>=2.12
Requires-Dist: safetensors<1,>=0.7
Requires-Dist: sigorbit>=0.1.0
Requires-Dist: tomli<3,>=2.4; python_version < '3.11'
Requires-Dist: torch>=2.7
Requires-Dist: torchvision>=0.22
Provides-Extra: dev
Requires-Dist: bandit[toml]<2,>=1.9.4; extra == 'dev'
Requires-Dist: build<2,>=1.3; extra == 'dev'
Requires-Dist: mypy<3,>=2.3.0; extra == 'dev'
Requires-Dist: pip-audit<3,>=2.10; extra == 'dev'
Requires-Dist: pytest-cov<8,>=7; extra == 'dev'
Requires-Dist: pytest<10,>=9; extra == 'dev'
Requires-Dist: ruff<1,>=0.15; extra == 'dev'
Requires-Dist: twine<8,>=7.0.0; extra == 'dev'
Provides-Extra: hf
Requires-Dist: datasets<6,>=4; extra == 'hf'
Description-Content-Type: text/markdown

# SigOrbit Trainer

Offline, auditable training workflows for [SigOrbit](https://github.com/jordi-murgo/sigorbit).
The project trains the 257 px SO(2)-canonicalized signature encoder from random
initialization without requiring an unpublished initializer. C8 and C4
steerable backbones are supported; the C4 variant trains 2.7× faster with
comparable margin.

> **Code-only boundary:** MIT covers this repository's code only. This project
> contains no signatures, datasets, embeddings, checkpoints, or trained weights,
> and does not grant rights to any of them. A public dataset listing or Hugging
> Face card is not evidence of permission.

## Status

**Alpha.** The source is published on GitHub; there is no PyPI release yet.
The three-stage trainer, epoch-boundary resume, strict provenance records and a
completed from-scratch 257 px run are implemented. Historical checkpoint metrics
remain historical evidence, not a reproduction claim.

## Architecture and pipeline

The trainer does not define a second neural network. It pins `sigorbit==0.1.0`
and imports the runtime package's `ModelConfig`, `SteerableEncoder` and
`CanonicalizedEncoder` classes:

![Mermaid diagram](https://mermaid.ink/svg/JSV7aW5pdDogeyJ0aGVtZSI6ImJhc2UiLCJ0aGVtZVZhcmlhYmxlcyI6eyJwcmltYXJ5Q29sb3IiOiIjZThmMGZlIiwicHJpbWFyeVRleHRDb2xvciI6IiMxYTFhMWEiLCJwcmltYXJ5Qm9yZGVyQ29sb3IiOiIjNGE3YWI1IiwibGluZUNvbG9yIjoiIzY2NjY2NiIsImJhY2tncm91bmQiOiIjZmZmZmZmIiwibWFpbkJrZyI6IiNmNWY4ZmMiLCJjbHVzdGVyQmtnIjoiI2ZhZmJmYyIsImNsdXN0ZXJCb3JkZXIiOiIjYzhkM2UwIn19fSUlCmZsb3djaGFydCBUQgogICAgc3ViZ3JhcGggSW5wdXRbIlByZXByb2Nlc3NpbmciXQogICAgICAgIElNR1siQ3JvcHBlZCBzaWduYXR1cmUgaW1hZ2UiXQogICAgICAgIEdSQVlbIkdyYXlzY2FsZSDihpIgMjU3w5cyNTcgYmljdWJpYyDihpIgWy0xLCAxXSJdCiAgICAgICAgSU1HIC0tPiBHUkFZCiAgICBlbmQKCiAgICBzdWJncmFwaCBDYW5vblsiT3JpZW50YXRpb25DYW5vbmljYWxpemVyIChTTygyKSkiXQogICAgICAgIGRpcmVjdGlvbiBUQgogICAgICAgIENDMVsiQ29udjJkIDHihpIxNiBrNSBzMiBwMjxici8-KyBSZUxVICsgQmF0Y2hOb3JtMmQiXQogICAgICAgIENDMlsiQ29udjJkIDE24oaSMzIgazUgczIgcDI8YnIvPisgUmVMVSArIEJhdGNoTm9ybTJkIl0KICAgICAgICBDQzNbIkNvbnYyZCAzMuKGkjY0IGszIHMyIHAxPGJyLz4rIFJlTFUgKyBCYXRjaE5vcm0yZCJdCiAgICAgICAgQ0NQWyJBZGFwdGl2ZUF2Z1Bvb2wyZCgxKSDihpIgRmxhdHRlbiJdCiAgICAgICAgQ0xJTlsiTGluZWFyIDY04oaSMjxici8-4oaSIChjb3MgzrgsIHNpbiDOuCksIEwyLW5vcm1hbGl6ZWQiXQogICAgICAgIENBRkZbImFmZmluZV9ncmlkICsgZ3JpZF9zYW1wbGU8YnIvPihiaWN1YmljLCByb3RhdGlvbiBvbmx5KSJdCiAgICAgICAgQ0MxIC0tPiBDQzIgLS0-IENDMyAtLT4gQ0NQIC0tPiBDTElOIC0tPiBDQUZGCiAgICBlbmQKCiAgICBzdWJncmFwaCBCYWNrYm9uZVsiU3RlZXJhYmxlRW5jb2RlciAoQzQgb3IgQzgtc3RlZXJhYmxlIENOTiwgZTJjbm4pIl0KICAgICAgICBkaXJlY3Rpb24gVEIKICAgICAgICBTVEVNWyJTdGVtPGJyLz5SMkNvbnYgMeKGkjI0wrdOIHJlZ3VsYXIgazcgcDM8YnIvPisgSW5uZXJCYXRjaE5vcm0gKyBSZUxVPGJyLz4rIEJsdXJQb29sIC8yIChOID0gZ3JvdXBfb3JkZXIpIl0KICAgICAgICBMMVsiTGF5ZXIgMSAoLzQpPGJyLz5SMkNvbnYgMjTihpI0OCBrNSBwMiArIElCTiArIFJlTFU8YnIvPlIyQ29udiA0OOKGkjQ4IGs1IHAyICsgSUJOICsgUmVMVTxici8-KyBCbHVyUG9vbCAvMiJdCiAgICAgICAgTDJbIkxheWVyIDIgKC84KTxici8-UjJDb252IDQ44oaSOTYgazUgcDIgKyBJQk4gKyBSZUxVPGJyLz5SMkNvbnYgOTbihpI5NiBrNSBwMiArIElCTiArIFJlTFU8YnIvPisgQmx1clBvb2wgLzIiXQogICAgICAgIEwzWyJMYXllciAzICgvMTYpPGJyLz5SMkNvbnYgOTbihpIxMjggazUgcDIgKyBJQk4gKyBSZUxVPGJyLz5SMkNvbnYgMTI44oaSMTI4IGs1IHAyICsgSUJOICsgUmVMVTxici8-KyBCbHVyUG9vbCAvMiJdCiAgICAgICAgR1BbIkdyb3VwUG9vbGluZzxici8-bWF4IG92ZXIgQzQvQzggZmliZXI8YnIvPuKGkiAxMjggaW52YXJpYW50IGNoYW5uZWxzIl0KICAgICAgICBTVEVNIC0tPiBMMSAtLT4gTDIgLS0-IEwzIC0tPiBHUAogICAgZW5kCgogICAgc3ViZ3JhcGggSGVhZFsiRW1iZWRkaW5nIGhlYWQiXQogICAgICAgIGRpcmVjdGlvbiBUQgogICAgICAgIFBPT0xbIkFkYXB0aXZlQXZnUG9vbDJkKDEpPGJyLz7ihpIgRmxhdHRlbiDihpIgMTI4Il0KICAgICAgICBGQzFbIkxpbmVhciAxMjjihpI1MTI8YnIvPisgQmF0Y2hOb3JtMWQgKyBSZUxVPGJyLz4rIERyb3BvdXQgMC4zIl0KICAgICAgICBGQzJbIkxpbmVhciA1MTLihpIyNTY8YnIvPisgQmF0Y2hOb3JtMWQiXQogICAgICAgIE5PUk1bIkwyLW5vcm1hbGl6ZSJdCiAgICAgICAgUE9PTCAtLT4gRkMxIC0tPiBGQzIgLS0-IE5PUk0KICAgIGVuZAoKICAgIEdSQVkgLS0-IENDMQogICAgQ0FGRiAtLT4gU1RFTQogICAgR1AgLS0-IFBPT0wKICAgIE5PUk0gLS0-IE9VVFsiMjU2LUQgTDItbm9ybWFsaXplZCBlbWJlZGRpbmciXQoKICAgIHN0eWxlIElucHV0IGZpbGw6IzFhMmEzYyxjb2xvcjojZmZmCiAgICBzdHlsZSBDYW5vbiBmaWxsOiMxYTNhNWMsY29sb3I6I2ZmZgogICAgc3R5bGUgQmFja2JvbmUgZmlsbDojMmQ1YTJkLGNvbG9yOiNmZmYKICAgIHN0eWxlIEhlYWQgZmlsbDojNGEzYTFjLGNvbG9yOiNmZmYKICAgIHN0eWxlIE9VVCBmaWxsOiM1YzFhMWEsY29sb3I6I2ZmZgo)
The end-to-end workflow wraps three training stages:

1. validate an immutable local manifest and a separate rights attestation;
2. train the C4 or C8 backbone from random weights with PK sampling, discrete rotation
   augmentation and a temporary ArcFace head;
3. restore the best backbone and train only the SO(2) canonicalizer against
   known synthetic angles;
4. jointly optimize canonicalizer, backbone and ArcFace head using identity,
   circular-orientation and cosine-consistency objectives;
5. evaluate on signer-disjoint validation identities, restore the best joint
   checkpoint and export an inference-only SigOrbit artifact.

There are no reflections: the symmetry is SO(2)+C_N (N = group_order), not O(2)/D_N.
The ArcFace classifier, optimizers and schedulers exist only in recovery
checkpoints; the deployable artifact contains the canonicalizer and backbone.

## Install for development

```bash
git clone https://github.com/jordi-murgo/sigorbit-trainer.git
cd sigorbit-trainer
uv sync --extra dev
```

## Safe smoke test

The smoke source generates non-person synthetic strokes in memory:

```bash
uv run sigorbit-train config validate configs/smoke.toml
uv run sigorbit-train run configs/smoke.toml
```

Interrupted runs can resume from the directory named by
`RUN/checkpoints/latest.json`:

```bash
sigorbit-train resume CONFIG.toml --checkpoint RUN/checkpoints/stage=...-epoch=...-step=...
```

Resume is exact at a completed epoch boundary when the configuration, dataset,
class map, architecture, device topology, optimizer, scheduler, and RNG schema
match. The current alpha does not checkpoint mid-epoch.

Runtime controls keep the optimization schedule separate from resource-saving
stops. `joint.epochs` remains the cosine-scheduler horizon, while
`joint.min_epochs` prevents patience from stopping the joint stage before that
floor. `runtime.precision = "bf16"` autocasts CUDA training forwards after a
startup capability check; parameters and exported weights remain FP32.

The checkpoint directory retains only targets referenced by `latest.json` and
the per-stage `best-*.json` pointers. Superseded epoch checkpoints are removed
after each atomic pointer update.

## Training with authorized local data

Training never downloads data inside the trainer process. A standalone script
downloads, deduplicates, and materializes an authorized Hugging Face imagefolder
dataset into the offline contract in one step:

```bash
HF_TOKEN=hf_... scripts/prepare_dataset.sh
```

Options: `HF_DATASET`, `HF_REVISION`, `DATASET_ID`, `DEDUPLICATE`,
`OUTPUT_DIR`, `WORK_DIR`. See `scripts/prepare_dataset.sh --help` for details.

If an authorized dataset is already saved as a local Hugging Face `DatasetDict`,
materialize it manually:

```bash
sigorbit-train dataset import-hf-disk \
  /secure/source-dataset \
  /secure/sigorbit-dataset \
  --dataset-id authorized-signatures \
  --revision IMMUTABLE_SOURCE_REVISION \
  --assert-genuine-only

sigorbit-train dataset attest \
  /secure/sigorbit-dataset/dataset.toml \
  /secure/sigorbit-dataset/rights.attestation.json \
  --purpose research-only \
  --authorization-reference APPROVAL_REFERENCE \
  --assert-authorized-use

sigorbit-train dataset validate \
  /secure/sigorbit-dataset/dataset.toml \
  --attestation /secure/sigorbit-dataset/rights.attestation.json

uv run sigorbit-train run configs/c8-257-final.toml
```

The importer only reads an already-local directory. `attest` records the
operator's assertion and does not create legal rights. Keep both materialized
data and attestation outside the repository.

The historical CEDAR/BHSig260-derived aggregate may only be used after acquiring
the sources under applicable terms and documenting authorization. Its trained
weights must not be published or used commercially without a separate legal,
privacy, and model-release review.

Read [`docs/DATA_POLICY.md`](https://github.com/jordi-murgo/sigorbit-trainer/blob/main/docs/DATA_POLICY.md),
[`docs/REPRODUCIBILITY.md`](https://github.com/jordi-murgo/sigorbit-trainer/blob/main/docs/REPRODUCIBILITY.md), and
[`docs/MODEL_RELEASE_POLICY.md`](https://github.com/jordi-murgo/sigorbit-trainer/blob/main/docs/MODEL_RELEASE_POLICY.md) before training.

The validated configurations and observed runs are documented in
[`docs/TRAINING_RECIPE.md`](https://github.com/jordi-murgo/sigorbit-trainer/blob/main/docs/TRAINING_RECIPE.md),
[`docs/RESULTS_c8-257-final.md`](https://github.com/jordi-murgo/sigorbit-trainer/blob/main/docs/RESULTS_c8-257-final.md), and
[`docs/RESULTS_c4-257-b64.md`](https://github.com/jordi-murgo/sigorbit-trainer/blob/main/docs/RESULTS_c4-257-b64.md).

## Randomized splits

Manifest splits can be repartitioned by writer before training, which is the
recommended way to check that a result is not an artefact of one partition:

```bash
sigorbit-train dataset split-preview CONFIG.toml --random-seed 1234
sigorbit-train run CONFIG.toml --random-seed 1234
```

Splits move whole signers, never individual samples, and the seed enters the
dataset fingerprint so a resume cannot silently change the partition. See
[`docs/DATASET_MANIFEST.md`](https://github.com/jordi-murgo/sigorbit-trainer/blob/main/docs/DATASET_MANIFEST.md).

## Evaluation

Training reports clean leave-one-out retrieval plus per-angle top-1, median
margin, and fragile rate on validation identities. The manifest `test` split is
never used for selection and requires an explicit opt-in:

```bash
sigorbit-train evaluate CONFIG.toml \
  --checkpoint RUN/sigorbit-c8-257-retrained-v1.pt --split validation
sigorbit-train evaluate CONFIG.toml \
  --checkpoint RUN/sigorbit-c8-257-retrained-v1.pt --split test --allow-test-split
```

## Relationship with SigOrbit

`sigorbit-trainer` owns data manifests, augmentation, losses, optimization,
resume state, evaluation, and run provenance. `sigorbit` owns the encoder
architecture, inference preprocessing, and runtime checkpoint compatibility.
The trainer pins `sigorbit==0.1.0`; architecture changes require a new trainer
release and model identifier.

## Licence

MIT for this repository's code. Dataset, model-weight, privacy, and third-party
rights are separate. See `NOTICE`.
