Trackers

End-to-end tracking algorithms.

End-to-end tracker implementations.

This module provides complete tracker implementations that combine filtering, data association, and track management.

class pytcl.trackers.SingleTargetTracker(state_dim, meas_dim, F, H, Q, R, gate_threshold=None)[source]

Bases: object

Single-target tracker using Kalman filtering.

This tracker maintains a single track and provides predict/update functionality with optional gating.

Parameters:
  • state_dim (int) – Dimension of state vector.

  • meas_dim (int) – Dimension of measurement vector.

  • F (callable or ndarray) – State transition matrix or function F(dt) -> ndarray.

  • H (ndarray) – Measurement matrix.

  • Q (callable or ndarray) – Process noise covariance or function Q(dt) -> ndarray.

  • R (ndarray) – Measurement noise covariance.

  • gate_threshold (float, optional) – Chi-squared gate threshold (default: None, no gating).

Examples

>>> import numpy as np
>>> # Constant velocity model in 2D
>>> F = lambda dt: np.array([[1, dt, 0, 0],
...                          [0, 1, 0, 0],
...                          [0, 0, 1, dt],
...                          [0, 0, 0, 1]])
>>> H = np.array([[1, 0, 0, 0],
...               [0, 0, 1, 0]])
>>> Q = lambda dt: 0.1 * np.eye(4)
>>> R = np.eye(2) * 0.5
>>> tracker = SingleTargetTracker(4, 2, F, H, Q, R)
>>> tracker.initialize(np.array([0, 1, 0, 1]), np.eye(4))
>>> tracker.predict(1.0)
>>> tracker.update(np.array([1.1, 1.2]))
__init__(state_dim, meas_dim, F, H, Q, R, gate_threshold=None)[source]
initialize(state, covariance, time=0.0)[source]

Initialize the tracker with initial state.

Parameters:
  • state (array_like) – Initial state estimate.

  • covariance (array_like) – Initial state covariance.

  • time (float, optional) – Initial time (default: 0).

property is_initialized: bool

Check if tracker is initialized.

property state: TrackState | None

Get current track state.

predict(dt)[source]

Predict state to new time.

Parameters:

dt (float) – Time step.

Returns:

Predicted state.

Return type:

TrackState

Raises:

RuntimeError – If tracker is not initialized.

update(measurement)[source]

Update state with measurement.

Parameters:

measurement (array_like) – Measurement vector.

Returns:

  • state (TrackState) – Updated state.

  • likelihood (float) – Measurement likelihood (Mahalanobis distance).

Raises:

RuntimeError – If tracker is not initialized.

Return type:

tuple[TrackState, float]

predict_measurement()[source]

Predict measurement and innovation covariance.

Returns:

  • z_pred (ndarray) – Predicted measurement.

  • S (ndarray) – Innovation covariance.

Return type:

tuple[ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[float64]]]

class pytcl.trackers.TrackState(state, covariance, time)[source]

Bases: NamedTuple

State of a single target track.

state

State estimate vector.

Type:

ndarray

covariance

State covariance matrix.

Type:

ndarray

time

Time of state estimate.

Type:

float

state: ndarray[tuple[Any, ...], dtype[float64]]

Alias for field number 0

covariance: ndarray[tuple[Any, ...], dtype[float64]]

Alias for field number 1

time: float

Alias for field number 2

class pytcl.trackers.MultiTargetTracker(state_dim, meas_dim, F, H, Q, R, gate_probability=0.99, confirm_hits=3, confirm_window=5, max_misses=5, init_covariance=None)[source]

Bases: object

Multi-target tracker with GNN data association.

This tracker maintains multiple tracks and handles: - Track initiation from unassociated measurements - Track update via GNN data association - Track confirmation (M-of-N logic) - Track deletion (miss count)

Parameters:
  • state_dim (int) – Dimension of state vector.

  • meas_dim (int) – Dimension of measurement vector.

  • F (callable or ndarray) – State transition matrix or function F(dt) -> ndarray.

  • H (ndarray) – Measurement matrix.

  • Q (callable or ndarray) – Process noise covariance or function Q(dt) -> ndarray.

  • R (ndarray) – Measurement noise covariance.

  • gate_probability (float, optional) – Gate probability for association (default: 0.99).

  • confirm_hits (int, optional) – Hits needed to confirm track (default: 3).

  • confirm_window (int, optional) – Window for M-of-N confirmation (default: 5).

  • max_misses (int, optional) – Consecutive misses before deletion (default: 5).

  • init_covariance (ndarray, optional) – Initial covariance for new tracks. If None, uses 100*R projected to state.

Examples

>>> import numpy as np
>>> # Constant velocity model
>>> F = lambda dt: np.array([[1, dt, 0, 0],
...                          [0, 1, 0, 0],
...                          [0, 0, 1, dt],
...                          [0, 0, 0, 1]])
>>> H = np.array([[1, 0, 0, 0],
...               [0, 0, 1, 0]])
>>> Q = lambda dt: 0.1 * np.eye(4)
>>> R = np.eye(2) * 0.5
>>> tracker = MultiTargetTracker(4, 2, F, H, Q, R)
>>> # Process measurements
>>> measurements = [np.array([1, 2]), np.array([5, 6])]
>>> tracks = tracker.process(measurements, dt=1.0)
__init__(state_dim, meas_dim, F, H, Q, R, gate_probability=0.99, confirm_hits=3, confirm_window=5, max_misses=5, init_covariance=None)[source]
property tracks: List[Track]

Get list of active tracks.

property confirmed_tracks: List[Track]

Get list of confirmed tracks only.

process(measurements, dt)[source]

Process measurements at new time step.

Parameters:
  • measurements (list of array_like) – List of measurement vectors.

  • dt (float) – Time step since last update.

Returns:

Active tracks after update.

Return type:

list of Track

class pytcl.trackers.Track(id, state, covariance, status, hits, misses, time)[source]

Bases: NamedTuple

Multi-target track.

id

Unique track identifier.

Type:

int

state

State estimate vector.

Type:

ndarray

covariance

State covariance matrix.

Type:

ndarray

status

Track status.

Type:

TrackStatus

hits

Number of measurement updates.

Type:

int

misses

Number of consecutive missed detections.

Type:

int

time

Time of last update.

Type:

float

id: int

Alias for field number 0

state: ndarray[tuple[Any, ...], dtype[float64]]

Alias for field number 1

covariance: ndarray[tuple[Any, ...], dtype[float64]]

Alias for field number 2

status: TrackStatus

Alias for field number 3

hits: int

Alias for field number 4

misses: int

Alias for field number 5

time: float

Alias for field number 6

class pytcl.trackers.TrackStatus(value)[source]

Bases: Enum

Track status enumeration.

TENTATIVE = 'tentative'
CONFIRMED = 'confirmed'
DELETED = 'deleted'
class pytcl.trackers.MHTTrackStatus(value)[source]

Bases: Enum

Track status in MHT.

TENTATIVE = 'tentative'
CONFIRMED = 'confirmed'
DELETED = 'deleted'
class pytcl.trackers.MHTTrack(id, state, covariance, score, status, history, parent_id, scan_created, n_hits, n_misses)[source]

Bases: NamedTuple

Track state within MHT.

id

Unique track identifier.

Type:

int

state

State estimate vector.

Type:

ndarray

covariance

State covariance matrix.

Type:

ndarray

score

Log-likelihood ratio score.

Type:

float

status

Track status.

Type:

MHTTrackStatus

history

Measurement indices associated with this track branch. -1 indicates a missed detection.

Type:

list of int

parent_id

ID of parent track (-1 for root tracks).

Type:

int

scan_created

Scan number when this track branch was created.

Type:

int

n_hits

Number of measurement updates.

Type:

int

n_misses

Number of consecutive misses.

Type:

int

id: int

Alias for field number 0

state: ndarray[tuple[Any, ...], dtype[floating]]

Alias for field number 1

covariance: ndarray[tuple[Any, ...], dtype[floating]]

Alias for field number 2

score: float

Alias for field number 3

status: MHTTrackStatus

Alias for field number 4

history: List[int]

Alias for field number 5

parent_id: int

Alias for field number 6

scan_created: int

Alias for field number 7

n_hits: int

Alias for field number 8

n_misses: int

Alias for field number 9

class pytcl.trackers.Hypothesis(id, probability, track_ids, scan_created, parent_id)[source]

Bases: NamedTuple

A global hypothesis in MHT.

A hypothesis represents a consistent assignment of measurements to tracks across multiple scans. Each hypothesis maintains a set of track branches that are mutually compatible.

id

Unique hypothesis identifier.

Type:

int

probability

Posterior probability of this hypothesis.

Type:

float

track_ids

IDs of tracks included in this hypothesis.

Type:

list of int

scan_created

Scan number when this hypothesis was created.

Type:

int

parent_id

ID of parent hypothesis (-1 for initial hypothesis).

Type:

int

id: int

Alias for field number 0

probability: float

Alias for field number 1

track_ids: List[int]

Alias for field number 2

scan_created: int

Alias for field number 3

parent_id: int

Alias for field number 4

class pytcl.trackers.HypothesisAssignment(track_id, measurement_idx, likelihood)[source]

Bases: NamedTuple

A track-to-measurement assignment within a hypothesis.

track_id

Track ID.

Type:

int

measurement_idx

Measurement index (-1 for missed detection).

Type:

int

likelihood

Likelihood of this assignment.

Type:

float

track_id: int

Alias for field number 0

measurement_idx: int

Alias for field number 1

likelihood: float

Alias for field number 2

class pytcl.trackers.HypothesisTree(max_hypotheses=100, n_scan=3, min_probability=1e-06)[source]

Bases: object

Manages hypothesis tree for MHT.

The hypothesis tree represents all possible interpretations of measurement-to-track associations across multiple scans.

Parameters:
  • max_hypotheses (int) – Maximum number of hypotheses to maintain.

  • n_scan (int) – Number of scans for N-scan pruning.

  • min_probability (float) – Minimum hypothesis probability threshold.

hypotheses

Current set of hypotheses.

Type:

list of Hypothesis

tracks

Mapping from track_id to MHTTrack.

Type:

dict

current_scan

Current scan number.

Type:

int

__init__(max_hypotheses=100, n_scan=3, min_probability=1e-06)[source]
hypotheses: List[Hypothesis]
tracks: Dict[int, MHTTrack]
initialize(initial_tracks=None)[source]

Initialize the hypothesis tree.

Parameters:

initial_tracks (list of MHTTrack, optional) – Initial tracks to include.

add_track(track)[source]

Add a new track to the tree.

Parameters:

track (MHTTrack) – Track to add.

Returns:

track_id – ID assigned to the track.

Return type:

int

expand_hypotheses(associations, likelihoods, new_tracks)[source]

Expand hypotheses with new associations.

Parameters:
  • associations (list of dict) – Valid joint associations.

  • likelihoods (list of float) – Likelihood of each association.

  • new_tracks (dict) – Mapping from association_idx to list of new tracks created by that association.

prune()[source]

Apply all pruning strategies.

get_best_hypothesis()[source]

Get the most probable hypothesis.

get_best_tracks()[source]

Get tracks from the best hypothesis.

get_confirmed_tracks()[source]

Get confirmed tracks from the best hypothesis.

pytcl.trackers.generate_joint_associations(gated, n_tracks, n_meas)[source]

Generate all valid joint measurement-to-track associations.

A valid association satisfies: - Each measurement is assigned to at most one track - Each track is assigned to at most one measurement - Only gated track-measurement pairs are considered

Parameters:
  • gated (ndarray) – Boolean gating matrix, shape (n_tracks, n_meas). gated[i, j] = True if track i can be associated with measurement j.

  • n_tracks (int) – Number of tracks.

  • n_meas (int) – Number of measurements.

Returns:

associations – List of valid associations. Each dict maps track_id to meas_idx. meas_idx = -1 indicates missed detection.

Return type:

list of dict

Examples

>>> gated = np.array([[True, True], [True, True]])
>>> associations = generate_joint_associations(gated, 2, 2)
>>> len(associations)  # All valid 2-track, 2-measurement associations
9
pytcl.trackers.compute_association_likelihood(association, likelihood_matrix, detection_prob, clutter_density, n_meas)[source]

Compute likelihood of a joint association.

Parameters:
  • association (dict) – Mapping from track_id to measurement_idx (-1 for miss).

  • likelihood_matrix (ndarray) – Likelihood values, shape (n_tracks, n_meas).

  • detection_prob (float) – Probability of detection.

  • clutter_density (float) – Spatial density of clutter (false alarms).

  • n_meas (int) – Total number of measurements.

Returns:

likelihood – Joint likelihood of the association.

Return type:

float

Examples

>>> import numpy as np
>>> # 2 tracks, 2 measurements
>>> likelihood_matrix = np.array([[0.9, 0.1],
...                                [0.1, 0.8]])
>>> # Association: track 0 -> meas 0, track 1 -> meas 1
>>> association = {0: 0, 1: 1}
>>> lik = compute_association_likelihood(
...     association, likelihood_matrix,
...     detection_prob=0.9, clutter_density=1e-6, n_meas=2
... )
>>> lik > 0
True
>>> # Association with missed detection
>>> assoc_miss = {0: 0, 1: -1}  # track 1 misses
>>> lik_miss = compute_association_likelihood(
...     assoc_miss, likelihood_matrix,
...     detection_prob=0.9, clutter_density=1e-6, n_meas=2
... )
>>> lik > lik_miss  # Full detection more likely
True
pytcl.trackers.n_scan_prune(hypotheses, tracks, n_scan, current_scan)[source]

N-scan pruning of hypotheses.

Removes hypotheses that diverged from the most likely hypothesis more than n_scan scans ago. This implements “deferred decision” pruning where associations older than N scans are committed to.

Parameters:
  • hypotheses (list of Hypothesis) – Current hypotheses.

  • tracks (dict) – Mapping from track_id to MHTTrack.

  • n_scan (int) – Number of scans to look back.

  • current_scan (int) – Current scan number.

Returns:

  • pruned_hypotheses (list of Hypothesis) – Hypotheses surviving pruning.

  • committed_track_ids (set) – Track IDs that are now committed (survived N-scan).

Return type:

Tuple[List[Hypothesis], Set[int]]

Examples

>>> import numpy as np
>>> from pytcl.trackers.hypothesis import (
...     Hypothesis, MHTTrack, MHTTrackStatus, n_scan_prune
... )
>>> # Two hypotheses, tracks with different creation scans
>>> track1 = MHTTrack(id=0, state=np.zeros(2), covariance=np.eye(2),
...                   score=1.0, status=MHTTrackStatus.CONFIRMED,
...                   history=[0], parent_id=-1, scan_created=0,
...                   n_hits=3, n_misses=0)
>>> track2 = MHTTrack(id=1, state=np.zeros(2), covariance=np.eye(2),
...                   score=0.5, status=MHTTrackStatus.TENTATIVE,
...                   history=[1], parent_id=-1, scan_created=2,
...                   n_hits=1, n_misses=0)
>>> tracks = {0: track1, 1: track2}
>>> hyp1 = Hypothesis(id=0, probability=0.8, track_ids=[0],
...                   scan_created=0, parent_id=-1)
>>> hyp2 = Hypothesis(id=1, probability=0.2, track_ids=[1],
...                   scan_created=2, parent_id=-1)
>>> pruned, committed = n_scan_prune([hyp1, hyp2], tracks, n_scan=2,
...                                   current_scan=3)
>>> len(pruned) >= 1
True

Notes

N-scan pruning works by: 1. Finding tracks that agree across all high-probability hypotheses

at scan (current_scan - n_scan)

  1. Removing hypotheses that disagree with the “committed” decision

pytcl.trackers.prune_hypotheses_by_probability(hypotheses, max_hypotheses, min_probability=1e-06)[source]

Prune hypotheses by probability threshold and count limit.

Parameters:
  • hypotheses (list of Hypothesis) – Current hypotheses.

  • max_hypotheses (int) – Maximum number of hypotheses to retain.

  • min_probability (float) – Minimum probability threshold.

Returns:

pruned – Pruned and renormalized hypotheses.

Return type:

list of Hypothesis

Examples

