Spaces:
Running on Zero
Running on Zero
| """The deterministic methods, as first-class adapters. | |
| Directive §4, and it is the sentence most easily skipped in the whole document: | |
| > Do not use a neural model when deterministic signal processing is better. | |
| So optical flow, FFT periodicity, contour measurement and reference-marker | |
| calibration are registered here alongside SAM and DINOv3 rather than living in a | |
| utilities module. They sit in the same registry, answer the same | |
| `availability()`, and appear in the same listing, because a reader comparing the | |
| stack should see that two of the capabilities with the clearest path forward | |
| need no weights at all. | |
| **These adapters are always available**, which no other adapter in this package | |
| can say. There is no artefact to be absent, no card to be checksummed, no | |
| licence to be refused and no gate to fail — which is most of the argument for | |
| preferring them. `availability()` still exists and still answers, because a | |
| caller should not have to know which kind of adapter it is holding. | |
| The honesty property is not weaker here, it is only located differently. A | |
| neural adapter refuses by having no model; these refuse by measuring whether the | |
| signal was present, in `periodicity`'s two gates and in `geometry`'s refusal to | |
| invent a scale. | |
| """ | |
| from __future__ import annotations | |
| from pathlib import Path | |
| from app.adapters.base import ( | |
| Adapter, | |
| AdapterSpec, | |
| Availability, | |
| MeasuredCost, | |
| Measurement, | |
| Modality, | |
| Placement, | |
| Task, | |
| ) | |
| from app.adapters.signal.geometry import ( | |
| NoReference, | |
| Scale, | |
| measure_region, | |
| scale_from_marker, | |
| ) | |
| from app.adapters.signal.respiration import RespirationResult, respiratory_rate | |
| RESPIRATION_SPEC = AdapterSpec( | |
| adapter_id="respiration-flow-fft", | |
| runtime="opencv-numpy", | |
| tasks=(Task.MEASURE,), | |
| modalities=(Modality.VIDEO,), | |
| directive_role=( | |
| "§14 cattle respiratory rate — video, flank region, optical flow, " | |
| "periodicity, FFT, breaths per minute. §4 names optical flow, FFT and " | |
| "periodic motion analysis as OpenCV work rather than model work." | |
| ), | |
| requires_artefact=False, | |
| placement=Placement.CPU_SERVICE, | |
| placement_reason=( | |
| "No weights, and the arithmetic is cheap — but the clip is long. Dense " | |
| "flow costs 4.3 ms per frame pair at 320 px, so §14's 30–60 second " | |
| "capture is 4–8 seconds of flow plus decode, measured at 9.7 s median " | |
| "for a 31-second clip. **That is past the inline ceiling**, so this " | |
| "capability needs a queue rather than a bigger box. It is also the " | |
| "strongest on-device candidate in the stack: OpenCV is on the phone " | |
| "already, the video never has to leave it, and ADR 0002's offline-first " | |
| "promise is kept for free." | |
| ), | |
| measured=MeasuredCost( | |
| hardware=( | |
| "Apple M-series laptop (NOT the target container). OpenCV 5.0.0 " | |
| "reports 11 threads and ignores setNumThreads(), so a " | |
| "single-threaded figure could not be taken on this build" | |
| ), | |
| threads=11, | |
| sample=( | |
| "Cow_crosses_cattle_grid.webm, 925 frames, 30.86 s at 29.97 fps, " | |
| "whole frame, decode plus flow plus spectrum" | |
| ), | |
| runs=7, | |
| median_seconds=9.68, | |
| peak_rss_mb=239.0, | |
| measured_on="2026-08-21", | |
| ), | |
| notes=( | |
| "**The latency figure is load-sensitive and should be read as a band, " | |
| "not a point.** Seven runs give a 9.68 s median over a 9.29–12.58 s " | |
| "spread, and separate sessions on the same machine and the same clip " | |
| "produced medians of 12.15 s and 14.17 s. The previously recorded " | |
| "9.07 s / 331 MB does not reproduce in any configuration tried: memory " | |
| "is consistently around 239 MB, and no threading setting moves the " | |
| "latency, because this OpenCV build does not honour setNumThreads. " | |
| "What survives all of it is the conclusion — every measurement is past " | |
| "the 8 s inline ceiling, so this capability needs a queue.\n\n" | |
| "**What the metronome validates is the extractor, not this adapter.** " | |
| "On footage whose Commons description states 96 beats per minute, " | |
| "`signal.dominant_rate` returns 96.48, and 48.38 on a crop of the " | |
| "pendulum alone, the swing being half the tick rate — a 0.5% error " | |
| "against a stated rate on real video. But `Metronome.webm` is 11.71 " | |
| "seconds, and `respiration.MIN_CAPTURE_SECONDS` is 20, so " | |
| "`measure()` refuses all three of those regions before any signal " | |
| "processing runs. The only clip with a ground truth cannot reach the " | |
| "code path this adapter exposes, and an earlier version of this note " | |
| "read as though it had. `tests/test_adapters.py` asserts the gap so it " | |
| "cannot be quietly re-closed in prose.\n\n" | |
| "**No cattle rate is validated** — all three real cattle clips are " | |
| "refused, two for being shorter than the capture protocol and one for " | |
| "having no clear rhythm. What is missing is not model work: it is a " | |
| "thirty-second clip of a cow's flank with somebody's counted breath " | |
| "rate beside it." | |
| ), | |
| ) | |
| GEOMETRY_SPEC = AdapterSpec( | |
| adapter_id="marker-geometry", | |
| runtime="opencv-numpy", | |
| tasks=(Task.MEASURE,), | |
| modalities=(Modality.IMAGE,), | |
| directive_role=( | |
| "§4 geometry, contour measurement and reference-marker calibration; " | |
| "§9's 'approximate visible area: 12–16 cm²' for a wound, and §22's " | |
| "fallback scale when metric depth is unreliable." | |
| ), | |
| requires_artefact=False, | |
| placement=Placement.ON_DEVICE, | |
| placement_reason=( | |
| "Marker detection and a contour area are microseconds of arithmetic on " | |
| "a phone. Running it on the device means the farmer learns the card was " | |
| "not in shot while still standing next to the animal, which is the " | |
| "difference between a re-capture and a lost record." | |
| ), | |
| notes=( | |
| "**Unmeasured, and unexercised on a real photograph.** No image " | |
| "available to this project contains an Animap reference marker, so the " | |
| "marker-detection half has never run on anything real. The arithmetic " | |
| "either side of it is exercised by unit tests. Do not quote an area " | |
| "from this until somebody has photographed a printed card beside a " | |
| "ruler." | |
| ), | |
| ) | |
| class DeterministicAdapter(Adapter): | |
| """Signal processing and geometry. Always available, never guessing.""" | |
| def __init__(self, spec: AdapterSpec) -> None: | |
| self.spec = spec | |
| def availability(self) -> Availability: | |
| # OpenCV and NumPy are production dependencies, so there is genuinely | |
| # nothing to check. Importing cv2 here to prove it would make a health | |
| # probe pay for a 60 MB import. | |
| return Availability(True) | |
| def load(self) -> "DeterministicAdapter": | |
| return self | |
| class RespirationAdapter(DeterministicAdapter): | |
| """§14, end to end.""" | |
| def __init__(self) -> None: | |
| super().__init__(RESPIRATION_SPEC) | |
| def measure( | |
| self, | |
| video_path: Path | str, | |
| *, | |
| region: tuple[float, float, float, float] | None = None, | |
| ) -> RespirationResult: | |
| return respiratory_rate(video_path, region=region) | |
| class GeometryAdapter(DeterministicAdapter): | |
| """§9 and §4, once something in the frame has a known size.""" | |
| def __init__(self) -> None: | |
| super().__init__(GEOMETRY_SPEC) | |
| def scale(self, image, marker_side_mm: float) -> Scale: | |
| """Pixels per millimetre from a printed marker. | |
| Propagates `NoReference` rather than returning a default. A frame with | |
| no marker has no scale, and the honest answer is a re-capture prompt. | |
| """ | |
| return scale_from_marker(image, marker_side_mm) | |
| def region_size(self, mask, scale: Scale) -> dict: | |
| return measure_region(mask, scale) | |
| def try_scale(self, image, marker_side_mm: float) -> Measurement: | |
| """The same thing, as a `Measurement` a runner can put in a result.""" | |
| try: | |
| found = self.scale(image, marker_side_mm) | |
| except NoReference as absent: | |
| return Measurement( | |
| kind="scale", value=None, unit="px/mm", usable=False, | |
| detail=str(absent), | |
| ) | |
| return Measurement( | |
| kind="scale", value=round(found.pixels_per_mm, 4), unit="px/mm", | |
| usable=True, | |
| support={"relative_error": round(found.relative_error, 4)}, | |
| detail=found.source, | |
| ) | |
| def deterministic_adapters() -> list[Adapter]: | |
| return [RespirationAdapter(), GeometryAdapter()] | |