SignalMap
Unsupervised condition monitoring for any recorded signal — with verdicts you can check without trusting us.
You give it healthy data. It learns what healthy looks like and flags deviations. No fault labels, no per-sensor tuning. Vibration, acoustics, current — anything you can record.
The part we care most about: every verdict it produces is a signed receipt that a stranger can verify offline with a script that imports nothing from this project. And where the method cannot honestly decide, it says REFUSED rather than guessing.
Install
python3 -m pip install 'signalmap[all]' # quote the brackets so zsh does not glob them
signalmap plugins # confirm the install
[distill] alone is enough for distill / fit / monitor on .npy and
.csv banks. Parquet needs pyarrow, which [all] includes.
Two commands
signalmap fit --dataset healthy.parquet --healthy-label normal --out detector.pt
signalmap monitor --source replay --dataset live.parquet --detector detector.pt
Nothing is labelled as a fault anywhere in that flow. examples/ reproduces it
end to end on public bearing data.
What it actually does, and where it stops
On CWRU bearing data, fitting on 945 healthy frames and monitoring 1183 frames catches 238/238 faults with 2/945 false alarms, fully unsupervised.
That number does not transfer, and we measured it rather than assuming it. On nine domains under one frozen recipe — MIMII, Paderborn, MAFAULDA and two constructed controls — the shipped alarm's gap between faulty and healthy is not above zero in eight of them, and in six it never fires at all.
Worse, the direction is not even stable. On MIMII valve and Paderborn phase current the detector ranks anomalies as more normal than normal (AUC 0.2786 and 0.3172, bootstrap CI clear of 0.5), while on MIMII slider it ranks them the expected way (0.8254). There is a theorem behind that, and it applies to any detector that sees only healthy data:
The score is a function of the healthy distribution alone. Hold the fitted detector fixed, vary only the never-observed anomaly distribution, and the AUC attains every value in [0, 1].
So "far from healthy means faulty" is an assumption, not a result. Read the study → — preregistered before any data was touched, nine domains, signed receipts, ten amendments including our own errors.
What changed because of it
The detector no longer assumes the direction. It starts out refusing:
det = DistilledDetector.fit(spec, healthy_windows)
det.decide(w).verdict # 'REFUSED' — direction unknown
v = det.calibrate_direction(anchor_windows, labels, groups=recording_ids)
v.sign, v.ci_lo, v.ci_hi # +1 or -1, only when the CI clears 0.5
det.decide(w).verdict # now 'ALARM' or 'QUIET'
A handful of labelled anchors settles the direction. Without them, REFUSED is
the correct answer, and groups makes the bootstrap resample recordings rather
than windows — twenty windows cut from one signal are one observation.
Verdicts you can check without us
Every verdict-producing command writes a signed JSON receipt: the claim, the
verdict (INCLUDED / EXCLUDED / PASS / REFUSED), the evidence, the sha256
of every input, and an Ed25519 signature. The signing key stays on your machine;
only the public key travels.
The verifier is one file that imports nothing from signalmap — stdlib plus
cryptography:
signalmap corpus --out receipts/ # the shipped verdicts
curl -sO https://raw.githubusercontent.com/MrPredic/signalmap/main/tools/verify_receipt.py
pip install cryptography
python3 verify_receipt.py receipts/cwru_rqa.receipt.json
# PASS — verdict INCLUDED, integrity only (NOT authenticity)
python3 verify_receipt.py receipts/cwru_rqa.receipt.json --pubkey <hex>
# PASS — verdict INCLUDED, authentic (pinned key)
Flip one byte in a receipt and verification fails. That does not make a verdict
true — it makes it attributable and tamper-evident. Without --pubkey you get
integrity only, and the tool says so rather than letting you misread it.
The verifier is hardened against 23 concrete attacks on the gap between the
bytes that get signed and the fields a human reads: duplicate JSON keys,
NaN/Infinity literals, integers past 2⁵³, unpaired surrogates, non-UTF-8
input, and a pinned-key check that used to compare transcriptions rather than
key bytes.
distill: features chosen with a receipt
signalmap distill --bank recordings/ --label-by prefix --out artifacts/spec.json
Searches a compositional feature grammar for the few programs that separate
your recordings, under a capacity gate (budget = C × n_recordings). Every
run emits a gauntlet receipt: leakage-free nested LOGO accuracy, a
group-permutation p-value, a label-shuffle null, and cost per window.
Premium families (--premium rqa,coherence) run as opt-in challengers against
the distilled base and enter spec.json only on a CI-solid win. Of the eight
preregistered verdicts shipped in this repo, six are exclusions — the
mechanism refuses far more often than it admits.
More
study/— the sign-identifiability study: preregistration, theory, receipts, and the bank manifests with the sha256 of all 7623 recordings.examples/— reproduce the CWRU result and the acoustic and electrical recipes.signalmap plugins— sources, sinks, featurizers and models are all pluggable.- Model artifacts (
.pt) are a trust boundary: loaded withweights_only=True, arbitrary pickle objects rejected.
Experimental work — cross-modal discovery, compositional search, capture adapters for salvaged hardware — lives behind its own flags and is labelled as experimental where it appears. Nothing on this page depends on it.
Contributing
Bug reports and reproductions are the most useful contributions: if a verdict on
your data looks wrong, the receipt makes it checkable, and that is exactly the
kind of issue we want. Run pytest -q before opening a PR.
License
Apache-2.0.
Release files for signalmap 0.5.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| signalmap-0.5.3.tar.gz | 104.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| signalmap-0.5.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 226.5 kB
Release files / signalmap-0.5.3.tar.gz
| Download URL | signalmap-0.5.3.tar.gz |
|---|---|
| Size | 104.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8857a0d1a46fcafb7b4524c16ecfc82dfcca4a6a2e6d1eead91948d8f5e2f0d9
|
|
BLAKE2b-256 checksum How to use checksums |
3ea67e07692acde38f1593f8a362b24585ee03035232df8f2c3a40da5d8fb6c8
|
| 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 Aug 8, 2026.
Transparency logRelease files / signalmap-0.5.3-py3-none-any.whl
| Download URL | signalmap-0.5.3-py3-none-any.whl |
|---|---|
| Size | 122.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9b1dc761a409997267d4d9c467566ce0d15a1fa2a0ff98f4285bf0763df4abbe
|
|
BLAKE2b-256 checksum How to use checksums |
84042d3d7025b24642d5d414c3bdcdfffa89d2886c8715047278e7a505683c4b
|
| 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 Aug 8, 2026.
Transparency log