@camera.ui/rust-postprocessor 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.yarnrc.yml ADDED
@@ -0,0 +1 @@
1
+ nodeLinker: node-modules
package/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ All notable changes to this project will be documented in this file.
2
+
3
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
4
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+
6
+ ## [X.X.X] - ???
7
+
8
+ - Initial Release
@@ -0,0 +1 @@
1
+ # Contributing
package/Cargo.toml ADDED
@@ -0,0 +1,29 @@
1
+ [package]
2
+ edition = "2021"
3
+ name = "cameraui-rust-postprocessor"
4
+ version = "0.0.0"
5
+
6
+ [lib]
7
+ crate-type = ["cdylib"]
8
+
9
+ [dependencies]
10
+ napi = { version = "3.8.4", default-features = false, features = ["napi9"] }
11
+ napi-derive = "3.5.3"
12
+
13
+ # Tracker engine — Rust port of the Python `norfair` library used by Frigate.
14
+ # Same battle-tested algorithm: Kalman filter, IoU/Euclidean distance, ReID lifecycle.
15
+ norfair-rs = { version = "0.4.1", default-features = false }
16
+
17
+ # Stable nalgebra for the few places we construct DMatrix ourselves
18
+ nalgebra = "0.34.2"
19
+
20
+ # SIMD primitives for our NMS implementation (f32x8 = 8 floats parallel).
21
+ wide = "1.2.0"
22
+
23
+ [build-dependencies]
24
+ napi-build = "2.3.1"
25
+
26
+ [profile.release]
27
+ lto = true
28
+ strip = "symbols"
29
+ codegen-units = 1
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @camera.ui/rust-postprocessor
2
+
3
+ ---
4
+
5
+ *Part of the camera.ui ecosystem - A comprehensive camera management solution.*
package/build.rs ADDED
@@ -0,0 +1,5 @@
1
+ extern crate napi_build;
2
+
3
+ fn main() {
4
+ napi_build::setup();
5
+ }
package/index.d.ts ADDED
@@ -0,0 +1,254 @@
1
+ /* auto-generated by NAPI-RS */
2
+ /* eslint-disable */
3
+ /**
4
+ * Multi-class IoU + Kalman object tracker.
5
+ *
6
+ * Wraps `norfair-rs` with one sub-tracker per class label. Track ids are
7
+ * stable across frames and globally unique across classes. The tracker is
8
+ * resolution-independent — feed normalized `[0.0, 1.0]` coordinates.
9
+ */
10
+ export declare class ObjectTracker {
11
+ constructor(options?: ObjectTrackerOptions | undefined | null)
12
+ /**
13
+ * Process one frame's detections and return active tracks plus any
14
+ * line-crossing events that fired this frame. Detections without
15
+ * overlap to existing tracks spawn new ones; existing tracks without a
16
+ * matching detection are kept alive via Kalman extrapolation until
17
+ * `hitCounterMax` frames have elapsed.
18
+ *
19
+ * `timestampMs` is forwarded onto emitted crossing events for sequencing
20
+ * — pass `Date.now()` (or any monotonic millisecond clock).
21
+ */
22
+ update(detections: Array<Detection>, timestampMs: number): UpdateResult
23
+ /**
24
+ * Replace the configured crossing lines. Pass an empty array to disable
25
+ * line crossings entirely. The `aspectRatio` is the camera's
26
+ * `width / height` so the perpendicular crossing line ends up visually
27
+ * perpendicular to the handle the user drew.
28
+ *
29
+ * Crossing memory is cleared on every reconfigure so existing tracks
30
+ * will fire again the moment they cross a freshly-edited line.
31
+ */
32
+ setLines(lines: Array<DetectionLine>, aspectRatio: number): void
33
+ /**
34
+ * Replace the configured detection zones (privacy masks + active
35
+ * include/exclude regions). Pass an empty array to disable zone
36
+ * filtering entirely. Coordinates are in `[0, 100]` UI space — the
37
+ * tracker normalizes them internally and auto-closes polygons.
38
+ *
39
+ * The filter runs at the start of every `update()` call before
40
+ * detections reach the underlying tracker.
41
+ */
42
+ setZones(zones: Array<DetectionZone>): void
43
+ /**
44
+ * Set the minimum detection confidence threshold. Detections below
45
+ * this score are dropped at the start of every `update()` call before
46
+ * they reach the zone filter or tracker. Default 0.0 (no threshold).
47
+ */
48
+ setMinConfidence(minConfidence: number): void
49
+ /**
50
+ * Set how many frames a dead track stays available for ReID re-matching.
51
+ * When a new detection appears near a dead track's last position,
52
+ * norfair merges them — the old track ID is preserved with fresh
53
+ * Kalman state. Pass 0 to disable ReID. Use to tie track re-ID
54
+ * window to an external cascade timeout (e.g. `cascadeTimeout * fps`).
55
+ */
56
+ setReidHitCounterMax(frames: number): void
57
+ /**
58
+ * Refresh the ReID counter for all dead tracks back to max. Call every
59
+ * frame while a cascade is active so dead tracks never expire during
60
+ * the cascade window. Stop calling when the cascade ends.
61
+ */
62
+ refreshReid(): void
63
+ /**
64
+ * Apply the configured zones + confidence threshold to a list of
65
+ * detections WITHOUT advancing the tracker state. Returns the indices
66
+ * (positions in the input array) of the detections that pass the
67
+ * filter. Used for the external-sensor-write path where a plugin
68
+ * reports detections directly and we want to apply zone filtering
69
+ * without running the full tracker.
70
+ */
71
+ filterIndices(detections: Array<Detection>): Array<number>
72
+ /**
73
+ * Drop every active track. The next `update()` starts from a clean
74
+ * slate and track ids restart from 1. Use this to align tracker state
75
+ * with external segment boundaries (e.g. cascade activate/deactivate).
76
+ */
77
+ reset(): void
78
+ /** Total number of active tracks across all class buckets. */
79
+ get trackCount(): number
80
+ }
81
+
82
+ /** Bounding box used by [`box_iou`]. */
83
+ export interface BoundingBox {
84
+ x: number
85
+ y: number
86
+ width: number
87
+ height: number
88
+ }
89
+
90
+ /** Compute IoU between two normalized `[x, y, width, height]` boxes. */
91
+ export declare function boxIou(a: BoundingBox, b: BoundingBox): number
92
+
93
+ /** Detection in normalized image coordinates `[0.0, 1.0]`. */
94
+ export interface Detection {
95
+ x: number
96
+ y: number
97
+ width: number
98
+ height: number
99
+ confidence: number
100
+ label: string
101
+ }
102
+
103
+ /**
104
+ * User-facing crossing line definition.
105
+ *
106
+ * `points` are the two handle endpoints in `[0, 100]` UI coordinates.
107
+ * `labels` is the set of allowed detection labels — empty means "any label".
108
+ */
109
+ export interface DetectionLine {
110
+ name: string
111
+ direction: LineDirection
112
+ labels: Array<string>
113
+ points: Array<Array<number>>
114
+ }
115
+
116
+ /**
117
+ * User-facing detection zone definition (matches the SDK shape).
118
+ *
119
+ * `points` are polygon vertices in `[0, 100]` UI coordinates. The
120
+ * polygon is auto-closed so the caller does not need to repeat the
121
+ * first vertex at the end.
122
+ */
123
+ export interface DetectionZone {
124
+ labels: Array<string>
125
+ filter: ZoneFilterMode
126
+ /**
127
+ * Match mode (intersect vs contain). Mapped to `type` on the JS side
128
+ * of the SDK — the consumer is responsible for translating that field.
129
+ */
130
+ matchType: ZoneMatchType
131
+ isPrivacyMask: boolean
132
+ points: Array<Array<number>>
133
+ }
134
+
135
+ /** Crossing event emitted by [`ObjectTracker::update`]. */
136
+ export interface LineCrossingEvent {
137
+ lineName: string
138
+ direction: LineDirection
139
+ trackId: number
140
+ label: string
141
+ confidence: number
142
+ /** Frame timestamp in milliseconds (forwarded from the `update()` call). */
143
+ timestampMs: number
144
+ prevX: number
145
+ prevY: number
146
+ currX: number
147
+ currY: number
148
+ }
149
+
150
+ /**
151
+ * Crossing line direction filter.
152
+ *
153
+ * `"both"` accepts either direction. `"a-to-b"` and `"b-to-a"` only fire
154
+ * when the track moves from one specific side to the other.
155
+ */
156
+ export declare const enum LineDirection {
157
+ Both = 'both',
158
+ AToB = 'a-to-b',
159
+ BToA = 'b-to-a'
160
+ }
161
+
162
+ /**
163
+ * Cluster nearby/overlapping same-label detections into single union
164
+ * boxes via union-find. Two boxes join the same cluster if their
165
+ * top-left corners are within `closeThreshold` along both axes OR if
166
+ * their IoU exceeds `iouThreshold`. Each cluster collapses to one
167
+ * `Detection` whose box covers the axis-aligned union of all members
168
+ * (clamped to `[0, 1]`) and whose confidence is the maximum in the
169
+ * cluster. Different labels are never clustered together.
170
+ */
171
+ export declare function merge(detections: Array<Detection>, iouThreshold: number, closeThreshold: number): Array<Detection>
172
+
173
+ /**
174
+ * Run greedy non-maximum suppression on a list of detections.
175
+ *
176
+ * Detections are suppressed only against higher-confidence boxes of the
177
+ * same `label`. Output is sorted by confidence descending. Pass
178
+ * `maxDetections` to cap the result length.
179
+ */
180
+ export declare function nms(detections: Array<Detection>, iouThreshold: number, maxDetections?: number | undefined | null): Array<Detection>
181
+
182
+ /** Constructor options for [`ObjectTracker`]. */
183
+ export interface ObjectTrackerOptions {
184
+ /**
185
+ * IoU threshold above which a candidate is matched to an existing track.
186
+ * Higher = stricter matching. Default 0.3.
187
+ */
188
+ iouThreshold?: number
189
+ /**
190
+ * Frames a track survives without a fresh detection (Kalman extrapolation
191
+ * continues during this window). Default 15.
192
+ */
193
+ hitCounterMax?: number
194
+ /**
195
+ * Frames a new track must be matched before getting a permanent id —
196
+ * filters one-frame false positives. Default 3.
197
+ */
198
+ initializationDelay?: number
199
+ /**
200
+ * Frames a dead track stays available for ReID re-matching. When a new
201
+ * detection appears near a dead track, norfair merges them — the old
202
+ * track ID is preserved. Default: disabled.
203
+ */
204
+ reidHitCounterMax?: number
205
+ }
206
+
207
+ /** Detection with stable identity assigned by [`ObjectTracker`]. */
208
+ export interface TrackedDetection {
209
+ x: number
210
+ y: number
211
+ width: number
212
+ height: number
213
+ confidence: number
214
+ label: string
215
+ /** Stable id across frames. Resets only on `ObjectTracker::reset()`. */
216
+ trackId: number
217
+ /** Number of frames this track has existed (1 on first emit). */
218
+ trackAge: number
219
+ /**
220
+ * True when the box is being kept alive by Kalman extrapolation
221
+ * instead of a fresh detection match this frame.
222
+ */
223
+ trackLost: boolean
224
+ /**
225
+ * Average centroid speed in normalized units/second over a sliding
226
+ * ~1 s window of past positions. Consumers can compare against a
227
+ * threshold (typical: 0.05) to distinguish moving from stationary
228
+ * tracks. 0 when the track has only one sample so far.
229
+ */
230
+ trackSpeed: number
231
+ }
232
+
233
+ /**
234
+ * Result of [`ObjectTracker::update`] — active tracks plus any crossings
235
+ * fired this frame.
236
+ */
237
+ export interface UpdateResult {
238
+ tracked: Array<TrackedDetection>
239
+ crossings: Array<LineCrossingEvent>
240
+ }
241
+
242
+ /** How an active zone affects matching detections. */
243
+ export declare const enum ZoneFilterMode {
244
+ Include = 'include',
245
+ Exclude = 'exclude'
246
+ }
247
+
248
+ /** Where in the polygon the box must sit for the zone to apply. */
249
+ export declare const enum ZoneMatchType {
250
+ /** Any overlap with the polygon counts. */
251
+ Intersect = 'intersect',
252
+ /** All four corners of the box must be inside the polygon. */
253
+ Contain = 'contain'
254
+ }