Skip to content

Commit 8f1cc52

Browse files
committed
UPDATE: Docstrings
1 parent f521b47 commit 8f1cc52

2 files changed

Lines changed: 117 additions & 50 deletions

File tree

trackers/core/ksp/solver.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,19 +73,40 @@ def __init__(
7373
self.reset()
7474

7575
def reset(self) -> None:
76+
"""
77+
Reset the solver state, clearing all buffered detections and the graph.
78+
"""
7679
self.detection_per_frame = []
7780
self.graph = nx.DiGraph()
7881

7982
def append_frame(self, detections: sv.Detections) -> None:
83+
"""
84+
Add detections for the current frame to the solver's buffer.
85+
86+
Args:
87+
detections (sv.Detections): Detections for the current frame.
88+
"""
8089
self.detection_per_frame.append(detections)
8190

8291
def _get_center(self, bbox: np.ndarray) -> np.ndarray:
92+
"""
93+
Compute the center (x, y) of a bounding box.
94+
95+
Args:
96+
bbox (np.ndarray): Bounding box as [x1, y1, x2, y2].
97+
98+
Returns:
99+
np.ndarray: Center coordinates as (x, y).
100+
"""
83101
x1, y1, x2, y2 = bbox
84102
return np.array([(x1 + x2) / 2, (y1 + y2) / 2])
85103

86104
def set_entry_exit_regions(self, regions: List[Tuple[int, int, int, int]]) -> None:
87105
"""
88106
Set rectangular entry/exit zones (x1, y1, x2, y2).
107+
108+
Args:
109+
regions (List[Tuple[int, int, int, int]]): List of rectangular regions.
89110
"""
90111
self.entry_exit_regions = regions
91112

@@ -101,9 +122,9 @@ def set_border_entry_exit(
101122
102123
Args:
103124
use_border (bool): Enable/disable border-based entry/exit.
104-
borders (set): Set of borders to use. {"left", "right", "top", "bottom"}
105-
margin (int): Border thickness in pixels.
106-
frame_size (Tuple[int, int]): Size of the image (width, height).
125+
borders (Optional[Set[str]]): Set of borders to use.
126+
margin (Optional[int]): Border thickness in pixels.
127+
frame_size (Optional[Tuple[int, int]]): Size of the image (width, height).
107128
"""
108129
self.use_border_regions = use_border
109130
self.active_borders = (
@@ -113,6 +134,15 @@ def set_border_entry_exit(
113134
self.frame_size = frame_size
114135

115136
def _in_door(self, node: TrackNode) -> bool:
137+
"""
138+
Check if a node is inside any entry/exit region (rectangular or border).
139+
140+
Args:
141+
node (TrackNode): The node to check.
142+
143+
Returns:
144+
bool: True if in any entry/exit region, else False.
145+
"""
116146
x, y = node.position
117147

118148
# Check custom rectangular regions
@@ -137,6 +167,16 @@ def _in_door(self, node: TrackNode) -> bool:
137167
return False
138168

139169
def _edge_cost(self, nodeU: TrackNode, nodeV: TrackNode) -> float:
170+
"""
171+
Compute the cost of linking two detections (nodes) in the graph.
172+
173+
Args:
174+
nodeU (TrackNode): Source node.
175+
nodeV (TrackNode): Destination node.
176+
177+
Returns:
178+
float: Edge cost based on IoU, distance, size, and confidence weights.
179+
"""
140180
bboxU, bboxV = nodeU.bbox, nodeV.bbox
141181
conf_u, conf_v = nodeU.confidence, nodeV.confidence
142182

@@ -159,6 +199,10 @@ def _edge_cost(self, nodeU: TrackNode, nodeV: TrackNode) -> float:
159199
)
160200

161201
def _build_graph(self):
202+
"""
203+
Build the directed graph of detections for KSP computation.
204+
Nodes represent detections; edges represent possible associations.
205+
"""
162206
G = nx.DiGraph()
163207
G.add_node(self.source)
164208
G.add_node(self.sink)
@@ -198,6 +242,22 @@ def _build_graph(self):
198242
self.graph = G
199243

200244
def solve(self, k: Optional[int] = None) -> List[List[TrackNode]]:
245+
"""
246+
Solve the K-Shortest Paths problem on the constructed detection graph.
247+
248+
This method extracts up to k node-disjoint paths from the source to the sink in
249+
the detection graph, assigning each path as a unique object track. Edge reuse is
250+
penalized to encourage distinct tracks. The cost of each edge is determined by
251+
the configured weights for IoU, distance, size, and confidence.
252+
253+
Args:
254+
k (Optional[int]): The number of tracks (paths) to extract. If None, uses
255+
the maximum number of detections in any frame as the default.
256+
257+
Returns:
258+
List[List[TrackNode]]: A list of tracks, each track is a list of TrackNode
259+
objects representing the detections assigned to that track.
260+
"""
201261
self._build_graph()
202262

203263
G_base = self.graph.copy()

trackers/core/ksp/tracker.py

Lines changed: 54 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,60 +20,57 @@ class KSPTracker(BaseOfflineTracker):
2020
def __init__(
2121
self,
2222
path_overlap_penalty: Optional[int] = 40,
23-
iou_weight: Optional[int] = 0.9,
24-
dist_weight: Optional[int] = 0.1,
25-
size_weight: Optional[int] = 0.1,
26-
conf_weight: Optional[int] = 0.1,
23+
iou_weight: Optional[float] = 0.9,
24+
dist_weight: Optional[float] = 0.1,
25+
size_weight: Optional[float] = 0.1,
26+
conf_weight: Optional[float] = 0.1,
2727
entry_exit_regions: Optional[List[Tuple[int, int, int, int]]] = None,
2828
use_border: Optional[bool] = True,
2929
borders: Optional[Set[str]] = None,
3030
border_margin: Optional[int] = 40,
3131
frame_size: Optional[Tuple[int, int]] = (1920, 1080),
3232
) -> None:
3333
"""
34-
Initialize the KSPTracker and its solver.
34+
Initialize the KSPTracker and its underlying solver with region and cost configuration.
3535
3636
Args:
37-
path_overlap_penalty (Optional[int]): Penalty for reusing the same edge
38-
(detection pairing) in multiple tracks. Increasing this value encourages
39-
the tracker to produce more distinct, non-overlapping tracks by
40-
discouraging shared detections between tracks.
41-
42-
iou_weight (Optional[int]): Weight for the Intersection-over-Union (IoU)
43-
penalty in the edge cost. Higher values make the tracker favor linking
44-
detections with greater spatial overlap, which helps maintain track
45-
continuity for objects that move smoothly.
46-
47-
dist_weight (Optional[int]): Weight for the Euclidean distance between
48-
detection centers in the edge cost. Increasing this value penalizes
49-
large jumps between detections in consecutive frames, promoting
50-
smoother, more physically plausible tracks.
51-
52-
size_weight (Optional[int]): Weight for the size difference penalty in the
53-
edge cost. Higher values penalize linking detections with significantly
54-
different bounding box areas, which helps prevent identity switches when
55-
object size changes abruptly.
56-
57-
conf_weight (Optional[int]): Weight for the confidence penalty in the edge
58-
cost. Higher values penalize edges between detections with lower
59-
confidence scores, making the tracker prefer more reliable detections
60-
and reducing the impact of false positives.
61-
62-
entry_exit_regions (Optional[List[Tuple[int, int, int, int]]]): List of rectangular entry/exit regions.
63-
use_border (Optional[bool]): Enable/disable border-based entry/exit.
64-
borders (Optional[Set[str]]): Set of borders to use. {"left", "right", "top", "bottom"}
65-
border_margin (Optional[int]): Border thickness in pixels.
66-
frame_size (Optional[Tuple[int, int]]): Size of the image (width, height).
37+
path_overlap_penalty (Optional[int]): Penalty for reusing the same edge (detection pairing) in multiple tracks.
38+
Higher values encourage the tracker to produce more distinct, non-overlapping tracks by discouraging shared
39+
detections between tracks. Default is 40.
40+
iou_weight (Optional[float]): Weight for the Intersection-over-Union (IoU) penalty in the edge cost.
41+
Higher values make the tracker favor linking detections with greater spatial overlap, which helps maintain
42+
track continuity for objects that move smoothly. Default is 0.9.
43+
dist_weight (Optional[float]): Weight for the Euclidean distance between detection centers in the edge cost.
44+
Increasing this value penalizes large jumps between detections in consecutive frames, promoting smoother,
45+
more physically plausible tracks. Default is 0.1.
46+
size_weight (Optional[float]): Weight for the size difference penalty in the edge cost.
47+
Higher values penalize linking detections with significantly different bounding box areas, which helps
48+
prevent identity switches when object size changes abruptly. Default is 0.1.
49+
conf_weight (Optional[float]): Weight for the confidence penalty in the edge cost.
50+
Higher values penalize edges between detections with lower confidence scores, making the tracker prefer
51+
more reliable detections and reducing the impact of false positives. Default is 0.1.
52+
entry_exit_regions (Optional[List[Tuple[int, int, int, int]]]): List of rectangular entry/exit regions,
53+
each defined as (x1, y1, x2, y2) in pixel coordinates. These regions are used to determine when objects
54+
enter or exit the scene. Default is an empty list.
55+
use_border (Optional[bool]): Whether to enable border-based entry/exit logic. If True, objects entering or
56+
exiting through the image borders (as defined by `borders` and `border_margin`) are considered for
57+
entry/exit events. Default is True.
58+
borders (Optional[Set[str]]): Set of border sides to use for entry/exit logic. Valid values are any subset
59+
of {"left", "right", "top", "bottom"}. Default is all four borders.
60+
border_margin (Optional[int]): Thickness of the border region (in pixels) used for entry/exit detection.
61+
Default is 40.
62+
frame_size (Optional[Tuple[int, int]]): Size of the image frames as (width, height). Used to determine
63+
border region extents. Default is (1920, 1080).
6764
"""
68-
self.entry_exit_regions = (
65+
self.entry_exit_regions: List[Tuple[int, int, int, int]] = (
6966
entry_exit_regions if entry_exit_regions is not None else []
7067
)
71-
self.use_border = use_border
72-
self.borders = (
68+
self.use_border: bool = use_border if use_border is not None else True
69+
self.borders: Set[str] = (
7370
borders if borders is not None else {"left", "right", "top", "bottom"}
7471
)
75-
self.border_margin = border_margin
76-
self.frame_size = frame_size
72+
self.border_margin: int = border_margin if border_margin is not None else 40
73+
self.frame_size: Tuple[int, int] = frame_size if frame_size is not None else (1920, 1080)
7774
self._solver = KSPSolver(
7875
path_overlap_penalty=path_overlap_penalty,
7976
iou_weight=iou_weight,
@@ -113,7 +110,10 @@ def _update(self, detections: sv.Detections) -> sv.Detections:
113110

114111
def set_entry_exit_regions(self, regions: List[Tuple[int, int, int, int]]) -> None:
115112
"""
116-
Set rectangular entry/exit zones (x1, y1, x2, y2).
113+
Set rectangular entry/exit zones (x1, y1, x2, y2) and update both the tracker and solver.
114+
115+
Args:
116+
regions (List[Tuple[int, int, int, int]]): List of rectangular regions for entry/exit logic.
117117
"""
118118
self.entry_exit_regions = regions
119119
self._solver.set_entry_exit_regions(regions)
@@ -125,12 +125,19 @@ def set_border_entry_exit(
125125
margin: Optional[int] = 40,
126126
frame_size: Optional[Tuple[int, int]] = (1920, 1080),
127127
) -> None:
128-
self.use_border = use_border
129-
self.borders = (
130-
borders if borders is not None else {"left", "right", "top", "bottom"}
131-
)
132-
self.border_margin = margin
133-
self.frame_size = frame_size
128+
"""
129+
Configure border-based entry/exit zones and update both the tracker and solver.
130+
131+
Args:
132+
use_border (Optional[bool]): Enable/disable border-based entry/exit.
133+
borders (Optional[Set[str]]): Set of borders to use. {"left", "right", "top", "bottom"}
134+
margin (Optional[int]): Border thickness in pixels.
135+
frame_size (Optional[Tuple[int, int]]): Size of the image (width, height).
136+
"""
137+
self.use_border = use_border if use_border is not None else True
138+
self.borders = borders if borders is not None else {"left", "right", "top", "bottom"}
139+
self.border_margin = margin if margin is not None else 40
140+
self.frame_size = frame_size if frame_size is not None else (1920, 1080)
134141
self._solver.set_border_entry_exit(
135142
use_border=self.use_border,
136143
borders=self.borders,

0 commit comments

Comments
 (0)