diff --git a/services/memory/memory.py b/services/memory/memory.py index efa076c..33cf579 100644 --- a/services/memory/memory.py +++ b/services/memory/memory.py @@ -36,7 +36,10 @@ import numpy as np from libs.observability.metrics import redis_write_latency -from libs.schemas.memory import ActionHint, TrackEvent, TrackSequence +from libs.schemas.memory import ( + TrackEvent, + TrackSequence, +) from libs.schemas.tracking import TrackLifecycleEvent, TrackState from services.tracking.cross_camera_reid import CrossCameraReID from services.memory.baseline import ZoneBaseline @@ -344,8 +347,10 @@ def get_sequence(self, track_id: int, last_n: Optional[int] = None, camera_id: O key = self._seq_key(track_id) raw_list = self._r.lrange(key, -last_n, -1) if last_n else self._r.lrange(key, 0, -1) - events: list[TrackEvent] = [] - for raw in raw_list: + def get_active_track_ids(self, camera_id: str) -> set[int]: + members = self._r.smembers(self._active_key(camera_id)) + result: set[int] = set() + for m in members: try: data = json.loads(raw if isinstance(raw, str) else raw.decode()) events.append(TrackEvent(**data)) diff --git a/services/tracking/tracker.py b/services/tracking/tracker.py index b17cfa1..dcaf972 100644 --- a/services/tracking/tracker.py +++ b/services/tracking/tracker.py @@ -163,22 +163,46 @@ def update( y1 = float(ltwh[1]) x2 = x1 + float(ltwh[2]) y2 = y1 + float(ltwh[3]) - cx, cy = (x1 + x2) / 2, (y1 + y2) / 2 - zones = [z.name for z in get_zones_for_point(cx, cy)] + cx = (x1 + x2) / 2 + cy = (y1 + y2) / 2 + + matched_zones = get_zones_for_point(cx, cy) + + ZONE_PRIORITY = { + "keypad_area": 2, + "restricted_door": 1, + } + + matched_zones.sort( + key=lambda z: ZONE_PRIORITY.get(z.name, 0), + reverse=True, + ) + + zones = [z.name for z in matched_zones] - # ── Lifecycle: BORN ─────────────────────────────────────────── if tid not in self._known_ids: self._known_ids.add(tid) - self._emit_lifecycle(TrackState.BORN, tid, zones, 0.0) - logger.info(f"Track BORN: #{tid} in zones={zones}") - # ── Dwell time ──────────────────────────────────────────────── + self._emit_lifecycle( + TrackState.BORN, + tid, + zones, + 0.0, + ) + + logger.info( + f"Track BORN: #{tid} in zones={zones}" + ) + prev = self._active_tracks.get(tid) - dwell_frames = (prev.dwell_time_frames + 1) if prev else 1 + + dwell_frames = ( + prev.dwell_time_frames + 1 + ) if prev else 1 + dwell_secs = dwell_frames / self.fps - # ── Trajectory ──────────────────────────────────────────────── prev_traj = prev.trajectory if prev else [] new_point = TrajectoryPoint(x=cx, y=cy, frame_id=self._frame_id) trajectory = (prev_traj + [new_point])[-self.MAX_TRAJECTORY_LEN :] @@ -196,21 +220,30 @@ def update( zones_present=zones, last_seen_frame=self._frame_id, ) + self._active_tracks[tid] = obj - current_ids.add(tid) tracked_objects.append(obj) + current_ids.add(tid) + active_tracks.set(len(tracked_objects)) for obj in tracked_objects: track_dwell_seconds.observe(obj.dwell_time_seconds) # ── Lifecycle: LOST for tracks that disappeared ──────────────────── for tid, prev_obj in list(self._active_tracks.items()): + if tid not in current_ids: frames_since = self._frame_id - prev_obj.last_seen_frame track = None if frames_since == 1: - track = next((t for t in raw_tracks if int(t.track_id) == tid), None) + track = next( + ( + t for t in raw_tracks + if int(t.track_id) == tid + ), + None, + ) embedding = None if frames_since == 1: