Skip to content

fix: indentation error in tracker.py causing SyntaxError - #200

Merged
Devnil434 merged 2 commits into
Devnil434:mainfrom
saurabhhhcodes:fix-eagle-batch-1
Jul 23, 2026
Merged

fix: indentation error in tracker.py causing SyntaxError#200
Devnil434 merged 2 commits into
Devnil434:mainfrom
saurabhhhcodes:fix-eagle-batch-1

Conversation

@saurabhhhcodes

@saurabhhhcodes saurabhhhcodes commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Real bug: services/tracking/tracker.py:127-240 has broken indentation causing 'return outside function' SyntaxError

Summary by CodeRabbit

  • Bug Fixes
    • Fixed tracking so confirmed tracks are consistently computed and included in every generated frame.
    • Improved re-identification by restoring a track’s identity only when a strong similarity match is found.
    • Corrected zone detection, dwell time updates, and trajectory recording so they remain consistent after identity resolution.
    • Preserved proper lifecycle events for lost and dead tracks, including consistent rehydration shortly after disappearance.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 94f3c9a1-0940-414a-9c15-f699c3fcf919

📥 Commits

Reviewing files that changed from the base of the PR and between 500ca24 and 3d90dd0.

📒 Files selected for processing (1)
  • services/tracking/tracker.py

📝 Walkthrough

Walkthrough

Tracker.update() separates ReID recovery from confirmed-track processing, simplifies per-track updates, restructures LOST/DEAD handling, cleans expired embeddings, and returns the populated TrackedFrame.

Changes

Tracker update flow

Layer / File(s) Summary
ReID recovery and confirmed-track processing
services/tracking/tracker.py
Lost embeddings are matched to confirmed tracks, restoring IDs before geometry, zones, events, dwell time, trajectories, and TrackedObject creation.
Disappearance lifecycle and frame return
services/tracking/tracker.py
Missing tracks receive LOST/DEAD handling, embeddings are rehydrated or expired, and the method returns the accumulated TrackedFrame without the removed metrics and active-embedding updates.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • Devnil434/Eagle#53: Both changes modify Tracker.update() ReID recovery using lost embeddings and restored track identities.

Suggested labels: gssoc:approved

Suggested reviewers: saoneenandi, 2024itb047samata

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main fix in tracker.py and correctly describes the SyntaxError-inducing indentation issue.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@services/tracking/tracker.py`:
- Around line 199-240: Move the LOST/DEAD lifecycle scan, expired ReID cleanup,
and TrackedFrame return out of the lost-embedding candidate loop so they execute
once per update() call, including when _lost_embeddings is empty. In the
disappeared-track loop, initialize track for each iteration before checking its
features, or restrict the feature access to the frames_since == 1 branch,
preventing undefined or stale values while preserving embedding capture only for
newly lost tracks.
- Around line 127-156: Move the ReID logic beginning with the `hasattr(t,
"features")` check and the `_lost_embeddings` loop into the enclosing `for t in
raw_tracks` loop. Keep the lost-track scan conditional on a valid
`new_embedding`, so tracks without features skip similarity matching instead of
referencing an unassigned variable; preserve the existing match, restoration,
deletion, and logging behavior.
- Around line 157-198: Dedent the per-track processing block beginning with ltwh
conversion and ending with tracked_objects.append(obj) so it runs for every
confirmed track inside the raw-tracks loop, not inside the ReID similarity
condition or after its unconditional break. Preserve the existing BORN, dwell,
trajectory, TrackedObject, current_ids, and tracked_objects behavior; optionally
replace trajectory list concatenation with the Ruff-recommended form.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1e38f38b-3b9a-4626-8e03-32029f383e66

📥 Commits

Reviewing files that changed from the base of the PR and between 2291373 and 500ca24.

📒 Files selected for processing (1)
  • services/tracking/tracker.py

Comment thread services/tracking/tracker.py
Comment on lines +157 to +198
ltwh = t.to_ltwh()
x1 = float(ltwh[0])
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)]

# ── 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 ────────────────────────────────────────────────
prev = self._active_tracks.get(tid)
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:]

obj = TrackedObject(
track_id = tid,
label = "person",
bbox = [x1, y1, x2, y2],
confidence = float(t.det_conf or 0.0),
center = (cx, cy),
dwell_time_frames = dwell_frames,
dwell_time_seconds = round(dwell_secs, 2),
state = TrackState.ACTIVE,
trajectory = trajectory,
zones_present = zones,
last_seen_frame = self._frame_id,
)
if frames_since > self._tracker.max_age:
self._active_tracks[tid] = obj
current_ids.add(tid)
tracked_objects.append(obj)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Per-track processing only runs on a ReID match — normal tracks never produce a TrackedObject.

Lines 157-197 (bbox conversion, zone lookup, BORN emission, dwell time, trajectory, TrackedObject build/append) sit at the same indentation as break (line 155), i.e. nested inside if similarity > self.REID_SIMILARITY_THRESHOLD:. This means:

  • This block never runs for the common case of a normal confirmed track with no ReID recovery — tracked_objects/current_ids stay empty except when a ReID match happens.
  • It physically follows an unconditional break in the same suite, so it is unreachable dead code regardless.

This directly contradicts the AI summary/line-range description, which claims this block was fixed to run "after the ReID scan concludes, rather than being incorrectly nested." The code still shows the exact nesting bug described as fixed.

🐛 Proposed fix: dedent to run for every confirmed track
-                break
+                        break

-                ltwh = t.to_ltwh()
-                x1 = float(ltwh[0])
-                y1 = float(ltwh[1])
-                x2 = x1 + float(ltwh[2])
-                y2 = y1 + float(ltwh[3])
-                cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
+            ltwh = t.to_ltwh()
+            x1 = float(ltwh[0])
+            y1 = float(ltwh[1])
+            x2 = x1 + float(ltwh[2])
+            y2 = y1 + float(ltwh[3])
+            cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
             ...
-                self._active_tracks[tid] = obj
-                current_ids.add(tid)
-                tracked_objects.append(obj)
+            self._active_tracks[tid] = obj
+            current_ids.add(tid)
+            tracked_objects.append(obj)

(dedent lines 157-197 by one level, so they are siblings of the if hasattr(...) ReID block, still inside for t in raw_tracks:)

Optionally, per the Ruff hint, line 180 can use [*prev_traj, new_point] instead of list concatenation while you're touching this block.

🧰 Tools
🪛 Ruff (0.15.21)

[warning] 180-180: Consider [*prev_traj, new_point] instead of concatenation

Replace with [*prev_traj, new_point]

(RUF005)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/tracking/tracker.py` around lines 157 - 198, Dedent the per-track
processing block beginning with ltwh conversion and ending with
tracked_objects.append(obj) so it runs for every confirmed track inside the
raw-tracks loop, not inside the ReID similarity condition or after its
unconditional break. Preserve the existing BORN, dwell, trajectory,
TrackedObject, current_ids, and tracked_objects behavior; optionally replace
trajectory list concatenation with the Ruff-recommended form.

Comment on lines +199 to +240
# ── 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
if frames_since == 1:
track = next((t for t in raw_tracks if int(t.track_id) == tid), None)

if track is not None and hasattr(track, "features") and track.features:
self._lost_embeddings[tid] = {
"embedding": track.features[-1],
"last_seen": self._frame_id,
}

self._emit_lifecycle(
TrackState.DEAD, tid,
TrackState.LOST, tid,
prev_obj.zones_present,
prev_obj.dwell_time_seconds,
)
del self._active_tracks[tid]
logger.info(f"Track DEAD: #{tid} after {prev_obj.dwell_time_seconds:.1f}s")
# ── Cleanup expired ReID embeddings ──────────────────
expired_ids = [
tid for tid, data in self._lost_embeddings.items()
if self._frame_id - data["last_seen"] > self.max_age
]

for tid in expired_ids:
del self._lost_embeddings[tid]

return TrackedFrame(
frame_id = self._frame_id,
camera_id = self.camera_id,
tracks = tracked_objects,
timestamp_ms = time.time() * 1000,
fps = self.fps,
)
if frames_since > self._tracker.max_age:
self._emit_lifecycle(
TrackState.DEAD, tid,
prev_obj.zones_present,
prev_obj.dwell_time_seconds,
)
del self._active_tracks[tid]
logger.info(f"Track DEAD: #{tid} after {prev_obj.dwell_time_seconds:.1f}s")
# ── Cleanup expired ReID embeddings ──────────────────
expired_ids = [
tid for tid, data in self._lost_embeddings.items()
if self._frame_id - data["last_seen"] > self.max_age
]

for tid in expired_ids:
del self._lost_embeddings[tid]

return TrackedFrame(
frame_id = self._frame_id,
camera_id = self.camera_id,
tracks = tracked_objects,
timestamp_ms = time.time() * 1000,
fps = self.fps,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

LOST/DEAD handling, cleanup, and return are nested inside the ReID candidate loop — implicit None return / premature return.

Lines 199-240 sit at the same indentation as line 133 (age = ...), i.e. nested inside for lost_id, data in list(self._lost_embeddings.items()): (131). Consequences:

  • If self._lost_embeddings is empty (typical case), this whole block never executes, so update() falls through without hitting any return — implicitly returning None instead of the declared TrackedFrame, which will break any caller doing frame.tracks / frame.frame_id.
  • If non-empty, update() returns after examining only the first lost_id candidate, short-circuiting the LOST/DEAD scan, cleanup, and frame construction so they run at most once per _lost_embeddings entry instead of once per update() call.

Separately, at lines 203-206, track is assigned only inside if frames_since == 1: (203-204) but read by if track is not None and hasattr(track, "features")... at 206, which is a sibling of that if, not nested inside it — this will raise NameError the first time this path is hit, or silently reuse a stale track from a previous loop iteration afterward.

This contradicts the line-range description's claim that "LOST/DEAD for disappeared tracks" and the "return wiring" behavior are preserved as before.

🐛 Proposed fix: dedent to run once per `update()` call, and fix `track` scoping
-            # ── 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
-                    if frames_since == 1:
-                        track = next((t for t in raw_tracks if int(t.track_id) == tid), None)
-
-                    if track is not None and hasattr(track, "features") and track.features:
-                        self._lost_embeddings[tid] = {
-                            "embedding": track.features[-1],
-                            "last_seen": self._frame_id,
-                    }
-
-                    self._emit_lifecycle(
-                        TrackState.LOST, tid,
-                        prev_obj.zones_present,
-                        prev_obj.dwell_time_seconds,
-                    )
-                    if frames_since > self._tracker.max_age:
-                        self._emit_lifecycle(
-                            TrackState.DEAD, tid,
-                            prev_obj.zones_present,
-                            prev_obj.dwell_time_seconds,
-                        )
-                        del self._active_tracks[tid]
-                        logger.info(f"Track DEAD: #{tid} after {prev_obj.dwell_time_seconds:.1f}s")
-            # ── Cleanup expired ReID embeddings ──────────────────
-            expired_ids = [
-                tid for tid, data in self._lost_embeddings.items()
-                if self._frame_id - data["last_seen"] > self.max_age
-            ]
-
-            for tid in expired_ids:
-                del self._lost_embeddings[tid]
-
-            return TrackedFrame(
-                frame_id     = self._frame_id,
-                camera_id    = self.camera_id,
-                tracks       = tracked_objects,
-                timestamp_ms = time.time() * 1000,
-                fps          = self.fps,
-            )
+        # ── 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
+                if frames_since == 1:
+                    track = next((t for t in raw_tracks if int(t.track_id) == tid), None)
+                    if track is not None and hasattr(track, "features") and track.features:
+                        self._lost_embeddings[tid] = {
+                            "embedding": track.features[-1],
+                            "last_seen": self._frame_id,
+                        }
+
+                self._emit_lifecycle(
+                    TrackState.LOST, tid,
+                    prev_obj.zones_present,
+                    prev_obj.dwell_time_seconds,
+                )
+                if frames_since > self._tracker.max_age:
+                    self._emit_lifecycle(
+                        TrackState.DEAD, tid,
+                        prev_obj.zones_present,
+                        prev_obj.dwell_time_seconds,
+                    )
+                    del self._active_tracks[tid]
+                    logger.info(f"Track DEAD: #{tid} after {prev_obj.dwell_time_seconds:.1f}s")
+        # ── Cleanup expired ReID embeddings ──────────────────
+        expired_ids = [
+            tid for tid, data in self._lost_embeddings.items()
+            if self._frame_id - data["last_seen"] > self.max_age
+        ]
+        for tid in expired_ids:
+            del self._lost_embeddings[tid]
+
+        return TrackedFrame(
+            frame_id     = self._frame_id,
+            camera_id    = self.camera_id,
+            tracks       = tracked_objects,
+            timestamp_ms = time.time() * 1000,
+            fps          = self.fps,
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# ── 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
if frames_since == 1:
track = next((t for t in raw_tracks if int(t.track_id) == tid), None)
if track is not None and hasattr(track, "features") and track.features:
self._lost_embeddings[tid] = {
"embedding": track.features[-1],
"last_seen": self._frame_id,
}
self._emit_lifecycle(
TrackState.DEAD, tid,
TrackState.LOST, tid,
prev_obj.zones_present,
prev_obj.dwell_time_seconds,
)
del self._active_tracks[tid]
logger.info(f"Track DEAD: #{tid} after {prev_obj.dwell_time_seconds:.1f}s")
# ── Cleanup expired ReID embeddings ──────────────────
expired_ids = [
tid for tid, data in self._lost_embeddings.items()
if self._frame_id - data["last_seen"] > self.max_age
]
for tid in expired_ids:
del self._lost_embeddings[tid]
return TrackedFrame(
frame_id = self._frame_id,
camera_id = self.camera_id,
tracks = tracked_objects,
timestamp_ms = time.time() * 1000,
fps = self.fps,
)
if frames_since > self._tracker.max_age:
self._emit_lifecycle(
TrackState.DEAD, tid,
prev_obj.zones_present,
prev_obj.dwell_time_seconds,
)
del self._active_tracks[tid]
logger.info(f"Track DEAD: #{tid} after {prev_obj.dwell_time_seconds:.1f}s")
# ── Cleanup expired ReID embeddings ──────────────────
expired_ids = [
tid for tid, data in self._lost_embeddings.items()
if self._frame_id - data["last_seen"] > self.max_age
]
for tid in expired_ids:
del self._lost_embeddings[tid]
return TrackedFrame(
frame_id = self._frame_id,
camera_id = self.camera_id,
tracks = tracked_objects,
timestamp_ms = time.time() * 1000,
fps = self.fps,
)
# ── 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
if frames_since == 1:
track = next((t for t in raw_tracks if int(t.track_id) == tid), None)
if track is not None and hasattr(track, "features") and track.features:
self._lost_embeddings[tid] = {
"embedding": track.features[-1],
"last_seen": self._frame_id,
}
self._emit_lifecycle(
TrackState.LOST, tid,
prev_obj.zones_present,
prev_obj.dwell_time_seconds,
)
if frames_since > self._tracker.max_age:
self._emit_lifecycle(
TrackState.DEAD, tid,
prev_obj.zones_present,
prev_obj.dwell_time_seconds,
)
del self._active_tracks[tid]
logger.info(f"Track DEAD: #{tid} after {prev_obj.dwell_time_seconds:.1f}s")
# ── Cleanup expired ReID embeddings ──────────────────
expired_ids = [
tid for tid, data in self._lost_embeddings.items()
if self._frame_id - data["last_seen"] > self.max_age
]
for tid in expired_ids:
del self._lost_embeddings[tid]
return TrackedFrame(
frame_id = self._frame_id,
camera_id = self.camera_id,
tracks = tracked_objects,
timestamp_ms = time.time() * 1000,
fps = self.fps,
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/tracking/tracker.py` around lines 199 - 240, Move the LOST/DEAD
lifecycle scan, expired ReID cleanup, and TrackedFrame return out of the
lost-embedding candidate loop so they execute once per update() call, including
when _lost_embeddings is empty. In the disappeared-track loop, initialize track
for each iteration before checking its features, or restrict the feature access
to the frames_since == 1 branch, preventing undefined or stale values while
preserving embedding capture only for newly lost tracks.

@saurabhhhcodes

Copy link
Copy Markdown
Contributor Author

🔔 Friendly ping @Devnil434 — this real fix has passing CI and references an approved GSSoC issue. Ready for review/merge. Thanks!

1 similar comment
@saurabhhhcodes

Copy link
Copy Markdown
Contributor Author

🔔 Friendly ping @Devnil434 — this real fix has passing CI and references an approved GSSoC issue. Ready for review/merge. Thanks!

@Devnil434
Devnil434 merged commit dab6542 into Devnil434:main Jul 23, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants