@trillboards/edge-sdk 0.2.74 → 0.2.78
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/deploy/detector/README.md +133 -0
- package/deploy/detector/bench/verify_against_reference.py +120 -0
- package/deploy/detector/pyproject.toml +49 -0
- package/deploy/detector/tests/__init__.py +0 -0
- package/deploy/detector/tests/test_modelpath.py +108 -0
- package/deploy/detector/tests/test_server.py +591 -0
- package/deploy/detector/tests/test_session.py +242 -0
- package/deploy/detector/trillboards_detector/__init__.py +8 -0
- package/deploy/detector/trillboards_detector/__main__.py +4 -0
- package/deploy/detector/trillboards_detector/cli.py +141 -0
- package/deploy/detector/trillboards_detector/errors.py +16 -0
- package/deploy/detector/trillboards_detector/labels.py +107 -0
- package/deploy/detector/trillboards_detector/loader.py +257 -0
- package/deploy/detector/trillboards_detector/modelpath.py +74 -0
- package/deploy/detector/trillboards_detector/server.py +460 -0
- package/deploy/detector/trillboards_detector/session.py +354 -0
- package/deploy/macos/build-macos-pkg.sh +48 -0
- package/deploy/macos/com.trillboards.detector.plist +61 -0
- package/deploy/macos/scripts/ensure-detector.sh +85 -0
- package/deploy/macos/scripts/postinstall +28 -0
- package/deploy/macos/scripts/setup-detector.sh +185 -0
- package/deploy/systemd/install.sh +35 -0
- package/deploy/systemd/setup-detector.sh +130 -0
- package/deploy/systemd/trillboards-detector.service +54 -0
- package/deploy/windows/build-windows.ps1 +38 -0
- package/deploy/windows/installer.nsi +24 -1
- package/deploy/windows/register-detector-task.ps1 +161 -0
- package/deploy/windows/register-task.ps1 +27 -0
- package/deploy/windows/setup-detector.ps1 +176 -0
- package/deploy/windows/stop-existing.ps1 +1 -0
- package/deploy/windows/trillboards-edge.cmd +25 -0
- package/dist/EdgeAgent.js +1 -1
- package/dist/LocalDetectorClient.d.ts +92 -0
- package/dist/LocalDetectorClient.d.ts.map +1 -0
- package/dist/LocalDetectorClient.js +241 -0
- package/dist/LocalDetectorClient.js.map +1 -0
- package/dist/SensingProfileClient.d.ts +25 -22
- package/dist/SensingProfileClient.d.ts.map +1 -1
- package/dist/SensingProfileClient.js.map +1 -1
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.js +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/localInferenceSchema.d.ts +11 -2
- package/dist/localInferenceSchema.d.ts.map +1 -1
- package/dist/localInferenceSchema.js.map +1 -1
- package/dist/zoneContainment.d.ts +148 -0
- package/dist/zoneContainment.d.ts.map +1 -0
- package/dist/zoneContainment.js +172 -0
- package/dist/zoneContainment.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* zoneContainment — "is this thing in the area the operator drew?", in code.
|
|
3
|
+
*
|
|
4
|
+
* ── WHY THIS EXISTS ────────────────────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* The operator drags a rectangle on their camera and expects "only tell me about
|
|
7
|
+
* THIS". Today that promise is kept by writing the rectangle's coordinates into
|
|
8
|
+
* an English sentence and asking a vision-language model to respect it. Measured
|
|
9
|
+
* on production, before and after the description was repaired:
|
|
10
|
+
*
|
|
11
|
+
* - the focused field agreed with the UNRESTRICTED field on 99.5% of frames
|
|
12
|
+
* - after the fix the model demonstrably READS the constraint (divergence
|
|
13
|
+
* 0.34% -> 3.58%), and yet `focus_true` is still 0 of 391 in a window with
|
|
14
|
+
* MORE person activity than the one before it
|
|
15
|
+
*
|
|
16
|
+
* So the repaired prose suppresses, and whether it suppresses the RIGHT things
|
|
17
|
+
* is not knowable from the model's answer alone. A rectangle is geometry. Asking
|
|
18
|
+
* a language model to do geometry, and then having no way to check its working,
|
|
19
|
+
* is the part that has to stop.
|
|
20
|
+
*
|
|
21
|
+
* ── THE ANCHOR IS THE WHOLE PROBLEM, AND IT IS NOT A THRESHOLD ─────────────
|
|
22
|
+
*
|
|
23
|
+
* A person is a tall box. Someone standing on the pavement BESIDE a driveway
|
|
24
|
+
* overlaps the driveway's rectangle in image space with their head and shoulders
|
|
25
|
+
* while their feet are firmly outside it. Area-overlap answers "yes" and is
|
|
26
|
+
* wrong; whole-box containment answers "no" for anyone standing at the edge and
|
|
27
|
+
* is also wrong. Neither is rescued by tuning a fraction, because the error is
|
|
28
|
+
* not one of degree — the box's top is not where the person IS.
|
|
29
|
+
*
|
|
30
|
+
* A camera looks down. The ground plane is where a person occupies space, and
|
|
31
|
+
* the bottom edge of their box is where they touch it. So `groundContact` — the
|
|
32
|
+
* bottom-centre point — is the default, and it needs NO threshold: a point is
|
|
33
|
+
* inside a rectangle or it is not. That is the whole reason to prefer it. A
|
|
34
|
+
* magic fraction nobody can justify is exactly what this codebase asks not to
|
|
35
|
+
* invent, and here the physics removes the need for one.
|
|
36
|
+
*
|
|
37
|
+
* `areaOverlap` is offered for the cases where the ground plane is not the
|
|
38
|
+
* subject — a package on a shelf, a vehicle in a wide overhead view — and there
|
|
39
|
+
* the fraction is a REQUIRED, caller-supplied argument, never a default, so it
|
|
40
|
+
* appears in the route's own configuration where an operator's expectation can
|
|
41
|
+
* be compared against it.
|
|
42
|
+
*
|
|
43
|
+
* ── COORDINATE SPACES: EXACTLY ONE CONVERSION, HERE ────────────────────────
|
|
44
|
+
*
|
|
45
|
+
* Detector boxes are PIXELS in the submitted frame. `sensing_route_screens`
|
|
46
|
+
* stores `focus_x/y/width/height` NORMALIZED to [0,1] — that is what the portal
|
|
47
|
+
* writes and what `focusRegionFromScreenRow` reads. The two meet in exactly one
|
|
48
|
+
* place: this file. Every function takes the frame's dimensions explicitly
|
|
49
|
+
* rather than carrying a resolution around, because a stale resolution is
|
|
50
|
+
* silent and a missing argument is not.
|
|
51
|
+
*
|
|
52
|
+
* @reuse-audit
|
|
53
|
+
* considered: FaceDetector.iou (edge-sensing/src/processors/FaceDetector.ts)
|
|
54
|
+
* - not reused: IoU is a SYMMETRIC similarity between two detections, used
|
|
55
|
+
* for NMS and tracking. Containment is asymmetric — "how much of the BOX
|
|
56
|
+
* is in the RECT" — and IoU's denominator includes the rectangle's own
|
|
57
|
+
* area, so a small person inside a large zone scores near zero on IoU and
|
|
58
|
+
* 1.0 here. Same rectangles, different question. When the detector's own
|
|
59
|
+
* NMS lands it must call FaceDetector's iou, not a copy.
|
|
60
|
+
* considered: camera-alert-focus-selector.tsx (portal)
|
|
61
|
+
* - not reused: drag/clamp only, no containment predicate, and it is React
|
|
62
|
+
* in a different package. Its clamping is the reason `assertNormalizedRect`
|
|
63
|
+
* can be strict — the rect always arrives already inside [0,1].
|
|
64
|
+
*/
|
|
65
|
+
/** A rectangle in normalized [0,1] frame coordinates, as the operator drew it. */
|
|
66
|
+
export interface NormalizedRect {
|
|
67
|
+
x: number;
|
|
68
|
+
y: number;
|
|
69
|
+
width: number;
|
|
70
|
+
height: number;
|
|
71
|
+
}
|
|
72
|
+
/** A box in PIXELS of the frame it was detected in. */
|
|
73
|
+
export interface PixelBox {
|
|
74
|
+
x: number;
|
|
75
|
+
y: number;
|
|
76
|
+
width: number;
|
|
77
|
+
height: number;
|
|
78
|
+
}
|
|
79
|
+
export interface FrameSize {
|
|
80
|
+
width: number;
|
|
81
|
+
height: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* How to decide whether a box is "in" the zone.
|
|
85
|
+
*
|
|
86
|
+
* `groundContact` — the box's bottom-centre point falls inside the rect. The
|
|
87
|
+
* default, and the only one with a physical argument behind it.
|
|
88
|
+
*
|
|
89
|
+
* `areaOverlap` — at least `minOverlap` of the box's own area falls inside the
|
|
90
|
+
* rect. `minOverlap` is REQUIRED; there is no defensible default and inventing
|
|
91
|
+
* one would bury the decision.
|
|
92
|
+
*/
|
|
93
|
+
export type ZonePredicate = {
|
|
94
|
+
kind: 'groundContact';
|
|
95
|
+
} | {
|
|
96
|
+
kind: 'areaOverlap';
|
|
97
|
+
minOverlap: number;
|
|
98
|
+
};
|
|
99
|
+
export declare const DEFAULT_ZONE_PREDICATE: ZonePredicate;
|
|
100
|
+
export interface ZoneVerdict {
|
|
101
|
+
inside: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* Fraction of the box's area inside the rect, ALWAYS computed regardless of
|
|
104
|
+
* predicate.
|
|
105
|
+
*
|
|
106
|
+
* Reported even when unused because the alternative — logging only the boolean
|
|
107
|
+
* — makes a disputed verdict unreviewable after the fact. An operator saying
|
|
108
|
+
* "it missed someone standing right there" is answerable with 0.62 and
|
|
109
|
+
* unanswerable with `false`.
|
|
110
|
+
*/
|
|
111
|
+
overlapFraction: number;
|
|
112
|
+
/** The bottom-centre point in normalized coordinates, for the same reason. */
|
|
113
|
+
groundContact: {
|
|
114
|
+
x: number;
|
|
115
|
+
y: number;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* A rect outside [0,1] is a COORDINATE-SPACE error, and it must throw.
|
|
120
|
+
*
|
|
121
|
+
* The one way this happens is a caller passing pixels where normalized values
|
|
122
|
+
* belong — and pixels are numerically valid, so the verdict would just be
|
|
123
|
+
* silently wrong forever: a rect at x=640 clamps to the frame's right edge and
|
|
124
|
+
* every person reads as outside it. That failure has no symptom other than an
|
|
125
|
+
* alert that never fires, which is the failure this whole area keeps producing.
|
|
126
|
+
* Refusing loudly is the only version anybody notices.
|
|
127
|
+
*/
|
|
128
|
+
export declare function assertNormalizedRect(rect: NormalizedRect): void;
|
|
129
|
+
/** Convert a pixel box to normalized coordinates. The only conversion there is. */
|
|
130
|
+
export declare function normalizeBox(box: PixelBox, frame: FrameSize): NormalizedRect;
|
|
131
|
+
/**
|
|
132
|
+
* Is this detection inside the operator's rectangle?
|
|
133
|
+
*
|
|
134
|
+
* Throws on a malformed rect or frame — see `assertNormalizedRect`. Never
|
|
135
|
+
* returns a verdict it could not compute.
|
|
136
|
+
*/
|
|
137
|
+
export declare function evaluateZone(box: PixelBox, frame: FrameSize, rect: NormalizedRect, predicate?: ZonePredicate): ZoneVerdict;
|
|
138
|
+
/**
|
|
139
|
+
* Count the detections inside the zone.
|
|
140
|
+
*
|
|
141
|
+
* This is the integer the focused field has been asking a language model for.
|
|
142
|
+
* `null` rect means NO zone was drawn, and then the count is every detection —
|
|
143
|
+
* which is what the unfocused field already means, so absence degrades to the
|
|
144
|
+
* existing behaviour rather than to zero. A zone that silently counts nothing is
|
|
145
|
+
* indistinguishable from a quiet venue, and that is the whole failure mode here.
|
|
146
|
+
*/
|
|
147
|
+
export declare function countInZone(boxes: PixelBox[], frame: FrameSize, rect: NormalizedRect | null | undefined, predicate?: ZonePredicate): number;
|
|
148
|
+
//# sourceMappingURL=zoneContainment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zoneContainment.d.ts","sourceRoot":"","sources":["../src/zoneContainment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AAEH,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC7B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,uDAAuD;AACvD,MAAM,WAAW,QAAQ;IACvB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,GACzB;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhD,eAAO,MAAM,sBAAsB,EAAE,aAAyC,CAAC;AAE/E,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB;;;;;;;;OAQG;IACH,eAAe,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,aAAa,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACzC;AAQD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAgB/D;AAED,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,GAAG,cAAc,CAgB5E;AAiBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,cAAc,EACpB,SAAS,GAAE,aAAsC,GAChD,WAAW,CAmBb;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,QAAQ,EAAE,EACjB,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,EACvC,SAAS,GAAE,aAAsC,GAChD,MAAM,CAGR"}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* zoneContainment — "is this thing in the area the operator drew?", in code.
|
|
4
|
+
*
|
|
5
|
+
* ── WHY THIS EXISTS ────────────────────────────────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* The operator drags a rectangle on their camera and expects "only tell me about
|
|
8
|
+
* THIS". Today that promise is kept by writing the rectangle's coordinates into
|
|
9
|
+
* an English sentence and asking a vision-language model to respect it. Measured
|
|
10
|
+
* on production, before and after the description was repaired:
|
|
11
|
+
*
|
|
12
|
+
* - the focused field agreed with the UNRESTRICTED field on 99.5% of frames
|
|
13
|
+
* - after the fix the model demonstrably READS the constraint (divergence
|
|
14
|
+
* 0.34% -> 3.58%), and yet `focus_true` is still 0 of 391 in a window with
|
|
15
|
+
* MORE person activity than the one before it
|
|
16
|
+
*
|
|
17
|
+
* So the repaired prose suppresses, and whether it suppresses the RIGHT things
|
|
18
|
+
* is not knowable from the model's answer alone. A rectangle is geometry. Asking
|
|
19
|
+
* a language model to do geometry, and then having no way to check its working,
|
|
20
|
+
* is the part that has to stop.
|
|
21
|
+
*
|
|
22
|
+
* ── THE ANCHOR IS THE WHOLE PROBLEM, AND IT IS NOT A THRESHOLD ─────────────
|
|
23
|
+
*
|
|
24
|
+
* A person is a tall box. Someone standing on the pavement BESIDE a driveway
|
|
25
|
+
* overlaps the driveway's rectangle in image space with their head and shoulders
|
|
26
|
+
* while their feet are firmly outside it. Area-overlap answers "yes" and is
|
|
27
|
+
* wrong; whole-box containment answers "no" for anyone standing at the edge and
|
|
28
|
+
* is also wrong. Neither is rescued by tuning a fraction, because the error is
|
|
29
|
+
* not one of degree — the box's top is not where the person IS.
|
|
30
|
+
*
|
|
31
|
+
* A camera looks down. The ground plane is where a person occupies space, and
|
|
32
|
+
* the bottom edge of their box is where they touch it. So `groundContact` — the
|
|
33
|
+
* bottom-centre point — is the default, and it needs NO threshold: a point is
|
|
34
|
+
* inside a rectangle or it is not. That is the whole reason to prefer it. A
|
|
35
|
+
* magic fraction nobody can justify is exactly what this codebase asks not to
|
|
36
|
+
* invent, and here the physics removes the need for one.
|
|
37
|
+
*
|
|
38
|
+
* `areaOverlap` is offered for the cases where the ground plane is not the
|
|
39
|
+
* subject — a package on a shelf, a vehicle in a wide overhead view — and there
|
|
40
|
+
* the fraction is a REQUIRED, caller-supplied argument, never a default, so it
|
|
41
|
+
* appears in the route's own configuration where an operator's expectation can
|
|
42
|
+
* be compared against it.
|
|
43
|
+
*
|
|
44
|
+
* ── COORDINATE SPACES: EXACTLY ONE CONVERSION, HERE ────────────────────────
|
|
45
|
+
*
|
|
46
|
+
* Detector boxes are PIXELS in the submitted frame. `sensing_route_screens`
|
|
47
|
+
* stores `focus_x/y/width/height` NORMALIZED to [0,1] — that is what the portal
|
|
48
|
+
* writes and what `focusRegionFromScreenRow` reads. The two meet in exactly one
|
|
49
|
+
* place: this file. Every function takes the frame's dimensions explicitly
|
|
50
|
+
* rather than carrying a resolution around, because a stale resolution is
|
|
51
|
+
* silent and a missing argument is not.
|
|
52
|
+
*
|
|
53
|
+
* @reuse-audit
|
|
54
|
+
* considered: FaceDetector.iou (edge-sensing/src/processors/FaceDetector.ts)
|
|
55
|
+
* - not reused: IoU is a SYMMETRIC similarity between two detections, used
|
|
56
|
+
* for NMS and tracking. Containment is asymmetric — "how much of the BOX
|
|
57
|
+
* is in the RECT" — and IoU's denominator includes the rectangle's own
|
|
58
|
+
* area, so a small person inside a large zone scores near zero on IoU and
|
|
59
|
+
* 1.0 here. Same rectangles, different question. When the detector's own
|
|
60
|
+
* NMS lands it must call FaceDetector's iou, not a copy.
|
|
61
|
+
* considered: camera-alert-focus-selector.tsx (portal)
|
|
62
|
+
* - not reused: drag/clamp only, no containment predicate, and it is React
|
|
63
|
+
* in a different package. Its clamping is the reason `assertNormalizedRect`
|
|
64
|
+
* can be strict — the rect always arrives already inside [0,1].
|
|
65
|
+
*/
|
|
66
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
67
|
+
exports.DEFAULT_ZONE_PREDICATE = void 0;
|
|
68
|
+
exports.assertNormalizedRect = assertNormalizedRect;
|
|
69
|
+
exports.normalizeBox = normalizeBox;
|
|
70
|
+
exports.evaluateZone = evaluateZone;
|
|
71
|
+
exports.countInZone = countInZone;
|
|
72
|
+
exports.DEFAULT_ZONE_PREDICATE = { kind: 'groundContact' };
|
|
73
|
+
function assertFinite(n, name) {
|
|
74
|
+
if (typeof n !== 'number' || !Number.isFinite(n)) {
|
|
75
|
+
throw new TypeError(`${name} must be a finite number, got ${JSON.stringify(n)}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A rect outside [0,1] is a COORDINATE-SPACE error, and it must throw.
|
|
80
|
+
*
|
|
81
|
+
* The one way this happens is a caller passing pixels where normalized values
|
|
82
|
+
* belong — and pixels are numerically valid, so the verdict would just be
|
|
83
|
+
* silently wrong forever: a rect at x=640 clamps to the frame's right edge and
|
|
84
|
+
* every person reads as outside it. That failure has no symptom other than an
|
|
85
|
+
* alert that never fires, which is the failure this whole area keeps producing.
|
|
86
|
+
* Refusing loudly is the only version anybody notices.
|
|
87
|
+
*/
|
|
88
|
+
function assertNormalizedRect(rect) {
|
|
89
|
+
assertFinite(rect?.x, 'rect.x');
|
|
90
|
+
assertFinite(rect?.y, 'rect.y');
|
|
91
|
+
assertFinite(rect?.width, 'rect.width');
|
|
92
|
+
assertFinite(rect?.height, 'rect.height');
|
|
93
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
94
|
+
throw new RangeError(`rect must have positive extent, got ${rect.width}x${rect.height}`);
|
|
95
|
+
}
|
|
96
|
+
const outOfRange = rect.x < 0 || rect.y < 0 || rect.x + rect.width > 1 + 1e-9 || rect.y + rect.height > 1 + 1e-9;
|
|
97
|
+
if (outOfRange) {
|
|
98
|
+
throw new RangeError(`rect must be normalized to [0,1]; got x=${rect.x} y=${rect.y} w=${rect.width} h=${rect.height}. `
|
|
99
|
+
+ 'Pixel coordinates are numerically valid here and would silently never match.');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Convert a pixel box to normalized coordinates. The only conversion there is. */
|
|
103
|
+
function normalizeBox(box, frame) {
|
|
104
|
+
assertFinite(frame?.width, 'frame.width');
|
|
105
|
+
assertFinite(frame?.height, 'frame.height');
|
|
106
|
+
if (frame.width <= 0 || frame.height <= 0) {
|
|
107
|
+
throw new RangeError(`frame must have positive extent, got ${frame.width}x${frame.height}`);
|
|
108
|
+
}
|
|
109
|
+
assertFinite(box?.x, 'box.x');
|
|
110
|
+
assertFinite(box?.y, 'box.y');
|
|
111
|
+
assertFinite(box?.width, 'box.width');
|
|
112
|
+
assertFinite(box?.height, 'box.height');
|
|
113
|
+
return {
|
|
114
|
+
x: box.x / frame.width,
|
|
115
|
+
y: box.y / frame.height,
|
|
116
|
+
width: box.width / frame.width,
|
|
117
|
+
height: box.height / frame.height,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** Area of the intersection of two normalized rects. Zero when disjoint. */
|
|
121
|
+
function intersectionArea(a, b) {
|
|
122
|
+
const x1 = Math.max(a.x, b.x);
|
|
123
|
+
const y1 = Math.max(a.y, b.y);
|
|
124
|
+
const x2 = Math.min(a.x + a.width, b.x + b.width);
|
|
125
|
+
const y2 = Math.min(a.y + a.height, b.y + b.height);
|
|
126
|
+
const w = x2 - x1;
|
|
127
|
+
const h = y2 - y1;
|
|
128
|
+
return w > 0 && h > 0 ? w * h : 0;
|
|
129
|
+
}
|
|
130
|
+
function pointInRect(px, py, rect) {
|
|
131
|
+
return px >= rect.x && px <= rect.x + rect.width && py >= rect.y && py <= rect.y + rect.height;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Is this detection inside the operator's rectangle?
|
|
135
|
+
*
|
|
136
|
+
* Throws on a malformed rect or frame — see `assertNormalizedRect`. Never
|
|
137
|
+
* returns a verdict it could not compute.
|
|
138
|
+
*/
|
|
139
|
+
function evaluateZone(box, frame, rect, predicate = exports.DEFAULT_ZONE_PREDICATE) {
|
|
140
|
+
assertNormalizedRect(rect);
|
|
141
|
+
const nb = normalizeBox(box, frame);
|
|
142
|
+
const boxArea = nb.width * nb.height;
|
|
143
|
+
const overlapFraction = boxArea > 0 ? intersectionArea(nb, rect) / boxArea : 0;
|
|
144
|
+
const groundContact = { x: nb.x + nb.width / 2, y: nb.y + nb.height };
|
|
145
|
+
let inside;
|
|
146
|
+
if (predicate.kind === 'groundContact') {
|
|
147
|
+
inside = pointInRect(groundContact.x, groundContact.y, rect);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
assertFinite(predicate.minOverlap, 'predicate.minOverlap');
|
|
151
|
+
if (predicate.minOverlap <= 0 || predicate.minOverlap > 1) {
|
|
152
|
+
throw new RangeError(`minOverlap must be in (0,1], got ${predicate.minOverlap}`);
|
|
153
|
+
}
|
|
154
|
+
inside = overlapFraction >= predicate.minOverlap;
|
|
155
|
+
}
|
|
156
|
+
return { inside, overlapFraction, groundContact };
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Count the detections inside the zone.
|
|
160
|
+
*
|
|
161
|
+
* This is the integer the focused field has been asking a language model for.
|
|
162
|
+
* `null` rect means NO zone was drawn, and then the count is every detection —
|
|
163
|
+
* which is what the unfocused field already means, so absence degrades to the
|
|
164
|
+
* existing behaviour rather than to zero. A zone that silently counts nothing is
|
|
165
|
+
* indistinguishable from a quiet venue, and that is the whole failure mode here.
|
|
166
|
+
*/
|
|
167
|
+
function countInZone(boxes, frame, rect, predicate = exports.DEFAULT_ZONE_PREDICATE) {
|
|
168
|
+
if (!rect)
|
|
169
|
+
return boxes.length;
|
|
170
|
+
return boxes.reduce((n, b) => (evaluateZone(b, frame, rect, predicate).inside ? n + 1 : n), 0);
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=zoneContainment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zoneContainment.js","sourceRoot":"","sources":["../src/zoneContainment.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trillboards/edge-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.78",
|
|
4
4
|
"description": "Composable cross-platform Edge AI SDK for audience sensing on DOOH/CTV devices",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"prepublishOnly": "npm run build"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@trillboards/edge-core": "^0.4.
|
|
19
|
+
"@trillboards/edge-core": "^0.4.1",
|
|
20
20
|
"@trillboards/edge-sensing": "^0.2.0",
|
|
21
21
|
"@trillboards/edge-federated": "^0.2.0 || ^1.0.0",
|
|
22
22
|
"@trillboards/edge-ads": "^0.2.0",
|