@camstack/lib-pipeline-analysis 0.1.3 → 0.1.5
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/package.json +3 -2
- package/dist/index.d.mts +0 -333
- package/dist/index.d.ts +0 -333
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camstack/lib-pipeline-analysis",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Object tracking, state analysis, zone evaluation, and event emission for CamStack",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"camstack",
|
|
@@ -43,7 +43,8 @@
|
|
|
43
43
|
"dev": "tsup --watch",
|
|
44
44
|
"typecheck": "tsc --noEmit",
|
|
45
45
|
"test": "vitest run",
|
|
46
|
-
"test:watch": "vitest"
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"publish": "npm publish --access public"
|
|
47
48
|
},
|
|
48
49
|
"peerDependencies": {
|
|
49
50
|
"@camstack/types": "^0.1.0"
|
package/dist/index.d.mts
DELETED
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
import { SpatialDetection, TrackedDetection, TrackedObjectState, ZoneDefinition, ZoneEvent, DetectionEventType, Classification, DetectionEvent, TrackDetail, PipelineStatus, CameraLiveState, HeatmapData, ICameraAnalyticsProvider, TimeRangeOptions, ZoneHistoryPoint, HeatmapOptions, TrackFilter, TrackedObjectSummary, ZoneLiveState, IAddonFileStorage, FrameInput, EventSnapshot, ICamstackAddon, PipelineResult, PipelineConfig, KnownFace, KnownPlate, AddonManifest, AddonContext } from '@camstack/types';
|
|
2
|
-
|
|
3
|
-
interface Point {
|
|
4
|
-
readonly x: number;
|
|
5
|
-
readonly y: number;
|
|
6
|
-
}
|
|
7
|
-
interface LineSegment {
|
|
8
|
-
readonly p1: Point;
|
|
9
|
-
readonly p2: Point;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Point-in-polygon test using the ray casting algorithm.
|
|
13
|
-
* Returns true if the point is inside the polygon.
|
|
14
|
-
*/
|
|
15
|
-
declare function pointInPolygon(point: Point, polygon: readonly Point[]): boolean;
|
|
16
|
-
/**
|
|
17
|
-
* Line segment intersection — returns the intersection point or null if they don't intersect.
|
|
18
|
-
* Uses parametric form of line equations.
|
|
19
|
-
*/
|
|
20
|
-
declare function lineIntersection(a: LineSegment, b: LineSegment): Point | null;
|
|
21
|
-
/**
|
|
22
|
-
* Check if a movement from prev to curr crosses the given tripwire.
|
|
23
|
-
* Returns crossed=true and direction if crossed, null otherwise.
|
|
24
|
-
* Direction is determined by the cross product sign:
|
|
25
|
-
* - 'right' if moving from the left side of the tripwire to the right
|
|
26
|
-
* - 'left' if moving from the right side to the left
|
|
27
|
-
*/
|
|
28
|
-
declare function tripwireCrossing(prev: Point, curr: Point, tripwire: LineSegment): {
|
|
29
|
-
crossed: boolean;
|
|
30
|
-
direction: 'left' | 'right';
|
|
31
|
-
} | null;
|
|
32
|
-
/**
|
|
33
|
-
* Calculate the centroid (center point) of a bounding box.
|
|
34
|
-
*/
|
|
35
|
-
declare function bboxCentroid(bbox: {
|
|
36
|
-
x: number;
|
|
37
|
-
y: number;
|
|
38
|
-
w: number;
|
|
39
|
-
h: number;
|
|
40
|
-
}): Point;
|
|
41
|
-
/**
|
|
42
|
-
* Convert normalized coordinates (0–1 range) to pixel coordinates.
|
|
43
|
-
*/
|
|
44
|
-
declare function normalizeToPixel(point: Point, imageWidth: number, imageHeight: number): Point;
|
|
45
|
-
|
|
46
|
-
interface ITracker {
|
|
47
|
-
update(detections: readonly SpatialDetection[], frameTimestamp: number): TrackedDetection[];
|
|
48
|
-
getActiveTracks(): TrackedDetection[];
|
|
49
|
-
getLostTracks(): TrackedDetection[];
|
|
50
|
-
reset(): void;
|
|
51
|
-
}
|
|
52
|
-
interface TrackerConfig {
|
|
53
|
-
/** Frames without match before track is considered lost (default: 30) */
|
|
54
|
-
readonly maxAge: number;
|
|
55
|
-
/** Minimum detections before track is confirmed and returned (default: 3) */
|
|
56
|
-
readonly minHits: number;
|
|
57
|
-
/** Minimum IoU for a detection to match an existing track (default: 0.3) */
|
|
58
|
-
readonly iouThreshold: number;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
declare const DEFAULT_TRACKER_CONFIG: TrackerConfig;
|
|
62
|
-
declare class SortTracker implements ITracker {
|
|
63
|
-
private tracks;
|
|
64
|
-
private lostTracks;
|
|
65
|
-
private nextTrackId;
|
|
66
|
-
private readonly config;
|
|
67
|
-
constructor(config?: Partial<TrackerConfig>);
|
|
68
|
-
update(detections: readonly SpatialDetection[], frameTimestamp: number): TrackedDetection[];
|
|
69
|
-
getActiveTracks(): TrackedDetection[];
|
|
70
|
-
getLostTracks(): TrackedDetection[];
|
|
71
|
-
reset(): void;
|
|
72
|
-
private createTrack;
|
|
73
|
-
private getConfirmedTracks;
|
|
74
|
-
private ageTracksAndPruneLost;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Greedy assignment algorithm for matching detections to tracks based on IoU.
|
|
79
|
-
*
|
|
80
|
-
* Sorts all (track, detection) pairs by IoU descending, then greedily assigns
|
|
81
|
-
* pairs as long as neither the track nor detection has been assigned yet and
|
|
82
|
-
* the IoU meets the threshold.
|
|
83
|
-
*
|
|
84
|
-
* @param costMatrix [tracks × detections] — values are IoU (higher = better)
|
|
85
|
-
* @param threshold Minimum IoU to accept a match
|
|
86
|
-
* @returns matches, unmatchedTracks, unmatchedDetections indices
|
|
87
|
-
*/
|
|
88
|
-
declare function greedyAssignment(costMatrix: readonly (readonly number[])[], threshold: number): {
|
|
89
|
-
matches: [number, number][];
|
|
90
|
-
unmatchedTracks: number[];
|
|
91
|
-
unmatchedDetections: number[];
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
interface StateAnalyzerConfig {
|
|
95
|
-
/** Seconds without movement to transition to 'stationary' (default: 10) */
|
|
96
|
-
readonly stationaryThresholdSec: number;
|
|
97
|
-
/** Seconds stationary to transition to 'loitering' (default: 60) */
|
|
98
|
-
readonly loiteringThresholdSec: number;
|
|
99
|
-
/** Pixels/frame below which a track is considered not moving (default: 2) */
|
|
100
|
-
readonly velocityThreshold: number;
|
|
101
|
-
/** Track age (frames) below which it's considered 'entering' (default: 5) */
|
|
102
|
-
readonly enteringFrames: number;
|
|
103
|
-
}
|
|
104
|
-
declare const DEFAULT_STATE_ANALYZER_CONFIG: StateAnalyzerConfig;
|
|
105
|
-
declare class StateAnalyzer {
|
|
106
|
-
private readonly states;
|
|
107
|
-
private readonly config;
|
|
108
|
-
constructor(config?: Partial<StateAnalyzerConfig>);
|
|
109
|
-
analyze(tracks: readonly TrackedDetection[], timestamp: number): TrackedObjectState[];
|
|
110
|
-
private computeObjectState;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
declare class ZoneEvaluator {
|
|
114
|
-
/** Track which zones each track was in last frame */
|
|
115
|
-
private readonly trackZoneState;
|
|
116
|
-
evaluate(tracks: readonly TrackedDetection[], zones: readonly ZoneDefinition[], imageWidth: number, imageHeight: number, timestamp: number): ZoneEvent[];
|
|
117
|
-
/** Returns a snapshot of the current track → zones mapping */
|
|
118
|
-
getTrackZones(): Map<string, Set<string>>;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
interface EventFilterConfig {
|
|
122
|
-
/** Don't emit for tracks younger than N frames */
|
|
123
|
-
readonly minTrackAge: number;
|
|
124
|
-
/** Seconds between same event type for same track */
|
|
125
|
-
readonly cooldownSec: number;
|
|
126
|
-
/** Event types that are enabled */
|
|
127
|
-
readonly enabledTypes: readonly DetectionEventType[];
|
|
128
|
-
}
|
|
129
|
-
declare const DEFAULT_EVENT_FILTER_CONFIG: EventFilterConfig;
|
|
130
|
-
declare class EventFilter {
|
|
131
|
-
private readonly config;
|
|
132
|
-
/** key: `${trackId}:${eventType}` → last emitted timestamp */
|
|
133
|
-
private readonly lastEmitted;
|
|
134
|
-
constructor(config: EventFilterConfig);
|
|
135
|
-
shouldEmit(trackId: string, eventType: DetectionEventType, trackAge: number, timestamp: number): boolean;
|
|
136
|
-
/** Record an emission without a gate check — for events that bypass normal cooldown logic */
|
|
137
|
-
recordEmission(trackId: string, eventType: DetectionEventType, timestamp: number): void;
|
|
138
|
-
/** Remove cooldown entries for tracks that are no longer active */
|
|
139
|
-
cleanup(activeTrackIds: ReadonlySet<string>): void;
|
|
140
|
-
/** Reset all cooldown state */
|
|
141
|
-
reset(): void;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
declare class DetectionEventEmitter {
|
|
145
|
-
private readonly filter;
|
|
146
|
-
/** trackId → last known ObjectState */
|
|
147
|
-
private readonly previousStates;
|
|
148
|
-
constructor(filterConfig?: Partial<EventFilterConfig>);
|
|
149
|
-
emit(tracks: readonly TrackedDetection[], states: readonly TrackedObjectState[], zoneEvents: readonly ZoneEvent[], classifications: readonly {
|
|
150
|
-
trackId: string;
|
|
151
|
-
classifications: Classification[];
|
|
152
|
-
}[], deviceId: string): DetectionEvent[];
|
|
153
|
-
private tryEmit;
|
|
154
|
-
reset(): void;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
declare class TrackStore {
|
|
158
|
-
private readonly activeTracks;
|
|
159
|
-
update(tracks: readonly TrackedDetection[], states: readonly TrackedObjectState[], zoneEvents: readonly ZoneEvent[], classifications: readonly {
|
|
160
|
-
trackId: string;
|
|
161
|
-
classifications: Classification[];
|
|
162
|
-
}[]): void;
|
|
163
|
-
/** Attach a DetectionEvent to its associated track accumulator */
|
|
164
|
-
addEvent(trackId: string, event: DetectionEvent): void;
|
|
165
|
-
/** Get accumulated detail for an active track */
|
|
166
|
-
getTrackDetail(trackId: string): TrackDetail | null;
|
|
167
|
-
/** Called when a track ends — returns complete TrackDetail and removes from active set */
|
|
168
|
-
finishTrack(trackId: string): TrackDetail | null;
|
|
169
|
-
/** Get all active track IDs */
|
|
170
|
-
getActiveTrackIds(): string[];
|
|
171
|
-
/** Clear all accumulated state */
|
|
172
|
-
reset(): void;
|
|
173
|
-
private buildTrackDetail;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
declare class LiveStateManager {
|
|
177
|
-
private zones;
|
|
178
|
-
private readonly fpsCounter;
|
|
179
|
-
private readonly pipelineMsHistory;
|
|
180
|
-
private readonly maxPipelineHistory;
|
|
181
|
-
setZones(zones: ZoneDefinition[]): void;
|
|
182
|
-
buildState(deviceId: string, tracks: readonly TrackedDetection[], states: readonly TrackedObjectState[], trackZones: Map<string, Set<string>>, pipelineStatus: readonly PipelineStatus[], pipelineMs: number, lastEvent: DetectionEvent | undefined, timestamp: number): CameraLiveState;
|
|
183
|
-
reset(): void;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Grid-based accumulator for track centroid positions.
|
|
188
|
-
*
|
|
189
|
-
* Positions are normalized (0–1) and mapped into a grid of `gridSize × gridSize` cells.
|
|
190
|
-
* Each update increments the count of the corresponding cell.
|
|
191
|
-
*/
|
|
192
|
-
declare class HeatmapAggregator {
|
|
193
|
-
private readonly width;
|
|
194
|
-
private readonly height;
|
|
195
|
-
private readonly gridSize;
|
|
196
|
-
private readonly grid;
|
|
197
|
-
private maxCount;
|
|
198
|
-
constructor(width: number, height: number, gridSize: number);
|
|
199
|
-
/**
|
|
200
|
-
* Add a normalized point (0–1 range) to the heatmap.
|
|
201
|
-
* Points outside [0, 1] are clamped to the grid boundary.
|
|
202
|
-
*/
|
|
203
|
-
addPoint(x: number, y: number): void;
|
|
204
|
-
/** Add a pixel-coordinate point. Normalizes against the configured width/height. */
|
|
205
|
-
addPixelPoint(px: number, py: number): void;
|
|
206
|
-
getHeatmap(): HeatmapData;
|
|
207
|
-
reset(): void;
|
|
208
|
-
/** Total number of points accumulated */
|
|
209
|
-
get totalPoints(): number;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
declare class AnalyticsProvider implements ICameraAnalyticsProvider {
|
|
213
|
-
private readonly liveStateManager;
|
|
214
|
-
private readonly trackStore;
|
|
215
|
-
private readonly getZoneHistoryCb?;
|
|
216
|
-
private readonly getHeatmapCb?;
|
|
217
|
-
/** Cached live state reference — kept up to date by the pipeline orchestrator */
|
|
218
|
-
private lastLiveState;
|
|
219
|
-
constructor(liveStateManager: LiveStateManager, trackStore: TrackStore, getZoneHistoryCb?: ((deviceId: string, zoneId: string, options: TimeRangeOptions) => Promise<ZoneHistoryPoint[]>) | undefined, getHeatmapCb?: ((deviceId: string, options: HeatmapOptions) => Promise<HeatmapData>) | undefined,
|
|
220
|
-
/** Cached live state reference — kept up to date by the pipeline orchestrator */
|
|
221
|
-
lastLiveState?: CameraLiveState | null);
|
|
222
|
-
/** Called by the orchestrator each frame to update the cached live state */
|
|
223
|
-
updateLiveState(state: CameraLiveState): void;
|
|
224
|
-
getLiveState(_deviceId: string): CameraLiveState | null;
|
|
225
|
-
getTracks(_deviceId: string, filter?: TrackFilter): TrackedObjectSummary[];
|
|
226
|
-
getZoneState(_deviceId: string, zoneId: string): ZoneLiveState | null;
|
|
227
|
-
getZoneHistory(deviceId: string, zoneId: string, options: TimeRangeOptions): Promise<ZoneHistoryPoint[]>;
|
|
228
|
-
getHeatmap(deviceId: string, options: HeatmapOptions): Promise<HeatmapData>;
|
|
229
|
-
getTrackDetail(_deviceId: string, trackId: string): TrackDetail | null;
|
|
230
|
-
getCameraStatus(_deviceId: string): PipelineStatus[];
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
interface SnapshotConfig {
|
|
234
|
-
readonly enabled: boolean;
|
|
235
|
-
readonly saveThumbnail: boolean;
|
|
236
|
-
readonly saveAnnotatedFrame: boolean;
|
|
237
|
-
readonly saveDebugThumbnails: boolean;
|
|
238
|
-
/** File storage abstraction for snapshot persistence */
|
|
239
|
-
readonly mediaStorage?: IAddonFileStorage;
|
|
240
|
-
/** Legacy: output directory for direct file system writes */
|
|
241
|
-
readonly outputDir?: string;
|
|
242
|
-
}
|
|
243
|
-
declare const DEFAULT_SNAPSHOT_CONFIG: Omit<SnapshotConfig, 'mediaStorage' | 'outputDir'>;
|
|
244
|
-
/**
|
|
245
|
-
* Manages snapshot capture for detection events.
|
|
246
|
-
*
|
|
247
|
-
* Core operation: crop the detection bbox from the full frame and persist as JPEG.
|
|
248
|
-
* Annotated frame (bbox overlay) is optional and requires saveAnnotatedFrame = true.
|
|
249
|
-
*
|
|
250
|
-
* All persistence goes through mediaStorage (IAddonFileStorage).
|
|
251
|
-
*
|
|
252
|
-
* sharp is imported dynamically so the module can load even when sharp is unavailable
|
|
253
|
-
* (e.g., in test environments that mock this class).
|
|
254
|
-
*/
|
|
255
|
-
declare class SnapshotManager {
|
|
256
|
-
private readonly config;
|
|
257
|
-
constructor(config: SnapshotConfig);
|
|
258
|
-
capture(frame: FrameInput, detection: TrackedDetection, allDetections: readonly TrackedDetection[], eventId: string): Promise<EventSnapshot | undefined>;
|
|
259
|
-
private saveThumbnail;
|
|
260
|
-
private saveAnnotatedFrame;
|
|
261
|
-
/**
|
|
262
|
-
* Write output using mediaStorage if available, otherwise fall back to fs.writeFile.
|
|
263
|
-
*/
|
|
264
|
-
private writeOutput;
|
|
265
|
-
/**
|
|
266
|
-
* Resolve actual frame dimensions. JPEG frames from decoders may report
|
|
267
|
-
* width=0, height=0 — in that case read the real size from sharp metadata.
|
|
268
|
-
*/
|
|
269
|
-
private resolveFrameDimensions;
|
|
270
|
-
private sharpInputOptions;
|
|
271
|
-
private ensureOutputDir;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
interface AnalysisPipelineConfig {
|
|
275
|
-
readonly tracker?: Partial<TrackerConfig>;
|
|
276
|
-
readonly stateAnalyzer?: Partial<StateAnalyzerConfig>;
|
|
277
|
-
readonly eventFilter?: Partial<EventFilterConfig>;
|
|
278
|
-
readonly snapshot?: Partial<SnapshotConfig>;
|
|
279
|
-
/** Heatmap grid resolution (default: 32) */
|
|
280
|
-
readonly heatmapGridSize?: number;
|
|
281
|
-
/** Default frame dimensions when not available from FrameInput (default: 1920×1080) */
|
|
282
|
-
readonly defaultFrameWidth?: number;
|
|
283
|
-
readonly defaultFrameHeight?: number;
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* IAnalysisAddon-compatible interface.
|
|
287
|
-
*
|
|
288
|
-
* NOTE: The types package defines `processFrame(deviceId, frame)` on IAnalysisAddon.
|
|
289
|
-
* In practice the server orchestrator already calls the vision pipeline and passes
|
|
290
|
-
* the PipelineResult. We add an overloaded variant that accepts the PipelineResult
|
|
291
|
-
* as a third argument while remaining compatible with the base interface.
|
|
292
|
-
*/
|
|
293
|
-
interface IAnalysisAddon extends ICamstackAddon, ICameraAnalyticsProvider {
|
|
294
|
-
processFrame(deviceId: string, frame: FrameInput, pipelineResult?: PipelineResult): Promise<DetectionEvent[]>;
|
|
295
|
-
setCameraPipeline(deviceId: string, config: PipelineConfig): void;
|
|
296
|
-
setCameraZones(deviceId: string, zones: ZoneDefinition[]): void;
|
|
297
|
-
setKnownFaces(faces: KnownFace[]): void;
|
|
298
|
-
setKnownPlates(plates: KnownPlate[]): void;
|
|
299
|
-
onLiveStateChange(deviceId: string, callback: (state: CameraLiveState) => void): () => void;
|
|
300
|
-
onTrackFinished?: (deviceId: string, detail: TrackDetail) => void;
|
|
301
|
-
}
|
|
302
|
-
declare class AnalysisPipeline implements IAnalysisAddon {
|
|
303
|
-
readonly id = "pipeline-analysis";
|
|
304
|
-
readonly manifest: AddonManifest;
|
|
305
|
-
private readonly config;
|
|
306
|
-
private readonly cameras;
|
|
307
|
-
private knownFaces;
|
|
308
|
-
private knownPlates;
|
|
309
|
-
private readonly listeners;
|
|
310
|
-
/** Optional callback: server orchestrator can hook in to persist finished tracks */
|
|
311
|
-
onTrackFinished?: (deviceId: string, detail: TrackDetail) => void;
|
|
312
|
-
constructor(config?: AnalysisPipelineConfig);
|
|
313
|
-
initialize(_ctx: AddonContext): Promise<void>;
|
|
314
|
-
shutdown(): Promise<void>;
|
|
315
|
-
processFrame(deviceId: string, frame: FrameInput, pipelineResult?: PipelineResult): Promise<DetectionEvent[]>;
|
|
316
|
-
setCameraPipeline(deviceId: string, _config: PipelineConfig): void;
|
|
317
|
-
setCameraZones(deviceId: string, zones: ZoneDefinition[]): void;
|
|
318
|
-
setKnownFaces(faces: KnownFace[]): void;
|
|
319
|
-
setKnownPlates(plates: KnownPlate[]): void;
|
|
320
|
-
onLiveStateChange(deviceId: string, callback: (state: CameraLiveState) => void): () => void;
|
|
321
|
-
getLiveState(deviceId: string): CameraLiveState | null;
|
|
322
|
-
getTracks(deviceId: string, filter?: TrackFilter): TrackedObjectSummary[];
|
|
323
|
-
getZoneState(deviceId: string, zoneId: string): ZoneLiveState | null;
|
|
324
|
-
getZoneHistory(deviceId: string, zoneId: string, options: TimeRangeOptions): Promise<ZoneHistoryPoint[]>;
|
|
325
|
-
getHeatmap(deviceId: string, options: HeatmapOptions): Promise<HeatmapData>;
|
|
326
|
-
getTrackDetail(deviceId: string, trackId: string): TrackDetail | null;
|
|
327
|
-
getCameraStatus(deviceId: string): PipelineStatus[];
|
|
328
|
-
private getOrCreateCamera;
|
|
329
|
-
private extractDetections;
|
|
330
|
-
private matchClassifications;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
export { AnalysisPipeline, type AnalysisPipelineConfig, AnalyticsProvider, DEFAULT_EVENT_FILTER_CONFIG, DEFAULT_SNAPSHOT_CONFIG, DEFAULT_STATE_ANALYZER_CONFIG, DEFAULT_TRACKER_CONFIG, DetectionEventEmitter, EventFilter, type EventFilterConfig, HeatmapAggregator, type IAnalysisAddon, type ITracker, type LineSegment, LiveStateManager, type Point, type SnapshotConfig, SnapshotManager, SortTracker, StateAnalyzer, type StateAnalyzerConfig, TrackStore, type TrackerConfig, ZoneEvaluator, bboxCentroid, greedyAssignment, lineIntersection, normalizeToPixel, pointInPolygon, tripwireCrossing };
|
package/dist/index.d.ts
DELETED
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
import { SpatialDetection, TrackedDetection, TrackedObjectState, ZoneDefinition, ZoneEvent, DetectionEventType, Classification, DetectionEvent, TrackDetail, PipelineStatus, CameraLiveState, HeatmapData, ICameraAnalyticsProvider, TimeRangeOptions, ZoneHistoryPoint, HeatmapOptions, TrackFilter, TrackedObjectSummary, ZoneLiveState, IAddonFileStorage, FrameInput, EventSnapshot, ICamstackAddon, PipelineResult, PipelineConfig, KnownFace, KnownPlate, AddonManifest, AddonContext } from '@camstack/types';
|
|
2
|
-
|
|
3
|
-
interface Point {
|
|
4
|
-
readonly x: number;
|
|
5
|
-
readonly y: number;
|
|
6
|
-
}
|
|
7
|
-
interface LineSegment {
|
|
8
|
-
readonly p1: Point;
|
|
9
|
-
readonly p2: Point;
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Point-in-polygon test using the ray casting algorithm.
|
|
13
|
-
* Returns true if the point is inside the polygon.
|
|
14
|
-
*/
|
|
15
|
-
declare function pointInPolygon(point: Point, polygon: readonly Point[]): boolean;
|
|
16
|
-
/**
|
|
17
|
-
* Line segment intersection — returns the intersection point or null if they don't intersect.
|
|
18
|
-
* Uses parametric form of line equations.
|
|
19
|
-
*/
|
|
20
|
-
declare function lineIntersection(a: LineSegment, b: LineSegment): Point | null;
|
|
21
|
-
/**
|
|
22
|
-
* Check if a movement from prev to curr crosses the given tripwire.
|
|
23
|
-
* Returns crossed=true and direction if crossed, null otherwise.
|
|
24
|
-
* Direction is determined by the cross product sign:
|
|
25
|
-
* - 'right' if moving from the left side of the tripwire to the right
|
|
26
|
-
* - 'left' if moving from the right side to the left
|
|
27
|
-
*/
|
|
28
|
-
declare function tripwireCrossing(prev: Point, curr: Point, tripwire: LineSegment): {
|
|
29
|
-
crossed: boolean;
|
|
30
|
-
direction: 'left' | 'right';
|
|
31
|
-
} | null;
|
|
32
|
-
/**
|
|
33
|
-
* Calculate the centroid (center point) of a bounding box.
|
|
34
|
-
*/
|
|
35
|
-
declare function bboxCentroid(bbox: {
|
|
36
|
-
x: number;
|
|
37
|
-
y: number;
|
|
38
|
-
w: number;
|
|
39
|
-
h: number;
|
|
40
|
-
}): Point;
|
|
41
|
-
/**
|
|
42
|
-
* Convert normalized coordinates (0–1 range) to pixel coordinates.
|
|
43
|
-
*/
|
|
44
|
-
declare function normalizeToPixel(point: Point, imageWidth: number, imageHeight: number): Point;
|
|
45
|
-
|
|
46
|
-
interface ITracker {
|
|
47
|
-
update(detections: readonly SpatialDetection[], frameTimestamp: number): TrackedDetection[];
|
|
48
|
-
getActiveTracks(): TrackedDetection[];
|
|
49
|
-
getLostTracks(): TrackedDetection[];
|
|
50
|
-
reset(): void;
|
|
51
|
-
}
|
|
52
|
-
interface TrackerConfig {
|
|
53
|
-
/** Frames without match before track is considered lost (default: 30) */
|
|
54
|
-
readonly maxAge: number;
|
|
55
|
-
/** Minimum detections before track is confirmed and returned (default: 3) */
|
|
56
|
-
readonly minHits: number;
|
|
57
|
-
/** Minimum IoU for a detection to match an existing track (default: 0.3) */
|
|
58
|
-
readonly iouThreshold: number;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
declare const DEFAULT_TRACKER_CONFIG: TrackerConfig;
|
|
62
|
-
declare class SortTracker implements ITracker {
|
|
63
|
-
private tracks;
|
|
64
|
-
private lostTracks;
|
|
65
|
-
private nextTrackId;
|
|
66
|
-
private readonly config;
|
|
67
|
-
constructor(config?: Partial<TrackerConfig>);
|
|
68
|
-
update(detections: readonly SpatialDetection[], frameTimestamp: number): TrackedDetection[];
|
|
69
|
-
getActiveTracks(): TrackedDetection[];
|
|
70
|
-
getLostTracks(): TrackedDetection[];
|
|
71
|
-
reset(): void;
|
|
72
|
-
private createTrack;
|
|
73
|
-
private getConfirmedTracks;
|
|
74
|
-
private ageTracksAndPruneLost;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Greedy assignment algorithm for matching detections to tracks based on IoU.
|
|
79
|
-
*
|
|
80
|
-
* Sorts all (track, detection) pairs by IoU descending, then greedily assigns
|
|
81
|
-
* pairs as long as neither the track nor detection has been assigned yet and
|
|
82
|
-
* the IoU meets the threshold.
|
|
83
|
-
*
|
|
84
|
-
* @param costMatrix [tracks × detections] — values are IoU (higher = better)
|
|
85
|
-
* @param threshold Minimum IoU to accept a match
|
|
86
|
-
* @returns matches, unmatchedTracks, unmatchedDetections indices
|
|
87
|
-
*/
|
|
88
|
-
declare function greedyAssignment(costMatrix: readonly (readonly number[])[], threshold: number): {
|
|
89
|
-
matches: [number, number][];
|
|
90
|
-
unmatchedTracks: number[];
|
|
91
|
-
unmatchedDetections: number[];
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
interface StateAnalyzerConfig {
|
|
95
|
-
/** Seconds without movement to transition to 'stationary' (default: 10) */
|
|
96
|
-
readonly stationaryThresholdSec: number;
|
|
97
|
-
/** Seconds stationary to transition to 'loitering' (default: 60) */
|
|
98
|
-
readonly loiteringThresholdSec: number;
|
|
99
|
-
/** Pixels/frame below which a track is considered not moving (default: 2) */
|
|
100
|
-
readonly velocityThreshold: number;
|
|
101
|
-
/** Track age (frames) below which it's considered 'entering' (default: 5) */
|
|
102
|
-
readonly enteringFrames: number;
|
|
103
|
-
}
|
|
104
|
-
declare const DEFAULT_STATE_ANALYZER_CONFIG: StateAnalyzerConfig;
|
|
105
|
-
declare class StateAnalyzer {
|
|
106
|
-
private readonly states;
|
|
107
|
-
private readonly config;
|
|
108
|
-
constructor(config?: Partial<StateAnalyzerConfig>);
|
|
109
|
-
analyze(tracks: readonly TrackedDetection[], timestamp: number): TrackedObjectState[];
|
|
110
|
-
private computeObjectState;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
declare class ZoneEvaluator {
|
|
114
|
-
/** Track which zones each track was in last frame */
|
|
115
|
-
private readonly trackZoneState;
|
|
116
|
-
evaluate(tracks: readonly TrackedDetection[], zones: readonly ZoneDefinition[], imageWidth: number, imageHeight: number, timestamp: number): ZoneEvent[];
|
|
117
|
-
/** Returns a snapshot of the current track → zones mapping */
|
|
118
|
-
getTrackZones(): Map<string, Set<string>>;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
interface EventFilterConfig {
|
|
122
|
-
/** Don't emit for tracks younger than N frames */
|
|
123
|
-
readonly minTrackAge: number;
|
|
124
|
-
/** Seconds between same event type for same track */
|
|
125
|
-
readonly cooldownSec: number;
|
|
126
|
-
/** Event types that are enabled */
|
|
127
|
-
readonly enabledTypes: readonly DetectionEventType[];
|
|
128
|
-
}
|
|
129
|
-
declare const DEFAULT_EVENT_FILTER_CONFIG: EventFilterConfig;
|
|
130
|
-
declare class EventFilter {
|
|
131
|
-
private readonly config;
|
|
132
|
-
/** key: `${trackId}:${eventType}` → last emitted timestamp */
|
|
133
|
-
private readonly lastEmitted;
|
|
134
|
-
constructor(config: EventFilterConfig);
|
|
135
|
-
shouldEmit(trackId: string, eventType: DetectionEventType, trackAge: number, timestamp: number): boolean;
|
|
136
|
-
/** Record an emission without a gate check — for events that bypass normal cooldown logic */
|
|
137
|
-
recordEmission(trackId: string, eventType: DetectionEventType, timestamp: number): void;
|
|
138
|
-
/** Remove cooldown entries for tracks that are no longer active */
|
|
139
|
-
cleanup(activeTrackIds: ReadonlySet<string>): void;
|
|
140
|
-
/** Reset all cooldown state */
|
|
141
|
-
reset(): void;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
declare class DetectionEventEmitter {
|
|
145
|
-
private readonly filter;
|
|
146
|
-
/** trackId → last known ObjectState */
|
|
147
|
-
private readonly previousStates;
|
|
148
|
-
constructor(filterConfig?: Partial<EventFilterConfig>);
|
|
149
|
-
emit(tracks: readonly TrackedDetection[], states: readonly TrackedObjectState[], zoneEvents: readonly ZoneEvent[], classifications: readonly {
|
|
150
|
-
trackId: string;
|
|
151
|
-
classifications: Classification[];
|
|
152
|
-
}[], deviceId: string): DetectionEvent[];
|
|
153
|
-
private tryEmit;
|
|
154
|
-
reset(): void;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
declare class TrackStore {
|
|
158
|
-
private readonly activeTracks;
|
|
159
|
-
update(tracks: readonly TrackedDetection[], states: readonly TrackedObjectState[], zoneEvents: readonly ZoneEvent[], classifications: readonly {
|
|
160
|
-
trackId: string;
|
|
161
|
-
classifications: Classification[];
|
|
162
|
-
}[]): void;
|
|
163
|
-
/** Attach a DetectionEvent to its associated track accumulator */
|
|
164
|
-
addEvent(trackId: string, event: DetectionEvent): void;
|
|
165
|
-
/** Get accumulated detail for an active track */
|
|
166
|
-
getTrackDetail(trackId: string): TrackDetail | null;
|
|
167
|
-
/** Called when a track ends — returns complete TrackDetail and removes from active set */
|
|
168
|
-
finishTrack(trackId: string): TrackDetail | null;
|
|
169
|
-
/** Get all active track IDs */
|
|
170
|
-
getActiveTrackIds(): string[];
|
|
171
|
-
/** Clear all accumulated state */
|
|
172
|
-
reset(): void;
|
|
173
|
-
private buildTrackDetail;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
declare class LiveStateManager {
|
|
177
|
-
private zones;
|
|
178
|
-
private readonly fpsCounter;
|
|
179
|
-
private readonly pipelineMsHistory;
|
|
180
|
-
private readonly maxPipelineHistory;
|
|
181
|
-
setZones(zones: ZoneDefinition[]): void;
|
|
182
|
-
buildState(deviceId: string, tracks: readonly TrackedDetection[], states: readonly TrackedObjectState[], trackZones: Map<string, Set<string>>, pipelineStatus: readonly PipelineStatus[], pipelineMs: number, lastEvent: DetectionEvent | undefined, timestamp: number): CameraLiveState;
|
|
183
|
-
reset(): void;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Grid-based accumulator for track centroid positions.
|
|
188
|
-
*
|
|
189
|
-
* Positions are normalized (0–1) and mapped into a grid of `gridSize × gridSize` cells.
|
|
190
|
-
* Each update increments the count of the corresponding cell.
|
|
191
|
-
*/
|
|
192
|
-
declare class HeatmapAggregator {
|
|
193
|
-
private readonly width;
|
|
194
|
-
private readonly height;
|
|
195
|
-
private readonly gridSize;
|
|
196
|
-
private readonly grid;
|
|
197
|
-
private maxCount;
|
|
198
|
-
constructor(width: number, height: number, gridSize: number);
|
|
199
|
-
/**
|
|
200
|
-
* Add a normalized point (0–1 range) to the heatmap.
|
|
201
|
-
* Points outside [0, 1] are clamped to the grid boundary.
|
|
202
|
-
*/
|
|
203
|
-
addPoint(x: number, y: number): void;
|
|
204
|
-
/** Add a pixel-coordinate point. Normalizes against the configured width/height. */
|
|
205
|
-
addPixelPoint(px: number, py: number): void;
|
|
206
|
-
getHeatmap(): HeatmapData;
|
|
207
|
-
reset(): void;
|
|
208
|
-
/** Total number of points accumulated */
|
|
209
|
-
get totalPoints(): number;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
declare class AnalyticsProvider implements ICameraAnalyticsProvider {
|
|
213
|
-
private readonly liveStateManager;
|
|
214
|
-
private readonly trackStore;
|
|
215
|
-
private readonly getZoneHistoryCb?;
|
|
216
|
-
private readonly getHeatmapCb?;
|
|
217
|
-
/** Cached live state reference — kept up to date by the pipeline orchestrator */
|
|
218
|
-
private lastLiveState;
|
|
219
|
-
constructor(liveStateManager: LiveStateManager, trackStore: TrackStore, getZoneHistoryCb?: ((deviceId: string, zoneId: string, options: TimeRangeOptions) => Promise<ZoneHistoryPoint[]>) | undefined, getHeatmapCb?: ((deviceId: string, options: HeatmapOptions) => Promise<HeatmapData>) | undefined,
|
|
220
|
-
/** Cached live state reference — kept up to date by the pipeline orchestrator */
|
|
221
|
-
lastLiveState?: CameraLiveState | null);
|
|
222
|
-
/** Called by the orchestrator each frame to update the cached live state */
|
|
223
|
-
updateLiveState(state: CameraLiveState): void;
|
|
224
|
-
getLiveState(_deviceId: string): CameraLiveState | null;
|
|
225
|
-
getTracks(_deviceId: string, filter?: TrackFilter): TrackedObjectSummary[];
|
|
226
|
-
getZoneState(_deviceId: string, zoneId: string): ZoneLiveState | null;
|
|
227
|
-
getZoneHistory(deviceId: string, zoneId: string, options: TimeRangeOptions): Promise<ZoneHistoryPoint[]>;
|
|
228
|
-
getHeatmap(deviceId: string, options: HeatmapOptions): Promise<HeatmapData>;
|
|
229
|
-
getTrackDetail(_deviceId: string, trackId: string): TrackDetail | null;
|
|
230
|
-
getCameraStatus(_deviceId: string): PipelineStatus[];
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
interface SnapshotConfig {
|
|
234
|
-
readonly enabled: boolean;
|
|
235
|
-
readonly saveThumbnail: boolean;
|
|
236
|
-
readonly saveAnnotatedFrame: boolean;
|
|
237
|
-
readonly saveDebugThumbnails: boolean;
|
|
238
|
-
/** File storage abstraction for snapshot persistence */
|
|
239
|
-
readonly mediaStorage?: IAddonFileStorage;
|
|
240
|
-
/** Legacy: output directory for direct file system writes */
|
|
241
|
-
readonly outputDir?: string;
|
|
242
|
-
}
|
|
243
|
-
declare const DEFAULT_SNAPSHOT_CONFIG: Omit<SnapshotConfig, 'mediaStorage' | 'outputDir'>;
|
|
244
|
-
/**
|
|
245
|
-
* Manages snapshot capture for detection events.
|
|
246
|
-
*
|
|
247
|
-
* Core operation: crop the detection bbox from the full frame and persist as JPEG.
|
|
248
|
-
* Annotated frame (bbox overlay) is optional and requires saveAnnotatedFrame = true.
|
|
249
|
-
*
|
|
250
|
-
* All persistence goes through mediaStorage (IAddonFileStorage).
|
|
251
|
-
*
|
|
252
|
-
* sharp is imported dynamically so the module can load even when sharp is unavailable
|
|
253
|
-
* (e.g., in test environments that mock this class).
|
|
254
|
-
*/
|
|
255
|
-
declare class SnapshotManager {
|
|
256
|
-
private readonly config;
|
|
257
|
-
constructor(config: SnapshotConfig);
|
|
258
|
-
capture(frame: FrameInput, detection: TrackedDetection, allDetections: readonly TrackedDetection[], eventId: string): Promise<EventSnapshot | undefined>;
|
|
259
|
-
private saveThumbnail;
|
|
260
|
-
private saveAnnotatedFrame;
|
|
261
|
-
/**
|
|
262
|
-
* Write output using mediaStorage if available, otherwise fall back to fs.writeFile.
|
|
263
|
-
*/
|
|
264
|
-
private writeOutput;
|
|
265
|
-
/**
|
|
266
|
-
* Resolve actual frame dimensions. JPEG frames from decoders may report
|
|
267
|
-
* width=0, height=0 — in that case read the real size from sharp metadata.
|
|
268
|
-
*/
|
|
269
|
-
private resolveFrameDimensions;
|
|
270
|
-
private sharpInputOptions;
|
|
271
|
-
private ensureOutputDir;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
interface AnalysisPipelineConfig {
|
|
275
|
-
readonly tracker?: Partial<TrackerConfig>;
|
|
276
|
-
readonly stateAnalyzer?: Partial<StateAnalyzerConfig>;
|
|
277
|
-
readonly eventFilter?: Partial<EventFilterConfig>;
|
|
278
|
-
readonly snapshot?: Partial<SnapshotConfig>;
|
|
279
|
-
/** Heatmap grid resolution (default: 32) */
|
|
280
|
-
readonly heatmapGridSize?: number;
|
|
281
|
-
/** Default frame dimensions when not available from FrameInput (default: 1920×1080) */
|
|
282
|
-
readonly defaultFrameWidth?: number;
|
|
283
|
-
readonly defaultFrameHeight?: number;
|
|
284
|
-
}
|
|
285
|
-
/**
|
|
286
|
-
* IAnalysisAddon-compatible interface.
|
|
287
|
-
*
|
|
288
|
-
* NOTE: The types package defines `processFrame(deviceId, frame)` on IAnalysisAddon.
|
|
289
|
-
* In practice the server orchestrator already calls the vision pipeline and passes
|
|
290
|
-
* the PipelineResult. We add an overloaded variant that accepts the PipelineResult
|
|
291
|
-
* as a third argument while remaining compatible with the base interface.
|
|
292
|
-
*/
|
|
293
|
-
interface IAnalysisAddon extends ICamstackAddon, ICameraAnalyticsProvider {
|
|
294
|
-
processFrame(deviceId: string, frame: FrameInput, pipelineResult?: PipelineResult): Promise<DetectionEvent[]>;
|
|
295
|
-
setCameraPipeline(deviceId: string, config: PipelineConfig): void;
|
|
296
|
-
setCameraZones(deviceId: string, zones: ZoneDefinition[]): void;
|
|
297
|
-
setKnownFaces(faces: KnownFace[]): void;
|
|
298
|
-
setKnownPlates(plates: KnownPlate[]): void;
|
|
299
|
-
onLiveStateChange(deviceId: string, callback: (state: CameraLiveState) => void): () => void;
|
|
300
|
-
onTrackFinished?: (deviceId: string, detail: TrackDetail) => void;
|
|
301
|
-
}
|
|
302
|
-
declare class AnalysisPipeline implements IAnalysisAddon {
|
|
303
|
-
readonly id = "pipeline-analysis";
|
|
304
|
-
readonly manifest: AddonManifest;
|
|
305
|
-
private readonly config;
|
|
306
|
-
private readonly cameras;
|
|
307
|
-
private knownFaces;
|
|
308
|
-
private knownPlates;
|
|
309
|
-
private readonly listeners;
|
|
310
|
-
/** Optional callback: server orchestrator can hook in to persist finished tracks */
|
|
311
|
-
onTrackFinished?: (deviceId: string, detail: TrackDetail) => void;
|
|
312
|
-
constructor(config?: AnalysisPipelineConfig);
|
|
313
|
-
initialize(_ctx: AddonContext): Promise<void>;
|
|
314
|
-
shutdown(): Promise<void>;
|
|
315
|
-
processFrame(deviceId: string, frame: FrameInput, pipelineResult?: PipelineResult): Promise<DetectionEvent[]>;
|
|
316
|
-
setCameraPipeline(deviceId: string, _config: PipelineConfig): void;
|
|
317
|
-
setCameraZones(deviceId: string, zones: ZoneDefinition[]): void;
|
|
318
|
-
setKnownFaces(faces: KnownFace[]): void;
|
|
319
|
-
setKnownPlates(plates: KnownPlate[]): void;
|
|
320
|
-
onLiveStateChange(deviceId: string, callback: (state: CameraLiveState) => void): () => void;
|
|
321
|
-
getLiveState(deviceId: string): CameraLiveState | null;
|
|
322
|
-
getTracks(deviceId: string, filter?: TrackFilter): TrackedObjectSummary[];
|
|
323
|
-
getZoneState(deviceId: string, zoneId: string): ZoneLiveState | null;
|
|
324
|
-
getZoneHistory(deviceId: string, zoneId: string, options: TimeRangeOptions): Promise<ZoneHistoryPoint[]>;
|
|
325
|
-
getHeatmap(deviceId: string, options: HeatmapOptions): Promise<HeatmapData>;
|
|
326
|
-
getTrackDetail(deviceId: string, trackId: string): TrackDetail | null;
|
|
327
|
-
getCameraStatus(deviceId: string): PipelineStatus[];
|
|
328
|
-
private getOrCreateCamera;
|
|
329
|
-
private extractDetections;
|
|
330
|
-
private matchClassifications;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
export { AnalysisPipeline, type AnalysisPipelineConfig, AnalyticsProvider, DEFAULT_EVENT_FILTER_CONFIG, DEFAULT_SNAPSHOT_CONFIG, DEFAULT_STATE_ANALYZER_CONFIG, DEFAULT_TRACKER_CONFIG, DetectionEventEmitter, EventFilter, type EventFilterConfig, HeatmapAggregator, type IAnalysisAddon, type ITracker, type LineSegment, LiveStateManager, type Point, type SnapshotConfig, SnapshotManager, SortTracker, StateAnalyzer, type StateAnalyzerConfig, TrackStore, type TrackerConfig, ZoneEvaluator, bboxCentroid, greedyAssignment, lineIntersection, normalizeToPixel, pointInPolygon, tripwireCrossing };
|