@camstack/types 1.2.404 → 1.2.406
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/dist/addon.js +21 -2
- package/dist/addon.mjs +21 -2
- package/dist/capabilities/index.d.ts +6 -3
- package/dist/capabilities/notification-rules.cap.d.ts +3 -78
- package/dist/capabilities/osd-manager.cap.d.ts +0 -18
- package/dist/capabilities/pipeline-analytics.cap.d.ts +24 -0
- package/dist/capabilities/pipeline-orchestrator.cap.d.ts +36 -0
- package/dist/capabilities/platform-probe.cap.d.ts +34 -0
- package/dist/capabilities/running-state.cap.d.ts +76 -0
- package/dist/capabilities/schemas/package-llm-verdict.d.ts +14 -0
- package/dist/capabilities/schemas/zone-rule.d.ts +4 -0
- package/dist/capabilities/zone-rules.cap.d.ts +10 -0
- package/dist/client.js +10 -62
- package/dist/client.mjs +10 -62
- package/dist/{composition-eoYYVfOp.js → composition-RmnnN39T.js} +86 -2
- package/dist/{composition-CB6PMT9p.mjs → composition-xz-ud5EQ.mjs} +86 -2
- package/dist/device/cap-vocabulary/entries/appliances.d.ts +1 -1
- package/dist/enums/event-category.d.ts +4 -0
- package/dist/enums.js +1 -1
- package/dist/enums.mjs +1 -1
- package/dist/{event-category-fDPduoeC.mjs → event-category-Ceif8n-v.mjs} +4 -0
- package/dist/{event-category-CXrwjC3e.js → event-category-DG6Vir8Z.js} +4 -0
- package/dist/expression/builtins.d.ts +7 -18
- package/dist/expression/expression-source.d.ts +14 -2
- package/dist/generated/addon-api.d.ts +14 -0
- package/dist/generated/addon-cap-facts.d.ts +1 -1
- package/dist/generated/cap-status-types.d.ts +3 -1
- package/dist/generated/capability-router-map.d.ts +5 -2
- package/dist/generated/device-local-state.d.ts +3 -0
- package/dist/generated/device-proxy.d.ts +3 -0
- package/dist/generated/device-scoped-caps.d.ts +1 -1
- package/dist/generated/history-catalog.d.ts +1 -1
- package/dist/generated/method-access-map.d.ts +1 -1
- package/dist/generated/method-device-selectors.d.ts +1 -1
- package/dist/generated/runtime-state-durability.d.ts +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2534 -1415
- package/dist/index.mjs +2471 -1415
- package/dist/interfaces/config-ui.d.ts +69 -2
- package/dist/interfaces/platform.d.ts +7 -0
- package/dist/node-color.d.ts +1 -1
- package/dist/node.js +1 -1
- package/dist/node.mjs +1 -1
- package/dist/pipeline/auto-tune/accelerator-compute.d.ts +116 -0
- package/dist/pipeline/auto-tune/detector-performance.d.ts +5 -0
- package/dist/pipeline/auto-tune/hardware-class.d.ts +5 -4
- package/dist/pipeline/auto-tune/index.d.ts +1 -0
- package/dist/pipeline/auto-tune/plan-schemas.d.ts +100 -0
- package/dist/pipeline/auto-tune/recommend.d.ts +31 -9
- package/dist/pipeline/second-pass/index.d.ts +2 -0
- package/dist/pipeline/second-pass/motion-crop-plan.d.ts +183 -0
- package/dist/pipeline/second-pass/small-subject-recovery.d.ts +216 -0
- package/dist/running-state/assignment.d.ts +34 -0
- package/dist/running-state/defaults.d.ts +3 -0
- package/dist/running-state/from-operation.d.ts +14 -0
- package/dist/{sleep-CHIEEJkY.mjs → sleep-CYfIZzGv.mjs} +7 -3
- package/dist/{sleep-BKS4OPMo.js → sleep-UP964hNP.js} +7 -3
- package/package.json +1 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The MEASURED motion-crop recipe (D396) — per camera, default off.
|
|
3
|
+
*
|
|
4
|
+
* ## What this adds to the pass that already exists
|
|
5
|
+
*
|
|
6
|
+
* `small-subject-recovery.ts` ships a second detector pass gated on an
|
|
7
|
+
* UNCOVERED motion region: a region is worth a crop when it is small AND
|
|
8
|
+
* mostly NOT accounted for by a full-frame box. That rule was built to find a
|
|
9
|
+
* subject standing NEXT TO one the detector already sees, and it works.
|
|
10
|
+
*
|
|
11
|
+
* The measurement says the cheaper question is the better one. On camera 615,
|
|
12
|
+
* against the production detector (`yolov9m-320-int8`, python+openvino) on the
|
|
13
|
+
* node that runs that camera, over 38 visually confirmed positive frames from
|
|
14
|
+
* six real animal pass-bys: full-frame recall at our animal floor **24%**,
|
|
15
|
+
* detecting on motion-derived crops **42%**, the same crops with the motion
|
|
16
|
+
* area bar lowered **74%**. Per pass-by 4/6 → 6/6 at our floor, 3/6 → 6/6 at
|
|
17
|
+
* a 0.70 floor. The score distribution moves with it — full frame median
|
|
18
|
+
* 0.780 / p25 0.526, crops 0.840 / p25 0.652.
|
|
19
|
+
*
|
|
20
|
+
* So this rule keeps every moving region, and treats a tracked box that
|
|
21
|
+
* intersects one as a SEED rather than as an exclusion.
|
|
22
|
+
*
|
|
23
|
+
* ## Additive, never substitutive
|
|
24
|
+
*
|
|
25
|
+
* On frames where motion is silent the crop arm scores 0% and the full-frame
|
|
26
|
+
* arm still scores 5% — and the full frame is the only arm that can see a
|
|
27
|
+
* STATIONARY subject at all. Both recipes only ever ADD boxes to the
|
|
28
|
+
* full-frame result; nothing here can remove one.
|
|
29
|
+
*
|
|
30
|
+
* ## Cost
|
|
31
|
+
*
|
|
32
|
+
* Mean 0.25 extra detector invocations per frame at the shipped motion
|
|
33
|
+
* sensitivity and 0.88 at the lowered bar, on the node that would run it. A
|
|
34
|
+
* crop costs about the same as a full frame (45.2 ms vs 40.8 ms p50) — it is
|
|
35
|
+
* not cheaper, it is better aimed — and the whole thing is bounded by the
|
|
36
|
+
* motion duty cycle. The runner's own per-camera rate limit
|
|
37
|
+
* (`RECOVERY_MIN_INTERVAL_MS`) is the ceiling above that.
|
|
38
|
+
*
|
|
39
|
+
* ## What this is NOT (D52)
|
|
40
|
+
*
|
|
41
|
+
* Not a detail/CLIP crop, for the same reason `small-subject-recovery.ts` is
|
|
42
|
+
* not one: `deriveDetailCropRect` derives the rectangle an enrichment step is
|
|
43
|
+
* fed from a KNOWN subject box, and `object-clip` is one vector index that
|
|
44
|
+
* tolerates exactly one such derivation. This module derives a square from a
|
|
45
|
+
* MOTION region, before any subject exists, for the FIRST-LEVEL detector; its
|
|
46
|
+
* output is a detection, never a vector. Nothing here calls the detail
|
|
47
|
+
* derivation and nothing here may be reused for one.
|
|
48
|
+
*
|
|
49
|
+
* Pure: no runner state, no IPC, no pixels.
|
|
50
|
+
*
|
|
51
|
+
* Lives in `@camstack/types` so the live runner (`addon-pipeline`) and the
|
|
52
|
+
* offline accuracy evaluation (`addon-benchmark`) plan the SAME crops: one
|
|
53
|
+
* module, two callers, no copy to drift.
|
|
54
|
+
*/
|
|
55
|
+
import type { ObjectDetection } from '../../types/detection.js';
|
|
56
|
+
import type { PipelineStepInputOutput } from '../../capabilities/pipeline-executor.cap.js';
|
|
57
|
+
import { type NormalizedRect, type RecoveryCrop, type RecoveryPlanInput } from './small-subject-recovery.js';
|
|
58
|
+
/**
|
|
59
|
+
* Which region rule this camera's second pass runs.
|
|
60
|
+
*
|
|
61
|
+
* The selector is per camera and defaults to the shipped rule, exactly like
|
|
62
|
+
* the motion area bar's own `minArea: 0` opt-in (D394): a deploy changes
|
|
63
|
+
* nothing until an operator moves a camera.
|
|
64
|
+
*/
|
|
65
|
+
export type RecoveryRecipe = 'uncovered-region' | 'motion-crop';
|
|
66
|
+
/**
|
|
67
|
+
* Step-settings key of the opt-in, on the `object-detection` step — so it is
|
|
68
|
+
* per camera through `pipelineOrchestrator.setCameraStepSettings` (a
|
|
69
|
+
* camera-semantic key, D927), and it rides the same
|
|
70
|
+
* `PipelineStepInputOutput.settings` the per-macro confidence floors ride.
|
|
71
|
+
*/
|
|
72
|
+
export declare const MOTION_CROP_DISPATCH_KEY = "motionCropDispatch";
|
|
73
|
+
/**
|
|
74
|
+
* Read the opt-in. Anything that is not the boolean `true` is the shipped
|
|
75
|
+
* rule: a stored string or a number is a mis-write (this repo has shipped a
|
|
76
|
+
* setting written into the wrong sqlite table before), and a mis-write must
|
|
77
|
+
* not silently change what a camera detects on.
|
|
78
|
+
*/
|
|
79
|
+
export declare function readRecoveryRecipe(settings: Readonly<Record<string, unknown>> | undefined): RecoveryRecipe;
|
|
80
|
+
/**
|
|
81
|
+
* The recipe this camera's resolved step tree selects.
|
|
82
|
+
*
|
|
83
|
+
* Reads the ENABLED first-level detector's own settings, which is where
|
|
84
|
+
* `pipelineOrchestrator.setCameraStepSettings` writes a per-camera value. A
|
|
85
|
+
* tree with no enabled detector runs no second pass at all, so the answer
|
|
86
|
+
* there is the shipped rule and it is never used.
|
|
87
|
+
*/
|
|
88
|
+
export declare function readRecoveryRecipeForSteps(steps: readonly PipelineStepInputOutput[]): RecoveryRecipe;
|
|
89
|
+
/**
|
|
90
|
+
* A motion box is grown ×1.5 about its centre before anything else.
|
|
91
|
+
*
|
|
92
|
+
* The motion step's box is the CHANGED pixels, which on a moving animal is the
|
|
93
|
+
* leading edge plus its shadow, not the animal. The detector needs the subject
|
|
94
|
+
* and the context it was trained on.
|
|
95
|
+
*/
|
|
96
|
+
export declare const MOTION_CROP_MOTION_PAD = 1.5;
|
|
97
|
+
/**
|
|
98
|
+
* An intersecting tracked box is grown ×1.25 — less, because it is already a
|
|
99
|
+
* subject rectangle rather than a change mask.
|
|
100
|
+
*/
|
|
101
|
+
export declare const MOTION_CROP_TRACKED_PAD = 1.25;
|
|
102
|
+
/**
|
|
103
|
+
* Regions per frame, largest area first.
|
|
104
|
+
*
|
|
105
|
+
* Measured as NON-BINDING: over the 156 frames of the recall harness the cap
|
|
106
|
+
* bound on 0 of them. It is the bound on a storm, not a tuning knob, and the
|
|
107
|
+
* runner reports when it binds so a camera on which it starts to is visible.
|
|
108
|
+
*/
|
|
109
|
+
export declare const MOTION_CROP_MAX_REGIONS = 3;
|
|
110
|
+
/**
|
|
111
|
+
* The floor a `person` or `vehicle` box must clear when it came out of a CROP
|
|
112
|
+
* rather than out of the full frame.
|
|
113
|
+
*
|
|
114
|
+
* Collected, not assumed: 3 085 quiet frames produced 27 motion-positive
|
|
115
|
+
* frames and 28 crop invocations. The full-frame arm produced 0 boxes above
|
|
116
|
+
* any gate; the crop arm produced 3, of which two are genuine false positives,
|
|
117
|
+
* and both are night-IR corners upscaled into a 320 model — `vehicle 0.749`
|
|
118
|
+
* and `person 0.701`. Zero false `animal` at any gate, which is why `animal`
|
|
119
|
+
* is deliberately absent from this table: the class the pass exists to recover
|
|
120
|
+
* is the class that never produced a false positive, and gating it would cost
|
|
121
|
+
* the whole measured gain.
|
|
122
|
+
*
|
|
123
|
+
* This is a CROP-ONLY floor, applied on top of the per-macro floors the
|
|
124
|
+
* executor already enforced. A full-frame `person` at 0.70 is untouched — the
|
|
125
|
+
* full-frame arm is the one arm with ground truth behind its floor
|
|
126
|
+
* (`minConfidencePerson` 0.65, raised on 2026-07-30 from a measured
|
|
127
|
+
* distribution).
|
|
128
|
+
*
|
|
129
|
+
* The cost of this floor is stated rather than assumed away: it can also
|
|
130
|
+
* remove a real distant person or vehicle recovered from a crop at 0.70–0.80.
|
|
131
|
+
* Nothing in the quiet-footage sample measures that, so the floor is a
|
|
132
|
+
* constant here rather than a slider, and the runner reports every drop it
|
|
133
|
+
* makes per camera.
|
|
134
|
+
*/
|
|
135
|
+
export declare const CROP_CLASS_FLOORS: Readonly<Record<string, number>>;
|
|
136
|
+
/** One box the crop floor removed, with the numbers that removed it. */
|
|
137
|
+
export interface CropFloorDrop {
|
|
138
|
+
readonly macroClass: string;
|
|
139
|
+
readonly score: number;
|
|
140
|
+
readonly floor: number;
|
|
141
|
+
}
|
|
142
|
+
/** What {@link applyCropClassFloors} kept, and everything it did not. */
|
|
143
|
+
export interface CropFloorVerdict {
|
|
144
|
+
readonly kept: readonly ObjectDetection[];
|
|
145
|
+
readonly dropped: readonly CropFloorDrop[];
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Apply the crop-only class floors, REPORTING every drop.
|
|
149
|
+
*
|
|
150
|
+
* A class with no entry in {@link CROP_CLASS_FLOORS} is kept: a floor this
|
|
151
|
+
* table cannot express is not a floor of zero, and silently dropping a class
|
|
152
|
+
* nobody measured is the exact shape of silent drop this repo has paid for
|
|
153
|
+
* three separate times.
|
|
154
|
+
*/
|
|
155
|
+
export declare function applyCropClassFloors(detections: readonly ObjectDetection[]): CropFloorVerdict;
|
|
156
|
+
/**
|
|
157
|
+
* The regions the measured recipe judges this frame on.
|
|
158
|
+
*
|
|
159
|
+
* Motion boxes padded {@link MOTION_CROP_MOTION_PAD}; every full-frame box
|
|
160
|
+
* that intersects a RAW motion box padded {@link MOTION_CROP_TRACKED_PAD} and
|
|
161
|
+
* added as a seed of its own; everything that overlaps merged; the result
|
|
162
|
+
* clamped to the frame, ordered largest first and capped at
|
|
163
|
+
* {@link MOTION_CROP_MAX_REGIONS}.
|
|
164
|
+
*
|
|
165
|
+
* There is deliberately no coverage gate and no size gate here. Those belong
|
|
166
|
+
* to the shipped rule, and they are what makes it a rule about a subject
|
|
167
|
+
* BESIDE a detected one; the measurement says the pass is worth running on the
|
|
168
|
+
* detected subject's own region too.
|
|
169
|
+
*/
|
|
170
|
+
export declare function planMotionCropRegions(input: RecoveryPlanInput): readonly NormalizedRect[];
|
|
171
|
+
/**
|
|
172
|
+
* The crops the measured recipe runs on this frame.
|
|
173
|
+
*
|
|
174
|
+
* Dedupe is by CONTAINMENT only. Two squares that merely overlap both run: the
|
|
175
|
+
* pixels the second adds are pixels no other crop shows the detector, and the
|
|
176
|
+
* fusion step downstream already refuses a duplicate BOX. Dropping a crop
|
|
177
|
+
* because it overlapped would drop the subject sitting at its edge, which is
|
|
178
|
+
* the whole population this pass exists for.
|
|
179
|
+
*
|
|
180
|
+
* A frame that cannot hold the smallest crop yields nothing: it is already
|
|
181
|
+
* seen at scale.
|
|
182
|
+
*/
|
|
183
|
+
export declare function planMotionCrops(input: RecoveryPlanInput): readonly RecoveryCrop[];
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small-subject recovery — a second detector pass at NATIVE resolution on
|
|
3
|
+
* the moving regions the full-frame pass did not account for.
|
|
4
|
+
*
|
|
5
|
+
* ## Why
|
|
6
|
+
*
|
|
7
|
+
* The first-level detector sees the frame at its input size (320 px on every
|
|
8
|
+
* hardware-aware default). A 4K frame is reduced 12×: a dog measured at
|
|
9
|
+
* 89–277 px in the source is 7–38 px for the model and is not emitted. Camera 615, six
|
|
10
|
+
* instants with a dog in the scene, five with no `animal` detection at all —
|
|
11
|
+
* nothing downstream (tracker, gate, notifications) ever saw a subject. The
|
|
12
|
+
* confirmation gate, which re-runs the same detector on a 320 px native crop
|
|
13
|
+
* of the PERSON, found the dog at 0.74–0.91 as `bestIncompatibleClass`. The
|
|
14
|
+
* measurement that decided the constants below is
|
|
15
|
+
* `docs/benchmarks/pipeline-frame-model-eval.md`, case `miss-dog-615`: a
|
|
16
|
+
* 640–800 px native square around the moving region brings the dog to
|
|
17
|
+
* 0.82–0.91 with the SAME 320 model; a 1280 px square loses it again.
|
|
18
|
+
*
|
|
19
|
+
* ## What this is NOT (D52)
|
|
20
|
+
*
|
|
21
|
+
* Not a detail/CLIP crop. `deriveDetailCropRect` derives the rectangle a
|
|
22
|
+
* detail or embedding step is fed from a KNOWN subject box, and one vector
|
|
23
|
+
* index tolerates exactly one such derivation. This module derives a square
|
|
24
|
+
* from a MOTION region, before any subject exists, for the FIRST-LEVEL
|
|
25
|
+
* detector; its output is a detection, never a vector. Nothing here may be
|
|
26
|
+
* reused for a detail crop, and nothing here calls the detail derivation.
|
|
27
|
+
*
|
|
28
|
+
* ## When it runs — NOT only when the frame is empty
|
|
29
|
+
*
|
|
30
|
+
* The trigger is an UNCOVERED MOTION REGION, deliberately broader than "the
|
|
31
|
+
* full-frame pass found nothing". Camera 615, 2026-09-06 19:54:33 local: the
|
|
32
|
+
* gate DID confirm an `animal` birth (bestScore 0.8983) — and the track held
|
|
33
|
+
* 9 positions over 1 666 ms, then coasted out its `maxMissedMs` (4 000 ms) and
|
|
34
|
+
* died at 19:54:39, before the dog reached the Aiuola zone. Both of its object
|
|
35
|
+
* events carry `zones: []`, while in the same seconds the animal was in fact
|
|
36
|
+
* at x≈3200, inside the zone. So the defect is not only "the subject is never
|
|
37
|
+
* born" — it is "the subject is born and then the full-frame pass stops seeing
|
|
38
|
+
* it", and a pass gated on "no detections at all" would not have run on a
|
|
39
|
+
* single one of those frames, because the PERSON was still detected.
|
|
40
|
+
*
|
|
41
|
+
* Gating on the motion region instead makes the pass fire in both cases with
|
|
42
|
+
* one rule: a moving thing the full-frame boxes do not account for. A live
|
|
43
|
+
* track feeding on recovered boxes needs one hit per `maxMissedMs` to survive
|
|
44
|
+
* and `minHits` (3) to be born; at {@link RECOVERY_MIN_INTERVAL_MS} the pass
|
|
45
|
+
* supplies 2/s, which is 8× the coasting budget and births a subject in 1.5 s.
|
|
46
|
+
*
|
|
47
|
+
* ## Where
|
|
48
|
+
*
|
|
49
|
+
* In the runner, in-process, on the frame the full pass just ran on. The
|
|
50
|
+
* pixels come from the same process-local `frameRef` lease the full pass
|
|
51
|
+
* used; the crop is resolved as one compressed ≤640 px view. Frames never
|
|
52
|
+
* cross a process boundary at frame rate (D9/D18) — a second crop of the
|
|
53
|
+
* same frame on the same channel does not change that.
|
|
54
|
+
*
|
|
55
|
+
* Pure: no runner state, no IPC, no pixels. The runner wires the lease, the
|
|
56
|
+
* executor call and the rate limit around it.
|
|
57
|
+
*
|
|
58
|
+
* Lives in `@camstack/types` so the live runner (`addon-pipeline`) and the
|
|
59
|
+
* offline accuracy evaluation (`addon-benchmark`) plan the SAME crops: one
|
|
60
|
+
* module, two callers, no copy to drift.
|
|
61
|
+
*/
|
|
62
|
+
import type { ObjectDetection } from '../../types/detection.js';
|
|
63
|
+
import type { PipelineStepInputOutput } from '../../capabilities/pipeline-executor.cap.js';
|
|
64
|
+
/** The object-detection step id — the first-level detector both recipes run. */
|
|
65
|
+
export declare const OBJECT_DETECTION_STEP_ID = "object-detection";
|
|
66
|
+
/** Fraction of a frame, `[0, 1]` on both axes. */
|
|
67
|
+
export interface NormalizedRect {
|
|
68
|
+
readonly x: number;
|
|
69
|
+
readonly y: number;
|
|
70
|
+
readonly w: number;
|
|
71
|
+
readonly h: number;
|
|
72
|
+
}
|
|
73
|
+
export interface FrameDims {
|
|
74
|
+
readonly width: number;
|
|
75
|
+
readonly height: number;
|
|
76
|
+
}
|
|
77
|
+
/** A square of the NATIVE frame, in native pixels. */
|
|
78
|
+
export interface RecoveryCrop {
|
|
79
|
+
readonly left: number;
|
|
80
|
+
readonly top: number;
|
|
81
|
+
readonly side: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Smallest crop side, native pixels. Below this the model no longer
|
|
85
|
+
* magnifies enough to matter on a 4K frame, and on a 1080p frame 640 is
|
|
86
|
+
* already a third of the picture. Measured: 480–800 px all recover the dog.
|
|
87
|
+
*/
|
|
88
|
+
export declare const RECOVERY_CROP_MIN_SIDE_PX = 640;
|
|
89
|
+
/**
|
|
90
|
+
* Largest crop side. A 320 model on a 1024 px square is a 3.2× reduction and
|
|
91
|
+
* scored the dog at 0.52–0.76; at 1280 px it was absent again. A region wider
|
|
92
|
+
* than this is tiled, never zoomed out.
|
|
93
|
+
*/
|
|
94
|
+
export declare const RECOVERY_CROP_MAX_SIDE_PX = 800;
|
|
95
|
+
/** Context around the region before squaring, as a fraction of its larger side. */
|
|
96
|
+
export declare const RECOVERY_CROP_PADDING_RATIO = 0.25;
|
|
97
|
+
/**
|
|
98
|
+
* The view the crop is resolved at, compressed. Never larger than the crop
|
|
99
|
+
* (the floor is {@link RECOVERY_CROP_MIN_SIDE_PX}), so it never upscales, and
|
|
100
|
+
* the model letterboxes it to its own input from here as it does the frame.
|
|
101
|
+
*/
|
|
102
|
+
export declare const RECOVERY_VIEW_PX = 640;
|
|
103
|
+
/**
|
|
104
|
+
* Crops per frame. Each is one more inference on the same pool (15–25 ms on
|
|
105
|
+
* CoreML for a 640 view); two cover a person-and-dog blob and a second mover.
|
|
106
|
+
*/
|
|
107
|
+
export declare const RECOVERY_MAX_CROPS_PER_FRAME = 2;
|
|
108
|
+
/**
|
|
109
|
+
* The pass runs at most this often per camera — at 10 fps that is one frame
|
|
110
|
+
* in five. A subject the full pass misses is missed for seconds, not for one
|
|
111
|
+
* frame, so the recovery does not need every frame to find it.
|
|
112
|
+
*
|
|
113
|
+
* The floor under this number is the tracker: a track coasts for
|
|
114
|
+
* `maxMissedMs` (4 000 ms by default) before it is dropped, so 2 recoveries a
|
|
115
|
+
* second keep a subject the full-frame pass has lost ALIVE with 8× margin,
|
|
116
|
+
* and reach `minHits` (3) in 1.5 s. Raising it past ~1 300 ms would start to
|
|
117
|
+
* lose tracks between passes.
|
|
118
|
+
*/
|
|
119
|
+
export declare const RECOVERY_MIN_INTERVAL_MS = 500;
|
|
120
|
+
/**
|
|
121
|
+
* A region larger than this share of the frame is not a small subject: a
|
|
122
|
+
* lighting change, a car filling the picture, a person at the lens. The
|
|
123
|
+
* full-frame pass already sees those at scale.
|
|
124
|
+
*/
|
|
125
|
+
export declare const RECOVERY_MAX_REGION_FRAME_FRACTION = 0.25;
|
|
126
|
+
/**
|
|
127
|
+
* How much of a region must lie OUTSIDE every full-frame detection for the
|
|
128
|
+
* pass to run on it. A person's own blob (dilated, with shadow) is covered
|
|
129
|
+
* 70–90% by the person box; a person-and-dog blob is covered ~55% by it.
|
|
130
|
+
*/
|
|
131
|
+
export declare const RECOVERY_MIN_UNCOVERED_FRACTION = 0.35;
|
|
132
|
+
/** Motion regions older than this describe a different frame. */
|
|
133
|
+
export declare const RECOVERY_MOTION_MAX_AGE_MS = 2000;
|
|
134
|
+
/** A recovered box overlapping a same-class full-frame box this much is the same subject. */
|
|
135
|
+
export declare const RECOVERY_FUSE_IOU = 0.5;
|
|
136
|
+
/**
|
|
137
|
+
* A recovered box CONTAINED this much in a same-class full-frame box is a part
|
|
138
|
+
* of it — a torso the crop cut out of a person the full pass already has.
|
|
139
|
+
*/
|
|
140
|
+
export declare const RECOVERY_FUSE_CONTAINMENT = 0.7;
|
|
141
|
+
/** A box within this many crop-view pixels of the crop edge was clipped by it. */
|
|
142
|
+
export declare const RECOVERY_EDGE_MARGIN_PX = 2;
|
|
143
|
+
/** Area of a normalised rectangle, never negative. Shared with the D396 recipe. */
|
|
144
|
+
export declare function normalizedArea(r: NormalizedRect): number;
|
|
145
|
+
/**
|
|
146
|
+
* THE square-placement rule of this pass — one owner, shared by both recipes.
|
|
147
|
+
*
|
|
148
|
+
* A crop is a square of `side` native pixels centred where it can be and slid
|
|
149
|
+
* inside the frame where it cannot. Both region rules place their squares
|
|
150
|
+
* through this function so a crop means the same rectangle whichever rule
|
|
151
|
+
* chose the region.
|
|
152
|
+
*/
|
|
153
|
+
export declare function squareAround(centreX: number, centreY: number, side: number, native: FrameDims): RecoveryCrop;
|
|
154
|
+
export interface RecoveryPlanInput {
|
|
155
|
+
/** Motion regions of this frame, normalised. */
|
|
156
|
+
readonly regions: readonly NormalizedRect[];
|
|
157
|
+
/** The full-frame pass's boxes, normalised. */
|
|
158
|
+
readonly detections: readonly NormalizedRect[];
|
|
159
|
+
/** The NATIVE frame the crops are cut from. */
|
|
160
|
+
readonly native: FrameDims;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The crops the second pass runs on this frame — possibly none.
|
|
164
|
+
*
|
|
165
|
+
* A region qualifies when it is small (`RECOVERY_MAX_REGION_FRAME_FRACTION`)
|
|
166
|
+
* and mostly uncovered by the full-frame boxes
|
|
167
|
+
* (`RECOVERY_MIN_UNCOVERED_FRACTION`). Qualifying regions are served largest
|
|
168
|
+
* uncovered area first under `RECOVERY_MAX_CROPS_PER_FRAME`; a crop whose
|
|
169
|
+
* centre already lies inside a planned crop is folded into it. A frame that
|
|
170
|
+
* cannot hold the smallest crop yields nothing: it is already seen at scale.
|
|
171
|
+
*/
|
|
172
|
+
/** Why a motion region is, or is not, worth a native crop. */
|
|
173
|
+
export type RecoveryRegionVerdict = 'eligible' | 'too-large' | 'covered';
|
|
174
|
+
/** How the regions of one frame fell against the two gates. */
|
|
175
|
+
export interface RecoverySkipCounts {
|
|
176
|
+
readonly tooLarge: number;
|
|
177
|
+
readonly covered: number;
|
|
178
|
+
readonly eligible: number;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* WHY A FRAME PLANNED NO CROP, AS A NUMBER (D378).
|
|
182
|
+
*
|
|
183
|
+
* The pass report counts the passes that RAN, so "the second pass stops running
|
|
184
|
+
* when a person is beside the dog" — a plausible reading of
|
|
185
|
+
* {@link RECOVERY_MAX_REGION_FRAME_FRACTION} and
|
|
186
|
+
* {@link RECOVERY_MIN_UNCOVERED_FRACTION}, and a live hypothesis on camera 615
|
|
187
|
+
* — could not be confirmed or killed from the logs at all. This classifies the
|
|
188
|
+
* regions a frame offered, against the same predicate the planner filters on.
|
|
189
|
+
*/
|
|
190
|
+
export declare function countRecoverySkips(input: RecoveryPlanInput): RecoverySkipCounts;
|
|
191
|
+
export declare function planRecoveryCrops(input: RecoveryPlanInput): readonly RecoveryCrop[];
|
|
192
|
+
/**
|
|
193
|
+
* Boxes the executor produced on the crop VIEW → the result's own space.
|
|
194
|
+
*
|
|
195
|
+
* Three spaces meet here: the view the model saw (`view`, ≤640 px), the native
|
|
196
|
+
* square it was cut from (`crop`, native px) and the space the full-frame
|
|
197
|
+
* result reports in (`result`, native px on the `frameRef` path, the inline
|
|
198
|
+
* ≤640 view otherwise). A box touching the view edge was clipped by the crop
|
|
199
|
+
* and is dropped: half a person is not a person, and the tracker would take
|
|
200
|
+
* the half as the whole.
|
|
201
|
+
*/
|
|
202
|
+
export declare function remapCropDetections(detections: readonly ObjectDetection[], crop: RecoveryCrop, view: FrameDims, result: FrameDims, native: FrameDims): readonly ObjectDetection[];
|
|
203
|
+
/**
|
|
204
|
+
* Class-aware NMS of the recovered boxes against the full-frame ones. Every
|
|
205
|
+
* full-frame box is kept as it is — the second pass only ADDS. A recovered box
|
|
206
|
+
* is added when no full-frame box of the same macro class is the same subject
|
|
207
|
+
* (`RECOVERY_FUSE_IOU`) or contains it (`RECOVERY_FUSE_CONTAINMENT`). Ids are
|
|
208
|
+
* re-minted so a crop's `d0` cannot collide with the frame's.
|
|
209
|
+
*/
|
|
210
|
+
export declare function fuseRecoveredDetections(full: readonly ObjectDetection[], recovered: readonly ObjectDetection[]): readonly ObjectDetection[];
|
|
211
|
+
/**
|
|
212
|
+
* The step the second pass runs: the enabled first-level object detector of
|
|
213
|
+
* the camera's own tree, with its own model and no children. Same model as
|
|
214
|
+
* the full pass so a recovered box is comparable to a full-frame one.
|
|
215
|
+
*/
|
|
216
|
+
export declare function selectRecoveryStep(steps: readonly PipelineStepInputOutput[]): PipelineStepInputOutput | null;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure builder for Customize → Running assignment (D933). Same contract as
|
|
3
|
+
* admin-ui battery presets: returns null when the source cannot be expressed.
|
|
4
|
+
*/
|
|
5
|
+
import type { CompositionFieldsFeature, CompositionSourceRef } from '../composition/composition.js';
|
|
6
|
+
import type { CompositionFieldKind } from '../composition/composition-report.js';
|
|
7
|
+
export type PresetMode = 'add' | 'replace';
|
|
8
|
+
export interface RunningAssignmentSource {
|
|
9
|
+
readonly source: CompositionSourceRef;
|
|
10
|
+
readonly cap: string;
|
|
11
|
+
readonly fieldPath: string;
|
|
12
|
+
readonly kind: CompositionFieldKind;
|
|
13
|
+
}
|
|
14
|
+
export interface RunningAssignmentInput {
|
|
15
|
+
readonly mode: PresetMode;
|
|
16
|
+
readonly target: CompositionSourceRef;
|
|
17
|
+
readonly source: RunningAssignmentSource;
|
|
18
|
+
readonly changedAtField: string | null;
|
|
19
|
+
readonly invert: boolean;
|
|
20
|
+
readonly thresholdWatts: number;
|
|
21
|
+
readonly startDelayMs: number;
|
|
22
|
+
readonly stopDelayMs: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function runningAssignmentFeature(input: RunningAssignmentInput): CompositionFieldsFeature | null;
|
|
25
|
+
export interface RunningAssignmentRecovered {
|
|
26
|
+
readonly source: RunningAssignmentSource;
|
|
27
|
+
readonly invert: boolean;
|
|
28
|
+
readonly thresholdWatts: number;
|
|
29
|
+
readonly startDelayMs: number;
|
|
30
|
+
readonly stopDelayMs: number;
|
|
31
|
+
}
|
|
32
|
+
/** Recover what {@link runningAssignmentFeature} wrote, else null. */
|
|
33
|
+
export declare function runningAssignmentRecovered(stored: CompositionFieldsFeature, target: CompositionSourceRef): RunningAssignmentRecovered | null;
|
|
34
|
+
export declare function isRunningAssignmentFeature(stored: CompositionFieldsFeature, mode: PresetMode, target: CompositionSourceRef): boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DishwasherOperationState } from '../capabilities/dishwasher.cap.js';
|
|
2
|
+
import type { LawnMowerActivity } from '../capabilities/lawn-mower-control.cap.js';
|
|
3
|
+
import type { MediaPlayerState } from '../capabilities/media-player.cap.js';
|
|
4
|
+
import type { VacuumState } from '../capabilities/vacuum-control.cap.js';
|
|
5
|
+
import type { RunningStateStatus } from '../capabilities/running-state.cap.js';
|
|
6
|
+
/** Prior boolean clock used when republishing without moving `since`. */
|
|
7
|
+
export interface RunningStatePrevious {
|
|
8
|
+
readonly running: boolean | null;
|
|
9
|
+
readonly since: number | null;
|
|
10
|
+
}
|
|
11
|
+
export declare function runningFromDishwasherOperation(operation: DishwasherOperationState | null, program: string | null, previous: RunningStatePrevious | undefined, now: number): RunningStateStatus;
|
|
12
|
+
export declare function runningFromVacuumState(state: VacuumState | null, previous: RunningStatePrevious | undefined, now: number): RunningStateStatus;
|
|
13
|
+
export declare function runningFromLawnMower(activity: LawnMowerActivity | null, previous: RunningStatePrevious | undefined, now: number): RunningStateStatus;
|
|
14
|
+
export declare function runningFromMediaPlayer(state: MediaPlayerState | null, previous: RunningStatePrevious | undefined, now: number): RunningStateStatus;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as EventCategory } from "./event-category-
|
|
1
|
+
import { t as EventCategory } from "./event-category-Ceif8n-v.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
//#region src/audio/audio-chunk-format.ts
|
|
4
4
|
/**
|
|
@@ -504,7 +504,7 @@ var WELL_KNOWN_TAB_MAP = Object.fromEntries(WELL_KNOWN_TABS.map((t) => [t.id, t]
|
|
|
504
504
|
* `hydrateSchema` to skip the `value` injection for structural-only fields.
|
|
505
505
|
*/
|
|
506
506
|
function isValuelessField(field) {
|
|
507
|
-
return field.type === "separator" || field.type === "info" || field.type === "qr-code" || field.type === "button" || field.type === "object-array" || field.type === "widget" || field.type === "addon-action-button" || field.type === "device-action-button";
|
|
507
|
+
return field.type === "separator" || field.type === "info" || field.type === "qr-code" || field.type === "button" || field.type === "object-array" || field.type === "widget" || field.type === "addon-action-button" || field.type === "device-action-button" || field.type === "account-sign-in" || field.type === "secret-presence";
|
|
508
508
|
}
|
|
509
509
|
/**
|
|
510
510
|
* Base collector: flatten a hydrated `ConfigUISchemaWithValues` into
|
|
@@ -7327,6 +7327,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
7327
7327
|
getStatus: (input) => dispatch("recording-onboard", "recordingOnboard", "getStatus", "query", input)
|
|
7328
7328
|
};
|
|
7329
7329
|
const recordingSignalInterface = { getStatus: (input) => dispatch("recording-signal", "recordingSignal", "getStatus", "query", input) };
|
|
7330
|
+
const runningStateInterface = { getStatus: (input) => dispatch("running-state", "runningState", "getStatus", "query", input) };
|
|
7330
7331
|
const sceneMonitorInterface = {
|
|
7331
7332
|
listScenes: (input) => dispatch("scene-monitor", "sceneMonitor", "listScenes", "query", input),
|
|
7332
7333
|
listScenesBatch: (input) => dispatch("scene-monitor", "sceneMonitor", "listScenesBatch", "query", input),
|
|
@@ -7653,6 +7654,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
7653
7654
|
ptzAutotrack: createSliceHandle(stateSource, binding.deviceId, "ptz-autotrack"),
|
|
7654
7655
|
recordingOnboard: createSliceHandle(stateSource, binding.deviceId, "recording-onboard"),
|
|
7655
7656
|
recordingSignal: createSliceHandle(stateSource, binding.deviceId, "recording-signal"),
|
|
7657
|
+
runningState: createSliceHandle(stateSource, binding.deviceId, "running-state"),
|
|
7656
7658
|
sceneMonitor: createSliceHandle(stateSource, binding.deviceId, "scene-monitor"),
|
|
7657
7659
|
scriptRunner: createSliceHandle(stateSource, binding.deviceId, "script-runner"),
|
|
7658
7660
|
smoke: createSliceHandle(stateSource, binding.deviceId, "smoke"),
|
|
@@ -7741,6 +7743,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
7741
7743
|
...bound("recording") ? { recording: recordingInterface } : {},
|
|
7742
7744
|
...bound("recording-onboard") ? { recordingOnboard: recordingOnboardInterface } : {},
|
|
7743
7745
|
...bound("recording-signal") ? { recordingSignal: recordingSignalInterface } : {},
|
|
7746
|
+
...bound("running-state") ? { runningState: runningStateInterface } : {},
|
|
7744
7747
|
...bound("scene-monitor") ? { sceneMonitor: sceneMonitorInterface } : {},
|
|
7745
7748
|
...bound("script-runner") ? { scriptRunner: scriptRunnerInterface } : {},
|
|
7746
7749
|
...bound("smoke") ? { smoke: smokeInterface } : {},
|
|
@@ -7791,7 +7794,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
7791
7794
|
* can answer "may a rule actuate this?" without importing the schema barrel
|
|
7792
7795
|
* (~144MB RSS per runner, D28).
|
|
7793
7796
|
*
|
|
7794
|
-
* Coverage:
|
|
7797
|
+
* Coverage: 91 device-scoped capabilities.
|
|
7795
7798
|
*/
|
|
7796
7799
|
var DEVICE_SCOPED_CAPS = new Set([
|
|
7797
7800
|
"accessories",
|
|
@@ -7863,6 +7866,7 @@ var DEVICE_SCOPED_CAPS = new Set([
|
|
|
7863
7866
|
"recording",
|
|
7864
7867
|
"recording-onboard",
|
|
7865
7868
|
"recording-signal",
|
|
7869
|
+
"running-state",
|
|
7866
7870
|
"scene-monitor",
|
|
7867
7871
|
"script-runner",
|
|
7868
7872
|
"smoke",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_event_category = require("./event-category-
|
|
1
|
+
const require_event_category = require("./event-category-DG6Vir8Z.js");
|
|
2
2
|
let zod = require("zod");
|
|
3
3
|
//#region src/audio/audio-chunk-format.ts
|
|
4
4
|
/**
|
|
@@ -504,7 +504,7 @@ var WELL_KNOWN_TAB_MAP = Object.fromEntries(WELL_KNOWN_TABS.map((t) => [t.id, t]
|
|
|
504
504
|
* `hydrateSchema` to skip the `value` injection for structural-only fields.
|
|
505
505
|
*/
|
|
506
506
|
function isValuelessField(field) {
|
|
507
|
-
return field.type === "separator" || field.type === "info" || field.type === "qr-code" || field.type === "button" || field.type === "object-array" || field.type === "widget" || field.type === "addon-action-button" || field.type === "device-action-button";
|
|
507
|
+
return field.type === "separator" || field.type === "info" || field.type === "qr-code" || field.type === "button" || field.type === "object-array" || field.type === "widget" || field.type === "addon-action-button" || field.type === "device-action-button" || field.type === "account-sign-in" || field.type === "secret-presence";
|
|
508
508
|
}
|
|
509
509
|
/**
|
|
510
510
|
* Base collector: flatten a hydrated `ConfigUISchemaWithValues` into
|
|
@@ -7327,6 +7327,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
7327
7327
|
getStatus: (input) => dispatch("recording-onboard", "recordingOnboard", "getStatus", "query", input)
|
|
7328
7328
|
};
|
|
7329
7329
|
const recordingSignalInterface = { getStatus: (input) => dispatch("recording-signal", "recordingSignal", "getStatus", "query", input) };
|
|
7330
|
+
const runningStateInterface = { getStatus: (input) => dispatch("running-state", "runningState", "getStatus", "query", input) };
|
|
7330
7331
|
const sceneMonitorInterface = {
|
|
7331
7332
|
listScenes: (input) => dispatch("scene-monitor", "sceneMonitor", "listScenes", "query", input),
|
|
7332
7333
|
listScenesBatch: (input) => dispatch("scene-monitor", "sceneMonitor", "listScenesBatch", "query", input),
|
|
@@ -7653,6 +7654,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
7653
7654
|
ptzAutotrack: createSliceHandle(stateSource, binding.deviceId, "ptz-autotrack"),
|
|
7654
7655
|
recordingOnboard: createSliceHandle(stateSource, binding.deviceId, "recording-onboard"),
|
|
7655
7656
|
recordingSignal: createSliceHandle(stateSource, binding.deviceId, "recording-signal"),
|
|
7657
|
+
runningState: createSliceHandle(stateSource, binding.deviceId, "running-state"),
|
|
7656
7658
|
sceneMonitor: createSliceHandle(stateSource, binding.deviceId, "scene-monitor"),
|
|
7657
7659
|
scriptRunner: createSliceHandle(stateSource, binding.deviceId, "script-runner"),
|
|
7658
7660
|
smoke: createSliceHandle(stateSource, binding.deviceId, "smoke"),
|
|
@@ -7741,6 +7743,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
7741
7743
|
...bound("recording") ? { recording: recordingInterface } : {},
|
|
7742
7744
|
...bound("recording-onboard") ? { recordingOnboard: recordingOnboardInterface } : {},
|
|
7743
7745
|
...bound("recording-signal") ? { recordingSignal: recordingSignalInterface } : {},
|
|
7746
|
+
...bound("running-state") ? { runningState: runningStateInterface } : {},
|
|
7744
7747
|
...bound("scene-monitor") ? { sceneMonitor: sceneMonitorInterface } : {},
|
|
7745
7748
|
...bound("script-runner") ? { scriptRunner: scriptRunnerInterface } : {},
|
|
7746
7749
|
...bound("smoke") ? { smoke: smokeInterface } : {},
|
|
@@ -7791,7 +7794,7 @@ function createDeviceProxy(api, binding, opts) {
|
|
|
7791
7794
|
* can answer "may a rule actuate this?" without importing the schema barrel
|
|
7792
7795
|
* (~144MB RSS per runner, D28).
|
|
7793
7796
|
*
|
|
7794
|
-
* Coverage:
|
|
7797
|
+
* Coverage: 91 device-scoped capabilities.
|
|
7795
7798
|
*/
|
|
7796
7799
|
var DEVICE_SCOPED_CAPS = new Set([
|
|
7797
7800
|
"accessories",
|
|
@@ -7863,6 +7866,7 @@ var DEVICE_SCOPED_CAPS = new Set([
|
|
|
7863
7866
|
"recording",
|
|
7864
7867
|
"recording-onboard",
|
|
7865
7868
|
"recording-signal",
|
|
7869
|
+
"running-state",
|
|
7866
7870
|
"scene-monitor",
|
|
7867
7871
|
"script-runner",
|
|
7868
7872
|
"smoke",
|