>>> from pytcl.trackers.hypothesis import Hypothesis, prune_hypotheses_by_probability
>>> # 5 hypotheses with varying probabilities
>>> hyps = [
...     Hypothesis(id=0, probability=0.5, track_ids=[0], scan_created=0, parent_id=-1),
...     Hypothesis(id=1, probability=0.3, track_ids=[1], scan_created=0, parent_id=-1),
...     Hypothesis(id=2, probability=0.1, track_ids=[2], scan_created=0, parent_id=-1),
...     Hypothesis(id=3, probability=0.05, track_ids=[3], scan_created=0, parent_id=-1),
...     Hypothesis(id=4, probability=1e-8, track_ids=[4], scan_created=0, parent_id=-1),
... ]
>>> pruned = prune_hypotheses_by_probability(hyps, max_hypotheses=3)
>>> len(pruned)  # Only top 3 kept
3
>>> sum(h.probability for h in pruned)  # Renormalized to 1
1.0
class pytcl.trackers.MHTConfig(n_scan=3, max_hypotheses=100, detection_prob=0.9, clutter_density=1e-06, gate_probability=0.99, confirm_threshold=3, delete_threshold=5, min_hypothesis_prob=1e-06, new_track_weight=0.1)[source]

Bases: NamedTuple

Configuration for MHT tracker.

n_scan

Number of scans for N-scan pruning. Default 3.

Type:

int

max_hypotheses

Maximum number of hypotheses to maintain. Default 100.

Type:

int

detection_prob

Probability of detection (Pd). Default 0.9.

Type:

float

clutter_density

Spatial density of false alarms. Default 1e-6.

Type:

float

gate_probability

Gating probability for chi-squared test. Default 0.99.

Type:

float

confirm_threshold

Number of hits to confirm a track. Default 3.

Type:

int

delete_threshold

Number of consecutive misses to delete a track. Default 5.

Type:

int

min_hypothesis_prob

Minimum hypothesis probability. Default 1e-6.

Type:

float

new_track_weight

Prior weight for new track hypothesis. Default 0.1.

Type:

float

n_scan: int

Alias for field number 0

max_hypotheses: int

Alias for field number 1

detection_prob: float

Alias for field number 2

clutter_density: float

Alias for field number 3

gate_probability: float

Alias for field number 4

confirm_threshold: int

Alias for field number 5

delete_threshold: int

Alias for field number 6

min_hypothesis_prob: float

Alias for field number 7

new_track_weight: float

Alias for field number 8

class pytcl.trackers.MHTResult(confirmed_tracks, tentative_tracks, all_tracks, n_hypotheses, best_hypothesis_prob)[source]

Bases: NamedTuple

Result of MHT processing step.

confirmed_tracks

Tracks that are confirmed.

Type:

list of MHTTrack

tentative_tracks

Tracks that are tentative.

Type:

list of MHTTrack

all_tracks

All active tracks from best hypothesis.

Type:

list of MHTTrack

n_hypotheses

Number of active hypotheses.

Type:

int

best_hypothesis_prob

Probability of the best hypothesis.

Type:

float

confirmed_tracks: List[MHTTrack]

Alias for field number 0

tentative_tracks: List[MHTTrack]

Alias for field number 1

all_tracks: List[MHTTrack]

Alias for field number 2

n_hypotheses: int

Alias for field number 3

best_hypothesis_prob: float

Alias for field number 4

class pytcl.trackers.MHTTracker(state_dim, meas_dim, F, H, Q, R, config=None, init_covariance=None)[source]

Bases: object

Multiple Hypothesis Tracking (MHT) tracker.

Maintains multiple hypotheses about measurement-to-track associations, with N-scan pruning for complexity control.

Parameters:
  • state_dim (int) – Dimension of state vector.

  • meas_dim (int) – Dimension of measurement vector.

  • F (callable or ndarray) – State transition matrix or function F(dt) -> ndarray.

  • H (ndarray) – Measurement matrix.

  • Q (callable or ndarray) – Process noise covariance or function Q(dt) -> ndarray.

  • R (ndarray) – Measurement noise covariance.

  • config (MHTConfig, optional) – Tracker configuration. Uses defaults if not provided.

  • init_covariance (ndarray, optional) – Initial covariance for new tracks.

Examples

>>> import numpy as np
>>> # Constant velocity model
>>> F = lambda dt: np.array([[1, dt, 0, 0],
...                          [0, 1, 0, 0],
...                          [0, 0, 1, dt],
...                          [0, 0, 0, 1]])
>>> H = np.array([[1, 0, 0, 0],
...               [0, 0, 1, 0]])
>>> Q = lambda dt: 0.1 * np.eye(4)
>>> R = np.eye(2) * 0.5
>>> tracker = MHTTracker(4, 2, F, H, Q, R)
>>> # Process measurements
>>> measurements = [np.array([1, 2]), np.array([5, 6])]
>>> result = tracker.process(measurements, dt=1.0)
__init__(state_dim, meas_dim, F, H, Q, R, config=None, init_covariance=None)[source]
process(measurements, dt)[source]

Process measurements at new time step.

Parameters:
  • measurements (list of array_like) – List of measurement vectors.

  • dt (float) – Time step since last update.

Returns:

result – Tracking result with confirmed and tentative tracks.

Return type:

MHTResult

property tracks: List[MHTTrack]

Get all tracks from best hypothesis.

property confirmed_tracks: List[MHTTrack]

Get confirmed tracks from best hypothesis.

property n_hypotheses: int

Number of active hypotheses.

Single Target Tracking

Single target tracking algorithms.

Single-target tracker implementation.

This module provides a simple single-target tracker using Kalman filtering.

class pytcl.trackers.single_target.SingleTargetTracker(state_dim, meas_dim, F, H, Q, R, gate_threshold=None)[source]

Bases: object

Single-target tracker using Kalman filtering.

This tracker maintains a single track and provides predict/update functionality with optional gating.

Parameters:
  • state_dim (int) – Dimension of state vector.

  • meas_dim (int) – Dimension of measurement vector.

  • F (callable or ndarray) – State transition matrix or function F(dt) -> ndarray.

  • H (ndarray) – Measurement matrix.

  • Q (callable or ndarray) – Process noise covariance or function Q(dt) -> ndarray.

  • R (ndarray) – Measurement noise covariance.

  • gate_threshold (float, optional) – Chi-squared gate threshold (default: None, no gating).

Examples

>>> import numpy as np
>>> # Constant velocity model in 2D
>>> F = lambda dt: np.array([[1, dt, 0, 0],
...                          [0, 1, 0, 0],
...                          [0, 0, 1, dt],
...                          [0, 0, 0, 1]])
>>> H = np.array([[1, 0, 0, 0],
...               [0, 0, 1, 0]])
>>> Q = lambda dt: 0.1 * np.eye(4)
>>> R = np.eye(2) * 0.5
>>> tracker = SingleTargetTracker(4, 2, F, H, Q, R)
>>> tracker.initialize(np.array([0, 1, 0, 1]), np.eye(4))
>>> tracker.predict(1.0)
>>> tracker.update(np.array([1.1, 1.2]))
__init__(state_dim, meas_dim, F, H, Q, R, gate_threshold=None)[source]
initialize(state, covariance, time=0.0)[source]

Initialize the tracker with initial state.

Parameters:
  • state (array_like) – Initial state estimate.

  • covariance (array_like) – Initial state covariance.

  • time (float, optional) – Initial time (default: 0).

property is_initialized: bool

Check if tracker is initialized.

property state: TrackState | None

Get current track state.

predict(dt)[source]

Predict state to new time.

Parameters:

dt (float) – Time step.

Returns:

Predicted state.

Return type:

TrackState

Raises:

RuntimeError – If tracker is not initialized.

update(measurement)[source]

Update state with measurement.

Parameters:

measurement (array_like) – Measurement vector.

Returns:

  • state (TrackState) – Updated state.

  • likelihood (float) – Measurement likelihood (Mahalanobis distance).

Raises:

RuntimeError – If tracker is not initialized.

Return type:

tuple[TrackState, float]

predict_measurement()[source]

Predict measurement and innovation covariance.

Returns:

  • z_pred (ndarray) – Predicted measurement.

  • S (ndarray) – Innovation covariance.

Return type:

tuple[ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[float64]]]

class pytcl.trackers.single_target.TrackState(state, covariance, time)[source]

Bases: NamedTuple

State of a single target track.

state

State estimate vector.

Type:

ndarray

covariance

State covariance matrix.

Type:

ndarray

time

Time of state estimate.

Type:

float

state: ndarray[tuple[Any, ...], dtype[float64]]

Alias for field number 0

covariance: ndarray[tuple[Any, ...], dtype[float64]]

Alias for field number 1

time: float

Alias for field number 2

Multi-Target Tracking

Multi-target tracking algorithms including track management.

Multi-target tracker implementation.

This module provides a multi-target tracker using GNN data association and Kalman filtering with track management (initiation, maintenance, deletion).

class pytcl.trackers.multi_target.MultiTargetTracker(state_dim, meas_dim, F, H, Q, R, gate_probability=0.99, confirm_hits=3, confirm_window=5, max_misses=5, init_covariance=None)[source]

Bases: object

Multi-target tracker with GNN data association.

This tracker maintains multiple tracks and handles: - Track initiation from unassociated measurements - Track update via GNN data association - Track confirmation (M-of-N logic) - Track deletion (miss count)

Parameters:
  • state_dim (int) – Dimension of state vector.

  • meas_dim (int) – Dimension of measurement vector.

  • F (callable or ndarray) – State transition matrix or function F(dt) -> ndarray.

  • H (ndarray) – Measurement matrix.

  • Q (callable or ndarray) – Process noise covariance or function Q(dt) -> ndarray.

  • R (ndarray) – Measurement noise covariance.

  • gate_probability (float, optional) – Gate probability for association (default: 0.99).

  • confirm_hits (int, optional) – Hits needed to confirm track (default: 3).

  • confirm_window (int, optional) – Window for M-of-N confirmation (default: 5).

  • max_misses (int, optional) – Consecutive misses before deletion (default: 5).

  • init_covariance (ndarray, optional) – Initial covariance for new tracks. If None, uses 100*R projected to state.

Examples

>>> import numpy as np
>>> # Constant velocity model
>>> F = lambda dt: np.array([[1, dt, 0, 0],
...                          [0, 1, 0, 0],
...                          [0, 0, 1, dt],
...                          [0, 0, 0, 1]])
>>> H = np.array([[1, 0, 0, 0],
...               [0, 0, 1, 0]])
>>> Q = lambda dt: 0.1 * np.eye(4)
>>> R = np.eye(2) * 0.5
>>> tracker = MultiTargetTracker(4, 2, F, H, Q, R)
>>> # Process measurements
>>> measurements = [np.array([1, 2]), np.array([5, 6])]
>>> tracks = tracker.process(measurements, dt=1.0)
__init__(state_dim, meas_dim, F, H, Q, R, gate_probability=0.99, confirm_hits=3, confirm_window=5, max_misses=5, init_covariance=None)[source]
property tracks: List[Track]

Get list of active tracks.

property confirmed_tracks: List[Track]

Get list of confirmed tracks only.

process(measurements, dt)[source]

Process measurements at new time step.

Parameters:
  • measurements (list of array_like) – List of measurement vectors.

  • dt (float) – Time step since last update.

Returns:

Active tracks after update.

Return type:

list of Track

class pytcl.trackers.multi_target.Track(id, state, covariance, status, hits, misses, time)[source]

Bases: NamedTuple

Multi-target track.

id

Unique track identifier.

Type:

int

state

State estimate vector.

Type:

ndarray

covariance

State covariance matrix.

Type:

ndarray

status

Track status.

Type:

TrackStatus

hits

Number of measurement updates.

Type:

int

misses

Number of consecutive missed detections.

Type:

int

time

Time of last update.

Type:

float

id: int

Alias for field number 0

state: ndarray[tuple[Any, ...], dtype[float64]]

Alias for field number 1

covariance: ndarray[tuple[Any, ...], dtype[float64]]

Alias for field number 2

status: TrackStatus

Alias for field number 3

hits: int

Alias for field number 4

misses: int

Alias for field number 5

time: float

Alias for field number 6

class pytcl.trackers.multi_target.TrackStatus(value)[source]

Bases: Enum

Track status enumeration.

TENTATIVE = 'tentative'
CONFIRMED = 'confirmed'
DELETED = 'deleted'

Multiple Hypothesis Tracking (MHT)

Hypothesis-oriented MHT implementation.

Multiple Hypothesis Tracking (MHT) implementation.

MHT maintains multiple hypotheses about measurement-to-track associations, deferring hard decisions until more information is available. This allows the tracker to recover from association errors.

This implementation uses track-oriented MHT with N-scan pruning.

References

class pytcl.trackers.mht.MHTConfig(n_scan=3, max_hypotheses=100, detection_prob=0.9, clutter_density=1e-06, gate_probability=0.99, confirm_threshold=3, delete_threshold=5, min_hypothesis_prob=1e-06, new_track_weight=0.1)[source]

Bases: NamedTuple

Configuration for MHT tracker.

n_scan

Number of scans for N-scan pruning. Default 3.

Type:

int

max_hypotheses

Maximum number of hypotheses to maintain. Default 100.

Type:

int

detection_prob

Probability of detection (Pd). Default 0.9.

Type:

float

clutter_density

Spatial density of false alarms. Default 1e-6.

Type:

float

gate_probability

Gating probability for chi-squared test. Default 0.99.

Type:

float

confirm_threshold

Number of hits to confirm a track. Default 3.

Type:

int

delete_threshold

Number of consecutive misses to delete a track. Default 5.

Type:

int

min_hypothesis_prob

Minimum hypothesis probability. Default 1e-6.

Type:

float

new_track_weight

Prior weight for new track hypothesis. Default 0.1.

Type:

float

n_scan: int

Alias for field number 0

max_hypotheses: int

Alias for field number 1

detection_prob: float

Alias for field number 2

clutter_density: float

Alias for field number 3

gate_probability: float

Alias for field number 4

confirm_threshold: int

Alias for field number 5

delete_threshold: int

Alias for field number 6

min_hypothesis_prob: float

Alias for field number 7

new_track_weight: float

Alias for field number 8

class pytcl.trackers.mht.MHTResult(confirmed_tracks, tentative_tracks, all_tracks, n_hypotheses, best_hypothesis_prob)[source]

Bases: NamedTuple

Result of MHT processing step.

confirmed_tracks

Tracks that are confirmed.

Type:

list of MHTTrack

tentative_tracks

Tracks that are tentative.

Type:

list of MHTTrack

all_tracks

All active tracks from best hypothesis.

Type:

list of MHTTrack

n_hypotheses

Number of active hypotheses.

Type:

int

best_hypothesis_prob

Probability of the best hypothesis.

Type:

float

confirmed_tracks: List[MHTTrack]

Alias for field number 0

tentative_tracks: List[MHTTrack]

Alias for field number 1

all_tracks: List[MHTTrack]

Alias for field number 2

n_hypotheses: int

Alias for field number 3

best_hypothesis_prob: float

Alias for field number 4

class pytcl.trackers.mht.MHTTracker(state_dim, meas_dim, F, H, Q, R, config=None, init_covariance=None)[source]

Bases: object

Multiple Hypothesis Tracking (MHT) tracker.

Maintains multiple hypotheses about measurement-to-track associations, with N-scan pruning for complexity control.

Parameters:
  • state_dim (int) – Dimension of state vector.

  • meas_dim (int) – Dimension of measurement vector.

  • F (callable or ndarray) – State transition matrix or function F(dt) -> ndarray.

  • H (ndarray) – Measurement matrix.

  • Q (callable or ndarray) – Process noise covariance or function Q(dt) -> ndarray.

  • R (ndarray) – Measurement noise covariance.

  • config (MHTConfig, optional) – Tracker configuration. Uses defaults if not provided.

  • init_covariance (ndarray, optional) – Initial covariance for new tracks.

Examples

>>> import numpy as np
>>> # Constant velocity model
>>> F = lambda dt: np.array([[1, dt, 0, 0],
...                          [0, 1, 0, 0],
...                          [0, 0, 1, dt],
...                          [0, 0, 0, 1]])
>>> H = np.array([[1, 0, 0, 0],
...               [0, 0, 1, 0]])
>>> Q = lambda dt: 0.1 * np.eye(4)
>>> R = np.eye(2) * 0.5
>>> tracker = MHTTracker(4, 2, F, H, Q, R)
>>> # Process measurements
>>> measurements = [np.array([1, 2]), np.array([5, 6])]
>>> result = tracker.process(measurements, dt=1.0)
__init__(state_dim, meas_dim, F, H, Q, R, config=None, init_covariance=None)[source]
process(measurements, dt)[source]

Process measurements at new time step.

Parameters:
  • measurements (list of array_like) – List of measurement vectors.

  • dt (float) – Time step since last update.

Returns:

result – Tracking result with confirmed and tentative tracks.

Return type:

MHTResult

property tracks: List[MHTTrack]

Get all tracks from best hypothesis.

property confirmed_tracks: List[MHTTrack]

Get confirmed tracks from best hypothesis.

property n_hypotheses: int

Number of active hypotheses.

Hypothesis Management

Track hypothesis creation, scoring, and pruning.

Hypothesis management for Multiple Hypothesis Tracking (MHT).

This module provides data structures and algorithms for managing hypothesis trees in track-oriented MHT implementations.

References

class pytcl.trackers.hypothesis.MHTTrackStatus(value)[source]

Bases: Enum

Track status in MHT.

TENTATIVE = 'tentative'
CONFIRMED = 'confirmed'
DELETED = 'deleted'
class pytcl.trackers.hypothesis.MHTTrack(id, state, covariance, score, status, history, parent_id, scan_created, n_hits, n_misses)[source]

Bases: NamedTuple

Track state within MHT.

id

Unique track identifier.

Type:

int

state

State estimate vector.

Type:

ndarray

covariance

State covariance matrix.

Type:

ndarray

score

Log-likelihood ratio score.

Type:

float

status

Track status.

Type:

MHTTrackStatus

history

Measurement indices associated with this track branch. -1 indicates a missed detection.

Type:

list of int

parent_id

ID of parent track (-1 for root tracks).

Type:

int

scan_created

Scan number when this track branch was created.

Type:

int

n_hits

Number of measurement updates.

Type:

int

n_misses

Number of consecutive misses.

Type:

int

id: int

Alias for field number 0

state: ndarray[tuple[Any, ...], dtype[floating]]

Alias for field number 1

covariance: ndarray[tuple[Any, ...], dtype[floating]]

Alias for field number 2

score: float

Alias for field number 3

status: MHTTrackStatus

Alias for field number 4

history: List[int]

Alias for field number 5

parent_id: int

Alias for field number 6

scan_created: int

Alias for field number 7

n_hits: int

Alias for field number 8

n_misses: int

Alias for field number 9

class pytcl.trackers.hypothesis.Hypothesis(id, probability, track_ids, scan_created, parent_id)[source]

Bases: NamedTuple

A global hypothesis in MHT.

A hypothesis represents a consistent assignment of measurements to tracks across multiple scans. Each hypothesis maintains a set of track branches that are mutually compatible.

id

Unique hypothesis identifier.

Type:

int

probability

Posterior probability of this hypothesis.

Type:

float

track_ids

IDs of tracks included in this hypothesis.

Type:

list of int

scan_created

Scan number when this hypothesis was created.

Type:

int

parent_id

ID of parent hypothesis (-1 for initial hypothesis).

Type:

int

id: int

Alias for field number 0

probability: float

Alias for field number 1

track_ids: List[int]

Alias for field number 2

scan_created: int

Alias for field number 3

parent_id: int

Alias for field number 4

class pytcl.trackers.hypothesis.HypothesisAssignment(track_id, measurement_idx, likelihood)[source]

Bases: NamedTuple

A track-to-measurement assignment within a hypothesis.

track_id

Track ID.

Type:

int

measurement_idx

Measurement index (-1 for missed detection).

Type:

int

likelihood

Likelihood of this assignment.

Type:

float

track_id: int

Alias for field number 0

measurement_idx: int

Alias for field number 1

likelihood: float

Alias for field number 2

pytcl.trackers.hypothesis.generate_joint_associations(gated, n_tracks, n_meas)[source]

Generate all valid joint measurement-to-track associations.

A valid association satisfies: - Each measurement is assigned to at most one track - Each track is assigned to at most one measurement - Only gated track-measurement pairs are considered

Parameters:
  • gated (ndarray) – Boolean gating matrix, shape (n_tracks, n_meas). gated[i, j] = True if track i can be associated with measurement j.

  • n_tracks (int) – Number of tracks.

  • n_meas (int) – Number of measurements.

Returns:

associations – List of valid associations. Each dict maps track_id to meas_idx. meas_idx = -1 indicates missed detection.

Return type:

list of dict

Examples

>>> gated = np.array([[True, True], [True, True]])
>>> associations = generate_joint_associations(gated, 2, 2)
>>> len(associations)  # All valid 2-track, 2-measurement associations
9
pytcl.trackers.hypothesis.compute_association_likelihood(association, likelihood_matrix, detection_prob, clutter_density, n_meas)[source]

Compute likelihood of a joint association.

Parameters:
  • association (dict) – Mapping from track_id to measurement_idx (-1 for miss).

  • likelihood_matrix (ndarray) – Likelihood values, shape (n_tracks, n_meas).

  • detection_prob (float) – Probability of detection.

  • clutter_density (float) – Spatial density of clutter (false alarms).

  • n_meas (int) – Total number of measurements.

Returns:

likelihood – Joint likelihood of the association.

Return type:

float

Examples

>>> import numpy as np
>>> # 2 tracks, 2 measurements
>>> likelihood_matrix = np.array([[0.9, 0.1],
...                                [0.1, 0.8]])
>>> # Association: track 0 -> meas 0, track 1 -> meas 1
>>> association = {0: 0, 1: 1}
>>> lik = compute_association_likelihood(
...     association, likelihood_matrix,
...     detection_prob=0.9, clutter_density=1e-6, n_meas=2
... )
>>> lik > 0
True
>>> # Association with missed detection
>>> assoc_miss = {0: 0, 1: -1}  # track 1 misses
>>> lik_miss = compute_association_likelihood(
...     assoc_miss, likelihood_matrix,
...     detection_prob=0.9, clutter_density=1e-6, n_meas=2
... )
>>> lik > lik_miss  # Full detection more likely
True
pytcl.trackers.hypothesis.n_scan_prune(hypotheses, tracks, n_scan, current_scan)[source]

N-scan pruning of hypotheses.

Removes hypotheses that diverged from the most likely hypothesis more than n_scan scans ago. This implements “deferred decision” pruning where associations older than N scans are committed to.

Parameters:
  • hypotheses (list of Hypothesis) – Current hypotheses.

  • tracks (dict) – Mapping from track_id to MHTTrack.

  • n_scan (int) – Number of scans to look back.

  • current_scan (int) – Current scan number.

Returns:

  • pruned_hypotheses (list of Hypothesis) – Hypotheses surviving pruning.

  • committed_track_ids (set) – Track IDs that are now committed (survived N-scan).

Return type:

Tuple[List[Hypothesis], Set[int]]

Examples

>>> import numpy as np
>>> from pytcl.trackers.hypothesis import (
...     Hypothesis, MHTTrack, MHTTrackStatus, n_scan_prune
... )
>>> # Two hypotheses, tracks with different creation scans
>>> track1 = MHTTrack(id=0, state=np.zeros(2), covariance=np.eye(2),
...                   score=1.0, status=MHTTrackStatus.CONFIRMED,
...                   history=[0], parent_id=-1, scan_created=0,
...                   n_hits=3, n_misses=0)
>>> track2 = MHTTrack(id=1, state=np.zeros(2), covariance=np.eye(2),
...                   score=0.5, status=MHTTrackStatus.TENTATIVE,
...                   history=[1], parent_id=-1, scan_created=2,
...                   n_hits=1, n_misses=0)
>>> tracks = {0: track1, 1: track2}
>>> hyp1 = Hypothesis(id=0, probability=0.8, track_ids=[0],
...                   scan_created=0, parent_id=-1)
>>> hyp2 = Hypothesis(id=1, probability=0.2, track_ids=[1],
...                   scan_created=2, parent_id=-1)
>>> pruned, committed = n_scan_prune([hyp1, hyp2], tracks, n_scan=2,
...                                   current_scan=3)
>>> len(pruned) >= 1
True

Notes

N-scan pruning works by: 1. Finding tracks that agree across all high-probability hypotheses

at scan (current_scan - n_scan)

  1. Removing hypotheses that disagree with the “committed” decision

pytcl.trackers.hypothesis.prune_hypotheses_by_probability(hypotheses, max_hypotheses, min_probability=1e-06)[source]

Prune hypotheses by probability threshold and count limit.

Parameters:
  • hypotheses (list of Hypothesis) – Current hypotheses.

  • max_hypotheses (int) – Maximum number of hypotheses to retain.

  • min_probability (float) – Minimum probability threshold.

Returns:

pruned – Pruned and renormalized hypotheses.

Return type:

list of Hypothesis

Examples

>>> from pytcl.trackers.hypothesis import Hypothesis, prune_hypotheses_by_probability
>>> # 5 hypotheses with varying probabilities
>>> hyps = [
...     Hypothesis(id=0, probability=0.5, track_ids=[0], scan_created=0, parent_id=-1),
...     Hypothesis(id=1, probability=0.3, track_ids=[1], scan_created=0, parent_id=-1),
...     Hypothesis(id=2, probability=0.1, track_ids=[2], scan_created=0, parent_id=-1),
...     Hypothesis(id=3, probability=0.05, track_ids=[3], scan_created=0, parent_id=-1),
...     Hypothesis(id=4, probability=1e-8, track_ids=[4], scan_created=0, parent_id=-1),
... ]
>>> pruned = prune_hypotheses_by_probability(hyps, max_hypotheses=3)
>>> len(pruned)  # Only top 3 kept
3
>>> sum(h.probability for h in pruned)  # Renormalized to 1
1.0
class pytcl.trackers.hypothesis.HypothesisTree(max_hypotheses=100, n_scan=3, min_probability=1e-06)[source]

Bases: object

Manages hypothesis tree for MHT.

The hypothesis tree represents all possible interpretations of measurement-to-track associations across multiple scans.

Parameters:
  • max_hypotheses (int) – Maximum number of hypotheses to maintain.

  • n_scan (int) – Number of scans for N-scan pruning.

  • min_probability (float) – Minimum hypothesis probability threshold.

hypotheses

Current set of hypotheses.

Type:

list of Hypothesis

tracks

Mapping from track_id to MHTTrack.

Type:

dict

current_scan

Current scan number.

Type:

int

__init__(max_hypotheses=100, n_scan=3, min_probability=1e-06)[source]
hypotheses: List[Hypothesis]
tracks: Dict[int, MHTTrack]
initialize(initial_tracks=None)[source]

Initialize the hypothesis tree.

Parameters:

initial_tracks (list of MHTTrack, optional) – Initial tracks to include.

add_track(track)[source]

Add a new track to the tree.

Parameters:

track (MHTTrack) – Track to add.

Returns:

track_id – ID assigned to the track.

Return type:

int

expand_hypotheses(associations, likelihoods, new_tracks)[source]

Expand hypotheses with new associations.

Parameters:
  • associations (list of dict) – Valid joint associations.

  • likelihoods (list of float) – Likelihood of each association.

  • new_tracks (dict) – Mapping from association_idx to list of new tracks created by that association.

prune()[source]

Apply all pruning strategies.

get_best_hypothesis()[source]

Get the most probable hypothesis.

get_best_tracks()[source]

Get tracks from the best hypothesis.

get_confirmed_tracks()[source]

Get confirmed tracks from the best hypothesis.