Metadata-Version: 2.4
Name: gri-multitrack
Version: 0.2.0
Summary: Multi-target tracking over gri per-target estimators: ingest/routing, measurement-space scoring, GNN + MFA/MHT association, track lifecycle, LMB existence, and Poisson-binomial cardinality
Project-URL: Homepage, https://geosolresearch.com
Project-URL: Repository, https://gitlab.com/geosol-foss/python/gri-multitrack
Project-URL: Issues, https://gitlab.com/geosol-foss/python/gri-multitrack/-/issues
Project-URL: Changelog, https://gitlab.com/geosol-foss/python/gri-multitrack/-/releases
Author-email: GeoSol Research Inc <contact@geosolresearch.com>
License-Expression: MIT
License-File: LICENSE
Keywords: MFA,MHT,geolocation,lmb,multi-target,tracking
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Scientific/Engineering :: GIS
Requires-Python: >=3.12
Requires-Dist: gri-convolve>=0.2.0
Requires-Dist: gri-ell>=0.2.0
Requires-Dist: gri-geosim>=0.4.1
Requires-Dist: gri-kalman>=0.2.0
Requires-Dist: gri-obs>=0.1.0
Requires-Dist: gri-pos>=0.2.0
Requires-Dist: gri-trajectory>=0.1.0
Requires-Dist: gri-utils>=0.4.0
Requires-Dist: numpy>=2.3.3
Requires-Dist: scipy>=1.16.2
Provides-Extra: crosscheck
Requires-Dist: stonesoup>=1.8; extra == 'crosscheck'
Description-Content-Type: text/markdown

# gri-multitrack

Multi-target geolocation tracking. gri-multitrack combines the gri per-target
Kalman-IMM (`gri-kalman`, fed by `gri-obs` observables) with a multi-target policy
layer: ingest/routing of the feed-in data tiers, measurement-space scoring,
per-scan association, track lifecycle, existence (Labeled Multi-Bernoulli), and
a Poisson-binomial count distribution.

The governing seam is **"gri scores, gri-multitrack assigns"**: gri-kalman owns
per-target estimation and the measurement-space likelihoods; gri-multitrack owns
everything multi-target. The multi-target layer is **DIY on numpy/scipy** --
gri-multitrack is Stone-Soup-free (see the "Architecture pivot" in `AGENTS.md`).
The primary tracker class is `MultiTracker` (the "Crucible" name now belongs to
the companion 3D app).

See `PLAN.md` for the live program plan (state, backlog, decisions) and
`AGENTS.md` for repo guidance. Scenario generation, replay harnesses, scoring,
and the replay viewer live in the sibling **`gri-tracksim`** repo, which
depends on this engine and serializes its outputs (the engine itself is
serialization-free).

## Status

v1 of the original build plan is **complete** (see `PLAN.md`): ingest ->
score -> associate -> gri IMM update -> lifecycle -> LMB output, on geos, raw
TDOAs, and presence events, plus the outlier stream (clutter floors,
per-observation dispositions, the extensible cure catalog), the stationary
convolve resolver, split/merge with lineage, and batch RTS retrospectives.

Implemented:

- Ingest + router for the feed-in tiers (geo `Ell`, observables, presence).
- Per-track adapter over any gri `Tracker` (default `SmartSegmentedIMM`; CV /
  CoordinatedTurn / Static bank).
- Measurement-space scoring seam (Gaussian innovation + chi-squared gate).
- GNN scaffold associator (local scipy Hungarian) for bring-up.
- **MFA tracker** (`MfaTracker`): a local hypothesis-oriented MHT that defers at
  ambiguous crossings and resolves via accumulated kinematic likelihood -- the v1
  associator (loose-coupled; not Stone Soup's MFA). Standalone engine mirroring
  `MultiTracker`.
- Track lifecycle: birth from geos, M-of-N confirm, patient deletion.
- Existence r_i + Poisson-binomial count distribution.
- Presence ("is it on") -> `coast(t)` + existence bump.
- LMB output: labeled tracks + count distribution + per-track `predicted`
  horizons + the per-scan `association` diagnostic (gates / marginals /
  hypotheses), associator-agnostic.

Also implemented since the skeleton:

- Per-kind clutter likelihood floors and per-observation DISPOSITIONS
  (assigned / birthed / clutter / cured); the user-extensible `Cure` Protocol.
- The stationary resolver (convolve as the live estimator of a locked track;
  the cluster answer as `resolved`), split/merge with `TrackEvent` lineage,
  and `smoothed_tracks()` batch retrospectives.
- Serialization, GOSPA/OSPA metrics, and the replay viewer live in
  `gri-tracksim` (the engine stays serialization-free).

### Notable design choices

- **The per-track estimator is any gri-kalman `Tracker`; the default is
  `SmartSegmentedIMM`.** gri-kalman exposes one uniform interface
  (`update(ell, t)` / `update_observable` / `predict` / `coast` /
  `smoothed_track` / `result` / `is_initialized`) across `IMM`, `SmartIMM`,
  `SegmentedIMM`, and `SmartSegmentedIMM`. gri-multitrack defaults to the maneuver-segmenting,
  outlier-rejecting `SmartSegmentedIMM` the design calls for; pass
  `tracker_factory=make_imm` (or any `Tracker` factory) to swap it. The choice
  is isolated to `gri_multitrack/track.py`.
- **Stone-Soup-free; multi-target is DIY on numpy/scipy.** Both associators are
  local (GNN over scipy `linear_sum_assignment`; MFA a local hypothesis-oriented
  MHT). Stone Soup's MFA is filter-coupled and would cost the gri IMM, and is
  heavy (~48 MB of deps + `ortools`); see the `CLAUDE.md` "Architecture pivot".
  If LAP speed ever matters, add `lapsolver`/`lap` (tiny) -- not `ortools`. Stone
  Soup remains only as an optional dev-time GOSPA/OSPA cross-check.

## Install

Uses `uv` with editable path dependencies on the sibling gri repos (in
`../../foss/`).

```bash
uv sync                     # core (Stone-Soup-free)
uv sync --extra crosscheck  # optional: Stone Soup, for a dev-time GOSPA/OSPA check only
```

## Run

```bash
uv run python examples/two_target_demo.py   # end-to-end demo
uv run pytest                                # tests
uv run ruff check gri_multitrack test              # lint
uv run ty check                              # type check
```

## Quick use

```python
from gri_multitrack import MultiTracker

tracker = MultiTracker()
# each item is (payload, time_s); payload is an Ell, a gri-obs observable,
# or a PresenceObs.
outputs = tracker.process([(ell0, 0.0), (tdoa1, 1.0), (presence, 2.0)])

final = outputs[-1]
for t in final.tracks:
    print(t.label, t.existence, t.is_stationary, t.mode_probabilities)
print(final.count_distribution)  # Poisson-binomial P(N=k)
```

## Layout

- `gri_multitrack/ingest.py` -- feed-in types, routing, scan grouping.
- `gri_multitrack/track.py` -- per-track adapter over the gri IMM.
- `gri_multitrack/scoring.py` -- measurement-space likelihood + gate ("gri scores").
- `gri_multitrack/association.py` -- GNN scaffold + `Associator` protocol.
- `gri_multitrack/lifecycle.py` -- birth / confirm / delete / existence.
- `gri_multitrack/cardinality.py` -- Poisson-binomial count distribution.
- `gri_multitrack/output.py` -- Labeled Multi-Bernoulli output records.
- `gri_multitrack/tracker.py` -- the `MultiTracker` orchestrator.
