Skip to content

Latest commit

 

History

History
180 lines (128 loc) · 12.4 KB

File metadata and controls

180 lines (128 loc) · 12.4 KB

In 1918, Srinivasa Ramanujan wrote down a family of trigonometric sums to study the divisors of integers. A century later they turn out to be the perfect instrument for a factory floor: filters that hear one period and nothing else. RPR listens to a woven grid the way a piano tuner listens to a string — not asking does this look different? but does this still repeat the way it should? When a thread pulls, a pitch drifts, or a roller stamps its wear into the sheet, exactly one channel of the periodicity spectrum goes quiet. An agent then follows that silence — through millimeters and hertz, process logs and maintenance records — to the machine part that caused it.

Two questions a detector can ask

“Does this look different?” “Does this still repeat the way it should?”
Embedding-based detectors compare a patch to known-good patches in feature space. A thin scratch across a regular grid barely moves an embedding — the pattern is broken, and the detector shrugs. Ramanujan sums give filters with a rare property: the period-q filter responds only to genuinely period-q structure — not its harmonics, not its neighbors. Break the repeat, and exactly the right channel drops.

How it fits together

%%{init: {"theme": "base", "themeVariables": {
  "primaryColor": "#F0EEE6", "primaryTextColor": "#191919", "primaryBorderColor": "#B0AEA5",
  "lineColor": "#D97757", "fontFamily": "Georgia, serif", "fontSize": "15px"}}}%%
flowchart LR
    A[texture image<br><i>camera on the line</i>] --> B[Ramanujan filter bank<br><i>c_q filters, rows + cols<br>HCN period set, 2x faster</i>]
    B --> C[periodicity signature<br><i>per 32x32 patch,<br>vs memory bank of good</i>]
    C --> D[anomaly heatmap<br><i>cosine NN residual</i>]
    C --> E[disrupted period q<br><i>divisor-family attribution</i>]
    E --> F[physical mapping<br><i>q x pixel pitch → mm<br>belt speed ÷ mm → Hz</i>]
    F --> G[Claude-powered agent<br><i>process logs, incidents,<br>maintenance records</i>]
    G --> H[ranked root causes<br><i>every claim cites a number</i>]
    style D fill:#D97757,color:#ffffff,stroke:#D97757
    style E fill:#D97757,color:#ffffff,stroke:#D97757
    style H fill:#191919,color:#FAF9F5,stroke:#191919
Loading

The three demo textures (MVTec-AD directory layout, generated by data/make_demo_data.py):

grid · 8 px pitch · M-101 mesh · 12 px pitch · M-102 weave · 24 px weft · M-103
grid texture mesh texture weave texture

Validated before built on

The synthetic gate had to pass before anything else was written: an injected scratch and hole on a period-8 grid must spike ~100× above clean regions, and the reported disrupted period must be q = 8 — the texture’s true pitch — on the top patch and 9 of the top-10.

Synthetic validation: a period-8 grid with an injected scratch and hole, the RPR anomaly heatmap lighting both defects, and the periodicity spectrum with q = 8 annotated as disrupted

Injected scratch + hole on a period-8 grid: the heatmap lights both defects; the spectrum names the broken period.


Clean vs defect score separation histogram, and timing bars showing the HCN period set scoring 2x faster than the linear sweep

Clean vs defect separation, and the highly-composite-number speedup: 8 candidate periods instead of 32, ~2× faster.

Where it beats an embedding baseline

Four demo cases comparing RPR and embedding-baseline heatmaps: clean, scratch, stain, pitch shift
  • Clean → both channels stay quiet. Scratch → both fire, but only RPR names q = 8.
  • Stain → invisible to the baseline (0.0002, clean level) while RPR flags it at 0.036 — ~180× its clean score.
  • Pitch shift → texture that still looks right but repeats at the wrong pitch: RPR lights the entire band.
Periodicity spectrum of the pitch-shifted patch: the q=8 channel drops 80% versus the nearest clean reference

The plot embeddings can’t draw: the pitch-shifted patch’s q = 8 channel drops 80% against its nearest clean reference.

An agent that argues with numbers

q = 8 px  →  × 0.5 mm/px  =  4.0 mm repeat  →  ÷ into 200 mm/s  =  50.0 Hz
                                 50.0 Hz  =  gear mesh (2.0 Hz roller × 25 teeth)

The agent (Claude claude-opus-4-8, tool loop, schema-constrained JSON) investigates with four plant-data tools:

▸ get_process_parameters("M-101")            temperature_C: 82.1   belt_speed_mm_s: 200.0
▸ convert_period_to_physical_frequency(8)    spatial_period_mm: 4.0   temporal_frequency_hz: 50.0
▸ search_incident_history_by_frequency(50.0) INC-001 · 50.0 Hz · deviation 0.0% · same machine
▸ get_maintenance_log("M-101")               2026-06-28 · drive gear · non-OEM spare installed

Top root cause — confidence 0.95 Drive gear tooth wear → tension flutter at gear-mesh frequency

  • 8 px × 0.5 mm/px = 4.0 mm; at 200 mm/s → 50.0 Hz
  • Incident INC-001 matched at 0.0% deviation
  • 50.0 Hz = the machine’s gear-mesh frequency (2.0 Hz × 25 teeth)
  • Maintenance 2026-06-28: non-OEM drive gear installed

Recommended action: replace drive gear, re-tension belt.

Every hypothesis must cite (a) a specific parameter reading, (b) a matched incident with its % frequency deviation, or (c) an explicit no strong match — no generic “inspect the machine” filler. Without an ANTHROPIC_API_KEY, a deterministic matcher runs the same tools, so the demo never breaks.

Quickstart

pip install -r requirements.txt
python data/make_demo_data.py                 # generate the demo dataset
PYTHONPATH=. python tests/test_math_core.py   # unit tests (Ramanujan identities)
PYTHONPATH=. python tests/test_synthetic.py   # Phase-1 gate: synthetic validation
PYTHONPATH=. streamlit run frontend/app.py    # dashboard
# optional API server: PYTHONPATH=. uvicorn backend.api:app

Set ANTHROPIC_API_KEY (see .env.example) to run the root-cause agent on Claude; leave it unset for the offline deterministic path.

The math, in plain terms

Ramanujan sums — exact-period selectivity

c_q(n) = Σ exp(2πikn/q) over the k ∈ [1,q] coprime with q. The magic property: c_q contains only the frequencies k/q with gcd(k,q)=1 — the frequencies that belong to period q and to no smaller period. A comb filter at period 8 also fires for period-4 and period-2 signals; a Ramanujan filter at q=8 fires only for genuinely period-8 structure. (rpr/ramanujan_sums.py, tested against the identities c_q(0)=φ(q), c_q(n)=μ(q) for gcd(n,q)=1, integrality, periodicity.)

Ramanujan Filter Bank — and the normalization that actually matters

For each candidate period q we tile c_q(n) into an FIR filter, convolve, and square — “period-q energy” at each position (Vaidyanathan & Ramamurthy 2014). One detail cost us a debugging session: with plain unit-norm filters the channel energy is (energy in q's harmonic family) / φ(q), which hands low-φ channels a gain boost — a 25%-duty grid of period 8 then measures equal energy at q=4 and q=8. We normalize filters by √φ(q) (“equal per-harmonic gain”) so channel energies are comparable across q and the true fundamental wins. (rpr/filter_bank.py)

Candidate periods from highly composite numbers — 8 periods instead of 32, 2× faster

Instead of sweeping q = 1..32 we take the highly composite numbers ≤ 32 (1, 2, 4, 6, 12, 24) and close under divisors: {1, 2, 3, 4, 6, 8, 12, 24} — 8 candidate periods instead of 32, ~2× faster scoring with identical detection on the validation suite. HCNs have the richest divisor lattices, covering the periods that occur in engineered textures while skipping large primes. (rpr/period_selection.py)

Patch signatures, memory bank, and calibrated thresholds

Each RFB filter runs along rows and columns of the whole image; per-patch mean energies (via integral images) give a 2×|Q| signature per 32×32 patch. Signatures from good images form a memory bank; a test patch’s anomaly score is its cosine distance to the nearest bank entry. The anomaly threshold is calibrated on clean data by leave-one-image-out scoring of the reference set. (rpr/periodicity_signature.py, rpr/anomaly_scoring.py, backend/pipeline.py)

Which period broke? — divisor-family attribution

Attribution runs on raw (un-normalized) energies — a defect that erases texture also injects broadband energy, which masks the loss on normalized signatures. And it aggregates over divisor families: in Ramanujan periodicity theory a period-Q signal decomposes exactly over the c_d subspaces for d | Q, so a disrupted period-8 grid loses energy at q=8 and q=4. Each candidate fundamental is scored by its family’s summed energy drop, requiring the fundamental’s own channel to drop above the bank’s noise floor and to carry ≥20% of the family loss — which disqualifies both bare harmonics and spurious superperiods whose channels gained energy from the defect. (rpr/anomaly_scoring.py)

Scope and honest limitations

  • Periodic textures only. RPR is scoped to MVTec-AD-style periodic texture categories (grid, carpet, tile, wood, leather, mesh) and ships with a standard embedding baseline as the second channel for non-periodic categories. We do not claim RPR outperforms embedding baselines on non-periodic objects.
  • Known misses, stated. 26/27 correct verdicts on the demo set; the two misses are thin scratches on the coarse weave, just under the calibrated threshold.
  • Baseline channel. OpenCLIP ViT-B/32 when its weights are loadable; otherwise a multi-scale Gabor + local-statistics embedding (this build environment’s network policy blocked weight downloads). Results are labeled with whichever backend produced them.
  • Demo plant data. Process logs, incidents, and maintenance records are mock data crafted for a self-consistent story; the agent tooling is real — swap backend/tools.py for live sources.
  • Attribution of non-periodicity defects. Stains don’t disrupt any period — they’re caught through DC/low-frequency channels, and their reported “disrupted period” comes from a fallback path (largest energy injection).

Repository layout

rpr/        math core: ramanujan_sums, period_selection, filter_bank,
            periodicity_signature, anomaly_scoring
backend/    pipeline, baseline (CLIP/Gabor), physical_mapping, tools,
            root_cause_agent (Claude + deterministic), FastAPI api
frontend/   Streamlit dashboard
data/       demo dataset generator + mock process/incident/maintenance data
tests/      math-core unit tests + the synthetic validation gate
site/       the interactive project page (served at infinitule.github.io/RPR)
media/      repository banner · scripts/  figure + banner generation
docs/       video master prompt

Ramanujan Filter Bank after P. P. Vaidyanathan & P. Ramamurthy, “Ramanujan filter banks for estimation and tracking of periodicities,” IEEE ICASSP 2014. Root-cause analysis by a Claude-powered agent. Built at hackathon speed; validated before built on.