overstep — Library Specification
- Overview
overstep is a lightweight, dependency-minimal Python library for boundary violation detection of tracked objects in video. Core: given a boundary (polygon or line) and object detections, determine in/out status, penetration depth, crossing direction, and predicted violations. Support: a simple Kalman-filter tracker providing IDs and motion vectors.
Non-goals: object detection (user brings detections), advanced trackers (ByteTrack/OC-SORT), visualization beyond basic debug drawing, multi-camera.
Dependencies: numpy only. (scipy optional for Hungarian matching; fallback to greedy.)
- Package layout overstep/ init.py geometry.py # primitives: point-in-polygon, signed distance, segment intersection, clipping boundary.py # Boundary classes: PolygonZone, LineBoundary kalman.py # KalmanBoxTracker (single object) tracker.py # MultiObjectTracker (assignment + lifecycle) events.py # event types + debounced event engine types.py # dataclasses: Detection, Track, Violation, Event tests/ examples/
- Data types (types.py) python @dataclass class Detection: bbox: np.ndarray # [x1, y1, x2, y2] score: float = 1.0 class_id: int = -1
@dataclass class Track: id: int bbox: np.ndarray velocity: np.ndarray # [vx, vy] px/frame from KF age: int hits: int time_since_update: int
@dataclass class Violation: track_id: int inside: bool depth: float # signed distance: + inside, - outside overlap_ratio: float # 0..1, bbox area inside zone predicted_cross_in: int | None # frames until predicted crossing, None if diverging
@dataclass class Event: type: Literal["enter", "exit", "cross", "dwell"] track_id: int frame: int direction: Literal["ab", "ba"] | None # for line crossings meta: dict 4. Geometry (geometry.py)
Pure functions, numpy arrays, no state:
point_in_polygon(pt, poly) -> bool — ray casting (even-odd). Poly is (N,2) array. signed_distance(pt, poly) -> float — min point-to-segment distance over edges; positive if inside. segments_intersect(p1, p2, q1, q2) -> bool — CCW/orientation test. crossing_direction(p1, p2, a, b) -> Literal["ab","ba"] | None — cross product sign of movement vs line normal. clip_polygon(subject, clip) -> np.ndarray — Sutherland–Hodgman; used for overlap ratio. polygon_area(poly) -> float — shoelace.
Edge cases to handle: point exactly on edge (treat as inside), degenerate polygons (<3 pts → raise), collinear segments.
- Boundaries (boundary.py) python class PolygonZone: def init(self, polygon: ArrayLike, anchor: str = "bottom_center"): # anchor: which bbox point represents the object # options: "bottom_center", "center", "full_box" def check(self, track: Track) -> Violation
class LineBoundary: def init(self, a: Point, b: Point): def check(self, track: Track, prev_pos: Point) -> Violation # segment intersection between (prev_pos -> current) and (a, b) # depth = perpendicular signed distance; direction via cross product
Both stateless w.r.t. tracks — all history lives in the event engine.
- Kalman tracker (kalman.py, tracker.py)
State (SORT-style, constant velocity): x = [cx, cy, s, r, vcx, vcy, vs] where s = scale (area), r = aspect ratio (constant). Standard KF predict/update, hand-rolled with numpy (7x7 matrices, no filterpy dependency).
MultiObjectTracker:
python class MultiObjectTracker: def init(self, max_age=10, min_hits=3, iou_threshold=0.3): def update(self, detections: list[Detection]) -> list[Track] Predict all trackers → IoU matrix vs detections → greedy matching (sort by IoU desc), Hungarian if scipy available. Unmatched detections → new tentative tracks (confirmed after min_hits). Unmatched tracks → coast (predict only) for max_age frames, then delete. Expose velocity from KF state (convert vcx, vcy to px/frame). 7. Event engine (events.py) python class EventEngine: def init(self, boundary, debounce_frames=3, dwell_frames=90): def update(self, tracks: list[Track], frame: int) -> list[Event] Per-track state machine: OUTSIDE → (inside for debounce_frames consecutive) → INSIDE → emits enter. Symmetric for exit. dwell emitted once when a track stays INSIDE ≥ dwell_frames. Line boundaries emit cross with direction (debounced: one cross event per actual traversal, suppress jitter re-crossings within debounce window). Track deletion while INSIDE → emit exit with meta={"reason": "lost"}. 8. Top-level API python import overstep as ov
zone = ov.PolygonZone([(100,100), (500,100), (500,400), (100,400)]) tracker = ov.MultiObjectTracker() engine = ov.EventEngine(zone, debounce_frames=3)
for frame_idx, detections in enumerate(detection_stream): tracks = tracker.update(detections) violations = [zone.check(t) for t in tracks] events = engine.update(tracks, frame_idx) 9. Testing requirements Unit tests for every geometry function (concave polygons, on-edge points, collinear, direction signs). Synthetic trajectory tests: scripted object paths (straight cross, jitter at boundary, enter-dwell-exit, occlusion gap) asserting exact event sequences. KF sanity: constant-velocity object → velocity estimate converges within tolerance. Property test: signed_distance sign always agrees with point_in_polygon. 10. Milestones geometry.py + full tests boundary.py (Polygon + Line) + tests kalman.py + tracker.py + synthetic tests events.py state machine + trajectory tests Top-level API, README with quickstart, one example script using dummy detections Optional: overlap-ratio clipping, predicted-violation horizon
Constraints for implementation: Python ≥3.10, numpy as sole hard dependency, type hints everywhere, no classes in geometry.py (pure functions), docstrings with the algorithm name used.