supervision 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/dist/index.d.ts +16 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1587 -177
- package/dist/index.js.map +1 -1
- package/dist/mask-preparation.worker.js +110 -68
- package/dist/mask-preparation.worker.js.map +1 -1
- package/dist/post-processing/default-tracking-worker.d.ts +3 -0
- package/dist/post-processing/default-tracking-worker.d.ts.map +1 -0
- package/dist/post-processing/detection-post-processing-pipeline.d.ts +7 -0
- package/dist/post-processing/detection-post-processing-pipeline.d.ts.map +1 -0
- package/dist/post-processing/embedded-tracking-worker.d.ts +2 -0
- package/dist/post-processing/embedded-tracking-worker.d.ts.map +1 -0
- package/dist/post-processing/tracking-worker-protocol.d.ts +29 -0
- package/dist/post-processing/tracking-worker-protocol.d.ts.map +1 -0
- package/dist/post-processing/tracking.worker.d.ts +2 -0
- package/dist/post-processing/tracking.worker.d.ts.map +1 -0
- package/dist/render-preparation/mask-frame-artifact.d.ts +6 -0
- package/dist/render-preparation/mask-frame-artifact.d.ts.map +1 -1
- package/dist/render-preparation/mask-frame-compositor.d.ts +1 -0
- package/dist/render-preparation/mask-frame-compositor.d.ts.map +1 -1
- package/dist/render-preparation/mask-frame-preparer.d.ts.map +1 -1
- package/dist/render-preparation/mask-preparation-worker-protocol.d.ts +1 -0
- package/dist/render-preparation/mask-preparation-worker-protocol.d.ts.map +1 -1
- package/dist/renderers/annotation-shape-styles.d.ts +17 -0
- package/dist/renderers/annotation-shape-styles.d.ts.map +1 -0
- package/dist/renderers/media-renderer-core.d.ts.map +1 -1
- package/dist/renderers/media-renderer-scene.d.ts +17 -2
- package/dist/renderers/media-renderer-scene.d.ts.map +1 -1
- package/dist/renderers/pixi-annotation-overlay-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-focus-layer.d.ts +1 -0
- package/dist/renderers/pixi-focus-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-interaction-layer.d.ts +2 -0
- package/dist/renderers/pixi-interaction-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-interaction-presentation-layer.d.ts +2 -1
- package/dist/renderers/pixi-interaction-presentation-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-mask-halo.d.ts +94 -0
- package/dist/renderers/pixi-mask-halo.d.ts.map +1 -0
- package/dist/renderers/pixi-mask-layer.d.ts +10 -1
- package/dist/renderers/pixi-mask-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-media-scene.d.ts.map +1 -1
- package/dist/renderers/pixi-path.d.ts +13 -7
- package/dist/renderers/pixi-path.d.ts.map +1 -1
- package/dist/renderers/pixi-polygon-layer.d.ts.map +1 -1
- package/dist/renderers/pixi-region-layer.d.ts +42 -0
- package/dist/renderers/pixi-region-layer.d.ts.map +1 -0
- package/dist/renderers/pixi-scene-layer-slot.d.ts +1 -0
- package/dist/renderers/pixi-scene-layer-slot.d.ts.map +1 -1
- package/dist/renderers/pixi-vector-layer.d.ts +3 -1
- package/dist/renderers/pixi-vector-layer.d.ts.map +1 -1
- package/dist/sessions/media-session.d.ts.map +1 -1
- package/dist/tracking.worker.js +1485 -0
- package/dist/tracking.worker.js.map +1 -0
- package/dist/types/detection-post-processing.d.ts +62 -0
- package/dist/types/detection-post-processing.d.ts.map +1 -0
- package/dist/types/media-renderer.d.ts +1 -1
- package/dist/types/media-renderer.d.ts.map +1 -1
- package/dist/workers/worker-rpc-client.d.ts +2 -1
- package/dist/workers/worker-rpc-client.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.d.ts +23 -3
- package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.js +2545 -566
- package/node_modules/supervision-js-core/dist/index.js.map +1 -1
- package/node_modules/supervision-js-core/dist/interactions/detection-picker.d.ts +18 -0
- package/node_modules/supervision-js-core/dist/interactions/detection-picker.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/post-processing/byte-track-tracker.d.ts +4 -0
- package/node_modules/supervision-js-core/dist/post-processing/byte-track-tracker.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/post-processing/cbiou-tracker.d.ts +4 -0
- package/node_modules/supervision-js-core/dist/post-processing/cbiou-tracker.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/post-processing/oc-sort-tracker.d.ts +4 -0
- package/node_modules/supervision-js-core/dist/post-processing/oc-sort-tracker.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/post-processing/sort-tracker.d.ts +4 -0
- package/node_modules/supervision-js-core/dist/post-processing/sort-tracker.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/post-processing/tracking.d.ts +9 -0
- package/node_modules/supervision-js-core/dist/post-processing/tracking.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-presentation.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts +13 -6
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/box-corner-style.d.ts +14 -0
- package/node_modules/supervision-js-core/dist/styles/box-corner-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/styles/box-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/default-annotation-presentation.d.ts +19 -0
- package/node_modules/supervision-js-core/dist/styles/default-annotation-presentation.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/interaction-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/keypoint-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/marker-style.d.ts +14 -0
- package/node_modules/supervision-js-core/dist/styles/marker-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/styles/polygon-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/polyline-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/source-presentation.d.ts +8 -0
- package/node_modules/supervision-js-core/dist/styles/source-presentation.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/styles/stroke-style.d.ts +12 -0
- package/node_modules/supervision-js-core/dist/styles/stroke-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts +124 -4
- package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/box-corner-style.d.ts +31 -0
- package/node_modules/supervision-js-core/dist/types/box-corner-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/box-style.d.ts +4 -17
- package/node_modules/supervision-js-core/dist/types/box-style.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/detections.d.ts +11 -3
- package/node_modules/supervision-js-core/dist/types/detections.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/ellipse-style.d.ts +30 -0
- package/node_modules/supervision-js-core/dist/types/ellipse-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/interaction.d.ts +12 -0
- package/node_modules/supervision-js-core/dist/types/interaction.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/marker-style.d.ts +46 -0
- package/node_modules/supervision-js-core/dist/types/marker-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/mask-halo-style.d.ts +29 -0
- package/node_modules/supervision-js-core/dist/types/mask-halo-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts +18 -2
- package/node_modules/supervision-js-core/dist/types/media-rendering.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/types/paint-style.d.ts +36 -0
- package/node_modules/supervision-js-core/dist/types/paint-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/post-processing.d.ts +148 -0
- package/node_modules/supervision-js-core/dist/types/post-processing.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/types/shape-style.d.ts +112 -0
- package/node_modules/supervision-js-core/dist/types/shape-style.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/utils/detection-frames.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/utils/shape-geometry.d.ts +50 -0
- package/node_modules/supervision-js-core/dist/utils/shape-geometry.d.ts.map +1 -0
- package/node_modules/supervision-js-core/package.json +12 -5
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { copySortedDetectionFrames, detectionFrameOverlapsRange, includeDefined, MediaSourceStatus, MediaRendererPlaybackState, MediaRendererFit, createDefaultAnnotationPresentation, createArrayDetectionFrameSource, createBufferedDetectionTimeline, resolveAnnotationRendererPresentation, createIdleDetectionBufferState, MediaInteractionMode,
|
|
2
|
-
export { BaseBoxStyle, BaseFocusStyle, BaseInteractionStyle, BaseKeypointStyle, BaseLabelStyle, BaseMaskStyle, BasePolygonStyle, BasePolylineStyle, BoxShape, BoxStrokeAlignment, DEFAULT_DETECTION_CLASS_STYLES, DEFAULT_DETECTION_COLOR_SEQUENCE, DetectionBufferStatus, DetectionFrameRetentionMode, DetectionFrameSelectionMode, DetectionInteractionState, DetectionMaskEncoding, DetectionPickTarget, FocusTargetMode, KeypointMarkerShape, KeypointVisibility, LabelPlacement, LabelVisibilityMode, MaskRenderMode, MediaInteractionMode, MediaRendererFit, MediaRendererPlaybackState, MediaSessionActivityKind, MediaSessionActivityStatus, MediaSessionMode, MediaSessionStatus, MediaSourceStatus, SUPERVISION_ROBOFLOW_COLOR, annotationRendererKinds, annotationRenderers, createArrayDetectionFrameSource, createBufferedDetectionTimeline, createColdDetectionFrameSource, createCompositeDetectionFrameSource, createDefaultAnnotationPresentation, createMemoryColdDetectionFrameStore, createWritableDetectionFrameSource, normalizeDetectionClassName, pickDetectionAtPoint, resolveDetectionClassColorStyle } from 'supervision-js-core';
|
|
1
|
+
import { copySortedDetectionFrames, detectionFrameOverlapsRange, projectDetectionFrameForTracking, createSortTracker, createOCSortTracker, createCBIoUTracker, createByteTrackTracker, includeDefined, MediaSourceStatus, MediaRendererPlaybackState, MediaRendererFit, createDefaultAnnotationPresentation, createArrayDetectionFrameSource, createBufferedDetectionTimeline, resolveAnnotationRendererPresentation, createIdleDetectionBufferState, MediaInteractionMode, StrokeAlignment, BaseBoxStyle, centerRectToTopLeftRect, BoxShape, BaseFocusStyle, decodeCompressedRleMask, extractMaskRectRuns, AnnotationGestureStateKind, AnnotationHandleKind, getDetectionRect, DetectionPickTarget, createDetectionPickKey, rebaseDetectionPickToFrame, pickDetectionAtPoint, followDetectionPickAcrossFrames, haveSameDetectionPickIdentities, pickAnnotationHandle, getAnnotationHandles, createIdMaskFrame, encodeBinaryMask, rasterizePolygonToMask, LabelPlacement, BasePolygonStyle, BasePolylineStyle, BaseKeypointStyle, KeypointMarkerShape, MarkerSizeSpace, MarkerShape, ShapeInstructionKind, sampleEllipseArc, resolveEllipseSegmentCount, resolveMarkerGeometry, BaseInteractionStyle, DetectionInteractionState, MAX_ID_MASK_PALETTE_ENTRIES, DetectionBufferStatus, canReuseMaskStyleArtifacts, getBufferedDetectionTimelineFrameSnapshot, MAX_ID_MASK_STROKE_WIDTH, resolveMaskStyleOpacity, pickDetectionByMaskId, BoxStrokeAlignment, resolveStyleValue, resolveAnnotationStyleState, lightenColor, createViewportController, MediaSessionMode, DetectionFrameRetentionMode, createMemoryColdDetectionFrameStore, createWritableDetectionFrameSource, createCompositeDetectionFrameSource, MediaSessionActivityStatus, MediaSessionActivityKind, MediaSessionStatus, createSourceAwarePresentation } from 'supervision-js-core';
|
|
2
|
+
export { BaseBoxCornerStyle, BaseBoxStyle, BaseFocusStyle, BaseInteractionStyle, BaseKeypointStyle, BaseLabelStyle, BaseMarkerStyle, BaseMaskStyle, BasePolygonStyle, BasePolylineStyle, BoxShape, BoxStrokeAlignment, DEFAULT_DETECTION_CLASS_STYLES, DEFAULT_DETECTION_COLOR_SEQUENCE, DetectionBufferStatus, DetectionFrameRetentionMode, DetectionFrameSelectionMode, DetectionInteractionState, DetectionMaskEncoding, DetectionPickTarget, FocusTargetMode, KeypointMarkerShape, KeypointVisibility, LabelPlacement, LabelVisibilityMode, MarkerShape, MarkerSizeSpace, MaskRenderMode, MediaInteractionMode, MediaRendererFit, MediaRendererPlaybackState, MediaSessionActivityKind, MediaSessionActivityStatus, MediaSessionMode, MediaSessionStatus, MediaSourceStatus, RegionRendererComposeMode, RegionRendererRegionKind, RegionRendererSourceKind, SUPERVISION_ROBOFLOW_COLOR, TrackingGeometry, annotationRendererKinds, annotationRenderers, createArrayDetectionFrameSource, createBufferedDetectionTimeline, createByteTrackTracker, createCBIoUTracker, createColdDetectionFrameSource, createCompositeDetectionFrameSource, createDefaultAnnotationPresentation, createMemoryColdDetectionFrameStore, createOCSortTracker, createSortTracker, createWritableDetectionFrameSource, detectionPostProcessors, normalizeDetectionClassName, pickDetectionAtPoint, projectDetectionFrameForTracking, resolveDetectionClassColorStyle } from 'supervision-js-core';
|
|
3
3
|
|
|
4
4
|
const DEFAULT_DATABASE_NAME = "supervision-js-detection-frames";
|
|
5
5
|
const DEFAULT_CHUNK_DURATION_SECONDS = 1;
|
|
@@ -458,6 +458,434 @@ function getDetectionFrameDedupeKey(frame) {
|
|
|
458
458
|
: `index:${frame.frameIndex}`;
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
+
const EMBEDDED_TRACKING_WORKER_SOURCE = "(function () {\n 'use strict';\n\n /** COCO-compatible keypoint visibility values. */\n var KeypointVisibility;\n (function (KeypointVisibility) {\n KeypointVisibility[KeypointVisibility[\"NotLabeled\"] = 0] = \"NotLabeled\";\n KeypointVisibility[KeypointVisibility[\"Occluded\"] = 1] = \"Occluded\";\n KeypointVisibility[KeypointVisibility[\"Visible\"] = 2] = \"Visible\";\n })(KeypointVisibility || (KeypointVisibility = {}));\n var DetectionMaskEncoding;\n (function (DetectionMaskEncoding) {\n DetectionMaskEncoding[\"CompressedRle\"] = \"compressedRle\";\n })(DetectionMaskEncoding || (DetectionMaskEncoding = {}));\n\n var DetectionBufferStatus;\n (function (DetectionBufferStatus) {\n DetectionBufferStatus[\"Idle\"] = \"idle\";\n DetectionBufferStatus[\"Loading\"] = \"loading\";\n DetectionBufferStatus[\"Ready\"] = \"ready\";\n DetectionBufferStatus[\"Error\"] = \"error\";\n DetectionBufferStatus[\"Destroyed\"] = \"destroyed\";\n })(DetectionBufferStatus || (DetectionBufferStatus = {}));\n /**\n * How the renderer selects the active detection frame for a media timestamp.\n */\n var DetectionFrameSelectionMode;\n (function (DetectionFrameSelectionMode) {\n /**\n * Select the frame whose `[mediaTime, endTime)` interval contains the media\n * time. This is the default for interval annotations and timestamped sources.\n */\n DetectionFrameSelectionMode[\"Interval\"] = \"interval\";\n /**\n * Select from a known inference frame grid using `frameIndex`, `frameRate`,\n * and optional `frameIndexOriginTime`. This is useful when inference was run\n * on normalized frames and playback should snap detections to that grid.\n */\n DetectionFrameSelectionMode[\"NearestFrameIndex\"] = \"nearestFrameIndex\";\n })(DetectionFrameSelectionMode || (DetectionFrameSelectionMode = {}));\n var DetectionFrameRetentionMode;\n (function (DetectionFrameRetentionMode) {\n /**\n * Keep writable detections only in an in-memory store. Useful for ephemeral\n * live streams where old predictions should disappear when evicted.\n */\n DetectionFrameRetentionMode[\"MemoryOnly\"] = \"memoryOnly\";\n /**\n * Persist every written detection frame. Useful for finite media where seek\n * and replay should not require recomputing detections.\n */\n DetectionFrameRetentionMode[\"PersistAll\"] = \"persistAll\";\n /**\n * Persist only the most recent retention window. Useful for long-running\n * streams where replay is bounded to a recent time horizon.\n */\n DetectionFrameRetentionMode[\"PersistWindow\"] = \"persistWindow\";\n })(DetectionFrameRetentionMode || (DetectionFrameRetentionMode = {}));\n\n var AnnotationFrameMutationKind;\n (function (AnnotationFrameMutationKind) {\n AnnotationFrameMutationKind[\"Add\"] = \"add\";\n AnnotationFrameMutationKind[\"Remove\"] = \"remove\";\n AnnotationFrameMutationKind[\"Replace\"] = \"replace\";\n AnnotationFrameMutationKind[\"Transact\"] = \"transact\";\n AnnotationFrameMutationKind[\"Update\"] = \"update\";\n })(AnnotationFrameMutationKind || (AnnotationFrameMutationKind = {}));\n\n /**\n * Creates one stateful SORT tracker for a single ordered media sequence.\n *\n * Defaults, lifecycle semantics, and observation-only output mirror\n * roboflow/trackers SORT. Motion predictions remain internal to association.\n */\n function createSortTracker$1(options = {}) {\n const lostTrackBuffer = normalizeNonNegativeInteger$1(options.lostTrackBuffer ?? 30, \"lostTrackBuffer\");\n const frameRate = options.frameRate ?? 30;\n const trackActivationThreshold = options.trackActivationThreshold ?? 0.25;\n const minimumConsecutiveFrames = normalizePositiveInteger$1(options.minimumConsecutiveFrames ?? 3, \"minimumConsecutiveFrames\");\n const minimumIouThreshold = options.minimumIouThreshold ?? 0.3;\n if (!Number.isFinite(frameRate) || frameRate <= 0) {\n throw new Error(\"frameRate must be a finite positive value.\");\n }\n normalizeUnitInterval$1(trackActivationThreshold, \"trackActivationThreshold\");\n normalizeUnitInterval$1(minimumIouThreshold, \"minimumIouThreshold\");\n const scaledLostTrackBuffer = (frameRate / 30) * lostTrackBuffer;\n if (!Number.isFinite(scaledLostTrackBuffer)) {\n throw new Error(\"Scaled lostTrackBuffer overflows: frameRate / 30 * lostTrackBuffer must be finite.\");\n }\n const maximumFramesWithoutUpdate = lostTrackBuffer === 0 ? 0 : Math.max(1, Math.ceil(scaledLostTrackBuffer));\n let tracks = [];\n let nextTrackerId = 0;\n let previousFrameIndex;\n return {\n reset() {\n tracks = [];\n nextTrackerId = 0;\n previousFrameIndex = undefined;\n },\n update(detections, frameIndex) {\n const frameStep = resolveFrameStep(frameIndex, previousFrameIndex);\n previousFrameIndex = frameIndex ?? previousFrameIndex;\n const predicted = tracks.map((track) => track.predict(frameStep, frameRate));\n const { matches, unmatchedDetections } = associateDetectionsToTracks(detections, predicted, minimumIouThreshold);\n const trackerIds = new Map();\n for (const match of matches) {\n const track = tracks[match.trackIndex];\n const detection = detections[match.detectionIndex];\n track.update(detection);\n if (track.trackerId === undefined &&\n track.successfulUpdates >= minimumConsecutiveFrames) {\n track.trackerId = nextTrackerId;\n nextTrackerId += 1;\n }\n if (track.trackerId !== undefined) {\n trackerIds.set(detection.detectionIndex, track.trackerId);\n }\n }\n for (const detectionIndex of unmatchedDetections) {\n const detection = detections[detectionIndex];\n if ((detection.confidence ?? 1) >= trackActivationThreshold) {\n tracks.push(new KalmanBoxTrack(detection));\n }\n }\n tracks = tracks.filter((track) => track.timeSinceUpdate <= maximumFramesWithoutUpdate &&\n (track.successfulUpdates >= minimumConsecutiveFrames ||\n track.timeSinceUpdate === 0));\n return {\n activeTrackCount: tracks.length,\n assignments: detections.flatMap((detection) => {\n const trackerId = trackerIds.get(detection.detectionIndex);\n return trackerId === undefined\n ? []\n : [{ detectionIndex: detection.detectionIndex, trackerId }];\n }),\n confirmedTrackCount: tracks.filter((track) => track.trackerId !== undefined).length,\n };\n },\n };\n }\n class KalmanBoxTrack {\n consecutiveUpdates;\n trackerId;\n successfulUpdates = 1;\n timeSinceUpdate = 0;\n state;\n covariance = identity(8);\n constructor(detection, consecutiveUpdates = false) {\n this.consecutiveUpdates = consecutiveUpdates;\n this.state = [...rectToXyxy(detection.rect), 0, 0, 0, 0].map((value) => [\n value,\n ]);\n }\n predict(frameStep, frameRate) {\n if (this.consecutiveUpdates && this.timeSinceUpdate > 0) {\n this.successfulUpdates = 0;\n }\n const transition = identity(8);\n for (let index = 0; index < 4; index += 1) {\n transition[index][index + 4] = frameStep;\n }\n this.state = multiply(transition, this.state);\n this.covariance = add(multiply(multiply(transition, this.covariance), transpose(transition)), createProcessNoise(frameStep, frameRate));\n // Python SORT counts update calls for the fixed-rate lost-track budget.\n this.timeSinceUpdate += 1;\n return stateToRect(this.state);\n }\n update(detection) {\n const measurement = rectToXyxy(detection.rect).map((value) => [value]);\n const observation = [\n [1, 0, 0, 0, 0, 0, 0, 0],\n [0, 1, 0, 0, 0, 0, 0, 0],\n [0, 0, 1, 0, 0, 0, 0, 0],\n [0, 0, 0, 1, 0, 0, 0, 0],\n ];\n const measurementNoise = scale(identity(4), 0.1);\n const innovation = subtract(measurement, multiply(observation, this.state));\n const covarianceObservationTranspose = multiply(this.covariance, transpose(observation));\n const innovationCovariance = add(multiply(observation, covarianceObservationTranspose), measurementNoise);\n const gain = multiply(covarianceObservationTranspose, inverse(innovationCovariance));\n this.state = add(this.state, multiply(gain, innovation));\n const identityMinusGainObservation = subtract(identity(8), multiply(gain, observation));\n // Joseph form matches the Python implementation and is more stable.\n this.covariance = add(multiply(multiply(identityMinusGainObservation, this.covariance), transpose(identityMinusGainObservation)), multiply(multiply(gain, measurementNoise), transpose(gain)));\n this.timeSinceUpdate = 0;\n this.successfulUpdates += 1;\n }\n getStateRect() {\n return stateToRect(this.state);\n }\n }\n function associateDetectionsToTracks(detections, predicted, minimumIouThreshold) {\n if (predicted.length === 0 || detections.length === 0) {\n return {\n matches: [],\n unmatchedDetections: detections.map((_, index) => index),\n };\n }\n // Python SORT associates all detections class-agnostically using standard IoU.\n const scores = predicted.map((rect) => detections.map((detection) => intersectionOverUnion(rect, detection.rect)));\n const candidateMatches = maximizeAssignment(scores);\n const matches = [];\n const matchedDetections = new Set();\n for (const [trackIndex, detectionIndex] of candidateMatches) {\n if (scores[trackIndex][detectionIndex] < minimumIouThreshold)\n continue;\n matches.push({ detectionIndex, trackIndex });\n matchedDetections.add(detectionIndex);\n }\n matches.sort((left, right) => left.trackIndex - right.trackIndex);\n return {\n matches,\n unmatchedDetections: detections.flatMap((_, index) => matchedDetections.has(index) ? [] : [index]),\n };\n }\n /** Hungarian assignment for a rectangular score matrix. */\n function maximizeAssignment(scores) {\n if (scores.length === 0 || scores[0]?.length === 0) {\n return [];\n }\n const rowCount = scores.length;\n const columnCount = scores[0].length;\n const transposed = rowCount > columnCount;\n const costs = transposed\n ? Array.from({ length: columnCount }, (_, row) => Array.from({ length: rowCount }, (_, column) => 1 - scores[column][row]))\n : scores.map((row) => row.map((score) => 1 - score));\n const rows = costs.length;\n const columns = costs[0].length;\n const u = new Array(rows + 1).fill(0);\n const v = new Array(columns + 1).fill(0);\n const p = new Array(columns + 1).fill(0);\n const way = new Array(columns + 1).fill(0);\n for (let row = 1; row <= rows; row += 1) {\n p[0] = row;\n let column0 = 0;\n const minValue = new Array(columns + 1).fill(Number.POSITIVE_INFINITY);\n const used = new Array(columns + 1).fill(false);\n do {\n used[column0] = true;\n const row0 = p[column0];\n let delta = Number.POSITIVE_INFINITY;\n let column1 = 0;\n for (let column = 1; column <= columns; column += 1) {\n if (used[column])\n continue;\n const current = costs[row0 - 1][column - 1] - u[row0] - v[column];\n if (current < minValue[column]) {\n minValue[column] = current;\n way[column] = column0;\n }\n if (minValue[column] < delta) {\n delta = minValue[column];\n column1 = column;\n }\n }\n for (let column = 0; column <= columns; column += 1) {\n if (used[column]) {\n u[p[column]] += delta;\n v[column] -= delta;\n }\n else {\n minValue[column] -= delta;\n }\n }\n column0 = column1;\n } while (p[column0] !== 0);\n do {\n const column1 = way[column0];\n p[column0] = p[column1];\n column0 = column1;\n } while (column0 !== 0);\n }\n const assignments = [];\n for (let column = 1; column <= columns; column += 1) {\n if (p[column] === 0)\n continue;\n const row = p[column] - 1;\n assignments.push(transposed ? [column - 1, row] : [row, column - 1]);\n }\n return assignments;\n }\n function rectToXyxy(rect) {\n return [\n rect.x - rect.width / 2,\n rect.y - rect.height / 2,\n rect.x + rect.width / 2,\n rect.y + rect.height / 2,\n ];\n }\n function stateToRect(state) {\n // The Python XYXY estimator intentionally leaves corner velocities\n // unconstrained. Normalize only at the browser Rect boundary so a crossing\n // prediction cannot violate the positive-width/height storage contract.\n const x1 = Math.min(state[0][0], state[2][0]);\n const y1 = Math.min(state[1][0], state[3][0]);\n const x2 = Math.max(state[0][0], state[2][0]);\n const y2 = Math.max(state[1][0], state[3][0]);\n return {\n height: Math.max(Number.EPSILON, y2 - y1),\n width: Math.max(Number.EPSILON, x2 - x1),\n x: (x1 + x2) / 2,\n y: (y1 + y2) / 2,\n };\n }\n function createProcessNoise(frameStep, frameRate) {\n if (Math.abs(frameStep - 1) <= 0.004 * frameRate) {\n return scale(identity(8), 0.01);\n }\n const result = Array.from({ length: 8 }, () => new Array(8).fill(0));\n const dt2 = frameStep * frameStep;\n const dt3 = dt2 * frameStep;\n const dt4 = dt2 * dt2;\n for (let index = 0; index < 4; index += 1) {\n const velocityIndex = index + 4;\n result[index][index] = (0.01 * dt4) / 4;\n result[index][velocityIndex] = (0.01 * dt3) / 2;\n result[velocityIndex][index] = (0.01 * dt3) / 2;\n result[velocityIndex][velocityIndex] = 0.01 * dt2;\n }\n return result;\n }\n function intersectionOverUnion(left, right) {\n const leftX = left.x - left.width / 2;\n const leftY = left.y - left.height / 2;\n const rightX = right.x - right.width / 2;\n const rightY = right.y - right.height / 2;\n const intersectionWidth = Math.max(0, Math.min(leftX + left.width, rightX + right.width) -\n Math.max(leftX, rightX));\n const intersectionHeight = Math.max(0, Math.min(leftY + left.height, rightY + right.height) -\n Math.max(leftY, rightY));\n const intersection = intersectionWidth * intersectionHeight;\n const union = left.width * left.height + right.width * right.height - intersection;\n return union <= 0 ? 0 : intersection / union;\n }\n function resolveFrameStep(current, previous) {\n if (current === undefined || previous === undefined)\n return 1;\n return Math.max(1, current - previous);\n }\n function normalizePositiveInteger$1(value, label) {\n if (!Number.isInteger(value) || value < 1) {\n throw new Error(`${label} must be a positive integer.`);\n }\n return value;\n }\n function normalizeNonNegativeInteger$1(value, label) {\n if (!Number.isInteger(value) || value < 0) {\n throw new Error(`${label} must be a non-negative integer.`);\n }\n return value;\n }\n function normalizeUnitInterval$1(value, label) {\n if (!Number.isFinite(value) || value < 0 || value > 1) {\n throw new Error(`${label} must be between 0 and 1.`);\n }\n }\n function identity(size) {\n return Array.from({ length: size }, (_, row) => Array.from({ length: size }, (_, column) => (row === column ? 1 : 0)));\n }\n function scale(matrix, factor) {\n return matrix.map((row) => row.map((value) => value * factor));\n }\n function transpose(matrix) {\n return matrix[0].map((_, column) => matrix.map((row) => row[column]));\n }\n function add(left, right) {\n return left.map((row, rowIndex) => row.map((value, columnIndex) => value + right[rowIndex][columnIndex]));\n }\n function subtract(left, right) {\n return left.map((row, rowIndex) => row.map((value, columnIndex) => value - right[rowIndex][columnIndex]));\n }\n function multiply(left, right) {\n return left.map((row) => right[0].map((_, column) => row.reduce((sum, value, index) => sum + value * right[index][column], 0)));\n }\n function inverse(matrix) {\n const size = matrix.length;\n const augmented = matrix.map((row, index) => [\n ...row,\n ...identity(size)[index],\n ]);\n for (let column = 0; column < size; column += 1) {\n let pivot = column;\n for (let row = column + 1; row < size; row += 1) {\n if (Math.abs(augmented[row][column]) >\n Math.abs(augmented[pivot][column])) {\n pivot = row;\n }\n }\n if (Math.abs(augmented[pivot][column]) < 1e-12) {\n throw new Error(\"SORT Kalman covariance is singular.\");\n }\n [augmented[column], augmented[pivot]] = [\n augmented[pivot],\n augmented[column],\n ];\n const divisor = augmented[column][column];\n augmented[column] = augmented[column].map((value) => value / divisor);\n for (let row = 0; row < size; row += 1) {\n if (row === column)\n continue;\n const factor = augmented[row][column];\n augmented[row] = augmented[row].map((value, index) => value - factor * augmented[column][index]);\n }\n }\n return augmented.map((row) => row.slice(size));\n }\n\n /**\n * Creates one stateful ByteTrack tracker for a single ordered media sequence.\n *\n * Defaults and two-stage association mirror roboflow/trackers ByteTrack at\n * source commit 60b21c8a48676784085fbee455559f16b75a7c9a.\n * Motion predictions remain internal; only observed detections receive IDs.\n */\n function createByteTrackTracker$1(options = {}) {\n const lostTrackBuffer = normalizeNonNegativeInteger$1(options.lostTrackBuffer ?? 30, \"lostTrackBuffer\");\n const frameRate = options.frameRate ?? 30;\n const trackActivationThreshold = options.trackActivationThreshold ?? 0.7;\n const minimumConsecutiveFrames = normalizePositiveInteger$1(options.minimumConsecutiveFrames ?? 2, \"minimumConsecutiveFrames\");\n const minimumIouThreshold = options.minimumIouThreshold ?? 0.1;\n const highConfidenceDetectionThreshold = options.highConfidenceDetectionThreshold ?? 0.6;\n if (!Number.isFinite(frameRate) || frameRate <= 0) {\n throw new Error(\"frameRate must be a finite positive value.\");\n }\n normalizeUnitInterval$1(trackActivationThreshold, \"trackActivationThreshold\");\n normalizeUnitInterval$1(minimumIouThreshold, \"minimumIouThreshold\");\n normalizeUnitInterval$1(highConfidenceDetectionThreshold, \"highConfidenceDetectionThreshold\");\n const scaledLostTrackBuffer = (frameRate / 30) * lostTrackBuffer;\n if (!Number.isFinite(scaledLostTrackBuffer)) {\n throw new Error(\"Scaled lostTrackBuffer overflows: frameRate / 30 * lostTrackBuffer must be finite.\");\n }\n const maximumFramesWithoutUpdate = lostTrackBuffer === 0 ? 0 : Math.max(1, Math.ceil(scaledLostTrackBuffer));\n let tracks = [];\n let nextTrackerId = 0;\n let previousFrameIndex;\n return {\n reset() {\n tracks = [];\n nextTrackerId = 0;\n previousFrameIndex = undefined;\n },\n update(detections, frameIndex) {\n const frameStep = resolveFrameStep(frameIndex, previousFrameIndex);\n previousFrameIndex = frameIndex ?? previousFrameIndex;\n tracks.forEach((track) => track.predict(frameStep, frameRate));\n const highDetectionIndexes = [];\n const lowDetectionIndexes = [];\n detections.forEach((detection, index) => {\n if ((detection.confidence ?? 1) >= highConfidenceDetectionThreshold) {\n highDetectionIndexes.push(index);\n }\n else {\n lowDetectionIndexes.push(index);\n }\n });\n const assignments = new Map();\n const firstStage = associate(tracks, detections, highDetectionIndexes, minimumIouThreshold);\n for (const match of firstStage.matches) {\n updateMatchedTrack(tracks[match.trackIndex], detections[match.detectionIndex], assignments);\n }\n const remainingTracks = firstStage.unmatchedTrackIndexes.map((index) => tracks[index]);\n const secondStage = associate(remainingTracks, detections, lowDetectionIndexes, minimumIouThreshold);\n for (const match of secondStage.matches) {\n updateMatchedTrack(remainingTracks[match.trackIndex], detections[match.detectionIndex], assignments);\n }\n // Only unmatched high-confidence observations can start a track.\n for (const detectionIndex of firstStage.unmatchedDetectionIndexes) {\n const detection = detections[detectionIndex];\n if ((detection.confidence ?? 1) >= trackActivationThreshold) {\n tracks.push(new KalmanBoxTrack(detection, true));\n }\n }\n // Confirmation is sticky once an ID has been allocated. An unmatched\n // unconfirmed track is discarded immediately, as in Python ByteTrack.\n tracks = tracks.filter((track) => track.timeSinceUpdate <= maximumFramesWithoutUpdate &&\n (track.trackerId !== undefined ||\n track.successfulUpdates >= minimumConsecutiveFrames ||\n track.timeSinceUpdate === 0));\n return {\n activeTrackCount: tracks.length,\n assignments: detections.flatMap((detection) => {\n const trackerId = assignments.get(detection.detectionIndex);\n return trackerId === undefined\n ? []\n : [{ detectionIndex: detection.detectionIndex, trackerId }];\n }),\n confirmedTrackCount: tracks.filter((track) => track.trackerId !== undefined).length,\n };\n },\n };\n function updateMatchedTrack(track, detection, assignments) {\n track.update(detection);\n if (track.trackerId === undefined &&\n track.successfulUpdates >= minimumConsecutiveFrames) {\n track.trackerId = nextTrackerId;\n nextTrackerId += 1;\n }\n if (track.trackerId !== undefined) {\n assignments.set(detection.detectionIndex, track.trackerId);\n }\n }\n }\n function associate(tracks, detections, detectionIndexes, minimumIouThreshold) {\n if (tracks.length === 0 || detectionIndexes.length === 0) {\n return {\n matches: [],\n unmatchedDetectionIndexes: [...detectionIndexes],\n unmatchedTrackIndexes: tracks.map((_, index) => index),\n };\n }\n const scores = tracks.map((track) => {\n const predictedRect = track.getStateRect();\n return detectionIndexes.map((detectionIndex) => intersectionOverUnion(predictedRect, detections[detectionIndex].rect));\n });\n const matches = [];\n const matchedTracks = new Set();\n const matchedDetections = new Set();\n for (const [trackIndex, localDetectionIndex] of maximizeAssignment(scores)) {\n if (scores[trackIndex][localDetectionIndex] < minimumIouThreshold) {\n continue;\n }\n const detectionIndex = detectionIndexes[localDetectionIndex];\n matches.push({ detectionIndex, trackIndex });\n matchedTracks.add(trackIndex);\n matchedDetections.add(detectionIndex);\n }\n matches.sort((left, right) => left.trackIndex - right.trackIndex);\n return {\n matches,\n unmatchedDetectionIndexes: detectionIndexes.filter((index) => !matchedDetections.has(index)),\n unmatchedTrackIndexes: tracks.flatMap((_, index) => matchedTracks.has(index) ? [] : [index]),\n };\n }\n\n function associateTrackingScores(scores, trackCount, detectionCount, minimumScore, acceptanceScores = scores) {\n const matches = [];\n const matchedTracks = new Set();\n const matchedDetections = new Set();\n for (const [trackIndex, detectionIndex] of maximizeAssignment(scores)) {\n if (acceptanceScores[trackIndex][detectionIndex] < minimumScore)\n continue;\n matches.push({ detectionIndex, trackIndex });\n matchedTracks.add(trackIndex);\n matchedDetections.add(detectionIndex);\n }\n matches.sort((left, right) => left.trackIndex - right.trackIndex);\n return {\n matches,\n unmatchedDetectionIndexes: Array.from({ length: detectionCount }, (_, index) => index).filter((index) => !matchedDetections.has(index)),\n unmatchedTrackIndexes: Array.from({ length: trackCount }, (_, index) => index).filter((index) => !matchedTracks.has(index)),\n };\n }\n function pairwiseIou(tracks, detections, bufferRatio = 0) {\n return tracks.map((track) => detections.map((detection) => bufferedIntersectionOverUnion(track, detection, bufferRatio)));\n }\n function bufferedIntersectionOverUnion(left, right, bufferRatio) {\n const leftWidth = left.width * (1 + 2 * bufferRatio);\n const leftHeight = left.height * (1 + 2 * bufferRatio);\n const rightWidth = right.width * (1 + 2 * bufferRatio);\n const rightHeight = right.height * (1 + 2 * bufferRatio);\n const leftX = left.x - leftWidth / 2;\n const leftY = left.y - leftHeight / 2;\n const rightX = right.x - rightWidth / 2;\n const rightY = right.y - rightHeight / 2;\n const intersectionWidth = Math.max(0, Math.min(leftX + leftWidth, rightX + rightWidth) - Math.max(leftX, rightX));\n const intersectionHeight = Math.max(0, Math.min(leftY + leftHeight, rightY + rightHeight) -\n Math.max(leftY, rightY));\n const intersection = intersectionWidth * intersectionHeight;\n const union = leftWidth * leftHeight + rightWidth * rightHeight - intersection;\n return union <= 0 ? 0 : intersection / union;\n }\n\n /**\n * Kalman state estimator shared by the browser C-BIoU and OC-SORT ports.\n * Its layouts, covariance update, and gap-scaled constant-velocity model\n * mirror roboflow/trackers at 60b21c8.\n */\n class TrackingKalmanEstimator {\n representation;\n dimension;\n measurementDimension = 4;\n covariance;\n measurementNoise = identity(4);\n processNoise;\n state;\n baselineProcessNoise;\n positionIndexes;\n velocityIndexes;\n constructor(initialRect, representation) {\n this.representation = representation;\n const measurement = rectToMeasurement(initialRect, representation);\n this.dimension = representation === \"xcycsr\" ? 7 : 8;\n this.positionIndexes =\n representation === \"xcycsr\" ? [0, 1, 2] : [0, 1, 2, 3];\n this.velocityIndexes =\n representation === \"xcycsr\" ? [4, 5, 6] : [4, 5, 6, 7];\n this.state = Array.from({ length: this.dimension }, (_, index) => [\n measurement[index] ?? 0,\n ]);\n this.covariance = identity(this.dimension);\n this.processNoise = identity(this.dimension);\n this.baselineProcessNoise = identity(this.dimension);\n }\n predict(frameStep, frameRate) {\n if (this.representation === \"xcycsr\" &&\n this.state[2][0] + frameStep * this.state[6][0] <= 0) {\n this.state[6][0] = 0;\n }\n const transition = identity(this.dimension);\n this.positionIndexes.forEach((positionIndex, index) => {\n transition[positionIndex][this.velocityIndexes[index]] = frameStep;\n });\n this.processNoise = this.createProcessNoise(frameStep, frameRate);\n this.state = multiply(transition, this.state);\n this.covariance = add(multiply(multiply(transition, this.covariance), transpose(transition)), this.processNoise);\n }\n update(rect) {\n this.updateMeasurement(rectToMeasurement(rect, this.representation));\n }\n updateMeasurement(measurement) {\n const observation = Array.from({ length: 4 }, (_, row) => Array.from({ length: this.dimension }, (_, column) => row === column ? 1 : 0));\n const measurementColumn = measurement.map((value) => [value]);\n const innovation = subtract(measurementColumn, multiply(observation, this.state));\n const covarianceObservationTranspose = multiply(this.covariance, transpose(observation));\n const innovationCovariance = add(multiply(observation, covarianceObservationTranspose), this.measurementNoise);\n const gain = multiply(covarianceObservationTranspose, inverse(innovationCovariance));\n this.state = add(this.state, multiply(gain, innovation));\n const identityMinusGainObservation = subtract(identity(this.dimension), multiply(gain, observation));\n this.covariance = add(multiply(multiply(identityMinusGainObservation, this.covariance), transpose(identityMinusGainObservation)), multiply(multiply(gain, this.measurementNoise), transpose(gain)));\n }\n getRect() {\n return measurementToRect(this.state.slice(0, 4).map((row) => row[0]), this.representation);\n }\n setCovariances(options) {\n if (options.covariance)\n this.covariance = clone(options.covariance);\n if (options.measurementNoise) {\n this.measurementNoise = clone(options.measurementNoise);\n }\n if (options.processNoise) {\n this.processNoise = clone(options.processNoise);\n this.baselineProcessNoise = clone(options.processNoise);\n }\n }\n snapshot() {\n return {\n covariance: clone(this.covariance),\n measurementNoise: clone(this.measurementNoise),\n processNoise: clone(this.processNoise),\n state: clone(this.state),\n };\n }\n restore(snapshot) {\n this.covariance = clone(snapshot.covariance);\n this.measurementNoise = clone(snapshot.measurementNoise);\n this.processNoise = clone(snapshot.processNoise);\n this.state = clone(snapshot.state);\n }\n createProcessNoise(frameStep, frameRate) {\n if (Math.abs(frameStep - 1) <= 0.004 * frameRate) {\n return clone(this.baselineProcessNoise);\n }\n const result = Array.from({ length: this.dimension }, () => new Array(this.dimension).fill(0));\n const dt2 = frameStep * frameStep;\n const dt3 = dt2 * frameStep;\n const dt4 = dt2 * dt2;\n const kinematicIndexes = new Set([\n ...this.positionIndexes,\n ...this.velocityIndexes,\n ]);\n this.positionIndexes.forEach((positionIndex, index) => {\n const velocityIndex = this.velocityIndexes[index];\n const accelerationVariance = this.baselineProcessNoise[velocityIndex][velocityIndex];\n result[positionIndex][positionIndex] = (accelerationVariance * dt4) / 4;\n result[positionIndex][velocityIndex] = (accelerationVariance * dt3) / 2;\n result[velocityIndex][positionIndex] = (accelerationVariance * dt3) / 2;\n result[velocityIndex][velocityIndex] = accelerationVariance * dt2;\n });\n for (let index = 0; index < this.dimension; index += 1) {\n if (!kinematicIndexes.has(index)) {\n result[index][index] = this.baselineProcessNoise[index][index];\n }\n }\n return result;\n }\n }\n function rectToMeasurement(rect, representation) {\n if (representation === \"xcycwh\") {\n return [rect.x, rect.y, rect.width, rect.height];\n }\n return [\n rect.x,\n rect.y,\n rect.width * rect.height,\n rect.width / (rect.height + 1e-6),\n ];\n }\n function measurementToRect(measurement, representation) {\n const [x, y, third, fourth] = measurement;\n if (representation === \"xcycwh\") {\n return {\n height: Math.max(1e-3, fourth),\n width: Math.max(1e-3, third),\n x,\n y,\n };\n }\n const width = Math.sqrt(third * fourth);\n const height = width === 0 ? 0 : third / width;\n return {\n height: Math.max(Number.EPSILON, height),\n width: Math.max(Number.EPSILON, width),\n x,\n y,\n };\n }\n function diagonal(values) {\n return values.map((value, row) => values.map((_, column) => (row === column ? value : 0)));\n }\n function scaleMatrix(matrix, factor) {\n return scale(matrix, factor);\n }\n function clone(matrix) {\n return matrix.map((row) => [...row]);\n }\n\n const MINIMUM_DETECTION_CONFIDENCE = 0.1;\n class CBIoUTrack {\n trackerId;\n successfulUpdates = 1;\n timeSinceUpdate = 0;\n estimator;\n constructor(initial) {\n this.estimator = new TrackingKalmanEstimator(initial.rect, \"xcycwh\");\n this.setInitialNoise(initial.rect.width, initial.rect.height);\n }\n predict(frameStep, frameRate) {\n const current = this.estimator.getRect();\n this.estimator.setCovariances({\n processNoise: this.buildProcessNoise(Math.max(current.width, 1e-3), Math.max(current.height, 1e-3)),\n });\n this.estimator.predict(frameStep, frameRate);\n this.clampState();\n this.timeSinceUpdate += 1;\n }\n update(detection) {\n const current = this.estimator.getRect();\n this.estimator.setCovariances({\n measurementNoise: this.buildMeasurementNoise(Math.max(current.width, 1e-3), Math.max(current.height, 1e-3)),\n });\n this.estimator.update(detection.rect);\n this.clampState();\n this.timeSinceUpdate = 0;\n this.successfulUpdates += 1;\n }\n getRect() {\n return this.estimator.getRect();\n }\n setInitialNoise(width, height) {\n const sigmaPosition = 0.05;\n const sigmaVelocity = 0.00625;\n this.estimator.setCovariances({\n covariance: diagonal([\n (2 * sigmaPosition * width) ** 2,\n (2 * sigmaPosition * height) ** 2,\n (2 * sigmaPosition * width) ** 2,\n (2 * sigmaPosition * height) ** 2,\n (10 * sigmaVelocity * width) ** 2,\n (10 * sigmaVelocity * height) ** 2,\n (10 * sigmaVelocity * width) ** 2,\n (10 * sigmaVelocity * height) ** 2,\n ]),\n measurementNoise: this.buildMeasurementNoise(width, height),\n processNoise: this.buildProcessNoise(width, height),\n });\n }\n buildProcessNoise(width, height) {\n const sigmaPosition = 0.05;\n const sigmaVelocity = 0.00625;\n return diagonal([\n (sigmaPosition * width) ** 2,\n (sigmaPosition * height) ** 2,\n (sigmaPosition * width) ** 2,\n (sigmaPosition * height) ** 2,\n (sigmaVelocity * width) ** 2,\n (sigmaVelocity * height) ** 2,\n (sigmaVelocity * width) ** 2,\n (sigmaVelocity * height) ** 2,\n ]);\n }\n buildMeasurementNoise(width, height) {\n const sigmaMeasurement = 0.05;\n return diagonal([\n (sigmaMeasurement * width) ** 2,\n (sigmaMeasurement * height) ** 2,\n (sigmaMeasurement * width) ** 2,\n (sigmaMeasurement * height) ** 2,\n ]);\n }\n clampState() {\n this.estimator.state[2][0] = Math.max(this.estimator.state[2][0], 1e-3);\n this.estimator.state[3][0] = Math.max(this.estimator.state[3][0], 1e-3);\n }\n }\n /** Creates the detection-only C-BIoU implementation from roboflow/trackers. */\n function createCBIoUTracker$1(options = {}) {\n const lostTrackBuffer = normalizeNonNegativeInteger$1(options.lostTrackBuffer ?? 30, \"lostTrackBuffer\");\n const frameRate = options.frameRate ?? 30;\n const trackActivationThreshold = options.trackActivationThreshold ?? 0.7;\n const minimumConsecutiveFrames = normalizePositiveInteger$1(options.minimumConsecutiveFrames ?? 2, \"minimumConsecutiveFrames\");\n const minimumIouThresholdFirstAssociation = options.minimumIouThresholdFirstAssociation ?? 0.2;\n const minimumIouThresholdSecondAssociation = options.minimumIouThresholdSecondAssociation ?? 0.5;\n const minimumIouThresholdUnconfirmedAssociation = options.minimumIouThresholdUnconfirmedAssociation ?? 0.3;\n const highConfidenceDetectionThreshold = options.highConfidenceDetectionThreshold ?? 0.6;\n const instantFirstFrameActivation = options.instantFirstFrameActivation ?? true;\n const bufferRatioFirst = options.bufferRatioFirst ?? 0.3;\n const bufferRatioSecond = options.bufferRatioSecond ?? 0.5;\n if (!Number.isFinite(frameRate) || frameRate <= 0) {\n throw new Error(\"frameRate must be a finite positive value.\");\n }\n normalizeUnitInterval$1(trackActivationThreshold, \"trackActivationThreshold\");\n normalizeUnitInterval$1(minimumIouThresholdFirstAssociation, \"minimumIouThresholdFirstAssociation\");\n normalizeUnitInterval$1(minimumIouThresholdSecondAssociation, \"minimumIouThresholdSecondAssociation\");\n normalizeUnitInterval$1(minimumIouThresholdUnconfirmedAssociation, \"minimumIouThresholdUnconfirmedAssociation\");\n normalizeUnitInterval$1(highConfidenceDetectionThreshold, \"highConfidenceDetectionThreshold\");\n if (!Number.isFinite(bufferRatioFirst) || bufferRatioFirst < 0) {\n throw new Error(\"bufferRatioFirst must be a finite non-negative value.\");\n }\n if (!Number.isFinite(bufferRatioSecond) || bufferRatioSecond < 0) {\n throw new Error(\"bufferRatioSecond must be a finite non-negative value.\");\n }\n const scaledLostTrackBuffer = (frameRate / 30) * lostTrackBuffer;\n if (!Number.isFinite(scaledLostTrackBuffer)) {\n throw new Error(\"Scaled lostTrackBuffer overflows: frameRate / 30 * lostTrackBuffer must be finite.\");\n }\n const maximumFramesWithoutUpdate = lostTrackBuffer === 0 ? 0 : Math.max(1, Math.ceil(scaledLostTrackBuffer));\n let tracks = [];\n let nextTrackerId = 0;\n let frameId = 0;\n let previousFrameIndex;\n return {\n reset() {\n tracks = [];\n nextTrackerId = 0;\n frameId = 0;\n previousFrameIndex = undefined;\n },\n update(detections, frameIndex) {\n const frameStep = resolveFrameStep(frameIndex, previousFrameIndex);\n previousFrameIndex = frameIndex ?? previousFrameIndex;\n frameId += 1;\n tracks.forEach((track) => track.predict(frameStep, frameRate));\n const highDetectionIndexes = [];\n const lowDetectionIndexes = [];\n detections.forEach((detection, index) => {\n const confidence = detection.confidence ?? 1;\n if (confidence >= highConfidenceDetectionThreshold) {\n highDetectionIndexes.push(index);\n }\n else if (confidence > MINIMUM_DETECTION_CONFIDENCE) {\n lowDetectionIndexes.push(index);\n }\n });\n const confirmed = [];\n const unconfirmed = [];\n const lost = [];\n for (const track of tracks) {\n if (track.timeSinceUpdate > 1)\n lost.push(track);\n else if (track.trackerId !== undefined ||\n track.successfulUpdates >= minimumConsecutiveFrames) {\n confirmed.push(track);\n }\n else\n unconfirmed.push(track);\n }\n const assignments = new Map();\n const pool = [...confirmed, ...lost];\n const highDetections = highDetectionIndexes.map((index) => detections[index]);\n const firstScores = pairwiseIou(pool.map((track) => track.getRect()), highDetections.map((detection) => detection.rect), bufferRatioFirst).map((row) => row.map((score, index) => score * (highDetections[index].confidence ?? 1)));\n const firstAssociation = associateTrackingScores(firstScores, pool.length, highDetections.length, minimumIouThresholdFirstAssociation);\n for (const match of firstAssociation.matches) {\n updateMatchedTrack(pool[match.trackIndex], highDetections[match.detectionIndex], assignments);\n }\n const remainingTracked = firstAssociation.unmatchedTrackIndexes\n .map((index) => pool[index])\n .filter((track) => track.timeSinceUpdate === 1);\n const lowDetections = lowDetectionIndexes.map((index) => detections[index]);\n const secondScores = pairwiseIou(remainingTracked.map((track) => track.getRect()), lowDetections.map((detection) => detection.rect), bufferRatioSecond);\n const secondAssociation = associateTrackingScores(secondScores, remainingTracked.length, lowDetections.length, minimumIouThresholdSecondAssociation);\n for (const match of secondAssociation.matches) {\n updateMatchedTrack(remainingTracked[match.trackIndex], lowDetections[match.detectionIndex], assignments);\n }\n let unmatchedHighLocal = [...firstAssociation.unmatchedDetectionIndexes];\n let unmatchedUnconfirmed = unconfirmed.map((_, index) => index);\n if (unconfirmed.length > 0 && unmatchedHighLocal.length > 0) {\n const remainingHigh = unmatchedHighLocal.map((index) => highDetections[index]);\n const unconfirmedScores = pairwiseIou(unconfirmed.map((track) => track.getRect()), remainingHigh.map((detection) => detection.rect), bufferRatioFirst).map((row) => row.map((score, index) => score * (remainingHigh[index].confidence ?? 1)));\n const unconfirmedAssociation = associateTrackingScores(unconfirmedScores, unconfirmed.length, remainingHigh.length, minimumIouThresholdUnconfirmedAssociation);\n unmatchedUnconfirmed = unconfirmedAssociation.unmatchedTrackIndexes;\n for (const match of unconfirmedAssociation.matches) {\n updateMatchedTrack(unconfirmed[match.trackIndex], remainingHigh[match.detectionIndex], assignments);\n }\n unmatchedHighLocal =\n unconfirmedAssociation.unmatchedDetectionIndexes.map((index) => unmatchedHighLocal[index]);\n }\n const unmatchedUnconfirmedTracks = new Set(unmatchedUnconfirmed.map((index) => unconfirmed[index]));\n tracks = tracks.filter((track) => !unmatchedUnconfirmedTracks.has(track));\n for (const localIndex of unmatchedHighLocal) {\n const detection = highDetections[localIndex];\n if ((detection.confidence ?? 1) < trackActivationThreshold)\n continue;\n const track = new CBIoUTrack(detection);\n if (frameId === 1 && instantFirstFrameActivation) {\n track.trackerId = nextTrackerId++;\n assignments.set(detection.detectionIndex, track.trackerId);\n }\n tracks.push(track);\n }\n tracks = tracks.filter((track) => track.timeSinceUpdate <= maximumFramesWithoutUpdate &&\n (track.timeSinceUpdate === 0 ||\n track.trackerId !== undefined ||\n track.successfulUpdates >= minimumConsecutiveFrames));\n return createUpdate(detections, assignments, tracks);\n },\n };\n function updateMatchedTrack(track, detection, assignments) {\n track.update(detection);\n if (track.trackerId === undefined &&\n track.successfulUpdates >= minimumConsecutiveFrames) {\n track.trackerId = nextTrackerId++;\n }\n if (track.trackerId !== undefined) {\n assignments.set(detection.detectionIndex, track.trackerId);\n }\n }\n }\n function createUpdate(detections, assignments, tracks) {\n const resolvedAssignments = detections.flatMap((detection) => {\n const trackerId = assignments.get(detection.detectionIndex);\n return trackerId === undefined\n ? []\n : [{ detectionIndex: detection.detectionIndex, trackerId }];\n });\n return {\n activeTrackCount: tracks.length,\n assignments: resolvedAssignments,\n confirmedTrackCount: tracks.filter((track) => track.trackerId !== undefined)\n .length,\n };\n }\n\n class OCSortTrack {\n deltaT;\n age = 0;\n lastObservation;\n trackerId;\n successfulConsecutiveUpdates = 0;\n timeSinceUpdate = 0;\n velocity;\n frozenState;\n observed = true;\n observations = new Map();\n estimator;\n constructor(initial, deltaT) {\n this.deltaT = deltaT;\n this.lastObservation = initial;\n this.estimator = new TrackingKalmanEstimator(initial.rect, \"xcycsr\");\n this.configureNoise();\n }\n predict(frameStep, frameRate) {\n if (this.observed && this.timeSinceUpdate > 0) {\n this.frozenState = this.estimator.snapshot();\n this.observed = false;\n }\n this.estimator.predict(frameStep, frameRate);\n if (this.timeSinceUpdate > 0) {\n this.successfulConsecutiveUpdates = 0;\n }\n this.timeSinceUpdate += 1;\n this.age += 1;\n }\n update(detection, frameStep, frameRate) {\n const previous = this.getPreviousObservation();\n if (previous) {\n this.velocity = computeVelocity(previous, detection);\n }\n if (!this.observed && this.frozenState) {\n this.unfreeze(detection, frameStep, frameRate);\n }\n this.estimator.update(detection.rect);\n this.observed = true;\n this.timeSinceUpdate = 0;\n this.successfulConsecutiveUpdates += 1;\n this.lastObservation = detection;\n this.observations.set(this.age, detection);\n const cutoff = this.age - this.deltaT;\n for (const age of this.observations.keys()) {\n if (age < cutoff)\n this.observations.delete(age);\n }\n }\n getRect() {\n return this.estimator.getRect();\n }\n getPreviousObservation() {\n if (this.observations.size === 0)\n return undefined;\n for (let index = 0; index < this.deltaT; index += 1) {\n const delta = this.deltaT - index;\n const observation = this.observations.get(this.age - delta);\n if (observation)\n return observation;\n }\n const latestAge = Math.max(...this.observations.keys());\n return this.observations.get(latestAge);\n }\n configureNoise() {\n const measurementNoise = identity(4);\n for (let index = 2; index < 4; index += 1) {\n measurementNoise[index][index] *= 10;\n }\n const covariance = scaleMatrix(identity(7), 10);\n for (let index = 4; index < 7; index += 1) {\n covariance[index][index] *= 1000;\n }\n const processNoise = identity(7);\n processNoise[6][6] *= 0.01;\n for (let index = 4; index < 7; index += 1) {\n processNoise[index][index] *= 0.01;\n }\n this.estimator.setCovariances({\n covariance,\n measurementNoise,\n processNoise,\n });\n }\n unfreeze(detection, frameStep, frameRate) {\n if (!this.frozenState || this.timeSinceUpdate === 0)\n return;\n this.estimator.restore(this.frozenState);\n const timeGap = this.timeSinceUpdate;\n const start = rectToMeasurement(this.lastObservation.rect, \"xcycsr\");\n const end = rectToMeasurement(detection.rect, \"xcycsr\");\n const startWidth = Math.sqrt(start[2] * start[3]);\n const startHeight = start[3] === 0 ? 0 : Math.sqrt(start[2] / start[3]);\n const endWidth = Math.sqrt(end[2] * end[3]);\n const endHeight = end[3] === 0 ? 0 : Math.sqrt(end[2] / end[3]);\n for (let index = 0; index < timeGap; index += 1) {\n const progress = (index + 1) / timeGap;\n const x = start[0] + progress * (end[0] - start[0]);\n const y = start[1] + progress * (end[1] - start[1]);\n const width = startWidth + progress * (endWidth - startWidth);\n const height = startHeight + progress * (endHeight - startHeight);\n this.estimator.updateMeasurement([x, y, width * height, width / height]);\n if (index < timeGap - 1) {\n this.estimator.predict(frameStep, frameRate);\n }\n }\n this.frozenState = undefined;\n }\n }\n /** Creates the observation-centric SORT implementation from roboflow/trackers. */\n function createOCSortTracker$1(options = {}) {\n const lostTrackBuffer = normalizeNonNegativeInteger$1(options.lostTrackBuffer ?? 30, \"lostTrackBuffer\");\n const frameRate = options.frameRate ?? 30;\n const minimumConsecutiveFrames = normalizePositiveInteger$1(options.minimumConsecutiveFrames ?? 3, \"minimumConsecutiveFrames\");\n const minimumIouThreshold = options.minimumIouThreshold ?? 0.3;\n const directionConsistencyWeight = options.directionConsistencyWeight ?? 0.2;\n const highConfidenceDetectionThreshold = options.highConfidenceDetectionThreshold ?? 0.6;\n const deltaT = normalizePositiveInteger$1(options.deltaT ?? 3, \"deltaT\");\n if (!Number.isFinite(frameRate) || frameRate <= 0) {\n throw new Error(\"frameRate must be a finite positive value.\");\n }\n normalizeUnitInterval$1(minimumIouThreshold, \"minimumIouThreshold\");\n normalizeUnitInterval$1(directionConsistencyWeight, \"directionConsistencyWeight\");\n normalizeUnitInterval$1(highConfidenceDetectionThreshold, \"highConfidenceDetectionThreshold\");\n const scaledLostTrackBuffer = (frameRate / 30) * lostTrackBuffer;\n if (!Number.isFinite(scaledLostTrackBuffer)) {\n throw new Error(\"Scaled lostTrackBuffer overflows: frameRate / 30 * lostTrackBuffer must be finite.\");\n }\n const maximumFramesWithoutUpdate = lostTrackBuffer === 0 ? 0 : Math.max(1, Math.ceil(scaledLostTrackBuffer));\n let tracks = [];\n let frameCount = 0;\n let nextTrackerId = 0;\n let previousFrameIndex;\n return {\n reset() {\n tracks = [];\n frameCount = 0;\n nextTrackerId = 0;\n previousFrameIndex = undefined;\n },\n update(detections, frameIndex) {\n const frameStep = resolveFrameStep(frameIndex, previousFrameIndex);\n previousFrameIndex = frameIndex ?? previousFrameIndex;\n // Match roboflow/trackers: an empty stream before any track exists is\n // not part of OC-SORT's early-sequence activation window.\n if (tracks.length === 0 && detections.length === 0) {\n return {\n activeTrackCount: 0,\n assignments: [],\n confirmedTrackCount: 0,\n };\n }\n tracks.forEach((track) => track.predict(frameStep, frameRate));\n const highDetectionIndexes = detections.flatMap((detection, index) => detection.confidence === undefined ||\n detection.confidence >= highConfidenceDetectionThreshold\n ? [index]\n : []);\n const highDetections = highDetectionIndexes.map((index) => detections[index]);\n const iouScores = pairwiseIou(tracks.map((track) => track.getRect()), highDetections.map((detection) => detection.rect));\n const combinedScores = iouScores.map((row, trackIndex) => row.map((iou, detectionIndex) => {\n if (directionConsistencyWeight === 0)\n return iou;\n return (iou +\n directionConsistencyWeight *\n directionConsistency(tracks[trackIndex], highDetections[detectionIndex]) *\n (highDetections[detectionIndex].confidence ?? 1));\n }));\n const primary = associateTrackingScores(combinedScores, tracks.length, highDetections.length, minimumIouThreshold, iouScores);\n const assignments = new Map();\n for (const match of primary.matches) {\n updateMatchedTrack(tracks[match.trackIndex], highDetections[match.detectionIndex], assignments, frameStep);\n }\n let remainingHigh = [...primary.unmatchedDetectionIndexes];\n if (primary.unmatchedTrackIndexes.length > 0 &&\n remainingHigh.length > 0) {\n const unmatchedTracks = primary.unmatchedTrackIndexes.map((index) => tracks[index]);\n const unmatchedDetections = remainingHigh.map((index) => highDetections[index]);\n const recoveryScores = pairwiseIou(unmatchedTracks.map((track) => track.lastObservation.rect), unmatchedDetections.map((detection) => detection.rect));\n const recovery = associateTrackingScores(recoveryScores, unmatchedTracks.length, unmatchedDetections.length, minimumIouThreshold);\n for (const match of recovery.matches) {\n updateMatchedTrack(unmatchedTracks[match.trackIndex], unmatchedDetections[match.detectionIndex], assignments, frameStep);\n }\n remainingHigh = recovery.unmatchedDetectionIndexes.map((index) => remainingHigh[index]);\n }\n for (const localIndex of remainingHigh) {\n tracks.push(new OCSortTrack(highDetections[localIndex], deltaT));\n }\n tracks = tracks.filter((track) => track.timeSinceUpdate <= maximumFramesWithoutUpdate);\n frameCount += 1;\n const resolvedAssignments = detections.flatMap((detection) => {\n const trackerId = assignments.get(detection.detectionIndex);\n return trackerId === undefined\n ? []\n : [{ detectionIndex: detection.detectionIndex, trackerId }];\n });\n return {\n activeTrackCount: tracks.length,\n assignments: resolvedAssignments,\n confirmedTrackCount: tracks.filter((track) => track.trackerId !== undefined).length,\n };\n },\n };\n function updateMatchedTrack(track, detection, assignments, frameStep) {\n track.update(detection, frameStep, frameRate);\n const earlySequence = frameCount <= minimumConsecutiveFrames;\n const shouldEmit = (earlySequence && track.timeSinceUpdate === 0) ||\n track.successfulConsecutiveUpdates >= minimumConsecutiveFrames;\n if (shouldEmit) {\n if (track.trackerId === undefined) {\n track.trackerId = nextTrackerId++;\n }\n assignments.set(detection.detectionIndex, track.trackerId);\n }\n }\n }\n function computeVelocity(previous, current) {\n const deltaX = current.rect.x - previous.rect.x;\n const deltaY = current.rect.y - previous.rect.y;\n const norm = Math.sqrt(deltaX * deltaX + deltaY * deltaY) + 1e-6;\n return { x: deltaX / norm, y: deltaY / norm };\n }\n function directionConsistency(track, detection) {\n if (!track.velocity)\n return 0;\n const reference = track.getPreviousObservation() ?? track.lastObservation;\n const deltaX = detection.rect.x - reference.rect.x;\n const deltaY = detection.rect.y - reference.rect.y;\n const norm = Math.sqrt(deltaX * deltaX + deltaY * deltaY) + 1e-6;\n const cosine = Math.max(-1, Math.min(1, track.velocity.x * (deltaX / norm) + track.velocity.y * (deltaY / norm)));\n const angle = Math.acos(cosine);\n return (Math.PI / 2 - Math.abs(angle)) / Math.PI;\n }\n\n /** Public core facade for the internal tracker engine workspace. */\n function createSortTracker(options = {}) {\n return createSortTracker$1(options);\n }\n\n /** Public core facade for the internal tracker engine workspace. */\n function createByteTrackTracker(options = {}) {\n return createByteTrackTracker$1(options);\n }\n\n /** Public core facade for the internal tracker engine workspace. */\n function createCBIoUTracker(options = {}) {\n return createCBIoUTracker$1(options);\n }\n\n /** Public core facade for the internal tracker engine workspace. */\n function createOCSortTracker(options = {}) {\n return createOCSortTracker$1(options);\n }\n\n /** Geometry projected into a tracker. The original detection payload is retained. */\n var TrackingGeometry;\n (function (TrackingGeometry) {\n TrackingGeometry[\"Box\"] = \"box\";\n TrackingGeometry[\"Mask\"] = \"mask\";\n TrackingGeometry[\"Keypoints\"] = \"keypoints\";\n })(TrackingGeometry || (TrackingGeometry = {}));\n\n var DetectionMaskPayloadFormat;\n (function (DetectionMaskPayloadFormat) {\n DetectionMaskPayloadFormat[\"RawCocoRle\"] = \"rawCocoRle\";\n DetectionMaskPayloadFormat[\"DeflatedBase64\"] = \"deflatedBase64\";\n })(DetectionMaskPayloadFormat || (DetectionMaskPayloadFormat = {}));\n\n var DetectionPickTarget;\n (function (DetectionPickTarget) {\n DetectionPickTarget[\"Box\"] = \"box\";\n DetectionPickTarget[\"Edge\"] = \"edge\";\n DetectionPickTarget[\"Keypoint\"] = \"keypoint\";\n DetectionPickTarget[\"Label\"] = \"label\";\n DetectionPickTarget[\"Mask\"] = \"mask\";\n DetectionPickTarget[\"Polygon\"] = \"polygon\";\n DetectionPickTarget[\"Polyline\"] = \"polyline\";\n })(DetectionPickTarget || (DetectionPickTarget = {}));\n var MediaInteractionMode;\n (function (MediaInteractionMode) {\n MediaInteractionMode[\"Always\"] = \"always\";\n MediaInteractionMode[\"Disabled\"] = \"disabled\";\n MediaInteractionMode[\"PausedOnly\"] = \"pausedOnly\";\n })(MediaInteractionMode || (MediaInteractionMode = {}));\n\n var AnnotationGeometryKind;\n (function (AnnotationGeometryKind) {\n AnnotationGeometryKind[\"Box\"] = \"box\";\n AnnotationGeometryKind[\"Polygon\"] = \"polygon\";\n AnnotationGeometryKind[\"Polyline\"] = \"polyline\";\n AnnotationGeometryKind[\"Keypoints\"] = \"keypoints\";\n AnnotationGeometryKind[\"Mask\"] = \"mask\";\n })(AnnotationGeometryKind || (AnnotationGeometryKind = {}));\n var AnnotationGestureStateKind;\n (function (AnnotationGestureStateKind) {\n AnnotationGestureStateKind[\"Idle\"] = \"idle\";\n AnnotationGestureStateKind[\"Creating\"] = \"creating\";\n AnnotationGestureStateKind[\"Moving\"] = \"moving\";\n AnnotationGestureStateKind[\"Resizing\"] = \"resizing\";\n AnnotationGestureStateKind[\"DragSelecting\"] = \"dragSelecting\";\n })(AnnotationGestureStateKind || (AnnotationGestureStateKind = {}));\n var AnnotationHandleKind;\n (function (AnnotationHandleKind) {\n AnnotationHandleKind[\"Resize\"] = \"resize\";\n AnnotationHandleKind[\"Vertex\"] = \"vertex\";\n AnnotationHandleKind[\"AddVertex\"] = \"addVertex\";\n AnnotationHandleKind[\"Keypoint\"] = \"keypoint\";\n })(AnnotationHandleKind || (AnnotationHandleKind = {}));\n\n /** Alignment for strokes applied to closed rendered geometry. */\n var StrokeAlignment;\n (function (StrokeAlignment) {\n StrokeAlignment[\"Inside\"] = \"inside\";\n StrokeAlignment[\"Center\"] = \"center\";\n StrokeAlignment[\"Outside\"] = \"outside\";\n })(StrokeAlignment || (StrokeAlignment = {}));\n\n var BoxShape;\n (function (BoxShape) {\n BoxShape[\"Rect\"] = \"rect\";\n BoxShape[\"RoundedRect\"] = \"roundedRect\";\n })(BoxShape || (BoxShape = {}));\n\n var FocusTargetMode;\n (function (FocusTargetMode) {\n FocusTargetMode[\"Hovered\"] = \"hovered\";\n FocusTargetMode[\"Selected\"] = \"selected\";\n FocusTargetMode[\"HoveredAndSelected\"] = \"hoveredAndSelected\";\n FocusTargetMode[\"Ambient\"] = \"ambient\";\n })(FocusTargetMode || (FocusTargetMode = {}));\n\n var DetectionInteractionState;\n (function (DetectionInteractionState) {\n DetectionInteractionState[\"Hovered\"] = \"hovered\";\n DetectionInteractionState[\"Selected\"] = \"selected\";\n })(DetectionInteractionState || (DetectionInteractionState = {}));\n\n var LabelPlacement;\n (function (LabelPlacement) {\n LabelPlacement[\"Top\"] = \"top\";\n LabelPlacement[\"Bottom\"] = \"bottom\";\n LabelPlacement[\"InsideTop\"] = \"insideTop\";\n LabelPlacement[\"InsideBottom\"] = \"insideBottom\";\n LabelPlacement[\"Center\"] = \"center\";\n })(LabelPlacement || (LabelPlacement = {}));\n var LabelVisibilityMode;\n (function (LabelVisibilityMode) {\n LabelVisibilityMode[\"Always\"] = \"always\";\n LabelVisibilityMode[\"HoveredOnly\"] = \"hoveredOnly\";\n })(LabelVisibilityMode || (LabelVisibilityMode = {}));\n\n var MaskRenderMode;\n (function (MaskRenderMode) {\n MaskRenderMode[\"FillAndStroke\"] = \"fillAndStroke\";\n MaskRenderMode[\"FillOnly\"] = \"fillOnly\";\n MaskRenderMode[\"StrokeOnly\"] = \"strokeOnly\";\n })(MaskRenderMode || (MaskRenderMode = {}));\n\n /**\n * Discriminator for renderer-neutral shape draw instructions.\n */\n var ShapeInstructionKind;\n (function (ShapeInstructionKind) {\n ShapeInstructionKind[\"Ellipse\"] = \"ellipse\";\n ShapeInstructionKind[\"Marker\"] = \"marker\";\n ShapeInstructionKind[\"Path\"] = \"path\";\n })(ShapeInstructionKind || (ShapeInstructionKind = {}));\n var MarkerShape;\n (function (MarkerShape) {\n MarkerShape[\"Circle\"] = \"circle\";\n MarkerShape[\"Cross\"] = \"cross\";\n MarkerShape[\"Square\"] = \"square\";\n MarkerShape[\"Triangle\"] = \"triangle\";\n })(MarkerShape || (MarkerShape = {}));\n /**\n * Coordinate space for marker sizing. Media sizes scale with the viewport;\n * screen sizes stay constant on screen, matching stroke-width semantics.\n */\n var MarkerSizeSpace;\n (function (MarkerSizeSpace) {\n MarkerSizeSpace[\"Media\"] = \"media\";\n MarkerSizeSpace[\"Screen\"] = \"screen\";\n })(MarkerSizeSpace || (MarkerSizeSpace = {}));\n\n var KeypointMarkerShape;\n (function (KeypointMarkerShape) {\n KeypointMarkerShape[\"Circle\"] = \"circle\";\n KeypointMarkerShape[\"Cross\"] = \"cross\";\n })(KeypointMarkerShape || (KeypointMarkerShape = {}));\n\n var MediaRendererFit;\n (function (MediaRendererFit) {\n /**\n * Preserve media aspect ratio and fit the full frame inside the canvas.\n */\n MediaRendererFit[\"Contain\"] = \"contain\";\n /**\n * Preserve media aspect ratio and fill the canvas, cropping if necessary.\n */\n MediaRendererFit[\"Cover\"] = \"cover\";\n })(MediaRendererFit || (MediaRendererFit = {}));\n /**\n * Playback lifecycle state reported by a platform renderer.\n */\n var MediaRendererPlaybackState;\n (function (MediaRendererPlaybackState) {\n MediaRendererPlaybackState[\"Loading\"] = \"loading\";\n MediaRendererPlaybackState[\"Ready\"] = \"ready\";\n MediaRendererPlaybackState[\"Playing\"] = \"playing\";\n MediaRendererPlaybackState[\"Buffering\"] = \"buffering\";\n MediaRendererPlaybackState[\"Paused\"] = \"paused\";\n MediaRendererPlaybackState[\"Error\"] = \"error\";\n MediaRendererPlaybackState[\"Destroyed\"] = \"destroyed\";\n })(MediaRendererPlaybackState || (MediaRendererPlaybackState = {}));\n /**\n * Lower-level media source readiness.\n */\n var MediaSourceStatus;\n (function (MediaSourceStatus) {\n MediaSourceStatus[\"Loading\"] = \"loading\";\n MediaSourceStatus[\"Ready\"] = \"ready\";\n MediaSourceStatus[\"Error\"] = \"error\";\n MediaSourceStatus[\"Destroyed\"] = \"destroyed\";\n })(MediaSourceStatus || (MediaSourceStatus = {}));\n\n /**\n * Media-session operating mode.\n *\n * Core owns the names because file-like and stream-like lifecycle choices are\n * platform-neutral. Platform packages decide how these modes tune media,\n * storage, and renderer defaults.\n */\n var MediaSessionMode;\n (function (MediaSessionMode) {\n /**\n * Finite media. Defaults usually favor seek/replay and persistent detection\n * storage.\n */\n MediaSessionMode[\"File\"] = \"file\";\n /**\n * Live or append-only media. Defaults usually favor rolling windows and\n * bounded retention.\n */\n MediaSessionMode[\"Stream\"] = \"stream\";\n })(MediaSessionMode || (MediaSessionMode = {}));\n /**\n * Aggregate lifecycle state for a media session.\n */\n var MediaSessionStatus;\n (function (MediaSessionStatus) {\n MediaSessionStatus[\"Buffering\"] = \"buffering\";\n MediaSessionStatus[\"Destroyed\"] = \"destroyed\";\n MediaSessionStatus[\"Error\"] = \"error\";\n MediaSessionStatus[\"Loading\"] = \"loading\";\n MediaSessionStatus[\"Paused\"] = \"paused\";\n MediaSessionStatus[\"Playing\"] = \"playing\";\n MediaSessionStatus[\"Processing\"] = \"processing\";\n MediaSessionStatus[\"Ready\"] = \"ready\";\n })(MediaSessionStatus || (MediaSessionStatus = {}));\n /**\n * Subsystem currently affecting session readiness or presentation.\n */\n var MediaSessionActivityKind;\n (function (MediaSessionActivityKind) {\n MediaSessionActivityKind[\"DetectionsBuffering\"] = \"detectionsBuffering\";\n MediaSessionActivityKind[\"DetectionsLoading\"] = \"detectionsLoading\";\n MediaSessionActivityKind[\"Error\"] = \"error\";\n MediaSessionActivityKind[\"MediaNormalizing\"] = \"mediaNormalizing\";\n MediaSessionActivityKind[\"MediaOpening\"] = \"mediaOpening\";\n MediaSessionActivityKind[\"PlaybackBuffering\"] = \"playbackBuffering\";\n MediaSessionActivityKind[\"RenderPreparing\"] = \"renderPreparing\";\n })(MediaSessionActivityKind || (MediaSessionActivityKind = {}));\n /**\n * State of one session activity.\n */\n var MediaSessionActivityStatus;\n (function (MediaSessionActivityStatus) {\n MediaSessionActivityStatus[\"Error\"] = \"error\";\n MediaSessionActivityStatus[\"Running\"] = \"running\";\n MediaSessionActivityStatus[\"Waiting\"] = \"waiting\";\n })(MediaSessionActivityStatus || (MediaSessionActivityStatus = {}));\n\n let tracker;\n self.addEventListener(\"message\", (event) => {\n const request = event.data;\n try {\n let response;\n if (request.type === \"configure\") {\n switch (request.processor.algorithm) {\n case \"bytetrack\":\n tracker = createByteTrackTracker(request.processor.options);\n break;\n case \"cbiou\":\n tracker = createCBIoUTracker(request.processor.options);\n break;\n case \"ocsort\":\n tracker = createOCSortTracker(request.processor.options);\n break;\n default:\n tracker = createSortTracker(request.processor.options);\n }\n response = { requestId: request.requestId, type: \"success\" };\n }\n else if (request.type === \"reset\") {\n tracker?.reset();\n response = { requestId: request.requestId, type: \"success\" };\n }\n else {\n if (!tracker) {\n throw new Error(\"Tracking worker is not configured.\");\n }\n const startedAt = performance.now();\n const update = tracker.update(request.detections, request.frameIndex);\n response = {\n ...update,\n durationMs: performance.now() - startedAt,\n requestId: request.requestId,\n type: \"success\",\n };\n }\n self.postMessage(response);\n }\n catch (error) {\n self.postMessage({\n message: error instanceof Error ? error.message : \"Tracking worker failed.\",\n requestId: request.requestId,\n type: \"error\",\n });\n }\n });\n\n})();";
|
|
462
|
+
|
|
463
|
+
const DEFAULT_WORKER_NAME$1 = "supervision-detection-post-processing";
|
|
464
|
+
let defaultWorkerUrl$1;
|
|
465
|
+
function createDefaultDetectionPostProcessingWorkerFactory() {
|
|
466
|
+
return {
|
|
467
|
+
createWorker() {
|
|
468
|
+
defaultWorkerUrl$1 ??= URL.createObjectURL(new Blob([EMBEDDED_TRACKING_WORKER_SOURCE], {
|
|
469
|
+
type: "text/javascript",
|
|
470
|
+
}));
|
|
471
|
+
return new Worker(defaultWorkerUrl$1, { name: DEFAULT_WORKER_NAME$1 });
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function isTrackingWorkerResponse(value) {
|
|
477
|
+
if (!value || typeof value !== "object")
|
|
478
|
+
return false;
|
|
479
|
+
const candidate = value;
|
|
480
|
+
return (typeof candidate.requestId === "number" &&
|
|
481
|
+
(candidate.type === "success" ||
|
|
482
|
+
(candidate.type === "error" && typeof candidate.message === "string")));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
var DetectionPostProcessingMode;
|
|
486
|
+
(function (DetectionPostProcessingMode) {
|
|
487
|
+
DetectionPostProcessingMode["Auto"] = "auto";
|
|
488
|
+
DetectionPostProcessingMode["Worker"] = "worker";
|
|
489
|
+
DetectionPostProcessingMode["MainThread"] = "mainThread";
|
|
490
|
+
})(DetectionPostProcessingMode || (DetectionPostProcessingMode = {}));
|
|
491
|
+
|
|
492
|
+
function createWorkerRpcClient(options) {
|
|
493
|
+
let failureMessage = null;
|
|
494
|
+
let isDestroyed = false;
|
|
495
|
+
let nextRequestId = 1;
|
|
496
|
+
const pendingRequests = new Map();
|
|
497
|
+
options.worker.addEventListener("message", (event) => {
|
|
498
|
+
handleWorkerMessage(event.data);
|
|
499
|
+
});
|
|
500
|
+
options.worker.addEventListener("error", (event) => {
|
|
501
|
+
rejectPendingRequests(event.message || options.defaultErrorMessage);
|
|
502
|
+
});
|
|
503
|
+
options.worker.addEventListener("messageerror", () => {
|
|
504
|
+
rejectPendingRequests(`${options.defaultErrorMessage}: message error.`);
|
|
505
|
+
});
|
|
506
|
+
return {
|
|
507
|
+
destroy() {
|
|
508
|
+
if (isDestroyed) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
isDestroyed = true;
|
|
512
|
+
rejectPendingRequests("Worker RPC client has been destroyed.", {
|
|
513
|
+
markFailure: false,
|
|
514
|
+
});
|
|
515
|
+
options.worker.terminate();
|
|
516
|
+
},
|
|
517
|
+
getFailureMessage() {
|
|
518
|
+
return failureMessage;
|
|
519
|
+
},
|
|
520
|
+
request(message) {
|
|
521
|
+
if (isDestroyed) {
|
|
522
|
+
return Promise.reject(new Error("Worker RPC client has been destroyed."));
|
|
523
|
+
}
|
|
524
|
+
if (failureMessage) {
|
|
525
|
+
return Promise.reject(new Error(failureMessage));
|
|
526
|
+
}
|
|
527
|
+
const request = {
|
|
528
|
+
...message,
|
|
529
|
+
requestId: nextRequestId,
|
|
530
|
+
};
|
|
531
|
+
nextRequestId += 1;
|
|
532
|
+
return new Promise((resolve, reject) => {
|
|
533
|
+
pendingRequests.set(request.requestId, { reject, resolve });
|
|
534
|
+
try {
|
|
535
|
+
options.worker.postMessage(request);
|
|
536
|
+
}
|
|
537
|
+
catch (error) {
|
|
538
|
+
pendingRequests.delete(request.requestId);
|
|
539
|
+
failureMessage = getErrorMessage$2(error, options.defaultErrorMessage);
|
|
540
|
+
reject(new Error(failureMessage));
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
},
|
|
544
|
+
};
|
|
545
|
+
function handleWorkerMessage(message) {
|
|
546
|
+
if (!options.isResponse(message)) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const pendingRequest = pendingRequests.get(message.requestId);
|
|
550
|
+
if (!pendingRequest) {
|
|
551
|
+
options.onOrphanedResponse?.(message);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
pendingRequests.delete(message.requestId);
|
|
555
|
+
pendingRequest.resolve(message);
|
|
556
|
+
}
|
|
557
|
+
function rejectPendingRequests(message, options = { markFailure: true }) {
|
|
558
|
+
if (options.markFailure) {
|
|
559
|
+
failureMessage = message;
|
|
560
|
+
}
|
|
561
|
+
for (const pendingRequest of pendingRequests.values()) {
|
|
562
|
+
pendingRequest.reject(new Error(message));
|
|
563
|
+
}
|
|
564
|
+
pendingRequests.clear();
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function getErrorMessage$2(error, fallback) {
|
|
568
|
+
return error instanceof Error ? error.message : fallback;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const DEFAULT_MAX_PENDING_FRAMES = 90;
|
|
572
|
+
/**
|
|
573
|
+
* Creates an ordered, bounded stream processor for detections arriving out of
|
|
574
|
+
* order. Stateful processors run serially; the renderer remains independent.
|
|
575
|
+
*/
|
|
576
|
+
function createDetectionPostProcessingPipeline(options) {
|
|
577
|
+
const processor = validateProcessors(options.processors);
|
|
578
|
+
const maxPendingFrames = normalizePositiveInteger(options.maxPendingFrames ?? DEFAULT_MAX_PENDING_FRAMES, "maxPendingFrames");
|
|
579
|
+
let nextFrameIndex = normalizeFrameIndex(options.startFrameIndex ?? 0, "startFrameIndex");
|
|
580
|
+
let execution;
|
|
581
|
+
let destroyed = false;
|
|
582
|
+
let failedError;
|
|
583
|
+
let inFlightFrameIndex;
|
|
584
|
+
let processing = Promise.resolve({
|
|
585
|
+
acceptedFrameCount: 0,
|
|
586
|
+
processedFrameCount: 0,
|
|
587
|
+
processedFrames: [],
|
|
588
|
+
});
|
|
589
|
+
const pendingFrames = new Map();
|
|
590
|
+
let diagnostics = {
|
|
591
|
+
activeTrackCount: 0,
|
|
592
|
+
confirmedTrackCount: 0,
|
|
593
|
+
errorMessage: null,
|
|
594
|
+
executionMode: null,
|
|
595
|
+
lastFrameDurationMs: null,
|
|
596
|
+
nextFrameIndex,
|
|
597
|
+
pendingFrameCount: 0,
|
|
598
|
+
processedFrameCount: 0,
|
|
599
|
+
trackedDetectionCount: 0,
|
|
600
|
+
};
|
|
601
|
+
publishDiagnostics();
|
|
602
|
+
return {
|
|
603
|
+
async appendFrames(frames) {
|
|
604
|
+
assertActive();
|
|
605
|
+
assertHealthy();
|
|
606
|
+
const acceptedFrameCount = enqueueFrames(frames);
|
|
607
|
+
processing = processing.then(() => drainPendingFrames(acceptedFrameCount));
|
|
608
|
+
return processing;
|
|
609
|
+
},
|
|
610
|
+
destroy() {
|
|
611
|
+
if (destroyed)
|
|
612
|
+
return;
|
|
613
|
+
destroyed = true;
|
|
614
|
+
pendingFrames.clear();
|
|
615
|
+
execution?.destroy();
|
|
616
|
+
execution = undefined;
|
|
617
|
+
},
|
|
618
|
+
getDiagnostics() {
|
|
619
|
+
return { ...diagnostics };
|
|
620
|
+
},
|
|
621
|
+
async reset(resetOptions = {}) {
|
|
622
|
+
assertActive();
|
|
623
|
+
await processing.catch(() => undefined);
|
|
624
|
+
pendingFrames.clear();
|
|
625
|
+
inFlightFrameIndex = undefined;
|
|
626
|
+
nextFrameIndex = normalizeFrameIndex(resetOptions.startFrameIndex ?? options.startFrameIndex ?? 0, "startFrameIndex");
|
|
627
|
+
await execution?.reset();
|
|
628
|
+
failedError = undefined;
|
|
629
|
+
processing = Promise.resolve({
|
|
630
|
+
acceptedFrameCount: 0,
|
|
631
|
+
processedFrameCount: 0,
|
|
632
|
+
processedFrames: [],
|
|
633
|
+
});
|
|
634
|
+
diagnostics = {
|
|
635
|
+
...diagnostics,
|
|
636
|
+
activeTrackCount: 0,
|
|
637
|
+
confirmedTrackCount: 0,
|
|
638
|
+
errorMessage: null,
|
|
639
|
+
lastFrameDurationMs: null,
|
|
640
|
+
nextFrameIndex,
|
|
641
|
+
pendingFrameCount: 0,
|
|
642
|
+
processedFrameCount: 0,
|
|
643
|
+
trackedDetectionCount: 0,
|
|
644
|
+
};
|
|
645
|
+
publishDiagnostics();
|
|
646
|
+
},
|
|
647
|
+
};
|
|
648
|
+
function enqueueFrames(frames) {
|
|
649
|
+
let acceptedFrameCount = 0;
|
|
650
|
+
const additions = new Map();
|
|
651
|
+
for (const frame of frames) {
|
|
652
|
+
const frameIndex = normalizeFrameIndex(frame.frameIndex, "frameIndex");
|
|
653
|
+
if (frameIndex < nextFrameIndex) {
|
|
654
|
+
throw new Error(`Frame ${frameIndex} is behind the processed frontier ${nextFrameIndex}. Reset before revising causal history.`);
|
|
655
|
+
}
|
|
656
|
+
if (frameIndex === inFlightFrameIndex) {
|
|
657
|
+
throw new Error(`Frame ${frameIndex} is already being processed. Reset before revising causal history.`);
|
|
658
|
+
}
|
|
659
|
+
if (!pendingFrames.has(frameIndex) && !additions.has(frameIndex)) {
|
|
660
|
+
acceptedFrameCount += 1;
|
|
661
|
+
}
|
|
662
|
+
additions.set(frameIndex, frame);
|
|
663
|
+
}
|
|
664
|
+
const nextPendingCount = pendingFrames.size + acceptedFrameCount;
|
|
665
|
+
if (nextPendingCount > maxPendingFrames) {
|
|
666
|
+
throw new Error(`Out-of-order buffer limit exceeded (${nextPendingCount}/${maxPendingFrames}). Apply upstream backpressure or increase maxPendingFrames.`);
|
|
667
|
+
}
|
|
668
|
+
for (const [frameIndex, frame] of additions) {
|
|
669
|
+
pendingFrames.set(frameIndex, frame);
|
|
670
|
+
}
|
|
671
|
+
diagnostics = {
|
|
672
|
+
...diagnostics,
|
|
673
|
+
nextFrameIndex,
|
|
674
|
+
pendingFrameCount: pendingFrames.size,
|
|
675
|
+
};
|
|
676
|
+
publishDiagnostics();
|
|
677
|
+
return acceptedFrameCount;
|
|
678
|
+
}
|
|
679
|
+
async function drainPendingFrames(acceptedFrameCount) {
|
|
680
|
+
const processedFrames = [];
|
|
681
|
+
try {
|
|
682
|
+
execution ??= await createTrackingExecution(processor, options);
|
|
683
|
+
diagnostics = { ...diagnostics, executionMode: execution.mode };
|
|
684
|
+
while (!destroyed) {
|
|
685
|
+
const frame = pendingFrames.get(nextFrameIndex);
|
|
686
|
+
if (!frame)
|
|
687
|
+
break;
|
|
688
|
+
pendingFrames.delete(nextFrameIndex);
|
|
689
|
+
inFlightFrameIndex = nextFrameIndex;
|
|
690
|
+
const projections = projectDetectionFrameForTracking(frame, processor.geometry);
|
|
691
|
+
const result = await execution.process(projections, nextFrameIndex);
|
|
692
|
+
const processedFrame = attachTrackerIds(frame, result.assignments, options.mutateInput ?? true);
|
|
693
|
+
if (destroyed)
|
|
694
|
+
break;
|
|
695
|
+
await options.output?.appendFrames([processedFrame]);
|
|
696
|
+
options.onFrame?.(processedFrame);
|
|
697
|
+
processedFrames.push(processedFrame);
|
|
698
|
+
nextFrameIndex += 1;
|
|
699
|
+
inFlightFrameIndex = undefined;
|
|
700
|
+
diagnostics = {
|
|
701
|
+
...diagnostics,
|
|
702
|
+
activeTrackCount: result.activeTrackCount,
|
|
703
|
+
confirmedTrackCount: result.confirmedTrackCount,
|
|
704
|
+
errorMessage: null,
|
|
705
|
+
lastFrameDurationMs: result.durationMs,
|
|
706
|
+
nextFrameIndex,
|
|
707
|
+
pendingFrameCount: pendingFrames.size,
|
|
708
|
+
processedFrameCount: diagnostics.processedFrameCount + 1,
|
|
709
|
+
trackedDetectionCount: diagnostics.trackedDetectionCount + result.assignments.length,
|
|
710
|
+
};
|
|
711
|
+
publishDiagnostics();
|
|
712
|
+
}
|
|
713
|
+
return {
|
|
714
|
+
acceptedFrameCount,
|
|
715
|
+
processedFrameCount: processedFrames.length,
|
|
716
|
+
processedFrames,
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
failedError =
|
|
721
|
+
error instanceof Error ? error : new Error("Post-processing failed.");
|
|
722
|
+
diagnostics = {
|
|
723
|
+
...diagnostics,
|
|
724
|
+
errorMessage: failedError.message,
|
|
725
|
+
pendingFrameCount: pendingFrames.size,
|
|
726
|
+
};
|
|
727
|
+
publishDiagnostics();
|
|
728
|
+
throw failedError;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function publishDiagnostics() {
|
|
732
|
+
options.onDiagnostics?.({ ...diagnostics });
|
|
733
|
+
}
|
|
734
|
+
function assertActive() {
|
|
735
|
+
if (destroyed) {
|
|
736
|
+
throw new Error("Detection post-processing pipeline has been destroyed.");
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function assertHealthy() {
|
|
740
|
+
if (failedError) {
|
|
741
|
+
throw new Error(`Detection post-processing pipeline failed and must be reset: ${failedError.message}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
async function createTrackingExecution(processor, options) {
|
|
746
|
+
const mode = options.mode ?? DetectionPostProcessingMode.Auto;
|
|
747
|
+
if (mode === DetectionPostProcessingMode.MainThread) {
|
|
748
|
+
return createMainThreadExecution(processor);
|
|
749
|
+
}
|
|
750
|
+
let workerExecution;
|
|
751
|
+
try {
|
|
752
|
+
const workerFactory = options.workerFactory ??
|
|
753
|
+
createDefaultDetectionPostProcessingWorkerFactory();
|
|
754
|
+
workerExecution = createWorkerExecution(workerFactory.createWorker(), processor);
|
|
755
|
+
await workerExecution.reset();
|
|
756
|
+
return workerExecution;
|
|
757
|
+
}
|
|
758
|
+
catch (error) {
|
|
759
|
+
workerExecution?.destroy();
|
|
760
|
+
if (mode === DetectionPostProcessingMode.Worker)
|
|
761
|
+
throw error;
|
|
762
|
+
return createMainThreadExecution(processor);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function createMainThreadExecution(processor) {
|
|
766
|
+
let tracker;
|
|
767
|
+
switch (processor.algorithm) {
|
|
768
|
+
case "bytetrack":
|
|
769
|
+
tracker = createByteTrackTracker(processor.options);
|
|
770
|
+
break;
|
|
771
|
+
case "cbiou":
|
|
772
|
+
tracker = createCBIoUTracker(processor.options);
|
|
773
|
+
break;
|
|
774
|
+
case "ocsort":
|
|
775
|
+
tracker = createOCSortTracker(processor.options);
|
|
776
|
+
break;
|
|
777
|
+
default:
|
|
778
|
+
tracker = createSortTracker(processor.options);
|
|
779
|
+
}
|
|
780
|
+
return {
|
|
781
|
+
destroy() { },
|
|
782
|
+
mode: "mainThread",
|
|
783
|
+
async process(detections, frameIndex) {
|
|
784
|
+
const startedAt = performance.now();
|
|
785
|
+
const update = tracker.update(detections, frameIndex);
|
|
786
|
+
return { ...update, durationMs: performance.now() - startedAt };
|
|
787
|
+
},
|
|
788
|
+
async reset() {
|
|
789
|
+
tracker.reset();
|
|
790
|
+
},
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
function createWorkerExecution(worker, processor) {
|
|
794
|
+
const client = createWorkerRpcClient({
|
|
795
|
+
defaultErrorMessage: "Detection post-processing worker failed.",
|
|
796
|
+
isResponse: isTrackingWorkerResponse,
|
|
797
|
+
worker,
|
|
798
|
+
});
|
|
799
|
+
let configured = false;
|
|
800
|
+
const request = async (message) => {
|
|
801
|
+
const response = await client.request(message);
|
|
802
|
+
if (response.type === "error")
|
|
803
|
+
throw new Error(response.message);
|
|
804
|
+
return response;
|
|
805
|
+
};
|
|
806
|
+
return {
|
|
807
|
+
destroy() {
|
|
808
|
+
client.destroy();
|
|
809
|
+
},
|
|
810
|
+
mode: "worker",
|
|
811
|
+
async process(detections, frameIndex) {
|
|
812
|
+
if (!configured) {
|
|
813
|
+
await request({ processor, type: "configure" });
|
|
814
|
+
configured = true;
|
|
815
|
+
}
|
|
816
|
+
const response = await request({
|
|
817
|
+
detections,
|
|
818
|
+
frameIndex,
|
|
819
|
+
type: "process",
|
|
820
|
+
});
|
|
821
|
+
return {
|
|
822
|
+
activeTrackCount: response.activeTrackCount ?? 0,
|
|
823
|
+
assignments: response.assignments ?? [],
|
|
824
|
+
confirmedTrackCount: response.confirmedTrackCount ?? 0,
|
|
825
|
+
durationMs: response.durationMs ?? 0,
|
|
826
|
+
};
|
|
827
|
+
},
|
|
828
|
+
async reset() {
|
|
829
|
+
if (!configured) {
|
|
830
|
+
await request({ processor, type: "configure" });
|
|
831
|
+
configured = true;
|
|
832
|
+
}
|
|
833
|
+
else {
|
|
834
|
+
await request({ type: "reset" });
|
|
835
|
+
}
|
|
836
|
+
},
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function attachTrackerIds(frame, assignments, mutateInput) {
|
|
840
|
+
const trackerIds = new Map(assignments.map(({ detectionIndex, trackerId }) => [
|
|
841
|
+
detectionIndex,
|
|
842
|
+
trackerId,
|
|
843
|
+
]));
|
|
844
|
+
for (const [detectionIndex, detection] of frame.detections.entries()) {
|
|
845
|
+
if (mutateInput) {
|
|
846
|
+
const trackerId = trackerIds.get(detectionIndex);
|
|
847
|
+
if (trackerId === undefined) {
|
|
848
|
+
Reflect.deleteProperty(detection, "trackerId");
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
detection.trackerId = trackerId;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (mutateInput) {
|
|
856
|
+
return frame;
|
|
857
|
+
}
|
|
858
|
+
return {
|
|
859
|
+
...frame,
|
|
860
|
+
detections: frame.detections.map((detection, detectionIndex) => {
|
|
861
|
+
const semanticDetection = { ...detection };
|
|
862
|
+
Reflect.deleteProperty(semanticDetection, "trackerId");
|
|
863
|
+
const trackerId = trackerIds.get(detectionIndex);
|
|
864
|
+
return trackerId === undefined
|
|
865
|
+
? semanticDetection
|
|
866
|
+
: { ...semanticDetection, trackerId };
|
|
867
|
+
}),
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
function validateProcessors(processors) {
|
|
871
|
+
if (processors.length !== 1 || processors[0]?.kind !== "tracking") {
|
|
872
|
+
throw new Error("The first post-processing release requires exactly one tracking processor.");
|
|
873
|
+
}
|
|
874
|
+
return processors[0];
|
|
875
|
+
}
|
|
876
|
+
function normalizePositiveInteger(value, label) {
|
|
877
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
878
|
+
throw new Error(`${label} must be a positive integer.`);
|
|
879
|
+
}
|
|
880
|
+
return value;
|
|
881
|
+
}
|
|
882
|
+
function normalizeFrameIndex(value, label) {
|
|
883
|
+
if (!Number.isInteger(value) || value === undefined || value < 0) {
|
|
884
|
+
throw new Error(`${label} must be a non-negative integer.`);
|
|
885
|
+
}
|
|
886
|
+
return value;
|
|
887
|
+
}
|
|
888
|
+
|
|
461
889
|
async function collectInputMetadata(input, source) {
|
|
462
890
|
const [format, detectedMimeType, duration, primaryVideoTrack] = await Promise.all([
|
|
463
891
|
input.getFormat(),
|
|
@@ -2239,11 +2667,15 @@ async function createMediaRendererCore(options, providers) {
|
|
|
2239
2667
|
annotationOverlayStyle: options.annotationOverlayStyle,
|
|
2240
2668
|
backgroundColor: options.backgroundColor,
|
|
2241
2669
|
boxStyle: options.boxStyle,
|
|
2670
|
+
boxCornerStyle: options.boxCornerStyle,
|
|
2671
|
+
ellipseStyle: options.ellipseStyle,
|
|
2242
2672
|
focusStyle: options.focusStyle,
|
|
2243
2673
|
interactionStyle: options.interactionStyle,
|
|
2244
2674
|
keypointStyle: options.keypointStyle,
|
|
2245
2675
|
labelStyle: options.labelStyle,
|
|
2676
|
+
maskHaloStyle: options.maskHaloStyle,
|
|
2246
2677
|
maskStyle: options.maskStyle,
|
|
2678
|
+
markerStyle: options.markerStyle,
|
|
2247
2679
|
polygonStyle: options.polygonStyle,
|
|
2248
2680
|
polylineStyle: options.polylineStyle,
|
|
2249
2681
|
renderers: options.renderers,
|
|
@@ -2538,6 +2970,8 @@ async function createMediaRendererCore(options, providers) {
|
|
|
2538
2970
|
annotationOverlayStyle: currentPresentation.annotationOverlayStyle,
|
|
2539
2971
|
backgroundColor: currentPresentation.backgroundColor,
|
|
2540
2972
|
boxStyle: currentPresentation.boxStyle,
|
|
2973
|
+
boxCornerStyle: currentPresentation.boxCornerStyle,
|
|
2974
|
+
ellipseStyle: currentPresentation.ellipseStyle,
|
|
2541
2975
|
canInteract: () => canInteract(options, runtimeState.isPlaybackActive()),
|
|
2542
2976
|
container: options.container,
|
|
2543
2977
|
detectionTimeline,
|
|
@@ -2550,10 +2984,18 @@ async function createMediaRendererCore(options, providers) {
|
|
|
2550
2984
|
keypointStyle: currentPresentation.keypointStyle,
|
|
2551
2985
|
labelStyle: currentPresentation.labelStyle,
|
|
2552
2986
|
maskBrush: options.createMaskBrush?.(mediaDimensions) ?? options.maskBrush,
|
|
2987
|
+
maskHaloStyle: currentPresentation.maskHaloStyle,
|
|
2553
2988
|
maskStyle: currentPresentation.maskStyle,
|
|
2989
|
+
markerStyle: currentPresentation.markerStyle,
|
|
2554
2990
|
maxDevicePixelRatio: options.maxDevicePixelRatio,
|
|
2991
|
+
onPresentationUpdate(presentedSample) {
|
|
2992
|
+
if (!runtimeState.isDestroyed()) {
|
|
2993
|
+
runtimeState.recordPresentationUpdate(presentedSample);
|
|
2994
|
+
}
|
|
2995
|
+
},
|
|
2555
2996
|
polygonStyle: currentPresentation.polygonStyle,
|
|
2556
2997
|
polylineStyle: currentPresentation.polylineStyle,
|
|
2998
|
+
regionRenderers: resolveRegionRenderers(currentPresentation),
|
|
2557
2999
|
previewOverlay: options.previewOverlay,
|
|
2558
3000
|
renderPreparation: options.renderPreparation
|
|
2559
3001
|
? {
|
|
@@ -2699,6 +3141,10 @@ async function createMediaRendererCore(options, providers) {
|
|
|
2699
3141
|
}
|
|
2700
3142
|
}
|
|
2701
3143
|
}
|
|
3144
|
+
function resolveRegionRenderers(presentation) {
|
|
3145
|
+
return (presentation.renderers?.filter((renderer) => renderer.kind === "region") ??
|
|
3146
|
+
[]);
|
|
3147
|
+
}
|
|
2702
3148
|
async function openRendererMediaSource(options, providers) {
|
|
2703
3149
|
if (options.src !== undefined && options.source !== undefined) {
|
|
2704
3150
|
throw new Error("Provide either src or source, not both.");
|
|
@@ -2792,10 +3238,10 @@ function drawPixiPath(graphics, points, closed, stroke, viewportScale) {
|
|
|
2792
3238
|
if (points.length < 2) {
|
|
2793
3239
|
return;
|
|
2794
3240
|
}
|
|
2795
|
-
|
|
2796
|
-
appendDashedPath(graphics, points, closed, stroke.dash, viewportScale)
|
|
2797
|
-
|
|
2798
|
-
|
|
3241
|
+
const appendedDash = stroke.dash?.length
|
|
3242
|
+
? appendDashedPath(graphics, points, closed, stroke.dash, viewportScale)
|
|
3243
|
+
: false;
|
|
3244
|
+
if (!appendedDash) {
|
|
2799
3245
|
graphics.moveTo(points[0].x, points[0].y);
|
|
2800
3246
|
for (let index = 1; index < points.length; index += 1) {
|
|
2801
3247
|
graphics.lineTo(points[index].x, points[index].y);
|
|
@@ -2809,16 +3255,21 @@ function drawPixiPath(graphics, points, closed, stroke, viewportScale) {
|
|
|
2809
3255
|
function resolvePixiStroke(stroke, viewportScale) {
|
|
2810
3256
|
const pixiStroke = {
|
|
2811
3257
|
alpha: stroke.alpha,
|
|
3258
|
+
...(stroke.cap === undefined ? {} : { cap: stroke.cap }),
|
|
2812
3259
|
color: stroke.color,
|
|
3260
|
+
...(stroke.join === undefined ? {} : { join: stroke.join }),
|
|
3261
|
+
...(stroke.miterLimit === undefined
|
|
3262
|
+
? {}
|
|
3263
|
+
: { miterLimit: stroke.miterLimit }),
|
|
2813
3264
|
width: resolveScreenLength(stroke.width, viewportScale),
|
|
2814
3265
|
};
|
|
2815
3266
|
if (stroke.alignment === undefined)
|
|
2816
3267
|
return pixiStroke;
|
|
2817
3268
|
return {
|
|
2818
3269
|
...pixiStroke,
|
|
2819
|
-
alignment: stroke.alignment ===
|
|
3270
|
+
alignment: stroke.alignment === StrokeAlignment.Inside
|
|
2820
3271
|
? 1
|
|
2821
|
-
: stroke.alignment ===
|
|
3272
|
+
: stroke.alignment === StrokeAlignment.Outside
|
|
2822
3273
|
? 0
|
|
2823
3274
|
: 0.5,
|
|
2824
3275
|
};
|
|
@@ -2828,7 +3279,7 @@ function appendDashedPath(graphics, points, closed, dashPattern, viewportScale)
|
|
|
2828
3279
|
.filter((value) => Number.isFinite(value) && value > 0)
|
|
2829
3280
|
.map((value) => resolveScreenLength(value, viewportScale));
|
|
2830
3281
|
if (pattern.length === 0) {
|
|
2831
|
-
return;
|
|
3282
|
+
return false;
|
|
2832
3283
|
}
|
|
2833
3284
|
if (pattern.length % 2 === 1) {
|
|
2834
3285
|
pattern.push(...pattern);
|
|
@@ -2836,6 +3287,7 @@ function appendDashedPath(graphics, points, closed, dashPattern, viewportScale)
|
|
|
2836
3287
|
let patternIndex = 0;
|
|
2837
3288
|
let patternRemaining = pattern[0];
|
|
2838
3289
|
let drawing = true;
|
|
3290
|
+
let penDown = false;
|
|
2839
3291
|
const segmentCount = closed ? points.length : points.length - 1;
|
|
2840
3292
|
for (let segmentIndex = 0; segmentIndex < segmentCount; segmentIndex += 1) {
|
|
2841
3293
|
const start = points[segmentIndex];
|
|
@@ -2856,8 +3308,14 @@ function appendDashedPath(graphics, points, closed, dashPattern, viewportScale)
|
|
|
2856
3308
|
const x2 = start.x + dx * endRatio;
|
|
2857
3309
|
const y2 = start.y + dy * endRatio;
|
|
2858
3310
|
if (drawing) {
|
|
2859
|
-
|
|
3311
|
+
if (!penDown) {
|
|
3312
|
+
graphics.moveTo(x1, y1);
|
|
3313
|
+
}
|
|
2860
3314
|
graphics.lineTo(x2, y2);
|
|
3315
|
+
penDown = true;
|
|
3316
|
+
}
|
|
3317
|
+
else {
|
|
3318
|
+
penDown = false;
|
|
2861
3319
|
}
|
|
2862
3320
|
offset += step;
|
|
2863
3321
|
patternRemaining -= step;
|
|
@@ -2865,9 +3323,13 @@ function appendDashedPath(graphics, points, closed, dashPattern, viewportScale)
|
|
|
2865
3323
|
patternIndex = (patternIndex + 1) % pattern.length;
|
|
2866
3324
|
patternRemaining = pattern[patternIndex];
|
|
2867
3325
|
drawing = !drawing;
|
|
3326
|
+
if (!drawing) {
|
|
3327
|
+
penDown = false;
|
|
3328
|
+
}
|
|
2868
3329
|
}
|
|
2869
3330
|
}
|
|
2870
3331
|
}
|
|
3332
|
+
return true;
|
|
2871
3333
|
}
|
|
2872
3334
|
|
|
2873
3335
|
function createPixiBoxLayer(options) {
|
|
@@ -3104,13 +3566,19 @@ function createPixiFocusLayer(options) {
|
|
|
3104
3566
|
hide();
|
|
3105
3567
|
return;
|
|
3106
3568
|
}
|
|
3107
|
-
const
|
|
3569
|
+
const resolvedInstruction = focusStyle.resolve({
|
|
3108
3570
|
frame: context.frame,
|
|
3109
3571
|
hoveredPick: context.hoveredPick,
|
|
3110
3572
|
mediaTime: context.mediaTime,
|
|
3111
3573
|
selectedPick: context.selectedPick,
|
|
3112
3574
|
viewportScale: context.viewportScale,
|
|
3113
3575
|
});
|
|
3576
|
+
const instruction = resolvedInstruction
|
|
3577
|
+
? {
|
|
3578
|
+
...resolvedInstruction,
|
|
3579
|
+
targets: resolvedInstruction.targets.filter(({ detection }) => options.isDetectionVisible?.(detection) ?? true),
|
|
3580
|
+
}
|
|
3581
|
+
: undefined;
|
|
3114
3582
|
if (!instruction || instruction.targets.length === 0) {
|
|
3115
3583
|
transitionToHidden();
|
|
3116
3584
|
return;
|
|
@@ -3322,7 +3790,7 @@ function createFocusIdMaskRenderer(options) {
|
|
|
3322
3790
|
autoGenerateMipmaps: false,
|
|
3323
3791
|
dynamic: false,
|
|
3324
3792
|
height: 1,
|
|
3325
|
-
resource: createPlaceholderCanvas$
|
|
3793
|
+
resource: createPlaceholderCanvas$3(),
|
|
3326
3794
|
scaleMode: "nearest",
|
|
3327
3795
|
width: 1,
|
|
3328
3796
|
});
|
|
@@ -3414,7 +3882,7 @@ function createPremultipliedColor(fill) {
|
|
|
3414
3882
|
alpha,
|
|
3415
3883
|
]);
|
|
3416
3884
|
}
|
|
3417
|
-
function createPlaceholderCanvas$
|
|
3885
|
+
function createPlaceholderCanvas$3() {
|
|
3418
3886
|
const canvas = document.createElement("canvas");
|
|
3419
3887
|
canvas.height = 1;
|
|
3420
3888
|
canvas.width = 1;
|
|
@@ -3536,15 +4004,21 @@ function createPixiInteractionLayer(options) {
|
|
|
3536
4004
|
drawFrame(mediaTime) {
|
|
3537
4005
|
currentMediaTime = mediaTime;
|
|
3538
4006
|
activeFrame = options.detectionTimeline.selectFrame(mediaTime);
|
|
3539
|
-
if (
|
|
3540
|
-
pointerPoint = null;
|
|
3541
|
-
marqueeRect = null;
|
|
3542
|
-
setHoveredPick(null);
|
|
3543
|
-
setSelectedPick(null);
|
|
3544
|
-
}
|
|
3545
|
-
else {
|
|
4007
|
+
if (canHandleInteraction()) {
|
|
3546
4008
|
clearStalePicks(activeFrame);
|
|
4009
|
+
return;
|
|
4010
|
+
}
|
|
4011
|
+
pointerPoint = null;
|
|
4012
|
+
marqueeRect = null;
|
|
4013
|
+
setHoveredPick(null);
|
|
4014
|
+
if (isDestroyed || mode === MediaInteractionMode.Disabled) {
|
|
4015
|
+
setSelectedPick(null);
|
|
4016
|
+
return;
|
|
3547
4017
|
}
|
|
4018
|
+
// Paused-only interaction gates new picks during playback, but an
|
|
4019
|
+
// existing selection keeps following its detection so selection-driven
|
|
4020
|
+
// presentation such as focus survives frame advances.
|
|
4021
|
+
followSelectedPicks(activeFrame);
|
|
3548
4022
|
},
|
|
3549
4023
|
setSelectedDetection(selection) {
|
|
3550
4024
|
if (selection === null) {
|
|
@@ -3563,14 +4037,15 @@ function createPixiInteractionLayer(options) {
|
|
|
3563
4037
|
return createState();
|
|
3564
4038
|
},
|
|
3565
4039
|
cycleSelection(direction = 1) {
|
|
3566
|
-
|
|
4040
|
+
const visibleDetections = activeFrame?.detections.flatMap((detection, detectionIndex) => canPickDetection(detection) ? [{ detection, detectionIndex }] : []);
|
|
4041
|
+
if (!activeFrame || !visibleDetections?.length)
|
|
3567
4042
|
return null;
|
|
3568
|
-
const
|
|
3569
|
-
const
|
|
3570
|
-
|
|
3571
|
-
const detection =
|
|
4043
|
+
const currentVisibleIndex = visibleDetections.findIndex(({ detectionIndex }) => detectionIndex === selectedPick?.detectionIndex);
|
|
4044
|
+
const nextVisibleIndex = (currentVisibleIndex + direction + visibleDetections.length) %
|
|
4045
|
+
visibleDetections.length;
|
|
4046
|
+
const { detection, detectionIndex } = visibleDetections[nextVisibleIndex];
|
|
3572
4047
|
const next = createPickFromSelection({
|
|
3573
|
-
detectionIndex
|
|
4048
|
+
detectionIndex,
|
|
3574
4049
|
point: getDetectionCenter(detection),
|
|
3575
4050
|
});
|
|
3576
4051
|
setSelectedPick(next);
|
|
@@ -3699,6 +4174,8 @@ function createPixiInteractionLayer(options) {
|
|
|
3699
4174
|
return;
|
|
3700
4175
|
if (didDragMarquee && marqueeRect && activeFrame) {
|
|
3701
4176
|
selectedPicks = activeFrame.detections.flatMap((detection, detectionIndex) => {
|
|
4177
|
+
if (!canPickDetection(detection))
|
|
4178
|
+
return [];
|
|
3702
4179
|
const rect = getDetectionRect(detection);
|
|
3703
4180
|
if (!rect || !intersects(rect, marqueeRect))
|
|
3704
4181
|
return [];
|
|
@@ -3735,10 +4212,13 @@ function createPixiInteractionLayer(options) {
|
|
|
3735
4212
|
const pickPoint = { x: point.x, y: point.y };
|
|
3736
4213
|
const labelPick = options.pickLabelDetectionAtPoint?.(pickPoint, currentMediaTime);
|
|
3737
4214
|
const activeLabelPick = rebaseDetectionPickToFrame(labelPick ?? null, activeFrame);
|
|
3738
|
-
if (activeLabelPick)
|
|
4215
|
+
if (activeLabelPick && canPickDetection(activeLabelPick.detection)) {
|
|
3739
4216
|
return activeLabelPick;
|
|
4217
|
+
}
|
|
3740
4218
|
const geometryPick = pickDetectionAtPoint(activeFrame, pickPoint, {
|
|
3741
4219
|
...options.interaction,
|
|
4220
|
+
filter: (detection, detectionIndex) => canPickDetection(detection) &&
|
|
4221
|
+
(options.interaction.filter?.(detection, detectionIndex) ?? true),
|
|
3742
4222
|
includeMasks: options.pickMaskDetectionAtPoint === undefined,
|
|
3743
4223
|
maskMediaDimensions: options.getMediaDimensions?.(),
|
|
3744
4224
|
padding: pickPadding,
|
|
@@ -3748,7 +4228,9 @@ function createPixiInteractionLayer(options) {
|
|
|
3748
4228
|
}
|
|
3749
4229
|
const maskPick = options.pickMaskDetectionAtPoint?.(pickPoint, currentMediaTime);
|
|
3750
4230
|
const activeMaskPick = rebaseDetectionPickToFrame(maskPick ?? null, activeFrame);
|
|
3751
|
-
return activeMaskPick
|
|
4231
|
+
return activeMaskPick && canPickDetection(activeMaskPick.detection)
|
|
4232
|
+
? activeMaskPick
|
|
4233
|
+
: geometryPick;
|
|
3752
4234
|
}
|
|
3753
4235
|
function createPickFromSelection(selection) {
|
|
3754
4236
|
const frame = selection.mediaTime === undefined
|
|
@@ -3760,7 +4242,10 @@ function createPixiInteractionLayer(options) {
|
|
|
3760
4242
|
const detection = detectionIndex === undefined || detectionIndex < 0
|
|
3761
4243
|
? undefined
|
|
3762
4244
|
: frame?.detections[detectionIndex];
|
|
3763
|
-
if (!frame ||
|
|
4245
|
+
if (!frame ||
|
|
4246
|
+
detectionIndex === undefined ||
|
|
4247
|
+
!detection ||
|
|
4248
|
+
!canPickDetection(detection)) {
|
|
3764
4249
|
return null;
|
|
3765
4250
|
}
|
|
3766
4251
|
return {
|
|
@@ -3774,19 +4259,44 @@ function createPixiInteractionLayer(options) {
|
|
|
3774
4259
|
};
|
|
3775
4260
|
}
|
|
3776
4261
|
function clearStalePicks(frame) {
|
|
3777
|
-
|
|
3778
|
-
const
|
|
4262
|
+
// Hover stays bound to the pointer, so it keeps a per-frame lifetime.
|
|
4263
|
+
const nextHoveredPick = filterPick(rebaseDetectionPickToFrame(hoveredPick, frame));
|
|
3779
4264
|
if (hoveredPick && !nextHoveredPick) {
|
|
3780
4265
|
setHoveredPick(null);
|
|
3781
4266
|
}
|
|
3782
4267
|
else if (nextHoveredPick && nextHoveredPick !== hoveredPick) {
|
|
3783
|
-
|
|
4268
|
+
// Hover remains pointer-owned. Refresh its frame snapshot without
|
|
4269
|
+
// emitting a second event-driven redraw from the normal frame path.
|
|
4270
|
+
hoveredPick = nextHoveredPick;
|
|
4271
|
+
hoveredPickKey = createDetectionPickKey(nextHoveredPick);
|
|
3784
4272
|
}
|
|
3785
|
-
|
|
3786
|
-
|
|
4273
|
+
followSelectedPicks(frame);
|
|
4274
|
+
}
|
|
4275
|
+
function followSelectedPicks(frame) {
|
|
4276
|
+
if (selectedPicks.length === 0) {
|
|
4277
|
+
return;
|
|
4278
|
+
}
|
|
4279
|
+
const nextSelectedPicks = selectedPicks.flatMap((pick) => {
|
|
4280
|
+
const nextPick = filterPick(followDetectionPickAcrossFrames(pick, frame));
|
|
4281
|
+
return nextPick ? [nextPick] : [];
|
|
4282
|
+
});
|
|
4283
|
+
const currentKeys = selectedPicks.map(createDetectionPickKey).join("|");
|
|
4284
|
+
const nextKeys = nextSelectedPicks.map(createDetectionPickKey).join("|");
|
|
4285
|
+
const identityChanged = !haveSameDetectionPickIdentities(selectedPicks, nextSelectedPicks);
|
|
4286
|
+
selectedPicks = nextSelectedPicks;
|
|
4287
|
+
selectedPick = nextSelectedPicks.at(-1) ?? null;
|
|
4288
|
+
if (nextKeys === currentKeys) {
|
|
4289
|
+
return;
|
|
3787
4290
|
}
|
|
3788
|
-
|
|
3789
|
-
|
|
4291
|
+
selectedPickKey = createDetectionPickKey(selectedPick);
|
|
4292
|
+
// A followed pick re-bases onto every new frame, so its full pick key
|
|
4293
|
+
// changes at playback rate. The scene's normal frame path consumes this
|
|
4294
|
+
// refreshed state immediately; emitting onStateChange here would redraw
|
|
4295
|
+
// focus and interaction a second time. Public callbacks only report
|
|
4296
|
+
// identity or membership changes.
|
|
4297
|
+
if (identityChanged) {
|
|
4298
|
+
options.interaction.onSelect?.(selectedPick);
|
|
4299
|
+
options.interaction.onSelectionChange?.(selectedPicks);
|
|
3790
4300
|
}
|
|
3791
4301
|
}
|
|
3792
4302
|
function setHoveredPick(nextPick) {
|
|
@@ -3895,6 +4405,12 @@ function createPixiInteractionLayer(options) {
|
|
|
3895
4405
|
}
|
|
3896
4406
|
return options.canInteract();
|
|
3897
4407
|
}
|
|
4408
|
+
function canPickDetection(detection) {
|
|
4409
|
+
return options.canPickDetection?.(detection) ?? true;
|
|
4410
|
+
}
|
|
4411
|
+
function filterPick(pick) {
|
|
4412
|
+
return pick && canPickDetection(pick.detection) ? pick : null;
|
|
4413
|
+
}
|
|
3898
4414
|
}
|
|
3899
4415
|
function getDetectionCenter(detection) {
|
|
3900
4416
|
const rect = getDetectionRect(detection);
|
|
@@ -3940,8 +4456,11 @@ function compositeMaskFrame(instructions) {
|
|
|
3940
4456
|
}
|
|
3941
4457
|
return { data, height, width };
|
|
3942
4458
|
}
|
|
4459
|
+
function createMaskIdFrame(instructions) {
|
|
4460
|
+
return createIdMaskFrame(materializeMaskInstructions(instructions));
|
|
4461
|
+
}
|
|
3943
4462
|
async function createPngIdMaskFrame(instructions) {
|
|
3944
|
-
const frame =
|
|
4463
|
+
const frame = createMaskIdFrame(instructions);
|
|
3945
4464
|
if (!frame) {
|
|
3946
4465
|
return undefined;
|
|
3947
4466
|
}
|
|
@@ -4480,6 +4999,9 @@ function createPixiVectorLayer(options) {
|
|
|
4480
4999
|
let keypointStyle = options.keypointStyle === undefined
|
|
4481
5000
|
? new BaseKeypointStyle()
|
|
4482
5001
|
: options.keypointStyle;
|
|
5002
|
+
// Shape decorations are opt-in; no default style exists so configuring
|
|
5003
|
+
// nothing keeps the semantic-geometry skip untouched.
|
|
5004
|
+
let shapeStyle = options.shapeStyle ?? null;
|
|
4483
5005
|
let styleVersion = 0;
|
|
4484
5006
|
let drawnStyleVersion = -1;
|
|
4485
5007
|
// A versioned source can replace a frame without changing its timeline key.
|
|
@@ -4516,7 +5038,8 @@ function createPixiVectorLayer(options) {
|
|
|
4516
5038
|
const resolvedDetections = frame.detections.flatMap((detection, detectionIndex) => {
|
|
4517
5039
|
if (!detection.polygon &&
|
|
4518
5040
|
!detection.polyline &&
|
|
4519
|
-
!detection.keypoints
|
|
5041
|
+
!detection.keypoints &&
|
|
5042
|
+
!shapeStyle) {
|
|
4520
5043
|
return [];
|
|
4521
5044
|
}
|
|
4522
5045
|
const resolved = resolveDetection(detection, detectionIndex, frame, mediaTime, viewportScale);
|
|
@@ -4551,6 +5074,8 @@ function createPixiVectorLayer(options) {
|
|
|
4551
5074
|
polylineStyle = styles.polylineStyle;
|
|
4552
5075
|
if (styles.keypointStyle !== undefined)
|
|
4553
5076
|
keypointStyle = styles.keypointStyle;
|
|
5077
|
+
if (styles.shapeStyle !== undefined)
|
|
5078
|
+
shapeStyle = styles.shapeStyle;
|
|
4554
5079
|
styleVersion += 1;
|
|
4555
5080
|
},
|
|
4556
5081
|
destroy() {
|
|
@@ -4595,13 +5120,16 @@ function createPixiVectorLayer(options) {
|
|
|
4595
5120
|
const polygon = polygonStyle?.resolve(detection, context);
|
|
4596
5121
|
const polyline = polylineStyle?.resolve(detection, context);
|
|
4597
5122
|
const keypoints = keypointStyle?.resolve(detection, context);
|
|
4598
|
-
|
|
5123
|
+
const shapes = shapeStyle?.resolve(detection, context);
|
|
5124
|
+
if (!polygon && !polyline && !keypoints && !shapes?.length) {
|
|
4599
5125
|
return undefined;
|
|
5126
|
+
}
|
|
4600
5127
|
return {
|
|
4601
5128
|
key: detectionKey(detection, detectionIndex),
|
|
4602
5129
|
keypoints,
|
|
4603
5130
|
polygon,
|
|
4604
5131
|
polyline,
|
|
5132
|
+
shapes,
|
|
4605
5133
|
zIndex: detection.zIndex ?? detectionIndex,
|
|
4606
5134
|
};
|
|
4607
5135
|
}
|
|
@@ -4610,7 +5138,10 @@ function createPixiVectorLayer(options) {
|
|
|
4610
5138
|
if (!entry.cleared)
|
|
4611
5139
|
graphics.clear();
|
|
4612
5140
|
entry.cleared = false;
|
|
4613
|
-
const { keypoints, polygon, polyline } = detection;
|
|
5141
|
+
const { keypoints, polygon, polyline, shapes } = detection;
|
|
5142
|
+
for (const shape of shapes ?? []) {
|
|
5143
|
+
drawShapeInstruction(graphics, shape, viewportScale);
|
|
5144
|
+
}
|
|
4614
5145
|
if (polygon) {
|
|
4615
5146
|
graphics.poly(polygon.points.flatMap(({ x, y }) => [x, y]), true);
|
|
4616
5147
|
if (polygon.fill)
|
|
@@ -4628,31 +5159,102 @@ function createPixiVectorLayer(options) {
|
|
|
4628
5159
|
drawPixiPath(graphics, [edge.from, edge.to], false, edge.stroke, viewportScale);
|
|
4629
5160
|
}
|
|
4630
5161
|
for (const marker of keypoints.markers) {
|
|
4631
|
-
const radius = resolveScreenLength(marker.radius, viewportScale);
|
|
4632
5162
|
if (marker.shape === KeypointMarkerShape.Cross) {
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
5163
|
+
drawMarkerInstruction(graphics, {
|
|
5164
|
+
center: marker.point,
|
|
5165
|
+
kind: ShapeInstructionKind.Marker,
|
|
5166
|
+
shape: MarkerShape.Cross,
|
|
5167
|
+
size: marker.radius * 2,
|
|
5168
|
+
sizeSpace: MarkerSizeSpace.Screen,
|
|
5169
|
+
stroke: asOpenStroke(marker.stroke ?? { alpha: 1, color: 0xffffff, width: 2 }),
|
|
5170
|
+
}, viewportScale);
|
|
4641
5171
|
}
|
|
4642
5172
|
else {
|
|
4643
|
-
graphics
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
5173
|
+
drawMarkerInstruction(graphics, {
|
|
5174
|
+
center: marker.point,
|
|
5175
|
+
fill: marker.fill,
|
|
5176
|
+
kind: ShapeInstructionKind.Marker,
|
|
5177
|
+
shape: MarkerShape.Circle,
|
|
5178
|
+
size: marker.radius * 2,
|
|
5179
|
+
sizeSpace: MarkerSizeSpace.Screen,
|
|
5180
|
+
stroke: marker.stroke,
|
|
5181
|
+
}, viewportScale);
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
}
|
|
5185
|
+
}
|
|
5186
|
+
function drawShapeInstruction(graphics, instruction, viewportScale) {
|
|
5187
|
+
if (instruction.kind === ShapeInstructionKind.Ellipse) {
|
|
5188
|
+
const { closed, points } = sampleEllipseArc(instruction, resolveEllipseSegmentCount(instruction, viewportScale));
|
|
5189
|
+
if (closed && instruction.fill) {
|
|
5190
|
+
graphics.poly(points.flatMap(({ x, y }) => [x, y]), true);
|
|
5191
|
+
graphics.fill(instruction.fill);
|
|
5192
|
+
}
|
|
5193
|
+
if (instruction.stroke) {
|
|
5194
|
+
drawPixiPath(graphics, points, closed, instruction.stroke, viewportScale);
|
|
5195
|
+
}
|
|
5196
|
+
return;
|
|
5197
|
+
}
|
|
5198
|
+
if (instruction.kind === ShapeInstructionKind.Marker) {
|
|
5199
|
+
drawMarkerInstruction(graphics, instruction, viewportScale);
|
|
5200
|
+
return;
|
|
5201
|
+
}
|
|
5202
|
+
for (const segment of instruction.segments) {
|
|
5203
|
+
if (instruction.closed && instruction.fill) {
|
|
5204
|
+
graphics.poly(segment.flatMap(({ x, y }) => [x, y]), true);
|
|
5205
|
+
graphics.fill(instruction.fill);
|
|
5206
|
+
}
|
|
5207
|
+
drawPixiPath(graphics, segment, instruction.closed, instruction.stroke, viewportScale);
|
|
5208
|
+
}
|
|
5209
|
+
}
|
|
5210
|
+
function drawMarkerInstruction(graphics, instruction, viewportScale) {
|
|
5211
|
+
const geometry = resolveMarkerGeometry(instruction, viewportScale);
|
|
5212
|
+
if (geometry.kind === "circle") {
|
|
5213
|
+
const dashed = Boolean(instruction.stroke?.dash?.length);
|
|
5214
|
+
if (instruction.fill || !dashed) {
|
|
5215
|
+
graphics.circle(geometry.center.x, geometry.center.y, geometry.radius);
|
|
5216
|
+
if (instruction.fill)
|
|
5217
|
+
graphics.fill(instruction.fill);
|
|
5218
|
+
}
|
|
5219
|
+
if (instruction.stroke) {
|
|
5220
|
+
if (dashed) {
|
|
5221
|
+
const ellipse = {
|
|
5222
|
+
center: geometry.center,
|
|
5223
|
+
radiusX: geometry.radius,
|
|
5224
|
+
radiusY: geometry.radius,
|
|
5225
|
+
};
|
|
5226
|
+
const { points } = sampleEllipseArc(ellipse, resolveEllipseSegmentCount(ellipse, viewportScale));
|
|
5227
|
+
drawPixiPath(graphics, points, true, instruction.stroke, viewportScale);
|
|
5228
|
+
}
|
|
5229
|
+
else {
|
|
5230
|
+
graphics.stroke(resolvePixiStroke(instruction.stroke, viewportScale));
|
|
4652
5231
|
}
|
|
4653
5232
|
}
|
|
5233
|
+
return;
|
|
5234
|
+
}
|
|
5235
|
+
for (const subpath of geometry.subpaths) {
|
|
5236
|
+
if (geometry.closed && instruction.fill) {
|
|
5237
|
+
graphics.poly(subpath.flatMap(({ x, y }) => [x, y]), true);
|
|
5238
|
+
graphics.fill(instruction.fill);
|
|
5239
|
+
}
|
|
5240
|
+
if (instruction.stroke) {
|
|
5241
|
+
drawPixiPath(graphics, subpath, geometry.closed, instruction.stroke, viewportScale);
|
|
5242
|
+
}
|
|
4654
5243
|
}
|
|
4655
5244
|
}
|
|
5245
|
+
function asOpenStroke(stroke) {
|
|
5246
|
+
if (!stroke)
|
|
5247
|
+
return undefined;
|
|
5248
|
+
return {
|
|
5249
|
+
alpha: stroke.alpha,
|
|
5250
|
+
cap: stroke.cap,
|
|
5251
|
+
color: stroke.color,
|
|
5252
|
+
dash: stroke.dash,
|
|
5253
|
+
join: stroke.join,
|
|
5254
|
+
miterLimit: stroke.miterLimit,
|
|
5255
|
+
width: stroke.width,
|
|
5256
|
+
};
|
|
5257
|
+
}
|
|
4656
5258
|
function detectionKey(detection, detectionIndex) {
|
|
4657
5259
|
return detection.id === undefined
|
|
4658
5260
|
? `index:${detectionIndex}`
|
|
@@ -4782,7 +5384,8 @@ function createPixiInteractionPresentationLayer(options) {
|
|
|
4782
5384
|
}
|
|
4783
5385
|
function createActivePick(pick, frame, mediaTime, state) {
|
|
4784
5386
|
const activePick = rebaseDetectionPickToFrame(pick, frame);
|
|
4785
|
-
if (!activePick
|
|
5387
|
+
if (!activePick ||
|
|
5388
|
+
options.isDetectionVisible?.(activePick.detection) === false) {
|
|
4786
5389
|
return null;
|
|
4787
5390
|
}
|
|
4788
5391
|
const context = {
|
|
@@ -5039,7 +5642,7 @@ function createInteractionMaskRenderer(options) {
|
|
|
5039
5642
|
autoGenerateMipmaps: false,
|
|
5040
5643
|
dynamic: false,
|
|
5041
5644
|
height: 1,
|
|
5042
|
-
resource: createPlaceholderCanvas$
|
|
5645
|
+
resource: createPlaceholderCanvas$2(),
|
|
5043
5646
|
scaleMode: "nearest",
|
|
5044
5647
|
width: 1,
|
|
5045
5648
|
});
|
|
@@ -5150,7 +5753,7 @@ function writePaletteEntry(palette, maskId, color, alpha) {
|
|
|
5150
5753
|
palette[offset + 2] = (color & 0xff) / 255;
|
|
5151
5754
|
palette[offset + 3] = clampedAlpha;
|
|
5152
5755
|
}
|
|
5153
|
-
function createPlaceholderCanvas$
|
|
5756
|
+
function createPlaceholderCanvas$2() {
|
|
5154
5757
|
const canvas = document.createElement("canvas");
|
|
5155
5758
|
canvas.height = 1;
|
|
5156
5759
|
canvas.width = 1;
|
|
@@ -5298,7 +5901,7 @@ void main(void) {
|
|
|
5298
5901
|
}
|
|
5299
5902
|
`;
|
|
5300
5903
|
|
|
5301
|
-
const EMBEDDED_MASK_PREPARATION_WORKER_SOURCE = "(function () {\n 'use strict';\n\n /** COCO-compatible keypoint visibility values. */\n var KeypointVisibility;\n (function (KeypointVisibility) {\n KeypointVisibility[KeypointVisibility[\"NotLabeled\"] = 0] = \"NotLabeled\";\n KeypointVisibility[KeypointVisibility[\"Occluded\"] = 1] = \"Occluded\";\n KeypointVisibility[KeypointVisibility[\"Visible\"] = 2] = \"Visible\";\n })(KeypointVisibility || (KeypointVisibility = {}));\n var DetectionMaskEncoding;\n (function (DetectionMaskEncoding) {\n DetectionMaskEncoding[\"CompressedRle\"] = \"compressedRle\";\n })(DetectionMaskEncoding || (DetectionMaskEncoding = {}));\n\n var DetectionBufferStatus;\n (function (DetectionBufferStatus) {\n DetectionBufferStatus[\"Idle\"] = \"idle\";\n DetectionBufferStatus[\"Loading\"] = \"loading\";\n DetectionBufferStatus[\"Ready\"] = \"ready\";\n DetectionBufferStatus[\"Error\"] = \"error\";\n DetectionBufferStatus[\"Destroyed\"] = \"destroyed\";\n })(DetectionBufferStatus || (DetectionBufferStatus = {}));\n /**\n * How the renderer selects the active detection frame for a media timestamp.\n */\n var DetectionFrameSelectionMode;\n (function (DetectionFrameSelectionMode) {\n /**\n * Select the frame whose `[mediaTime, endTime)` interval contains the media\n * time. This is the default for interval annotations and timestamped sources.\n */\n DetectionFrameSelectionMode[\"Interval\"] = \"interval\";\n /**\n * Select from a known inference frame grid using `frameIndex`, `frameRate`,\n * and optional `frameIndexOriginTime`. This is useful when inference was run\n * on normalized frames and playback should snap detections to that grid.\n */\n DetectionFrameSelectionMode[\"NearestFrameIndex\"] = \"nearestFrameIndex\";\n })(DetectionFrameSelectionMode || (DetectionFrameSelectionMode = {}));\n var DetectionFrameRetentionMode;\n (function (DetectionFrameRetentionMode) {\n /**\n * Keep writable detections only in an in-memory store. Useful for ephemeral\n * live streams where old predictions should disappear when evicted.\n */\n DetectionFrameRetentionMode[\"MemoryOnly\"] = \"memoryOnly\";\n /**\n * Persist every written detection frame. Useful for finite media where seek\n * and replay should not require recomputing detections.\n */\n DetectionFrameRetentionMode[\"PersistAll\"] = \"persistAll\";\n /**\n * Persist only the most recent retention window. Useful for long-running\n * streams where replay is bounded to a recent time horizon.\n */\n DetectionFrameRetentionMode[\"PersistWindow\"] = \"persistWindow\";\n })(DetectionFrameRetentionMode || (DetectionFrameRetentionMode = {}));\n function decodeCompressedRleMask(mask) {\n if (mask.encoding !== DetectionMaskEncoding.CompressedRle) {\n throw new Error(`Unsupported detection mask encoding: ${mask.encoding}`);\n }\n const data = new Uint8Array(mask.width * mask.height);\n const counts = decodeCompressedRleCounts(mask.counts);\n let offset = 0;\n for (let index = 0; index < counts.length; index += 1) {\n const runLength = counts[index] ?? 0;\n const isForeground = index % 2 === 1;\n if (isForeground) {\n for (let runOffset = 0; runOffset < runLength; runOffset += 1) {\n const maskOffset = offset + runOffset;\n const x = Math.floor(maskOffset / mask.height);\n const y = maskOffset % mask.height;\n const rowMajorOffset = y * mask.width + x;\n if (rowMajorOffset < data.length) {\n data[rowMajorOffset] = 1;\n }\n }\n }\n offset += runLength;\n }\n return {\n data,\n height: mask.height,\n width: mask.width,\n };\n }\n function decodeCompressedRleCounts(counts) {\n const decoded = [];\n let index = 0;\n while (index < counts.length) {\n let value = 0;\n let shift = 0;\n let charCode;\n do {\n charCode = counts.charCodeAt(index) - 48;\n index += 1;\n value |= (charCode & 0x1f) << shift;\n shift += 5;\n } while (charCode & 0x20);\n if (charCode & 0x10) {\n value |= -1 << shift;\n }\n if (decoded.length > 2) {\n value += decoded[decoded.length - 2] ?? 0;\n }\n decoded.push(value);\n }\n return decoded;\n }\n function encodeCompressedRleCounts(counts) {\n return counts\n .map((count, index) => {\n let value = index > 2 ? count - counts[index - 2] : count;\n let encoded = \"\";\n let more = true;\n while (more) {\n let charCode = value & 0x1f;\n value >>= 5;\n more = !((value === 0 && (charCode & 0x10) === 0) ||\n (value === -1 && (charCode & 0x10) !== 0));\n if (more) {\n charCode |= 0x20;\n }\n encoded += String.fromCharCode(charCode + 48);\n }\n return encoded;\n })\n .join(\"\");\n }\n\n var AnnotationFrameMutationKind;\n (function (AnnotationFrameMutationKind) {\n AnnotationFrameMutationKind[\"Add\"] = \"add\";\n AnnotationFrameMutationKind[\"Remove\"] = \"remove\";\n AnnotationFrameMutationKind[\"Replace\"] = \"replace\";\n AnnotationFrameMutationKind[\"Transact\"] = \"transact\";\n AnnotationFrameMutationKind[\"Update\"] = \"update\";\n })(AnnotationFrameMutationKind || (AnnotationFrameMutationKind = {}));\n\n var DetectionPickTarget;\n (function (DetectionPickTarget) {\n DetectionPickTarget[\"Box\"] = \"box\";\n DetectionPickTarget[\"Edge\"] = \"edge\";\n DetectionPickTarget[\"Keypoint\"] = \"keypoint\";\n DetectionPickTarget[\"Label\"] = \"label\";\n DetectionPickTarget[\"Mask\"] = \"mask\";\n DetectionPickTarget[\"Polygon\"] = \"polygon\";\n DetectionPickTarget[\"Polyline\"] = \"polyline\";\n })(DetectionPickTarget || (DetectionPickTarget = {}));\n var MediaInteractionMode;\n (function (MediaInteractionMode) {\n MediaInteractionMode[\"Always\"] = \"always\";\n MediaInteractionMode[\"Disabled\"] = \"disabled\";\n MediaInteractionMode[\"PausedOnly\"] = \"pausedOnly\";\n })(MediaInteractionMode || (MediaInteractionMode = {}));\n\n function centerRectToTopLeftRect(rect) {\n return {\n height: rect.height,\n width: rect.width,\n x: rect.x - rect.width / 2,\n y: rect.y - rect.height / 2,\n };\n }\n function getPointsRect(points) {\n if (points.length === 0) {\n return undefined;\n }\n let minX = Number.POSITIVE_INFINITY;\n let minY = Number.POSITIVE_INFINITY;\n let maxX = Number.NEGATIVE_INFINITY;\n let maxY = Number.NEGATIVE_INFINITY;\n for (const point of points) {\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n return {\n height: maxY - minY,\n width: maxX - minX,\n x: (minX + maxX) / 2,\n y: (minY + maxY) / 2,\n };\n }\n\n var AnnotationGeometryKind;\n (function (AnnotationGeometryKind) {\n AnnotationGeometryKind[\"Box\"] = \"box\";\n AnnotationGeometryKind[\"Polygon\"] = \"polygon\";\n AnnotationGeometryKind[\"Polyline\"] = \"polyline\";\n AnnotationGeometryKind[\"Keypoints\"] = \"keypoints\";\n AnnotationGeometryKind[\"Mask\"] = \"mask\";\n })(AnnotationGeometryKind || (AnnotationGeometryKind = {}));\n var AnnotationGestureStateKind;\n (function (AnnotationGestureStateKind) {\n AnnotationGestureStateKind[\"Idle\"] = \"idle\";\n AnnotationGestureStateKind[\"Creating\"] = \"creating\";\n AnnotationGestureStateKind[\"Moving\"] = \"moving\";\n AnnotationGestureStateKind[\"Resizing\"] = \"resizing\";\n AnnotationGestureStateKind[\"DragSelecting\"] = \"dragSelecting\";\n })(AnnotationGestureStateKind || (AnnotationGestureStateKind = {}));\n var AnnotationHandleKind;\n (function (AnnotationHandleKind) {\n AnnotationHandleKind[\"Resize\"] = \"resize\";\n AnnotationHandleKind[\"Vertex\"] = \"vertex\";\n AnnotationHandleKind[\"AddVertex\"] = \"addVertex\";\n AnnotationHandleKind[\"Keypoint\"] = \"keypoint\";\n })(AnnotationHandleKind || (AnnotationHandleKind = {}));\n\n var BoxShape;\n (function (BoxShape) {\n BoxShape[\"Rect\"] = \"rect\";\n BoxShape[\"RoundedRect\"] = \"roundedRect\";\n })(BoxShape || (BoxShape = {}));\n var BoxStrokeAlignment;\n (function (BoxStrokeAlignment) {\n BoxStrokeAlignment[\"Inside\"] = \"inside\";\n BoxStrokeAlignment[\"Center\"] = \"center\";\n BoxStrokeAlignment[\"Outside\"] = \"outside\";\n })(BoxStrokeAlignment || (BoxStrokeAlignment = {}));\n\n var FocusTargetMode;\n (function (FocusTargetMode) {\n FocusTargetMode[\"Hovered\"] = \"hovered\";\n FocusTargetMode[\"Selected\"] = \"selected\";\n FocusTargetMode[\"HoveredAndSelected\"] = \"hoveredAndSelected\";\n FocusTargetMode[\"Ambient\"] = \"ambient\";\n })(FocusTargetMode || (FocusTargetMode = {}));\n\n var DetectionInteractionState;\n (function (DetectionInteractionState) {\n DetectionInteractionState[\"Hovered\"] = \"hovered\";\n DetectionInteractionState[\"Selected\"] = \"selected\";\n })(DetectionInteractionState || (DetectionInteractionState = {}));\n\n var LabelPlacement;\n (function (LabelPlacement) {\n LabelPlacement[\"Top\"] = \"top\";\n LabelPlacement[\"Bottom\"] = \"bottom\";\n LabelPlacement[\"InsideTop\"] = \"insideTop\";\n LabelPlacement[\"InsideBottom\"] = \"insideBottom\";\n LabelPlacement[\"Center\"] = \"center\";\n })(LabelPlacement || (LabelPlacement = {}));\n var LabelVisibilityMode;\n (function (LabelVisibilityMode) {\n LabelVisibilityMode[\"Always\"] = \"always\";\n LabelVisibilityMode[\"HoveredOnly\"] = \"hoveredOnly\";\n })(LabelVisibilityMode || (LabelVisibilityMode = {}));\n\n var MaskRenderMode;\n (function (MaskRenderMode) {\n MaskRenderMode[\"FillAndStroke\"] = \"fillAndStroke\";\n MaskRenderMode[\"FillOnly\"] = \"fillOnly\";\n MaskRenderMode[\"StrokeOnly\"] = \"strokeOnly\";\n })(MaskRenderMode || (MaskRenderMode = {}));\n\n var KeypointMarkerShape;\n (function (KeypointMarkerShape) {\n KeypointMarkerShape[\"Circle\"] = \"circle\";\n KeypointMarkerShape[\"Cross\"] = \"cross\";\n })(KeypointMarkerShape || (KeypointMarkerShape = {}));\n\n var DetectionMaskPayloadFormat;\n (function (DetectionMaskPayloadFormat) {\n DetectionMaskPayloadFormat[\"RawCocoRle\"] = \"rawCocoRle\";\n DetectionMaskPayloadFormat[\"DeflatedBase64\"] = \"deflatedBase64\";\n })(DetectionMaskPayloadFormat || (DetectionMaskPayloadFormat = {}));\n function encodeBinaryMask(data, width, height) {\n assertMaskDimensions(data, width, height);\n const runs = [];\n let currentValue = 0;\n let runLength = 0;\n for (let x = 0; x < width; x += 1) {\n for (let y = 0; y < height; y += 1) {\n const value = data[y * width + x] ? 1 : 0;\n if (value === currentValue) {\n runLength += 1;\n }\n else {\n runs.push(runLength);\n currentValue = value;\n runLength = 1;\n }\n }\n }\n runs.push(runLength);\n return {\n counts: encodeCompressedRleCounts(runs),\n encoding: DetectionMaskEncoding.CompressedRle,\n height,\n width,\n };\n }\n function assertMaskDimensions(data, width, height) {\n if (!Number.isInteger(width) || width <= 0) {\n throw new Error(\"Mask width must be a positive integer.\");\n }\n if (!Number.isInteger(height) || height <= 0) {\n throw new Error(\"Mask height must be a positive integer.\");\n }\n if (data.length !== width * height) {\n throw new Error(\"Mask data length must equal width * height.\");\n }\n }\n function rasterizePolygonToMask(points, dimensions) {\n const data = createEmptyMask(dimensions);\n if (points.length < 3) {\n return data;\n }\n const bounds = centerRectToTopLeftRect(getPointsRect(points));\n const startY = Math.max(0, Math.floor(bounds.y));\n const endY = Math.min(dimensions.height - 1, Math.ceil(bounds.y + bounds.height));\n for (let y = startY; y <= endY; y += 1) {\n const scanY = y + 0.5;\n const intersections = [];\n for (let index = 0; index < points.length; index += 1) {\n const current = points[index];\n const next = points[(index + 1) % points.length];\n if ((current.y <= scanY && next.y > scanY) ||\n (next.y <= scanY && current.y > scanY)) {\n const ratio = (scanY - current.y) / (next.y - current.y);\n intersections.push(current.x + ratio * (next.x - current.x));\n }\n }\n intersections.sort((left, right) => left - right);\n for (let index = 0; index < intersections.length - 1; index += 2) {\n const left = Math.max(0, Math.ceil(intersections[index]));\n const right = Math.min(dimensions.width - 1, Math.floor(intersections[index + 1]));\n for (let x = left; x <= right; x += 1) {\n data[y * dimensions.width + x] = 1;\n }\n }\n }\n return data;\n }\n function createEmptyMask(dimensions) {\n if (!Number.isInteger(dimensions.width) ||\n dimensions.width <= 0 ||\n !Number.isInteger(dimensions.height) ||\n dimensions.height <= 0) {\n throw new Error(\"Media dimensions must be positive integers.\");\n }\n return new Uint8Array(dimensions.width * dimensions.height);\n }\n\n const MAX_ID_MASK_PALETTE_ENTRIES = 64;\n const MAX_ID_MASK_STROKE_WIDTH = 16;\n function createIdMaskFrame(instructions) {\n if (instructions.length === 0) {\n return undefined;\n }\n const width = Math.max(...instructions.map(({ mask }) => mask.width));\n const height = Math.max(...instructions.map(({ mask }) => mask.height));\n const data = new Uint8Array(new ArrayBuffer(width * height));\n const fillPalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));\n const strokePalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));\n const strokeWidths = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4));\n let hasStroke = false;\n let maxStrokeWidth = 0;\n for (const instruction of instructions) {\n const detectionMaskId = instruction.detectionIndex + 1;\n if (detectionMaskId <= 0 ||\n detectionMaskId >= MAX_ID_MASK_PALETTE_ENTRIES) {\n return undefined;\n }\n writePaletteEntry(fillPalette, detectionMaskId, instruction.color, instruction.alpha);\n if (instruction.stroke && instruction.stroke.width > 0) {\n const strokeWidth = Math.min(Math.max(0, instruction.stroke.width), MAX_ID_MASK_STROKE_WIDTH);\n hasStroke = true;\n strokeWidths[detectionMaskId] = strokeWidth;\n maxStrokeWidth = Math.max(maxStrokeWidth, strokeWidth);\n writePaletteEntry(strokePalette, detectionMaskId, instruction.stroke.color, instruction.stroke.alpha);\n }\n const decodedMask = decodeCompressedRleMask(instruction.mask);\n for (let y = 0; y < decodedMask.height; y += 1) {\n for (let x = 0; x < decodedMask.width; x += 1) {\n const maskOffset = y * decodedMask.width + x;\n if (decodedMask.data[maskOffset]) {\n data[y * width + x] = detectionMaskId;\n }\n }\n }\n }\n return {\n data,\n fillPalette,\n hasStroke,\n height,\n maxStrokeWidth,\n strokePalette,\n strokeWidths,\n width,\n };\n }\n function writePaletteEntry(palette, id, color, alpha) {\n const offset = id * 4;\n palette[offset] = ((color >> 16) & 0xff) / 255;\n palette[offset + 1] = ((color >> 8) & 0xff) / 255;\n palette[offset + 2] = (color & 0xff) / 255;\n palette[offset + 3] = Math.max(0, Math.min(alpha, 1));\n }\n\n var MediaRendererFit;\n (function (MediaRendererFit) {\n /**\n * Preserve media aspect ratio and fit the full frame inside the canvas.\n */\n MediaRendererFit[\"Contain\"] = \"contain\";\n /**\n * Preserve media aspect ratio and fill the canvas, cropping if necessary.\n */\n MediaRendererFit[\"Cover\"] = \"cover\";\n })(MediaRendererFit || (MediaRendererFit = {}));\n /**\n * Playback lifecycle state reported by a platform renderer.\n */\n var MediaRendererPlaybackState;\n (function (MediaRendererPlaybackState) {\n MediaRendererPlaybackState[\"Loading\"] = \"loading\";\n MediaRendererPlaybackState[\"Ready\"] = \"ready\";\n MediaRendererPlaybackState[\"Playing\"] = \"playing\";\n MediaRendererPlaybackState[\"Buffering\"] = \"buffering\";\n MediaRendererPlaybackState[\"Paused\"] = \"paused\";\n MediaRendererPlaybackState[\"Error\"] = \"error\";\n MediaRendererPlaybackState[\"Destroyed\"] = \"destroyed\";\n })(MediaRendererPlaybackState || (MediaRendererPlaybackState = {}));\n /**\n * Lower-level media source readiness.\n */\n var MediaSourceStatus;\n (function (MediaSourceStatus) {\n MediaSourceStatus[\"Loading\"] = \"loading\";\n MediaSourceStatus[\"Ready\"] = \"ready\";\n MediaSourceStatus[\"Error\"] = \"error\";\n MediaSourceStatus[\"Destroyed\"] = \"destroyed\";\n })(MediaSourceStatus || (MediaSourceStatus = {}));\n\n /**\n * Media-session operating mode.\n *\n * Core owns the names because file-like and stream-like lifecycle choices are\n * platform-neutral. Platform packages decide how these modes tune media,\n * storage, and renderer defaults.\n */\n var MediaSessionMode;\n (function (MediaSessionMode) {\n /**\n * Finite media. Defaults usually favor seek/replay and persistent detection\n * storage.\n */\n MediaSessionMode[\"File\"] = \"file\";\n /**\n * Live or append-only media. Defaults usually favor rolling windows and\n * bounded retention.\n */\n MediaSessionMode[\"Stream\"] = \"stream\";\n })(MediaSessionMode || (MediaSessionMode = {}));\n /**\n * Aggregate lifecycle state for a media session.\n */\n var MediaSessionStatus;\n (function (MediaSessionStatus) {\n MediaSessionStatus[\"Buffering\"] = \"buffering\";\n MediaSessionStatus[\"Destroyed\"] = \"destroyed\";\n MediaSessionStatus[\"Error\"] = \"error\";\n MediaSessionStatus[\"Loading\"] = \"loading\";\n MediaSessionStatus[\"Paused\"] = \"paused\";\n MediaSessionStatus[\"Playing\"] = \"playing\";\n MediaSessionStatus[\"Processing\"] = \"processing\";\n MediaSessionStatus[\"Ready\"] = \"ready\";\n })(MediaSessionStatus || (MediaSessionStatus = {}));\n /**\n * Subsystem currently affecting session readiness or presentation.\n */\n var MediaSessionActivityKind;\n (function (MediaSessionActivityKind) {\n MediaSessionActivityKind[\"DetectionsBuffering\"] = \"detectionsBuffering\";\n MediaSessionActivityKind[\"DetectionsLoading\"] = \"detectionsLoading\";\n MediaSessionActivityKind[\"Error\"] = \"error\";\n MediaSessionActivityKind[\"MediaNormalizing\"] = \"mediaNormalizing\";\n MediaSessionActivityKind[\"MediaOpening\"] = \"mediaOpening\";\n MediaSessionActivityKind[\"PlaybackBuffering\"] = \"playbackBuffering\";\n MediaSessionActivityKind[\"RenderPreparing\"] = \"renderPreparing\";\n })(MediaSessionActivityKind || (MediaSessionActivityKind = {}));\n /**\n * State of one session activity.\n */\n var MediaSessionActivityStatus;\n (function (MediaSessionActivityStatus) {\n MediaSessionActivityStatus[\"Error\"] = \"error\";\n MediaSessionActivityStatus[\"Running\"] = \"running\";\n MediaSessionActivityStatus[\"Waiting\"] = \"waiting\";\n })(MediaSessionActivityStatus || (MediaSessionActivityStatus = {}));\n\n const PNG_SIGNATURE = new Uint8Array([\n 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,\n ]);\n function compositeMaskFrame(instructions) {\n const maskInstructions = materializeMaskInstructions(instructions);\n if (maskInstructions.length === 0) {\n return undefined;\n }\n const width = Math.max(...maskInstructions.map(({ mask }) => mask.width));\n const height = Math.max(...maskInstructions.map(({ mask }) => mask.height));\n const data = new Uint8ClampedArray(new ArrayBuffer(width * height * 4));\n for (const instruction of maskInstructions) {\n compositeInstruction(data, width, instruction);\n }\n return { data, height, width };\n }\n async function createPngIdMaskFrame(instructions) {\n const frame = createIdMaskFrame(materializeMaskInstructions(instructions));\n if (!frame) {\n return undefined;\n }\n return {\n ...frame,\n png: await encodeGrayscalePng({\n height: frame.height,\n pixels: frame.data,\n width: frame.width,\n }),\n };\n }\n function compositeInstruction(rgba, canvasWidth, instruction) {\n const decodedMask = decodeCompressedRleMask(instruction.mask);\n const fill = resolveRgbaColor(instruction.color, instruction.alpha);\n compositeMaskFill(rgba, canvasWidth, decodedMask, fill);\n if (instruction.stroke) {\n compositeMaskStroke(rgba, canvasWidth, decodedMask, instruction.stroke);\n }\n }\n function materializeMaskInstructions(instructions) {\n return instructions.map((instruction) => {\n if (instruction.mask) {\n return instruction;\n }\n const { height, points, width } = instruction.polygon;\n return {\n alpha: instruction.alpha,\n color: instruction.color,\n detectionIndex: instruction.detectionIndex,\n mask: encodeBinaryMask(rasterizePolygonToMask(points, { height, width }), width, height),\n stroke: instruction.stroke,\n };\n });\n }\n function compositeMaskFill(rgba, canvasWidth, decodedMask, fill) {\n for (let y = 0; y < decodedMask.height; y += 1) {\n for (let x = 0; x < decodedMask.width; x += 1) {\n const maskOffset = y * decodedMask.width + x;\n if (!decodedMask.data[maskOffset]) {\n continue;\n }\n writePixel(rgba, canvasWidth, x, y, fill);\n }\n }\n }\n function compositeMaskStroke(rgba, canvasWidth, decodedMask, stroke) {\n const width = Math.round(stroke.width);\n if (width <= 0) {\n return;\n }\n const strokeColor = resolveRgbaColor(stroke.color, stroke.alpha);\n for (let y = 0; y < decodedMask.height; y += 1) {\n for (let x = 0; x < decodedMask.width; x += 1) {\n if (!isMaskPixel(decodedMask, x, y) ||\n !isBoundaryPixel(decodedMask, x, y)) {\n continue;\n }\n for (let offsetY = -width; offsetY <= width; offsetY += 1) {\n for (let offsetX = -width; offsetX <= width; offsetX += 1) {\n const strokeX = x + offsetX;\n const strokeY = y + offsetY;\n if (isOutsideMaskBounds(decodedMask, strokeX, strokeY) ||\n isMaskPixel(decodedMask, strokeX, strokeY)) {\n continue;\n }\n writePixel(rgba, canvasWidth, strokeX, strokeY, strokeColor);\n }\n }\n }\n }\n }\n function isBoundaryPixel(mask, x, y) {\n for (let offsetY = -1; offsetY <= 1; offsetY += 1) {\n for (let offsetX = -1; offsetX <= 1; offsetX += 1) {\n if (offsetX === 0 && offsetY === 0) {\n continue;\n }\n const neighborX = x + offsetX;\n const neighborY = y + offsetY;\n if (isOutsideMaskBounds(mask, neighborX, neighborY) ||\n !isMaskPixel(mask, neighborX, neighborY)) {\n return true;\n }\n }\n }\n return false;\n }\n function isMaskPixel(mask, x, y) {\n return mask.data[y * mask.width + x] === 1;\n }\n function isOutsideMaskBounds(mask, x, y) {\n return x < 0 || y < 0 || x >= mask.width || y >= mask.height;\n }\n function resolveRgbaColor(color, alpha) {\n return {\n alpha: Math.round(Math.max(0, Math.min(alpha, 1)) * 255),\n blue: color & 0xff,\n green: (color >> 8) & 0xff,\n red: (color >> 16) & 0xff,\n };\n }\n function writePixel(rgba, canvasWidth, x, y, color) {\n const rgbaOffset = (y * canvasWidth + x) * 4;\n rgba[rgbaOffset] = color.red;\n rgba[rgbaOffset + 1] = color.green;\n rgba[rgbaOffset + 2] = color.blue;\n rgba[rgbaOffset + 3] = color.alpha;\n }\n async function encodeGrayscalePng(options) {\n if (typeof CompressionStream === \"undefined\") {\n throw new Error(\"CompressionStream is required to encode PNG ID masks.\");\n }\n const rawScanlines = createFilterlessPngScanlines(options);\n const ihdr = new Uint8Array(13);\n const ihdrView = new DataView(ihdr.buffer);\n ihdrView.setUint32(0, options.width);\n ihdrView.setUint32(4, options.height);\n ihdr[8] = 8;\n ihdr[9] = 0;\n ihdr[10] = 0;\n ihdr[11] = 0;\n ihdr[12] = 0;\n const compressed = new Uint8Array(await new Response(new Blob([rawScanlines])\n .stream()\n .pipeThrough(new CompressionStream(\"deflate\"))).arrayBuffer());\n return concatUint8Arrays([\n PNG_SIGNATURE,\n createPngChunk(\"IHDR\", ihdr),\n createPngChunk(\"IDAT\", compressed),\n createPngChunk(\"IEND\", new Uint8Array(0)),\n ]);\n }\n function createFilterlessPngScanlines(options) {\n const rowStride = options.width + 1;\n const scanlines = new Uint8Array(rowStride * options.height);\n for (let y = 0; y < options.height; y += 1) {\n const sourceOffset = y * options.width;\n const targetOffset = y * rowStride;\n scanlines[targetOffset] = 0;\n scanlines.set(options.pixels.subarray(sourceOffset, sourceOffset + options.width), targetOffset + 1);\n }\n return scanlines;\n }\n function createPngChunk(type, data) {\n const typeBytes = new TextEncoder().encode(type);\n const chunk = new Uint8Array(12 + data.length);\n const view = new DataView(chunk.buffer);\n view.setUint32(0, data.length);\n chunk.set(typeBytes, 4);\n chunk.set(data, 8);\n view.setUint32(8 + data.length, crc32(concatUint8Arrays([typeBytes, data])));\n return chunk;\n }\n const crc32Table = createCrc32Table();\n function createCrc32Table() {\n const table = new Uint32Array(256);\n for (let index = 0; index < table.length; index += 1) {\n let value = index;\n for (let bit = 0; bit < 8; bit += 1) {\n value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;\n }\n table[index] = value >>> 0;\n }\n return table;\n }\n function crc32(bytes) {\n let crc = 0xffffffff;\n for (const byte of bytes) {\n crc = crc32Table[(crc ^ byte) & 0xff] ^ (crc >>> 8);\n }\n return (crc ^ 0xffffffff) >>> 0;\n }\n function concatUint8Arrays(chunks) {\n const totalLength = chunks.reduce((total, chunk) => total + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n return result;\n }\n\n var PreparedMaskFrameKind;\n (function (PreparedMaskFrameKind) {\n PreparedMaskFrameKind[\"PngIdMask\"] = \"pngIdMask\";\n PreparedMaskFrameKind[\"RgbaImage\"] = \"rgbaImage\";\n })(PreparedMaskFrameKind || (PreparedMaskFrameKind = {}));\n\n var MaskPreparationWorkerMessageType;\n (function (MaskPreparationWorkerMessageType) {\n MaskPreparationWorkerMessageType[\"Complete\"] = \"complete\";\n MaskPreparationWorkerMessageType[\"Empty\"] = \"empty\";\n MaskPreparationWorkerMessageType[\"Error\"] = \"error\";\n MaskPreparationWorkerMessageType[\"Prepare\"] = \"prepare\";\n })(MaskPreparationWorkerMessageType || (MaskPreparationWorkerMessageType = {}));\n\n const workerScope = globalThis;\n workerScope.addEventListener(\"message\", (event) => {\n const message = event.data;\n if (message.type !== MaskPreparationWorkerMessageType.Prepare) {\n return;\n }\n void prepareMaskFrame(message);\n });\n async function prepareMaskFrame(message) {\n try {\n const pngIdMaskFrame = await createPngIdMaskWorkerResponse(message);\n if (pngIdMaskFrame) {\n workerScope.postMessage(pngIdMaskFrame, [\n pngIdMaskFrame.imageBitmap,\n pngIdMaskFrame.png.buffer,\n pngIdMaskFrame.fillPalette.buffer,\n pngIdMaskFrame.strokePalette.buffer,\n pngIdMaskFrame.strokeWidths.buffer,\n ]);\n return;\n }\n const compositedFrame = compositeMaskFrame(message.job.instructions);\n if (!compositedFrame) {\n workerScope.postMessage({\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Empty,\n });\n return;\n }\n const imageData = new ImageData(compositedFrame.data, compositedFrame.width, compositedFrame.height);\n const imageBitmap = createImageBitmapFromImageData(imageData);\n if (imageBitmap) {\n workerScope.postMessage({\n imageBitmap,\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Complete,\n }, [imageBitmap]);\n return;\n }\n workerScope.postMessage({\n imageData,\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Complete,\n }, [imageData.data.buffer]);\n }\n catch (error) {\n workerScope.postMessage({\n error: error instanceof Error\n ? error.message\n : \"Unable to prepare mask frame.\",\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Error,\n });\n }\n }\n async function createPngIdMaskWorkerResponse(message) {\n if (typeof Blob === \"undefined\" ||\n typeof createImageBitmap === \"undefined\" ||\n typeof CompressionStream === \"undefined\") {\n return undefined;\n }\n let frame;\n try {\n frame = await createPngIdMaskFrame(message.job.instructions);\n }\n catch {\n return undefined;\n }\n if (!frame) {\n return undefined;\n }\n let imageBitmap;\n try {\n imageBitmap = await createImageBitmap(new Blob([frame.png], { type: \"image/png\" }));\n }\n catch {\n return undefined;\n }\n return {\n artifactKind: PreparedMaskFrameKind.PngIdMask,\n fillPalette: frame.fillPalette,\n hasStroke: frame.hasStroke,\n imageBitmap,\n key: message.job.key,\n maxStrokeWidth: frame.maxStrokeWidth,\n png: frame.png,\n requestId: message.requestId,\n strokePalette: frame.strokePalette,\n strokeWidths: frame.strokeWidths,\n type: MaskPreparationWorkerMessageType.Complete,\n };\n }\n function createImageBitmapFromImageData(imageData) {\n if (typeof OffscreenCanvas === \"undefined\") {\n return null;\n }\n const canvas = new OffscreenCanvas(imageData.width, imageData.height);\n const context = canvas.getContext(\"2d\");\n if (!context) {\n return null;\n }\n context.putImageData(imageData, 0, 0);\n return canvas.transferToImageBitmap();\n }\n\n})();";
|
|
5904
|
+
const EMBEDDED_MASK_PREPARATION_WORKER_SOURCE = "(function () {\n 'use strict';\n\n /** COCO-compatible keypoint visibility values. */\n var KeypointVisibility;\n (function (KeypointVisibility) {\n KeypointVisibility[KeypointVisibility[\"NotLabeled\"] = 0] = \"NotLabeled\";\n KeypointVisibility[KeypointVisibility[\"Occluded\"] = 1] = \"Occluded\";\n KeypointVisibility[KeypointVisibility[\"Visible\"] = 2] = \"Visible\";\n })(KeypointVisibility || (KeypointVisibility = {}));\n var DetectionMaskEncoding;\n (function (DetectionMaskEncoding) {\n DetectionMaskEncoding[\"CompressedRle\"] = \"compressedRle\";\n })(DetectionMaskEncoding || (DetectionMaskEncoding = {}));\n\n var DetectionBufferStatus;\n (function (DetectionBufferStatus) {\n DetectionBufferStatus[\"Idle\"] = \"idle\";\n DetectionBufferStatus[\"Loading\"] = \"loading\";\n DetectionBufferStatus[\"Ready\"] = \"ready\";\n DetectionBufferStatus[\"Error\"] = \"error\";\n DetectionBufferStatus[\"Destroyed\"] = \"destroyed\";\n })(DetectionBufferStatus || (DetectionBufferStatus = {}));\n /**\n * How the renderer selects the active detection frame for a media timestamp.\n */\n var DetectionFrameSelectionMode;\n (function (DetectionFrameSelectionMode) {\n /**\n * Select the frame whose `[mediaTime, endTime)` interval contains the media\n * time. This is the default for interval annotations and timestamped sources.\n */\n DetectionFrameSelectionMode[\"Interval\"] = \"interval\";\n /**\n * Select from a known inference frame grid using `frameIndex`, `frameRate`,\n * and optional `frameIndexOriginTime`. This is useful when inference was run\n * on normalized frames and playback should snap detections to that grid.\n */\n DetectionFrameSelectionMode[\"NearestFrameIndex\"] = \"nearestFrameIndex\";\n })(DetectionFrameSelectionMode || (DetectionFrameSelectionMode = {}));\n var DetectionFrameRetentionMode;\n (function (DetectionFrameRetentionMode) {\n /**\n * Keep writable detections only in an in-memory store. Useful for ephemeral\n * live streams where old predictions should disappear when evicted.\n */\n DetectionFrameRetentionMode[\"MemoryOnly\"] = \"memoryOnly\";\n /**\n * Persist every written detection frame. Useful for finite media where seek\n * and replay should not require recomputing detections.\n */\n DetectionFrameRetentionMode[\"PersistAll\"] = \"persistAll\";\n /**\n * Persist only the most recent retention window. Useful for long-running\n * streams where replay is bounded to a recent time horizon.\n */\n DetectionFrameRetentionMode[\"PersistWindow\"] = \"persistWindow\";\n })(DetectionFrameRetentionMode || (DetectionFrameRetentionMode = {}));\n function decodeCompressedRleMask(mask) {\n if (mask.encoding !== DetectionMaskEncoding.CompressedRle) {\n throw new Error(`Unsupported detection mask encoding: ${mask.encoding}`);\n }\n const data = new Uint8Array(mask.width * mask.height);\n const counts = decodeCompressedRleCounts(mask.counts);\n let offset = 0;\n for (let index = 0; index < counts.length; index += 1) {\n const runLength = counts[index] ?? 0;\n const isForeground = index % 2 === 1;\n if (isForeground) {\n for (let runOffset = 0; runOffset < runLength; runOffset += 1) {\n const maskOffset = offset + runOffset;\n const x = Math.floor(maskOffset / mask.height);\n const y = maskOffset % mask.height;\n const rowMajorOffset = y * mask.width + x;\n if (rowMajorOffset < data.length) {\n data[rowMajorOffset] = 1;\n }\n }\n }\n offset += runLength;\n }\n return {\n data,\n height: mask.height,\n width: mask.width,\n };\n }\n function decodeCompressedRleCounts(counts) {\n const decoded = [];\n let index = 0;\n while (index < counts.length) {\n let value = 0;\n let shift = 0;\n let charCode;\n do {\n charCode = counts.charCodeAt(index) - 48;\n index += 1;\n value |= (charCode & 0x1f) << shift;\n shift += 5;\n } while (charCode & 0x20);\n if (charCode & 0x10) {\n value |= -1 << shift;\n }\n if (decoded.length > 2) {\n value += decoded[decoded.length - 2] ?? 0;\n }\n decoded.push(value);\n }\n return decoded;\n }\n function encodeCompressedRleCounts(counts) {\n return counts\n .map((count, index) => {\n let value = index > 2 ? count - counts[index - 2] : count;\n let encoded = \"\";\n let more = true;\n while (more) {\n let charCode = value & 0x1f;\n value >>= 5;\n more = !((value === 0 && (charCode & 0x10) === 0) ||\n (value === -1 && (charCode & 0x10) !== 0));\n if (more) {\n charCode |= 0x20;\n }\n encoded += String.fromCharCode(charCode + 48);\n }\n return encoded;\n })\n .join(\"\");\n }\n\n var AnnotationFrameMutationKind;\n (function (AnnotationFrameMutationKind) {\n AnnotationFrameMutationKind[\"Add\"] = \"add\";\n AnnotationFrameMutationKind[\"Remove\"] = \"remove\";\n AnnotationFrameMutationKind[\"Replace\"] = \"replace\";\n AnnotationFrameMutationKind[\"Transact\"] = \"transact\";\n AnnotationFrameMutationKind[\"Update\"] = \"update\";\n })(AnnotationFrameMutationKind || (AnnotationFrameMutationKind = {}));\n\n /** Geometry projected into a tracker. The original detection payload is retained. */\n var TrackingGeometry;\n (function (TrackingGeometry) {\n TrackingGeometry[\"Box\"] = \"box\";\n TrackingGeometry[\"Mask\"] = \"mask\";\n TrackingGeometry[\"Keypoints\"] = \"keypoints\";\n })(TrackingGeometry || (TrackingGeometry = {}));\n\n var DetectionMaskPayloadFormat;\n (function (DetectionMaskPayloadFormat) {\n DetectionMaskPayloadFormat[\"RawCocoRle\"] = \"rawCocoRle\";\n DetectionMaskPayloadFormat[\"DeflatedBase64\"] = \"deflatedBase64\";\n })(DetectionMaskPayloadFormat || (DetectionMaskPayloadFormat = {}));\n function encodeBinaryMask(data, width, height) {\n assertMaskDimensions(data, width, height);\n const runs = [];\n let currentValue = 0;\n let runLength = 0;\n for (let x = 0; x < width; x += 1) {\n for (let y = 0; y < height; y += 1) {\n const value = data[y * width + x] ? 1 : 0;\n if (value === currentValue) {\n runLength += 1;\n }\n else {\n runs.push(runLength);\n currentValue = value;\n runLength = 1;\n }\n }\n }\n runs.push(runLength);\n return {\n counts: encodeCompressedRleCounts(runs),\n encoding: DetectionMaskEncoding.CompressedRle,\n height,\n width,\n };\n }\n function assertMaskDimensions(data, width, height) {\n if (!Number.isInteger(width) || width <= 0) {\n throw new Error(\"Mask width must be a positive integer.\");\n }\n if (!Number.isInteger(height) || height <= 0) {\n throw new Error(\"Mask height must be a positive integer.\");\n }\n if (data.length !== width * height) {\n throw new Error(\"Mask data length must equal width * height.\");\n }\n }\n\n function centerRectToTopLeftRect(rect) {\n return {\n height: rect.height,\n width: rect.width,\n x: rect.x - rect.width / 2,\n y: rect.y - rect.height / 2,\n };\n }\n function getPointsRect(points) {\n if (points.length === 0) {\n return undefined;\n }\n let minX = Number.POSITIVE_INFINITY;\n let minY = Number.POSITIVE_INFINITY;\n let maxX = Number.NEGATIVE_INFINITY;\n let maxY = Number.NEGATIVE_INFINITY;\n for (const point of points) {\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n return {\n height: maxY - minY,\n width: maxX - minX,\n x: (minX + maxX) / 2,\n y: (minY + maxY) / 2,\n };\n }\n\n var DetectionPickTarget;\n (function (DetectionPickTarget) {\n DetectionPickTarget[\"Box\"] = \"box\";\n DetectionPickTarget[\"Edge\"] = \"edge\";\n DetectionPickTarget[\"Keypoint\"] = \"keypoint\";\n DetectionPickTarget[\"Label\"] = \"label\";\n DetectionPickTarget[\"Mask\"] = \"mask\";\n DetectionPickTarget[\"Polygon\"] = \"polygon\";\n DetectionPickTarget[\"Polyline\"] = \"polyline\";\n })(DetectionPickTarget || (DetectionPickTarget = {}));\n var MediaInteractionMode;\n (function (MediaInteractionMode) {\n MediaInteractionMode[\"Always\"] = \"always\";\n MediaInteractionMode[\"Disabled\"] = \"disabled\";\n MediaInteractionMode[\"PausedOnly\"] = \"pausedOnly\";\n })(MediaInteractionMode || (MediaInteractionMode = {}));\n\n var AnnotationGeometryKind;\n (function (AnnotationGeometryKind) {\n AnnotationGeometryKind[\"Box\"] = \"box\";\n AnnotationGeometryKind[\"Polygon\"] = \"polygon\";\n AnnotationGeometryKind[\"Polyline\"] = \"polyline\";\n AnnotationGeometryKind[\"Keypoints\"] = \"keypoints\";\n AnnotationGeometryKind[\"Mask\"] = \"mask\";\n })(AnnotationGeometryKind || (AnnotationGeometryKind = {}));\n var AnnotationGestureStateKind;\n (function (AnnotationGestureStateKind) {\n AnnotationGestureStateKind[\"Idle\"] = \"idle\";\n AnnotationGestureStateKind[\"Creating\"] = \"creating\";\n AnnotationGestureStateKind[\"Moving\"] = \"moving\";\n AnnotationGestureStateKind[\"Resizing\"] = \"resizing\";\n AnnotationGestureStateKind[\"DragSelecting\"] = \"dragSelecting\";\n })(AnnotationGestureStateKind || (AnnotationGestureStateKind = {}));\n var AnnotationHandleKind;\n (function (AnnotationHandleKind) {\n AnnotationHandleKind[\"Resize\"] = \"resize\";\n AnnotationHandleKind[\"Vertex\"] = \"vertex\";\n AnnotationHandleKind[\"AddVertex\"] = \"addVertex\";\n AnnotationHandleKind[\"Keypoint\"] = \"keypoint\";\n })(AnnotationHandleKind || (AnnotationHandleKind = {}));\n\n /** Alignment for strokes applied to closed rendered geometry. */\n var StrokeAlignment;\n (function (StrokeAlignment) {\n StrokeAlignment[\"Inside\"] = \"inside\";\n StrokeAlignment[\"Center\"] = \"center\";\n StrokeAlignment[\"Outside\"] = \"outside\";\n })(StrokeAlignment || (StrokeAlignment = {}));\n\n var BoxShape;\n (function (BoxShape) {\n BoxShape[\"Rect\"] = \"rect\";\n BoxShape[\"RoundedRect\"] = \"roundedRect\";\n })(BoxShape || (BoxShape = {}));\n\n var FocusTargetMode;\n (function (FocusTargetMode) {\n FocusTargetMode[\"Hovered\"] = \"hovered\";\n FocusTargetMode[\"Selected\"] = \"selected\";\n FocusTargetMode[\"HoveredAndSelected\"] = \"hoveredAndSelected\";\n FocusTargetMode[\"Ambient\"] = \"ambient\";\n })(FocusTargetMode || (FocusTargetMode = {}));\n\n var DetectionInteractionState;\n (function (DetectionInteractionState) {\n DetectionInteractionState[\"Hovered\"] = \"hovered\";\n DetectionInteractionState[\"Selected\"] = \"selected\";\n })(DetectionInteractionState || (DetectionInteractionState = {}));\n\n var LabelPlacement;\n (function (LabelPlacement) {\n LabelPlacement[\"Top\"] = \"top\";\n LabelPlacement[\"Bottom\"] = \"bottom\";\n LabelPlacement[\"InsideTop\"] = \"insideTop\";\n LabelPlacement[\"InsideBottom\"] = \"insideBottom\";\n LabelPlacement[\"Center\"] = \"center\";\n })(LabelPlacement || (LabelPlacement = {}));\n var LabelVisibilityMode;\n (function (LabelVisibilityMode) {\n LabelVisibilityMode[\"Always\"] = \"always\";\n LabelVisibilityMode[\"HoveredOnly\"] = \"hoveredOnly\";\n })(LabelVisibilityMode || (LabelVisibilityMode = {}));\n\n var MaskRenderMode;\n (function (MaskRenderMode) {\n MaskRenderMode[\"FillAndStroke\"] = \"fillAndStroke\";\n MaskRenderMode[\"FillOnly\"] = \"fillOnly\";\n MaskRenderMode[\"StrokeOnly\"] = \"strokeOnly\";\n })(MaskRenderMode || (MaskRenderMode = {}));\n\n /**\n * Discriminator for renderer-neutral shape draw instructions.\n */\n var ShapeInstructionKind;\n (function (ShapeInstructionKind) {\n ShapeInstructionKind[\"Ellipse\"] = \"ellipse\";\n ShapeInstructionKind[\"Marker\"] = \"marker\";\n ShapeInstructionKind[\"Path\"] = \"path\";\n })(ShapeInstructionKind || (ShapeInstructionKind = {}));\n var MarkerShape;\n (function (MarkerShape) {\n MarkerShape[\"Circle\"] = \"circle\";\n MarkerShape[\"Cross\"] = \"cross\";\n MarkerShape[\"Square\"] = \"square\";\n MarkerShape[\"Triangle\"] = \"triangle\";\n })(MarkerShape || (MarkerShape = {}));\n /**\n * Coordinate space for marker sizing. Media sizes scale with the viewport;\n * screen sizes stay constant on screen, matching stroke-width semantics.\n */\n var MarkerSizeSpace;\n (function (MarkerSizeSpace) {\n MarkerSizeSpace[\"Media\"] = \"media\";\n MarkerSizeSpace[\"Screen\"] = \"screen\";\n })(MarkerSizeSpace || (MarkerSizeSpace = {}));\n\n var KeypointMarkerShape;\n (function (KeypointMarkerShape) {\n KeypointMarkerShape[\"Circle\"] = \"circle\";\n KeypointMarkerShape[\"Cross\"] = \"cross\";\n })(KeypointMarkerShape || (KeypointMarkerShape = {}));\n function rasterizePolygonToMask(points, dimensions) {\n const data = createEmptyMask(dimensions);\n if (points.length < 3) {\n return data;\n }\n const bounds = centerRectToTopLeftRect(getPointsRect(points));\n const startY = Math.max(0, Math.floor(bounds.y));\n const endY = Math.min(dimensions.height - 1, Math.ceil(bounds.y + bounds.height));\n for (let y = startY; y <= endY; y += 1) {\n const scanY = y + 0.5;\n const intersections = [];\n for (let index = 0; index < points.length; index += 1) {\n const current = points[index];\n const next = points[(index + 1) % points.length];\n if ((current.y <= scanY && next.y > scanY) ||\n (next.y <= scanY && current.y > scanY)) {\n const ratio = (scanY - current.y) / (next.y - current.y);\n intersections.push(current.x + ratio * (next.x - current.x));\n }\n }\n intersections.sort((left, right) => left - right);\n for (let index = 0; index < intersections.length - 1; index += 2) {\n const left = Math.max(0, Math.ceil(intersections[index]));\n const right = Math.min(dimensions.width - 1, Math.floor(intersections[index + 1]));\n for (let x = left; x <= right; x += 1) {\n data[y * dimensions.width + x] = 1;\n }\n }\n }\n return data;\n }\n function createEmptyMask(dimensions) {\n if (!Number.isInteger(dimensions.width) ||\n dimensions.width <= 0 ||\n !Number.isInteger(dimensions.height) ||\n dimensions.height <= 0) {\n throw new Error(\"Media dimensions must be positive integers.\");\n }\n return new Uint8Array(dimensions.width * dimensions.height);\n }\n\n const MAX_ID_MASK_PALETTE_ENTRIES = 64;\n const MAX_ID_MASK_STROKE_WIDTH = 16;\n function createIdMaskFrame(instructions) {\n if (instructions.length === 0) {\n return undefined;\n }\n const width = Math.max(...instructions.map(({ mask }) => mask.width));\n const height = Math.max(...instructions.map(({ mask }) => mask.height));\n const data = new Uint8Array(new ArrayBuffer(width * height));\n const fillPalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));\n const strokePalette = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4 * 4));\n const strokeWidths = new Float32Array(new ArrayBuffer(MAX_ID_MASK_PALETTE_ENTRIES * 4));\n let hasStroke = false;\n let maxStrokeWidth = 0;\n for (const instruction of instructions) {\n const detectionMaskId = instruction.detectionIndex + 1;\n if (detectionMaskId <= 0 ||\n detectionMaskId >= MAX_ID_MASK_PALETTE_ENTRIES) {\n return undefined;\n }\n writePaletteEntry(fillPalette, detectionMaskId, instruction.color, instruction.alpha);\n if (instruction.stroke && instruction.stroke.width > 0) {\n const strokeWidth = Math.min(Math.max(0, instruction.stroke.width), MAX_ID_MASK_STROKE_WIDTH);\n hasStroke = true;\n strokeWidths[detectionMaskId] = strokeWidth;\n maxStrokeWidth = Math.max(maxStrokeWidth, strokeWidth);\n writePaletteEntry(strokePalette, detectionMaskId, instruction.stroke.color, instruction.stroke.alpha);\n }\n const decodedMask = decodeCompressedRleMask(instruction.mask);\n for (let y = 0; y < decodedMask.height; y += 1) {\n for (let x = 0; x < decodedMask.width; x += 1) {\n const maskOffset = y * decodedMask.width + x;\n if (decodedMask.data[maskOffset]) {\n data[y * width + x] = detectionMaskId;\n }\n }\n }\n }\n return {\n data,\n fillPalette,\n hasStroke,\n height,\n maxStrokeWidth,\n strokePalette,\n strokeWidths,\n width,\n };\n }\n function writePaletteEntry(palette, id, color, alpha) {\n const offset = id * 4;\n palette[offset] = ((color >> 16) & 0xff) / 255;\n palette[offset + 1] = ((color >> 8) & 0xff) / 255;\n palette[offset + 2] = (color & 0xff) / 255;\n palette[offset + 3] = Math.max(0, Math.min(alpha, 1));\n }\n\n var MediaRendererFit;\n (function (MediaRendererFit) {\n /**\n * Preserve media aspect ratio and fit the full frame inside the canvas.\n */\n MediaRendererFit[\"Contain\"] = \"contain\";\n /**\n * Preserve media aspect ratio and fill the canvas, cropping if necessary.\n */\n MediaRendererFit[\"Cover\"] = \"cover\";\n })(MediaRendererFit || (MediaRendererFit = {}));\n /**\n * Playback lifecycle state reported by a platform renderer.\n */\n var MediaRendererPlaybackState;\n (function (MediaRendererPlaybackState) {\n MediaRendererPlaybackState[\"Loading\"] = \"loading\";\n MediaRendererPlaybackState[\"Ready\"] = \"ready\";\n MediaRendererPlaybackState[\"Playing\"] = \"playing\";\n MediaRendererPlaybackState[\"Buffering\"] = \"buffering\";\n MediaRendererPlaybackState[\"Paused\"] = \"paused\";\n MediaRendererPlaybackState[\"Error\"] = \"error\";\n MediaRendererPlaybackState[\"Destroyed\"] = \"destroyed\";\n })(MediaRendererPlaybackState || (MediaRendererPlaybackState = {}));\n /**\n * Lower-level media source readiness.\n */\n var MediaSourceStatus;\n (function (MediaSourceStatus) {\n MediaSourceStatus[\"Loading\"] = \"loading\";\n MediaSourceStatus[\"Ready\"] = \"ready\";\n MediaSourceStatus[\"Error\"] = \"error\";\n MediaSourceStatus[\"Destroyed\"] = \"destroyed\";\n })(MediaSourceStatus || (MediaSourceStatus = {}));\n\n /**\n * Media-session operating mode.\n *\n * Core owns the names because file-like and stream-like lifecycle choices are\n * platform-neutral. Platform packages decide how these modes tune media,\n * storage, and renderer defaults.\n */\n var MediaSessionMode;\n (function (MediaSessionMode) {\n /**\n * Finite media. Defaults usually favor seek/replay and persistent detection\n * storage.\n */\n MediaSessionMode[\"File\"] = \"file\";\n /**\n * Live or append-only media. Defaults usually favor rolling windows and\n * bounded retention.\n */\n MediaSessionMode[\"Stream\"] = \"stream\";\n })(MediaSessionMode || (MediaSessionMode = {}));\n /**\n * Aggregate lifecycle state for a media session.\n */\n var MediaSessionStatus;\n (function (MediaSessionStatus) {\n MediaSessionStatus[\"Buffering\"] = \"buffering\";\n MediaSessionStatus[\"Destroyed\"] = \"destroyed\";\n MediaSessionStatus[\"Error\"] = \"error\";\n MediaSessionStatus[\"Loading\"] = \"loading\";\n MediaSessionStatus[\"Paused\"] = \"paused\";\n MediaSessionStatus[\"Playing\"] = \"playing\";\n MediaSessionStatus[\"Processing\"] = \"processing\";\n MediaSessionStatus[\"Ready\"] = \"ready\";\n })(MediaSessionStatus || (MediaSessionStatus = {}));\n /**\n * Subsystem currently affecting session readiness or presentation.\n */\n var MediaSessionActivityKind;\n (function (MediaSessionActivityKind) {\n MediaSessionActivityKind[\"DetectionsBuffering\"] = \"detectionsBuffering\";\n MediaSessionActivityKind[\"DetectionsLoading\"] = \"detectionsLoading\";\n MediaSessionActivityKind[\"Error\"] = \"error\";\n MediaSessionActivityKind[\"MediaNormalizing\"] = \"mediaNormalizing\";\n MediaSessionActivityKind[\"MediaOpening\"] = \"mediaOpening\";\n MediaSessionActivityKind[\"PlaybackBuffering\"] = \"playbackBuffering\";\n MediaSessionActivityKind[\"RenderPreparing\"] = \"renderPreparing\";\n })(MediaSessionActivityKind || (MediaSessionActivityKind = {}));\n /**\n * State of one session activity.\n */\n var MediaSessionActivityStatus;\n (function (MediaSessionActivityStatus) {\n MediaSessionActivityStatus[\"Error\"] = \"error\";\n MediaSessionActivityStatus[\"Running\"] = \"running\";\n MediaSessionActivityStatus[\"Waiting\"] = \"waiting\";\n })(MediaSessionActivityStatus || (MediaSessionActivityStatus = {}));\n\n const PNG_SIGNATURE = new Uint8Array([\n 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,\n ]);\n function compositeMaskFrame(instructions) {\n const maskInstructions = materializeMaskInstructions(instructions);\n if (maskInstructions.length === 0) {\n return undefined;\n }\n const width = Math.max(...maskInstructions.map(({ mask }) => mask.width));\n const height = Math.max(...maskInstructions.map(({ mask }) => mask.height));\n const data = new Uint8ClampedArray(new ArrayBuffer(width * height * 4));\n for (const instruction of maskInstructions) {\n compositeInstruction(data, width, instruction);\n }\n return { data, height, width };\n }\n function createMaskIdFrame(instructions) {\n return createIdMaskFrame(materializeMaskInstructions(instructions));\n }\n async function createPngIdMaskFrame(instructions) {\n const frame = createMaskIdFrame(instructions);\n if (!frame) {\n return undefined;\n }\n return {\n ...frame,\n png: await encodeGrayscalePng({\n height: frame.height,\n pixels: frame.data,\n width: frame.width,\n }),\n };\n }\n function compositeInstruction(rgba, canvasWidth, instruction) {\n const decodedMask = decodeCompressedRleMask(instruction.mask);\n const fill = resolveRgbaColor(instruction.color, instruction.alpha);\n compositeMaskFill(rgba, canvasWidth, decodedMask, fill);\n if (instruction.stroke) {\n compositeMaskStroke(rgba, canvasWidth, decodedMask, instruction.stroke);\n }\n }\n function materializeMaskInstructions(instructions) {\n return instructions.map((instruction) => {\n if (instruction.mask) {\n return instruction;\n }\n const { height, points, width } = instruction.polygon;\n return {\n alpha: instruction.alpha,\n color: instruction.color,\n detectionIndex: instruction.detectionIndex,\n mask: encodeBinaryMask(rasterizePolygonToMask(points, { height, width }), width, height),\n stroke: instruction.stroke,\n };\n });\n }\n function compositeMaskFill(rgba, canvasWidth, decodedMask, fill) {\n for (let y = 0; y < decodedMask.height; y += 1) {\n for (let x = 0; x < decodedMask.width; x += 1) {\n const maskOffset = y * decodedMask.width + x;\n if (!decodedMask.data[maskOffset]) {\n continue;\n }\n writePixel(rgba, canvasWidth, x, y, fill);\n }\n }\n }\n function compositeMaskStroke(rgba, canvasWidth, decodedMask, stroke) {\n const width = Math.round(stroke.width);\n if (width <= 0) {\n return;\n }\n const strokeColor = resolveRgbaColor(stroke.color, stroke.alpha);\n for (let y = 0; y < decodedMask.height; y += 1) {\n for (let x = 0; x < decodedMask.width; x += 1) {\n if (!isMaskPixel(decodedMask, x, y) ||\n !isBoundaryPixel(decodedMask, x, y)) {\n continue;\n }\n for (let offsetY = -width; offsetY <= width; offsetY += 1) {\n for (let offsetX = -width; offsetX <= width; offsetX += 1) {\n const strokeX = x + offsetX;\n const strokeY = y + offsetY;\n if (isOutsideMaskBounds(decodedMask, strokeX, strokeY) ||\n isMaskPixel(decodedMask, strokeX, strokeY)) {\n continue;\n }\n writePixel(rgba, canvasWidth, strokeX, strokeY, strokeColor);\n }\n }\n }\n }\n }\n function isBoundaryPixel(mask, x, y) {\n for (let offsetY = -1; offsetY <= 1; offsetY += 1) {\n for (let offsetX = -1; offsetX <= 1; offsetX += 1) {\n if (offsetX === 0 && offsetY === 0) {\n continue;\n }\n const neighborX = x + offsetX;\n const neighborY = y + offsetY;\n if (isOutsideMaskBounds(mask, neighborX, neighborY) ||\n !isMaskPixel(mask, neighborX, neighborY)) {\n return true;\n }\n }\n }\n return false;\n }\n function isMaskPixel(mask, x, y) {\n return mask.data[y * mask.width + x] === 1;\n }\n function isOutsideMaskBounds(mask, x, y) {\n return x < 0 || y < 0 || x >= mask.width || y >= mask.height;\n }\n function resolveRgbaColor(color, alpha) {\n return {\n alpha: Math.round(Math.max(0, Math.min(alpha, 1)) * 255),\n blue: color & 0xff,\n green: (color >> 8) & 0xff,\n red: (color >> 16) & 0xff,\n };\n }\n function writePixel(rgba, canvasWidth, x, y, color) {\n const rgbaOffset = (y * canvasWidth + x) * 4;\n rgba[rgbaOffset] = color.red;\n rgba[rgbaOffset + 1] = color.green;\n rgba[rgbaOffset + 2] = color.blue;\n rgba[rgbaOffset + 3] = color.alpha;\n }\n async function encodeGrayscalePng(options) {\n if (typeof CompressionStream === \"undefined\") {\n throw new Error(\"CompressionStream is required to encode PNG ID masks.\");\n }\n const rawScanlines = createFilterlessPngScanlines(options);\n const ihdr = new Uint8Array(13);\n const ihdrView = new DataView(ihdr.buffer);\n ihdrView.setUint32(0, options.width);\n ihdrView.setUint32(4, options.height);\n ihdr[8] = 8;\n ihdr[9] = 0;\n ihdr[10] = 0;\n ihdr[11] = 0;\n ihdr[12] = 0;\n const compressed = new Uint8Array(await new Response(new Blob([rawScanlines])\n .stream()\n .pipeThrough(new CompressionStream(\"deflate\"))).arrayBuffer());\n return concatUint8Arrays([\n PNG_SIGNATURE,\n createPngChunk(\"IHDR\", ihdr),\n createPngChunk(\"IDAT\", compressed),\n createPngChunk(\"IEND\", new Uint8Array(0)),\n ]);\n }\n function createFilterlessPngScanlines(options) {\n const rowStride = options.width + 1;\n const scanlines = new Uint8Array(rowStride * options.height);\n for (let y = 0; y < options.height; y += 1) {\n const sourceOffset = y * options.width;\n const targetOffset = y * rowStride;\n scanlines[targetOffset] = 0;\n scanlines.set(options.pixels.subarray(sourceOffset, sourceOffset + options.width), targetOffset + 1);\n }\n return scanlines;\n }\n function createPngChunk(type, data) {\n const typeBytes = new TextEncoder().encode(type);\n const chunk = new Uint8Array(12 + data.length);\n const view = new DataView(chunk.buffer);\n view.setUint32(0, data.length);\n chunk.set(typeBytes, 4);\n chunk.set(data, 8);\n view.setUint32(8 + data.length, crc32(concatUint8Arrays([typeBytes, data])));\n return chunk;\n }\n const crc32Table = createCrc32Table();\n function createCrc32Table() {\n const table = new Uint32Array(256);\n for (let index = 0; index < table.length; index += 1) {\n let value = index;\n for (let bit = 0; bit < 8; bit += 1) {\n value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;\n }\n table[index] = value >>> 0;\n }\n return table;\n }\n function crc32(bytes) {\n let crc = 0xffffffff;\n for (const byte of bytes) {\n crc = crc32Table[(crc ^ byte) & 0xff] ^ (crc >>> 8);\n }\n return (crc ^ 0xffffffff) >>> 0;\n }\n function concatUint8Arrays(chunks) {\n const totalLength = chunks.reduce((total, chunk) => total + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n return result;\n }\n\n var PreparedMaskFrameKind;\n (function (PreparedMaskFrameKind) {\n PreparedMaskFrameKind[\"PngIdMask\"] = \"pngIdMask\";\n PreparedMaskFrameKind[\"RgbaImage\"] = \"rgbaImage\";\n })(PreparedMaskFrameKind || (PreparedMaskFrameKind = {}));\n\n var MaskPreparationWorkerMessageType;\n (function (MaskPreparationWorkerMessageType) {\n MaskPreparationWorkerMessageType[\"Complete\"] = \"complete\";\n MaskPreparationWorkerMessageType[\"Empty\"] = \"empty\";\n MaskPreparationWorkerMessageType[\"Error\"] = \"error\";\n MaskPreparationWorkerMessageType[\"Prepare\"] = \"prepare\";\n })(MaskPreparationWorkerMessageType || (MaskPreparationWorkerMessageType = {}));\n\n const workerScope = globalThis;\n workerScope.addEventListener(\"message\", (event) => {\n const message = event.data;\n if (message.type !== MaskPreparationWorkerMessageType.Prepare) {\n return;\n }\n void prepareMaskFrame(message);\n });\n async function prepareMaskFrame(message) {\n try {\n const pngIdMaskFrame = await createPngIdMaskWorkerResponse(message);\n if (pngIdMaskFrame) {\n workerScope.postMessage(pngIdMaskFrame, [\n pngIdMaskFrame.imageBitmap,\n pngIdMaskFrame.png.buffer,\n pngIdMaskFrame.fillPalette.buffer,\n pngIdMaskFrame.strokePalette.buffer,\n pngIdMaskFrame.strokeWidths.buffer,\n ]);\n return;\n }\n const compositedFrame = compositeMaskFrame(message.job.instructions);\n if (!compositedFrame) {\n workerScope.postMessage({\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Empty,\n });\n return;\n }\n const imageData = new ImageData(compositedFrame.data, compositedFrame.width, compositedFrame.height);\n const imageBitmap = createImageBitmapFromImageData(imageData);\n const idMaskData = createMaskIdFrame(message.job.instructions)?.data;\n if (imageBitmap) {\n workerScope.postMessage({\n imageBitmap,\n idMaskData,\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Complete,\n }, [...(idMaskData ? [idMaskData.buffer] : []), imageBitmap]);\n return;\n }\n workerScope.postMessage({\n idMaskData,\n imageData,\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Complete,\n }, [...(idMaskData ? [idMaskData.buffer] : []), imageData.data.buffer]);\n }\n catch (error) {\n workerScope.postMessage({\n error: error instanceof Error\n ? error.message\n : \"Unable to prepare mask frame.\",\n key: message.job.key,\n requestId: message.requestId,\n type: MaskPreparationWorkerMessageType.Error,\n });\n }\n }\n async function createPngIdMaskWorkerResponse(message) {\n if (typeof Blob === \"undefined\" ||\n typeof createImageBitmap === \"undefined\" ||\n typeof CompressionStream === \"undefined\") {\n return undefined;\n }\n let frame;\n try {\n frame = await createPngIdMaskFrame(message.job.instructions);\n }\n catch {\n return undefined;\n }\n if (!frame) {\n return undefined;\n }\n let imageBitmap;\n try {\n imageBitmap = await createImageBitmap(new Blob([frame.png], { type: \"image/png\" }));\n }\n catch {\n return undefined;\n }\n return {\n artifactKind: PreparedMaskFrameKind.PngIdMask,\n fillPalette: frame.fillPalette,\n hasStroke: frame.hasStroke,\n imageBitmap,\n key: message.job.key,\n maxStrokeWidth: frame.maxStrokeWidth,\n png: frame.png,\n requestId: message.requestId,\n strokePalette: frame.strokePalette,\n strokeWidths: frame.strokeWidths,\n type: MaskPreparationWorkerMessageType.Complete,\n };\n }\n function createImageBitmapFromImageData(imageData) {\n if (typeof OffscreenCanvas === \"undefined\") {\n return null;\n }\n const canvas = new OffscreenCanvas(imageData.width, imageData.height);\n const context = canvas.getContext(\"2d\");\n if (!context) {\n return null;\n }\n context.putImageData(imageData, 0, 0);\n return canvas.transferToImageBitmap();\n }\n\n})();";
|
|
5302
5905
|
|
|
5303
5906
|
const DEFAULT_WORKER_NAME = "supervision-js-render-preparation";
|
|
5304
5907
|
let defaultWorkerUrl;
|
|
@@ -5361,85 +5964,6 @@ var MaskPreparationWorkerMessageType;
|
|
|
5361
5964
|
MaskPreparationWorkerMessageType["Prepare"] = "prepare";
|
|
5362
5965
|
})(MaskPreparationWorkerMessageType || (MaskPreparationWorkerMessageType = {}));
|
|
5363
5966
|
|
|
5364
|
-
function createWorkerRpcClient(options) {
|
|
5365
|
-
let failureMessage = null;
|
|
5366
|
-
let isDestroyed = false;
|
|
5367
|
-
let nextRequestId = 1;
|
|
5368
|
-
const pendingRequests = new Map();
|
|
5369
|
-
options.worker.addEventListener("message", (event) => {
|
|
5370
|
-
handleWorkerMessage(event.data);
|
|
5371
|
-
});
|
|
5372
|
-
options.worker.addEventListener("error", (event) => {
|
|
5373
|
-
rejectPendingRequests(event.message || options.defaultErrorMessage);
|
|
5374
|
-
});
|
|
5375
|
-
options.worker.addEventListener("messageerror", () => {
|
|
5376
|
-
rejectPendingRequests(`${options.defaultErrorMessage}: message error.`);
|
|
5377
|
-
});
|
|
5378
|
-
return {
|
|
5379
|
-
destroy() {
|
|
5380
|
-
if (isDestroyed) {
|
|
5381
|
-
return;
|
|
5382
|
-
}
|
|
5383
|
-
isDestroyed = true;
|
|
5384
|
-
rejectPendingRequests("Worker RPC client has been destroyed.", {
|
|
5385
|
-
markFailure: false,
|
|
5386
|
-
});
|
|
5387
|
-
options.worker.terminate();
|
|
5388
|
-
},
|
|
5389
|
-
getFailureMessage() {
|
|
5390
|
-
return failureMessage;
|
|
5391
|
-
},
|
|
5392
|
-
request(message) {
|
|
5393
|
-
if (isDestroyed) {
|
|
5394
|
-
return Promise.reject(new Error("Worker RPC client has been destroyed."));
|
|
5395
|
-
}
|
|
5396
|
-
if (failureMessage) {
|
|
5397
|
-
return Promise.reject(new Error(failureMessage));
|
|
5398
|
-
}
|
|
5399
|
-
const request = {
|
|
5400
|
-
...message,
|
|
5401
|
-
requestId: nextRequestId,
|
|
5402
|
-
};
|
|
5403
|
-
nextRequestId += 1;
|
|
5404
|
-
return new Promise((resolve, reject) => {
|
|
5405
|
-
pendingRequests.set(request.requestId, { reject, resolve });
|
|
5406
|
-
try {
|
|
5407
|
-
options.worker.postMessage(request);
|
|
5408
|
-
}
|
|
5409
|
-
catch (error) {
|
|
5410
|
-
pendingRequests.delete(request.requestId);
|
|
5411
|
-
failureMessage = getErrorMessage$2(error, options.defaultErrorMessage);
|
|
5412
|
-
reject(new Error(failureMessage));
|
|
5413
|
-
}
|
|
5414
|
-
});
|
|
5415
|
-
},
|
|
5416
|
-
};
|
|
5417
|
-
function handleWorkerMessage(message) {
|
|
5418
|
-
if (!options.isResponse(message)) {
|
|
5419
|
-
return;
|
|
5420
|
-
}
|
|
5421
|
-
const pendingRequest = pendingRequests.get(message.requestId);
|
|
5422
|
-
if (!pendingRequest) {
|
|
5423
|
-
options.onOrphanedResponse?.(message);
|
|
5424
|
-
return;
|
|
5425
|
-
}
|
|
5426
|
-
pendingRequests.delete(message.requestId);
|
|
5427
|
-
pendingRequest.resolve(message);
|
|
5428
|
-
}
|
|
5429
|
-
function rejectPendingRequests(message, options = { markFailure: true }) {
|
|
5430
|
-
if (options.markFailure) {
|
|
5431
|
-
failureMessage = message;
|
|
5432
|
-
}
|
|
5433
|
-
for (const pendingRequest of pendingRequests.values()) {
|
|
5434
|
-
pendingRequest.reject(new Error(message));
|
|
5435
|
-
}
|
|
5436
|
-
pendingRequests.clear();
|
|
5437
|
-
}
|
|
5438
|
-
}
|
|
5439
|
-
function getErrorMessage$2(error, fallback) {
|
|
5440
|
-
return error instanceof Error ? error.message : fallback;
|
|
5441
|
-
}
|
|
5442
|
-
|
|
5443
5967
|
function createMaskFramePreparer(options = {}) {
|
|
5444
5968
|
const mainThreadPreparer = createMainThreadMaskFramePreparer(RenderPreparationWorkerStatus.Disabled);
|
|
5445
5969
|
const mode = options.renderPreparation?.mode ?? RenderPreparationMode.Auto;
|
|
@@ -5573,6 +6097,7 @@ function createMainThreadMaskFramePreparer(workerStatus, message = null) {
|
|
|
5573
6097
|
canvas.height = 0;
|
|
5574
6098
|
},
|
|
5575
6099
|
height: compositedFrame.height,
|
|
6100
|
+
idMaskData: createMaskIdFrame(job.instructions)?.data,
|
|
5576
6101
|
key: job.key,
|
|
5577
6102
|
kind: PreparedMaskFrameKind.RgbaImage,
|
|
5578
6103
|
source: canvas,
|
|
@@ -5747,6 +6272,7 @@ function createPreparedFrameFromWorkerResponse(message) {
|
|
|
5747
6272
|
message.imageBitmap?.close();
|
|
5748
6273
|
},
|
|
5749
6274
|
height: message.imageBitmap.height,
|
|
6275
|
+
idMaskData: message.idMaskData,
|
|
5750
6276
|
key: message.key,
|
|
5751
6277
|
kind: PreparedMaskFrameKind.RgbaImage,
|
|
5752
6278
|
source: message.imageBitmap,
|
|
@@ -5770,6 +6296,7 @@ function createPreparedFrameFromWorkerResponse(message) {
|
|
|
5770
6296
|
canvas.height = 0;
|
|
5771
6297
|
},
|
|
5772
6298
|
height: message.imageData.height,
|
|
6299
|
+
idMaskData: message.idMaskData,
|
|
5773
6300
|
key: message.key,
|
|
5774
6301
|
kind: PreparedMaskFrameKind.RgbaImage,
|
|
5775
6302
|
source: canvas,
|
|
@@ -6638,7 +7165,7 @@ function createPixiIdMaskShaderRenderer(options) {
|
|
|
6638
7165
|
autoGenerateMipmaps: false,
|
|
6639
7166
|
dynamic: false,
|
|
6640
7167
|
height: 1,
|
|
6641
|
-
resource: createPlaceholderCanvas(),
|
|
7168
|
+
resource: createPlaceholderCanvas$1(),
|
|
6642
7169
|
scaleMode: "nearest",
|
|
6643
7170
|
width: 1,
|
|
6644
7171
|
});
|
|
@@ -6728,7 +7255,7 @@ function createPixiIdMaskShaderRenderer(options) {
|
|
|
6728
7255
|
mesh.shader = shader;
|
|
6729
7256
|
}
|
|
6730
7257
|
}
|
|
6731
|
-
function createPlaceholderCanvas() {
|
|
7258
|
+
function createPlaceholderCanvas$1() {
|
|
6732
7259
|
const canvas = document.createElement("canvas");
|
|
6733
7260
|
canvas.height = 1;
|
|
6734
7261
|
canvas.width = 1;
|
|
@@ -6878,15 +7405,219 @@ void main(void) {
|
|
|
6878
7405
|
}
|
|
6879
7406
|
`;
|
|
6880
7407
|
|
|
7408
|
+
/**
|
|
7409
|
+
* Renders blurred, per-detection-colored copies of the prepared id mask
|
|
7410
|
+
* beneath the mask layer. Each pass maps mask ids through a halo palette and
|
|
7411
|
+
* a GPU blur filter turns that coverage into a smooth glow following the
|
|
7412
|
+
* exact mask silhouette, reusing the already-prepared mask texture. Passes
|
|
7413
|
+
* are pooled and one runs per distinct spread.
|
|
7414
|
+
*/
|
|
7415
|
+
function createPixiMaskHaloRenderer(options) {
|
|
7416
|
+
const display = new options.Container();
|
|
7417
|
+
const placeholderSource = new options.ImageSource({
|
|
7418
|
+
autoGenerateMipmaps: false,
|
|
7419
|
+
dynamic: false,
|
|
7420
|
+
height: 1,
|
|
7421
|
+
resource: createPlaceholderCanvas(),
|
|
7422
|
+
scaleMode: "nearest",
|
|
7423
|
+
width: 1,
|
|
7424
|
+
});
|
|
7425
|
+
const geometry = new options.MeshGeometry({
|
|
7426
|
+
indices: new Uint32Array([0, 1, 2, 0, 2, 3]),
|
|
7427
|
+
positions: new Float32Array([
|
|
7428
|
+
0,
|
|
7429
|
+
0,
|
|
7430
|
+
options.mediaWidth,
|
|
7431
|
+
0,
|
|
7432
|
+
options.mediaWidth,
|
|
7433
|
+
options.mediaHeight,
|
|
7434
|
+
0,
|
|
7435
|
+
options.mediaHeight,
|
|
7436
|
+
]),
|
|
7437
|
+
shrinkBuffersToFit: true,
|
|
7438
|
+
topology: "triangle-list",
|
|
7439
|
+
uvs: new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]),
|
|
7440
|
+
});
|
|
7441
|
+
const passes = [];
|
|
7442
|
+
display.visible = false;
|
|
7443
|
+
return {
|
|
7444
|
+
destroy() {
|
|
7445
|
+
for (const pass of passes) {
|
|
7446
|
+
pass.mesh.destroy();
|
|
7447
|
+
pass.shader.destroy(true);
|
|
7448
|
+
}
|
|
7449
|
+
passes.length = 0;
|
|
7450
|
+
display.destroy();
|
|
7451
|
+
geometry.destroy();
|
|
7452
|
+
placeholderSource.destroy();
|
|
7453
|
+
},
|
|
7454
|
+
display,
|
|
7455
|
+
hide() {
|
|
7456
|
+
display.visible = false;
|
|
7457
|
+
},
|
|
7458
|
+
render(frame, texture, groups) {
|
|
7459
|
+
if (groups.length === 0) {
|
|
7460
|
+
display.visible = false;
|
|
7461
|
+
return;
|
|
7462
|
+
}
|
|
7463
|
+
for (const [index, group] of groups.entries()) {
|
|
7464
|
+
const pass = ensurePass(index);
|
|
7465
|
+
bindTexture(pass, texture.source);
|
|
7466
|
+
pass.uniforms.uniforms.uHaloPalette = group.palette;
|
|
7467
|
+
pass.uniforms.update();
|
|
7468
|
+
pass.blurFilter.strength = Math.max(group.spread, 0);
|
|
7469
|
+
pass.mesh.visible = true;
|
|
7470
|
+
}
|
|
7471
|
+
for (const pass of passes.slice(groups.length)) {
|
|
7472
|
+
pass.mesh.visible = false;
|
|
7473
|
+
}
|
|
7474
|
+
display.visible = true;
|
|
7475
|
+
},
|
|
7476
|
+
setOpacity(opacity) {
|
|
7477
|
+
display.alpha = opacity;
|
|
7478
|
+
},
|
|
7479
|
+
};
|
|
7480
|
+
function ensurePass(index) {
|
|
7481
|
+
const existing = passes[index];
|
|
7482
|
+
if (existing) {
|
|
7483
|
+
return existing;
|
|
7484
|
+
}
|
|
7485
|
+
const uniforms = new options.UniformGroup({
|
|
7486
|
+
uHaloPalette: {
|
|
7487
|
+
size: MAX_ID_MASK_PALETTE_ENTRIES,
|
|
7488
|
+
type: "vec4<f32>",
|
|
7489
|
+
value: new Float32Array(MAX_ID_MASK_PALETTE_ENTRIES * 4),
|
|
7490
|
+
},
|
|
7491
|
+
});
|
|
7492
|
+
const shader = createShader(uniforms);
|
|
7493
|
+
const mesh = new options.Mesh({ geometry, shader });
|
|
7494
|
+
const blurFilter = new options.BlurFilter({ quality: 4, strength: 8 });
|
|
7495
|
+
// Filters size their working texture from measured bounds, and a
|
|
7496
|
+
// custom-geometry mesh measures empty; an explicit bounds area keeps the
|
|
7497
|
+
// blur pass covering the full media rect.
|
|
7498
|
+
mesh.boundsArea = new options.Rectangle(0, 0, options.mediaWidth, options.mediaHeight);
|
|
7499
|
+
mesh.filters = [blurFilter];
|
|
7500
|
+
display.addChild(mesh);
|
|
7501
|
+
const pass = { blurFilter, mesh, shader, uniforms };
|
|
7502
|
+
passes.push(pass);
|
|
7503
|
+
return pass;
|
|
7504
|
+
}
|
|
7505
|
+
function bindTexture(pass, source) {
|
|
7506
|
+
try {
|
|
7507
|
+
pass.shader.resources.uTexture = source;
|
|
7508
|
+
pass.shader.resources.uSampler = source.style;
|
|
7509
|
+
}
|
|
7510
|
+
catch {
|
|
7511
|
+
try {
|
|
7512
|
+
pass.shader.destroy(true);
|
|
7513
|
+
}
|
|
7514
|
+
catch {
|
|
7515
|
+
// Pixi has already invalidated this shader's resource group.
|
|
7516
|
+
}
|
|
7517
|
+
pass.shader = createShader(pass.uniforms);
|
|
7518
|
+
pass.mesh.shader = pass.shader;
|
|
7519
|
+
pass.shader.resources.uTexture = source;
|
|
7520
|
+
pass.shader.resources.uSampler = source.style;
|
|
7521
|
+
}
|
|
7522
|
+
}
|
|
7523
|
+
function createShader(uniforms) {
|
|
7524
|
+
return options.Shader.from({
|
|
7525
|
+
gl: {
|
|
7526
|
+
fragment: maskHaloFragmentShader,
|
|
7527
|
+
vertex: maskHaloVertexShader,
|
|
7528
|
+
},
|
|
7529
|
+
resources: {
|
|
7530
|
+
haloUniforms: uniforms,
|
|
7531
|
+
uSampler: placeholderSource.style,
|
|
7532
|
+
uTexture: placeholderSource,
|
|
7533
|
+
},
|
|
7534
|
+
});
|
|
7535
|
+
}
|
|
7536
|
+
}
|
|
7537
|
+
/**
|
|
7538
|
+
* Builds one halo palette uniform: premultiplied halo color and alpha per
|
|
7539
|
+
* mask id, transparent for ids outside the group.
|
|
7540
|
+
*/
|
|
7541
|
+
function buildMaskHaloPalette(halos) {
|
|
7542
|
+
const palette = new Float32Array(MAX_ID_MASK_PALETTE_ENTRIES * 4);
|
|
7543
|
+
for (const [maskId, halo] of halos) {
|
|
7544
|
+
if (maskId <= 0 || maskId >= MAX_ID_MASK_PALETTE_ENTRIES) {
|
|
7545
|
+
continue;
|
|
7546
|
+
}
|
|
7547
|
+
const alpha = Math.max(0, Math.min(halo.alpha, 1));
|
|
7548
|
+
const offset = maskId * 4;
|
|
7549
|
+
palette[offset] = (((halo.color >> 16) & 0xff) / 255) * alpha;
|
|
7550
|
+
palette[offset + 1] = (((halo.color >> 8) & 0xff) / 255) * alpha;
|
|
7551
|
+
palette[offset + 2] = ((halo.color & 0xff) / 255) * alpha;
|
|
7552
|
+
palette[offset + 3] = alpha;
|
|
7553
|
+
}
|
|
7554
|
+
return palette;
|
|
7555
|
+
}
|
|
7556
|
+
function createPlaceholderCanvas() {
|
|
7557
|
+
const canvas = document.createElement("canvas");
|
|
7558
|
+
canvas.height = 1;
|
|
7559
|
+
canvas.width = 1;
|
|
7560
|
+
return canvas;
|
|
7561
|
+
}
|
|
7562
|
+
const maskHaloVertexShader = `#version 300 es
|
|
7563
|
+
precision highp float;
|
|
7564
|
+
|
|
7565
|
+
in vec2 aPosition;
|
|
7566
|
+
in vec2 aUV;
|
|
7567
|
+
|
|
7568
|
+
uniform mat3 uProjectionMatrix;
|
|
7569
|
+
uniform mat3 uWorldTransformMatrix;
|
|
7570
|
+
uniform vec4 uWorldColorAlpha;
|
|
7571
|
+
uniform mat3 uTransformMatrix;
|
|
7572
|
+
uniform vec4 uColor;
|
|
7573
|
+
|
|
7574
|
+
out vec2 vUV;
|
|
7575
|
+
out vec4 vColor;
|
|
7576
|
+
|
|
7577
|
+
void main(void) {
|
|
7578
|
+
mat3 modelViewProjectionMatrix =
|
|
7579
|
+
uProjectionMatrix * uWorldTransformMatrix * uTransformMatrix;
|
|
7580
|
+
|
|
7581
|
+
gl_Position =
|
|
7582
|
+
vec4((modelViewProjectionMatrix * vec3(aPosition, 1.0)).xy, 0.0, 1.0);
|
|
7583
|
+
vUV = aUV;
|
|
7584
|
+
vColor = uWorldColorAlpha * uColor;
|
|
7585
|
+
}
|
|
7586
|
+
`;
|
|
7587
|
+
const maskHaloFragmentShader = `#version 300 es
|
|
7588
|
+
precision highp float;
|
|
7589
|
+
precision highp int;
|
|
7590
|
+
|
|
7591
|
+
in vec2 vUV;
|
|
7592
|
+
in vec4 vColor;
|
|
7593
|
+
|
|
7594
|
+
uniform sampler2D uTexture;
|
|
7595
|
+
uniform vec4 uHaloPalette[${MAX_ID_MASK_PALETTE_ENTRIES}];
|
|
7596
|
+
|
|
7597
|
+
out vec4 finalColor;
|
|
7598
|
+
|
|
7599
|
+
void main(void) {
|
|
7600
|
+
float maskId = floor(texture(uTexture, vUV).r * 255.0 + 0.5);
|
|
7601
|
+
int paletteIndex =
|
|
7602
|
+
int(clamp(maskId, 0.0, float(${MAX_ID_MASK_PALETTE_ENTRIES - 1})));
|
|
7603
|
+
|
|
7604
|
+
finalColor = uHaloPalette[paletteIndex] * vColor;
|
|
7605
|
+
}
|
|
7606
|
+
`;
|
|
7607
|
+
|
|
6881
7608
|
const MAX_PENDING_MASK_HOLD_SECONDS = 0.05;
|
|
6882
7609
|
function createPixiMaskLayer(options) {
|
|
6883
7610
|
let mediaHeight = 0;
|
|
6884
7611
|
let mediaWidth = 0;
|
|
6885
7612
|
let idMaskRenderer;
|
|
7613
|
+
let haloRenderer;
|
|
7614
|
+
let currentMaskHaloStyle = options.maskHaloStyle ?? null;
|
|
7615
|
+
options.maskStyle;
|
|
6886
7616
|
let maskSprite;
|
|
6887
7617
|
let activeFrameKey = null;
|
|
6888
7618
|
let activeFrameMediaTime = null;
|
|
6889
7619
|
let activeIdMaskFrame = null;
|
|
7620
|
+
let activeRgbaMaskFrame = null;
|
|
6890
7621
|
let maskOpacity = resolveMaskStyleOpacity(options.maskStyle);
|
|
6891
7622
|
let maskPickCanvas;
|
|
6892
7623
|
let maskPickContext;
|
|
@@ -6894,6 +7625,7 @@ function createPixiMaskLayer(options) {
|
|
|
6894
7625
|
let visibleMaskMediaTime = null;
|
|
6895
7626
|
let isDestroyed = false;
|
|
6896
7627
|
const maskTextures = new Map();
|
|
7628
|
+
const haloTextures = new Map();
|
|
6897
7629
|
const preparedRenderWindow = createPreparedRenderWindow({
|
|
6898
7630
|
artifactKind: options.artifactKind,
|
|
6899
7631
|
detectionTimeline: options.detectionTimeline,
|
|
@@ -6939,10 +7671,19 @@ function createPixiMaskLayer(options) {
|
|
|
6939
7671
|
maskSprite.height = mediaHeight;
|
|
6940
7672
|
idMaskRenderer = createIdMaskRenderer();
|
|
6941
7673
|
idMaskRenderer?.setOpacity(maskOpacity);
|
|
7674
|
+
haloRenderer = createHaloRenderer();
|
|
7675
|
+
// Halo alpha is resolved from MaskHaloStyle per detection. It must not
|
|
7676
|
+
// inherit the independent mask renderer's global opacity.
|
|
7677
|
+
haloRenderer?.setOpacity(1);
|
|
6942
7678
|
if (!idMaskRenderer || !options.Container) {
|
|
6943
7679
|
return maskSprite;
|
|
6944
7680
|
}
|
|
6945
7681
|
const maskContainer = new options.Container();
|
|
7682
|
+
// The halo draws beneath the mask so the glow bleeds outward from the
|
|
7683
|
+
// silhouette while fills and borders stay crisp on top.
|
|
7684
|
+
if (haloRenderer) {
|
|
7685
|
+
maskContainer.addChild(haloRenderer.display);
|
|
7686
|
+
}
|
|
6946
7687
|
maskContainer.addChild(maskSprite, idMaskRenderer.mesh);
|
|
6947
7688
|
return maskContainer;
|
|
6948
7689
|
},
|
|
@@ -6952,6 +7693,7 @@ function createPixiMaskLayer(options) {
|
|
|
6952
7693
|
activeFrameKey = preparedFrame?.key ?? null;
|
|
6953
7694
|
activeFrameMediaTime = preparedFrame?.detectionFrame.mediaTime ?? null;
|
|
6954
7695
|
activeIdMaskFrame = null;
|
|
7696
|
+
activeRgbaMaskFrame = null;
|
|
6955
7697
|
hideSprite();
|
|
6956
7698
|
return;
|
|
6957
7699
|
}
|
|
@@ -6964,6 +7706,7 @@ function createPixiMaskLayer(options) {
|
|
|
6964
7706
|
if (preparedFrame.maskStatus === PreparedRenderFrameMaskStatus.Empty ||
|
|
6965
7707
|
preparedFrame.maskStatus === PreparedRenderFrameMaskStatus.Disabled) {
|
|
6966
7708
|
activeIdMaskFrame = null;
|
|
7709
|
+
activeRgbaMaskFrame = null;
|
|
6967
7710
|
hideSprite();
|
|
6968
7711
|
return;
|
|
6969
7712
|
}
|
|
@@ -7016,6 +7759,14 @@ function createPixiMaskLayer(options) {
|
|
|
7016
7759
|
applyMaskOpacity();
|
|
7017
7760
|
}
|
|
7018
7761
|
preparedRenderWindow.setMaskStyle(nextMaskStyle);
|
|
7762
|
+
refreshHalo();
|
|
7763
|
+
},
|
|
7764
|
+
setMaskHaloStyle(nextMaskHaloStyle) {
|
|
7765
|
+
if (nextMaskHaloStyle === undefined) {
|
|
7766
|
+
return;
|
|
7767
|
+
}
|
|
7768
|
+
currentMaskHaloStyle = nextMaskHaloStyle;
|
|
7769
|
+
refreshHalo();
|
|
7019
7770
|
},
|
|
7020
7771
|
destroy() {
|
|
7021
7772
|
if (isDestroyed) {
|
|
@@ -7026,17 +7777,22 @@ function createPixiMaskLayer(options) {
|
|
|
7026
7777
|
destroyTextures();
|
|
7027
7778
|
resetMaskPickCanvas();
|
|
7028
7779
|
idMaskRenderer?.destroy();
|
|
7780
|
+
haloRenderer?.destroy();
|
|
7029
7781
|
},
|
|
7030
7782
|
};
|
|
7031
7783
|
function showMaskFrame(maskFrame, mediaTime) {
|
|
7032
7784
|
visibleMaskMediaTime = mediaTime;
|
|
7033
7785
|
activeIdMaskFrame =
|
|
7034
7786
|
maskFrame.kind === PreparedMaskFrameKind.PngIdMask ? maskFrame : null;
|
|
7787
|
+
activeRgbaMaskFrame =
|
|
7788
|
+
maskFrame.kind === PreparedMaskFrameKind.RgbaImage ? maskFrame : null;
|
|
7035
7789
|
if (maskFrame.kind === PreparedMaskFrameKind.PngIdMask && idMaskRenderer) {
|
|
7036
7790
|
showIdMaskFrame(maskFrame);
|
|
7037
7791
|
return;
|
|
7038
7792
|
}
|
|
7039
|
-
|
|
7793
|
+
if (maskFrame.kind === PreparedMaskFrameKind.RgbaImage) {
|
|
7794
|
+
showRgbaMaskFrame(maskFrame);
|
|
7795
|
+
}
|
|
7040
7796
|
}
|
|
7041
7797
|
function getTexture(maskFrame) {
|
|
7042
7798
|
const existingTexture = maskTextures.get(maskFrame.key);
|
|
@@ -7060,6 +7816,40 @@ function createPixiMaskLayer(options) {
|
|
|
7060
7816
|
maskTextures.set(maskFrame.key, texture);
|
|
7061
7817
|
return texture;
|
|
7062
7818
|
}
|
|
7819
|
+
function getHaloTexture(maskFrame) {
|
|
7820
|
+
const existingTexture = haloTextures.get(maskFrame.key);
|
|
7821
|
+
if (existingTexture) {
|
|
7822
|
+
return existingTexture;
|
|
7823
|
+
}
|
|
7824
|
+
if (!maskFrame.idMaskData || typeof document === "undefined") {
|
|
7825
|
+
return undefined;
|
|
7826
|
+
}
|
|
7827
|
+
const canvas = document.createElement("canvas");
|
|
7828
|
+
const context = canvas.getContext("2d");
|
|
7829
|
+
if (!context || typeof context.createImageData !== "function") {
|
|
7830
|
+
return undefined;
|
|
7831
|
+
}
|
|
7832
|
+
canvas.width = maskFrame.width;
|
|
7833
|
+
canvas.height = maskFrame.height;
|
|
7834
|
+
const imageData = context.createImageData(maskFrame.width, maskFrame.height);
|
|
7835
|
+
for (let index = 0; index < maskFrame.idMaskData.length; index += 1) {
|
|
7836
|
+
const offset = index * 4;
|
|
7837
|
+
imageData.data[offset] = maskFrame.idMaskData[index];
|
|
7838
|
+
imageData.data[offset + 3] = 255;
|
|
7839
|
+
}
|
|
7840
|
+
context.putImageData(imageData, 0, 0);
|
|
7841
|
+
const source = new options.ImageSource({
|
|
7842
|
+
autoGenerateMipmaps: false,
|
|
7843
|
+
dynamic: false,
|
|
7844
|
+
height: maskFrame.height,
|
|
7845
|
+
resource: canvas,
|
|
7846
|
+
scaleMode: "nearest",
|
|
7847
|
+
width: maskFrame.width,
|
|
7848
|
+
});
|
|
7849
|
+
const texture = new options.Texture({ dynamic: false, source });
|
|
7850
|
+
haloTextures.set(maskFrame.key, texture);
|
|
7851
|
+
return texture;
|
|
7852
|
+
}
|
|
7063
7853
|
function showRgbaMaskFrame(maskFrame) {
|
|
7064
7854
|
if (!maskSprite) {
|
|
7065
7855
|
return;
|
|
@@ -7071,6 +7861,13 @@ function createPixiMaskLayer(options) {
|
|
|
7071
7861
|
maskSprite.height = mediaHeight;
|
|
7072
7862
|
maskSprite.visible = true;
|
|
7073
7863
|
idMaskRenderer?.hide();
|
|
7864
|
+
const haloTexture = getHaloTexture(maskFrame);
|
|
7865
|
+
if (haloTexture) {
|
|
7866
|
+
renderHalo(maskFrame, haloTexture);
|
|
7867
|
+
}
|
|
7868
|
+
else {
|
|
7869
|
+
haloRenderer?.hide();
|
|
7870
|
+
}
|
|
7074
7871
|
}
|
|
7075
7872
|
function showIdMaskFrame(maskFrame) {
|
|
7076
7873
|
if (!idMaskRenderer || !maskSprite) {
|
|
@@ -7078,14 +7875,82 @@ function createPixiMaskLayer(options) {
|
|
|
7078
7875
|
}
|
|
7079
7876
|
maskSprite.visible = false;
|
|
7080
7877
|
idMaskRenderer.render(maskFrame, getTexture(maskFrame));
|
|
7878
|
+
renderHalo(maskFrame, getTexture(maskFrame));
|
|
7879
|
+
}
|
|
7880
|
+
function renderHalo(maskFrame, texture) {
|
|
7881
|
+
if (!haloRenderer) {
|
|
7882
|
+
return;
|
|
7883
|
+
}
|
|
7884
|
+
const groups = resolveFrameHaloGroups();
|
|
7885
|
+
if (groups.length === 0) {
|
|
7886
|
+
haloRenderer.hide();
|
|
7887
|
+
return;
|
|
7888
|
+
}
|
|
7889
|
+
haloRenderer.render(maskFrame, texture, groups);
|
|
7890
|
+
}
|
|
7891
|
+
function refreshHalo() {
|
|
7892
|
+
if (activeIdMaskFrame) {
|
|
7893
|
+
renderHalo(activeIdMaskFrame, getTexture(activeIdMaskFrame));
|
|
7894
|
+
return;
|
|
7895
|
+
}
|
|
7896
|
+
if (activeRgbaMaskFrame) {
|
|
7897
|
+
const texture = getHaloTexture(activeRgbaMaskFrame);
|
|
7898
|
+
if (texture) {
|
|
7899
|
+
renderHalo(activeRgbaMaskFrame, texture);
|
|
7900
|
+
}
|
|
7901
|
+
}
|
|
7902
|
+
}
|
|
7903
|
+
/**
|
|
7904
|
+
* Halo instructions resolve per frame from the live halo style rather than
|
|
7905
|
+
* being baked into the prepared artifact, so halo edits never invalidate
|
|
7906
|
+
* prepared mask pixels. Detections are grouped by their requested spread
|
|
7907
|
+
* and every group renders in its own blur pass, preserving each
|
|
7908
|
+
* detection's public spread contract.
|
|
7909
|
+
*/
|
|
7910
|
+
function resolveFrameHaloGroups() {
|
|
7911
|
+
if (visibleMaskMediaTime === null || !currentMaskHaloStyle) {
|
|
7912
|
+
return [];
|
|
7913
|
+
}
|
|
7914
|
+
const frame = options.detectionTimeline.selectFrame(visibleMaskMediaTime);
|
|
7915
|
+
if (!frame) {
|
|
7916
|
+
return [];
|
|
7917
|
+
}
|
|
7918
|
+
const bySpread = new Map();
|
|
7919
|
+
for (const [detectionIndex, detection] of frame.detections.entries()) {
|
|
7920
|
+
if (!detection.mask) {
|
|
7921
|
+
continue;
|
|
7922
|
+
}
|
|
7923
|
+
const halo = currentMaskHaloStyle.resolve(detection, {
|
|
7924
|
+
detectionIndex,
|
|
7925
|
+
frame,
|
|
7926
|
+
mediaTime: visibleMaskMediaTime,
|
|
7927
|
+
});
|
|
7928
|
+
if (!halo || halo.alpha <= 0 || halo.spread <= 0) {
|
|
7929
|
+
continue;
|
|
7930
|
+
}
|
|
7931
|
+
let group = bySpread.get(halo.spread);
|
|
7932
|
+
if (!group) {
|
|
7933
|
+
group = new Map();
|
|
7934
|
+
bySpread.set(halo.spread, group);
|
|
7935
|
+
}
|
|
7936
|
+
group.set(detectionIndex + 1, { alpha: halo.alpha, color: halo.color });
|
|
7937
|
+
}
|
|
7938
|
+
return [...bySpread.entries()]
|
|
7939
|
+
.sort(([left], [right]) => left - right)
|
|
7940
|
+
.map(([spread, halos]) => ({
|
|
7941
|
+
palette: buildMaskHaloPalette(halos),
|
|
7942
|
+
spread,
|
|
7943
|
+
}));
|
|
7081
7944
|
}
|
|
7082
7945
|
function hideSprite() {
|
|
7083
7946
|
visibleMaskMediaTime = null;
|
|
7084
7947
|
activeIdMaskFrame = null;
|
|
7948
|
+
activeRgbaMaskFrame = null;
|
|
7085
7949
|
if (maskSprite) {
|
|
7086
7950
|
maskSprite.visible = false;
|
|
7087
7951
|
}
|
|
7088
7952
|
idMaskRenderer?.hide();
|
|
7953
|
+
haloRenderer?.hide();
|
|
7089
7954
|
}
|
|
7090
7955
|
function canHoldVisibleMaskFor(mediaTime) {
|
|
7091
7956
|
return (visibleMaskMediaTime !== null &&
|
|
@@ -7101,11 +7966,21 @@ function createPixiMaskLayer(options) {
|
|
|
7101
7966
|
function destroyTexture(key) {
|
|
7102
7967
|
const texture = maskTextures.get(key);
|
|
7103
7968
|
if (!texture) {
|
|
7969
|
+
destroyHaloTexture(key);
|
|
7104
7970
|
return;
|
|
7105
7971
|
}
|
|
7106
7972
|
releaseTextureBindings(key, texture);
|
|
7107
7973
|
maskTextures.delete(key);
|
|
7108
7974
|
texture.destroy(true);
|
|
7975
|
+
destroyHaloTexture(key);
|
|
7976
|
+
}
|
|
7977
|
+
function destroyHaloTexture(key) {
|
|
7978
|
+
const texture = haloTextures.get(key);
|
|
7979
|
+
if (!texture) {
|
|
7980
|
+
return;
|
|
7981
|
+
}
|
|
7982
|
+
haloTextures.delete(key);
|
|
7983
|
+
texture.destroy(true);
|
|
7109
7984
|
}
|
|
7110
7985
|
function destroyTextures() {
|
|
7111
7986
|
releaseTextureBindings();
|
|
@@ -7113,6 +7988,10 @@ function createPixiMaskLayer(options) {
|
|
|
7113
7988
|
texture.destroy(true);
|
|
7114
7989
|
}
|
|
7115
7990
|
maskTextures.clear();
|
|
7991
|
+
for (const texture of haloTextures.values()) {
|
|
7992
|
+
texture.destroy(true);
|
|
7993
|
+
}
|
|
7994
|
+
haloTextures.clear();
|
|
7116
7995
|
}
|
|
7117
7996
|
function releaseTextureBindings(key, texture) {
|
|
7118
7997
|
if (!texture || maskSprite?.texture === texture) {
|
|
@@ -7124,6 +8003,31 @@ function createPixiMaskLayer(options) {
|
|
|
7124
8003
|
idMaskRenderer?.clearTexture();
|
|
7125
8004
|
}
|
|
7126
8005
|
}
|
|
8006
|
+
function createHaloRenderer() {
|
|
8007
|
+
if (!options.BlurFilter ||
|
|
8008
|
+
!options.Container ||
|
|
8009
|
+
!options.Rectangle ||
|
|
8010
|
+
!options.Mesh ||
|
|
8011
|
+
!options.MeshGeometry ||
|
|
8012
|
+
!options.Shader ||
|
|
8013
|
+
!options.UniformGroup ||
|
|
8014
|
+
mediaWidth <= 0 ||
|
|
8015
|
+
mediaHeight <= 0) {
|
|
8016
|
+
return undefined;
|
|
8017
|
+
}
|
|
8018
|
+
return createPixiMaskHaloRenderer({
|
|
8019
|
+
BlurFilter: options.BlurFilter,
|
|
8020
|
+
Container: options.Container,
|
|
8021
|
+
Rectangle: options.Rectangle,
|
|
8022
|
+
ImageSource: options.ImageSource,
|
|
8023
|
+
Mesh: options.Mesh,
|
|
8024
|
+
MeshGeometry: options.MeshGeometry,
|
|
8025
|
+
Shader: options.Shader,
|
|
8026
|
+
UniformGroup: options.UniformGroup,
|
|
8027
|
+
mediaHeight,
|
|
8028
|
+
mediaWidth,
|
|
8029
|
+
});
|
|
8030
|
+
}
|
|
7127
8031
|
function createIdMaskRenderer() {
|
|
7128
8032
|
if (!options.Mesh ||
|
|
7129
8033
|
!options.MeshGeometry ||
|
|
@@ -7324,6 +8228,9 @@ function canPreparePolygonInstruction(instruction) {
|
|
|
7324
8228
|
return (instruction.points.length >= 3 &&
|
|
7325
8229
|
Boolean(instruction.fill || stroke) &&
|
|
7326
8230
|
!stroke?.dash?.length &&
|
|
8231
|
+
stroke?.cap === undefined &&
|
|
8232
|
+
stroke?.join === undefined &&
|
|
8233
|
+
stroke?.miterLimit === undefined &&
|
|
7327
8234
|
(stroke?.alignment === undefined ||
|
|
7328
8235
|
stroke.alignment === BoxStrokeAlignment.Center));
|
|
7329
8236
|
}
|
|
@@ -7368,6 +8275,367 @@ function resolvePreparedPolygonInstructions(options) {
|
|
|
7368
8275
|
return instructions;
|
|
7369
8276
|
}
|
|
7370
8277
|
|
|
8278
|
+
const sharedAssets = new WeakMap();
|
|
8279
|
+
/** Browser implementation for asset-backed region renderer descriptors. */
|
|
8280
|
+
function createPixiRegionLayer(options) {
|
|
8281
|
+
let container;
|
|
8282
|
+
let currentFrame;
|
|
8283
|
+
let currentMediaTime = 0;
|
|
8284
|
+
let currentViewportScale = 1;
|
|
8285
|
+
let destroyed = false;
|
|
8286
|
+
let renderers = [...options.regionRenderers];
|
|
8287
|
+
const assets = new Map();
|
|
8288
|
+
const entries = new Map();
|
|
8289
|
+
const pools = new Map();
|
|
8290
|
+
syncAssets();
|
|
8291
|
+
return {
|
|
8292
|
+
createContainer() {
|
|
8293
|
+
if (!container) {
|
|
8294
|
+
container = new options.Container();
|
|
8295
|
+
container.sortableChildren = true;
|
|
8296
|
+
}
|
|
8297
|
+
return container;
|
|
8298
|
+
},
|
|
8299
|
+
drawFrame(mediaTime, viewportScale = 1) {
|
|
8300
|
+
currentMediaTime = mediaTime;
|
|
8301
|
+
currentViewportScale = viewportScale;
|
|
8302
|
+
currentFrame = options.detectionTimeline.selectFrame(mediaTime);
|
|
8303
|
+
const activeKeys = new Set();
|
|
8304
|
+
const activeDetectionIndexes = new Set();
|
|
8305
|
+
for (const entry of entries.values())
|
|
8306
|
+
entry.active = false;
|
|
8307
|
+
if (currentFrame) {
|
|
8308
|
+
for (const [rendererIndex, renderer] of renderers.entries()) {
|
|
8309
|
+
const asset = assets.get(renderer.id);
|
|
8310
|
+
if (!asset?.asset)
|
|
8311
|
+
continue;
|
|
8312
|
+
for (const [detectionIndex, detection,] of currentFrame.detections.entries()) {
|
|
8313
|
+
const context = {
|
|
8314
|
+
detectionIndex,
|
|
8315
|
+
frame: currentFrame,
|
|
8316
|
+
mediaTime,
|
|
8317
|
+
viewportScale,
|
|
8318
|
+
...options.resolveContextState?.(detection),
|
|
8319
|
+
};
|
|
8320
|
+
if (context.hidden ||
|
|
8321
|
+
!matchesTarget(renderer, detection, context)) {
|
|
8322
|
+
continue;
|
|
8323
|
+
}
|
|
8324
|
+
const region = resolveRegion(renderer, detection);
|
|
8325
|
+
if (!region)
|
|
8326
|
+
continue;
|
|
8327
|
+
const key = resolveSpriteKey(renderer.id, detection, detectionIndex);
|
|
8328
|
+
const entry = ensureEntry(key, renderer.id, asset.src, asset.asset, detection.id);
|
|
8329
|
+
const position = positionSprite(entry.display, renderer, region, asset.asset);
|
|
8330
|
+
entry.baseX = position.x;
|
|
8331
|
+
entry.baseY = position.y;
|
|
8332
|
+
entry.detectionId = detection.id;
|
|
8333
|
+
entry.display.zIndex =
|
|
8334
|
+
(renderer.compose?.zIndex ?? 0) * 1_000_000 +
|
|
8335
|
+
rendererIndex * 10_000 +
|
|
8336
|
+
(detection.zIndex ?? detectionIndex);
|
|
8337
|
+
entry.display.visible = true;
|
|
8338
|
+
entry.active = true;
|
|
8339
|
+
activeKeys.add(key);
|
|
8340
|
+
activeDetectionIndexes.add(detectionIndex);
|
|
8341
|
+
}
|
|
8342
|
+
}
|
|
8343
|
+
}
|
|
8344
|
+
for (const [key, entry] of entries) {
|
|
8345
|
+
if (activeKeys.has(key))
|
|
8346
|
+
continue;
|
|
8347
|
+
entry.display.visible = false;
|
|
8348
|
+
pauseAnimatedDisplay(entry.display);
|
|
8349
|
+
entries.delete(key);
|
|
8350
|
+
const poolKey = resolvePoolKey(entry.rendererId, entry.src);
|
|
8351
|
+
const pool = pools.get(poolKey) ?? [];
|
|
8352
|
+
pool.push(entry);
|
|
8353
|
+
pools.set(poolKey, pool);
|
|
8354
|
+
}
|
|
8355
|
+
return { activeDetectionIndexes: [...activeDetectionIndexes] };
|
|
8356
|
+
},
|
|
8357
|
+
setRenderers(nextRenderers) {
|
|
8358
|
+
renderers = [...nextRenderers];
|
|
8359
|
+
syncAssets();
|
|
8360
|
+
this.drawFrame(currentMediaTime, currentViewportScale);
|
|
8361
|
+
},
|
|
8362
|
+
translateDetection(id, x, y) {
|
|
8363
|
+
let translated = false;
|
|
8364
|
+
for (const entry of entries.values()) {
|
|
8365
|
+
if (!entry.active || entry.detectionId !== id)
|
|
8366
|
+
continue;
|
|
8367
|
+
entry.display.position.set(entry.baseX + x, entry.baseY + y);
|
|
8368
|
+
translated = true;
|
|
8369
|
+
}
|
|
8370
|
+
return translated;
|
|
8371
|
+
},
|
|
8372
|
+
destroy() {
|
|
8373
|
+
destroyed = true;
|
|
8374
|
+
destroyAllDisplays();
|
|
8375
|
+
for (const asset of assets.values())
|
|
8376
|
+
asset.lease.release();
|
|
8377
|
+
assets.clear();
|
|
8378
|
+
container = undefined;
|
|
8379
|
+
currentFrame = undefined;
|
|
8380
|
+
},
|
|
8381
|
+
};
|
|
8382
|
+
function syncAssets() {
|
|
8383
|
+
const desired = new Map(renderers.map((renderer) => [renderer.id, renderer.source.asset.src]));
|
|
8384
|
+
for (const [id, asset] of assets) {
|
|
8385
|
+
if (desired.get(id) === asset.src)
|
|
8386
|
+
continue;
|
|
8387
|
+
destroyRendererDisplays(id);
|
|
8388
|
+
asset.lease.release();
|
|
8389
|
+
assets.delete(id);
|
|
8390
|
+
}
|
|
8391
|
+
for (const [id, src] of desired) {
|
|
8392
|
+
if (assets.has(id))
|
|
8393
|
+
continue;
|
|
8394
|
+
const lease = acquireAsset(options.Assets, src);
|
|
8395
|
+
const asset = { lease, src };
|
|
8396
|
+
assets.set(id, asset);
|
|
8397
|
+
void lease.asset.then((loadedAsset) => {
|
|
8398
|
+
if (destroyed || assets.get(id) !== asset)
|
|
8399
|
+
return;
|
|
8400
|
+
asset.asset = loadedAsset;
|
|
8401
|
+
options.onInvalidate?.();
|
|
8402
|
+
}, (error) => {
|
|
8403
|
+
// A failed asset is omitted. Replacing the descriptor retries it.
|
|
8404
|
+
options.onAssetError?.({ error, rendererId: id, src });
|
|
8405
|
+
});
|
|
8406
|
+
}
|
|
8407
|
+
}
|
|
8408
|
+
function ensureEntry(key, rendererId, src, asset, detectionId) {
|
|
8409
|
+
let entry = entries.get(key);
|
|
8410
|
+
if (entry)
|
|
8411
|
+
return entry;
|
|
8412
|
+
const poolKey = resolvePoolKey(rendererId, src);
|
|
8413
|
+
const pool = pools.get(poolKey);
|
|
8414
|
+
entry = pool?.pop();
|
|
8415
|
+
if (pool?.length === 0)
|
|
8416
|
+
pools.delete(poolKey);
|
|
8417
|
+
if (entry) {
|
|
8418
|
+
entry.detectionId = detectionId;
|
|
8419
|
+
resumeAnimatedDisplay(entry.display);
|
|
8420
|
+
}
|
|
8421
|
+
else {
|
|
8422
|
+
const display = isGifSource(asset)
|
|
8423
|
+
? new options.GifSprite({ autoPlay: true, loop: true, source: asset })
|
|
8424
|
+
: new options.Sprite({ texture: asset });
|
|
8425
|
+
display.anchor.set(0.5);
|
|
8426
|
+
container?.addChild(display);
|
|
8427
|
+
entry = {
|
|
8428
|
+
active: false,
|
|
8429
|
+
baseX: 0,
|
|
8430
|
+
baseY: 0,
|
|
8431
|
+
detectionId,
|
|
8432
|
+
display,
|
|
8433
|
+
rendererId,
|
|
8434
|
+
src,
|
|
8435
|
+
};
|
|
8436
|
+
}
|
|
8437
|
+
entries.set(key, entry);
|
|
8438
|
+
return entry;
|
|
8439
|
+
}
|
|
8440
|
+
function destroyRendererDisplays(rendererId) {
|
|
8441
|
+
for (const [key, entry] of entries) {
|
|
8442
|
+
if (entry.rendererId !== rendererId)
|
|
8443
|
+
continue;
|
|
8444
|
+
destroyDisplay(entry.display);
|
|
8445
|
+
entries.delete(key);
|
|
8446
|
+
}
|
|
8447
|
+
for (const [key, pool] of pools) {
|
|
8448
|
+
const retained = pool.filter((entry) => {
|
|
8449
|
+
if (entry.rendererId !== rendererId)
|
|
8450
|
+
return true;
|
|
8451
|
+
destroyDisplay(entry.display);
|
|
8452
|
+
return false;
|
|
8453
|
+
});
|
|
8454
|
+
if (retained.length > 0)
|
|
8455
|
+
pools.set(key, retained);
|
|
8456
|
+
else
|
|
8457
|
+
pools.delete(key);
|
|
8458
|
+
}
|
|
8459
|
+
}
|
|
8460
|
+
function destroyAllDisplays() {
|
|
8461
|
+
for (const entry of entries.values())
|
|
8462
|
+
destroyDisplay(entry.display);
|
|
8463
|
+
for (const pool of pools.values()) {
|
|
8464
|
+
for (const entry of pool)
|
|
8465
|
+
destroyDisplay(entry.display);
|
|
8466
|
+
}
|
|
8467
|
+
entries.clear();
|
|
8468
|
+
pools.clear();
|
|
8469
|
+
}
|
|
8470
|
+
}
|
|
8471
|
+
function destroyDisplay(display) {
|
|
8472
|
+
display.removeFromParent?.();
|
|
8473
|
+
// GifSprite sources are shared and released through Assets.unload().
|
|
8474
|
+
display.destroy();
|
|
8475
|
+
}
|
|
8476
|
+
function pauseAnimatedDisplay(display) {
|
|
8477
|
+
if ("stop" in display && typeof display.stop === "function")
|
|
8478
|
+
display.stop();
|
|
8479
|
+
}
|
|
8480
|
+
function resumeAnimatedDisplay(display) {
|
|
8481
|
+
if ("play" in display && typeof display.play === "function")
|
|
8482
|
+
display.play();
|
|
8483
|
+
}
|
|
8484
|
+
function matchesTarget(renderer, detection, context) {
|
|
8485
|
+
const { target } = renderer;
|
|
8486
|
+
return (matchesValue(target.id, detection.id) &&
|
|
8487
|
+
matchesValue(target.className, detection.className) &&
|
|
8488
|
+
matchesValue(target.sourceId, detection.sourceId) &&
|
|
8489
|
+
(target.resolve?.(detection, context) ?? true));
|
|
8490
|
+
}
|
|
8491
|
+
function matchesValue(configured, actual) {
|
|
8492
|
+
if (configured === undefined)
|
|
8493
|
+
return true;
|
|
8494
|
+
if (actual === undefined)
|
|
8495
|
+
return false;
|
|
8496
|
+
return Array.isArray(configured)
|
|
8497
|
+
? configured.includes(actual)
|
|
8498
|
+
: configured === actual;
|
|
8499
|
+
}
|
|
8500
|
+
function resolveRegion(renderer, detection) {
|
|
8501
|
+
if (renderer.region.kind === "bounds")
|
|
8502
|
+
return detection.rect;
|
|
8503
|
+
const fallbackSize = detection.rect
|
|
8504
|
+
? Math.max(1, detection.rect.width * 0.4)
|
|
8505
|
+
: undefined;
|
|
8506
|
+
if (renderer.region.anchor === "head") {
|
|
8507
|
+
const facePoints = detection.keypoints?.points
|
|
8508
|
+
.slice(0, 5)
|
|
8509
|
+
.filter((_, index) => (detection.keypoints?.visibility?.[index] ?? 2) > 0);
|
|
8510
|
+
if (facePoints && facePoints.length > 0) {
|
|
8511
|
+
const xs = facePoints.map(({ x }) => x);
|
|
8512
|
+
const ys = facePoints.map(({ y }) => y);
|
|
8513
|
+
const width = Math.max(fallbackSize ?? 1, (Math.max(...xs) - Math.min(...xs)) * 2);
|
|
8514
|
+
return {
|
|
8515
|
+
height: width,
|
|
8516
|
+
width,
|
|
8517
|
+
x: xs.reduce((sum, x) => sum + x, 0) / xs.length,
|
|
8518
|
+
y: ys.reduce((sum, y) => sum + y, 0) / ys.length,
|
|
8519
|
+
};
|
|
8520
|
+
}
|
|
8521
|
+
if (!detection.rect || fallbackSize === undefined)
|
|
8522
|
+
return undefined;
|
|
8523
|
+
return {
|
|
8524
|
+
height: fallbackSize,
|
|
8525
|
+
width: fallbackSize,
|
|
8526
|
+
x: detection.rect.x,
|
|
8527
|
+
y: detection.rect.y - detection.rect.height / 2,
|
|
8528
|
+
};
|
|
8529
|
+
}
|
|
8530
|
+
const point = detection.keypoints?.points[renderer.region.anchor];
|
|
8531
|
+
const visibility = detection.keypoints?.visibility?.[renderer.region.anchor] ?? 2;
|
|
8532
|
+
if (!point || visibility <= 0 || fallbackSize === undefined)
|
|
8533
|
+
return undefined;
|
|
8534
|
+
return { height: fallbackSize, width: fallbackSize, x: point.x, y: point.y };
|
|
8535
|
+
}
|
|
8536
|
+
function positionSprite(sprite, renderer, region, asset) {
|
|
8537
|
+
const scale = finiteOr(renderer.transform?.scale, 1);
|
|
8538
|
+
const opacity = Math.min(1, Math.max(0, finiteOr(renderer.transform?.opacity, 1)));
|
|
8539
|
+
const sourceWidth = Math.max(1, asset.width);
|
|
8540
|
+
const sourceHeight = Math.max(1, asset.height);
|
|
8541
|
+
const containScale = Math.min(region.width / sourceWidth, region.height / sourceHeight);
|
|
8542
|
+
const offset = renderer.transform?.offset;
|
|
8543
|
+
sprite.alpha = opacity;
|
|
8544
|
+
sprite.width = sourceWidth * containScale * scale;
|
|
8545
|
+
sprite.height = sourceHeight * containScale * scale;
|
|
8546
|
+
const position = {
|
|
8547
|
+
x: region.x + finiteOr(offset?.x, 0) * region.width,
|
|
8548
|
+
y: region.y + finiteOr(offset?.y, 0) * region.height,
|
|
8549
|
+
};
|
|
8550
|
+
sprite.position.set(position.x, position.y);
|
|
8551
|
+
sprite.rotation = finiteOr(renderer.transform?.rotation, 0);
|
|
8552
|
+
return position;
|
|
8553
|
+
}
|
|
8554
|
+
function isGifSource(asset) {
|
|
8555
|
+
return ("totalFrames" in asset && "frames" in asset && Array.isArray(asset.frames));
|
|
8556
|
+
}
|
|
8557
|
+
function resolvePoolKey(rendererId, src) {
|
|
8558
|
+
return JSON.stringify([rendererId, src]);
|
|
8559
|
+
}
|
|
8560
|
+
function resolveSpriteKey(rendererId, detection, detectionIndex) {
|
|
8561
|
+
return JSON.stringify([rendererId, detection.id ?? null, detectionIndex]);
|
|
8562
|
+
}
|
|
8563
|
+
function finiteOr(value, fallback) {
|
|
8564
|
+
return value !== undefined && Number.isFinite(value) ? value : fallback;
|
|
8565
|
+
}
|
|
8566
|
+
function acquireAsset(loader, src) {
|
|
8567
|
+
let cache = sharedAssets.get(loader);
|
|
8568
|
+
if (!cache) {
|
|
8569
|
+
cache = new Map();
|
|
8570
|
+
sharedAssets.set(loader, cache);
|
|
8571
|
+
}
|
|
8572
|
+
let entry = cache.get(src);
|
|
8573
|
+
if (!entry) {
|
|
8574
|
+
entry = { asset: loader.load(src), references: 0 };
|
|
8575
|
+
cache.set(src, entry);
|
|
8576
|
+
}
|
|
8577
|
+
entry.references += 1;
|
|
8578
|
+
let released = false;
|
|
8579
|
+
return {
|
|
8580
|
+
release() {
|
|
8581
|
+
if (released)
|
|
8582
|
+
return;
|
|
8583
|
+
released = true;
|
|
8584
|
+
entry.references -= 1;
|
|
8585
|
+
if (entry.references > 0)
|
|
8586
|
+
return;
|
|
8587
|
+
cache.delete(src);
|
|
8588
|
+
void entry.asset.then(() => loader.unload(src), () => undefined);
|
|
8589
|
+
},
|
|
8590
|
+
asset: entry.asset,
|
|
8591
|
+
};
|
|
8592
|
+
}
|
|
8593
|
+
|
|
8594
|
+
/**
|
|
8595
|
+
* Bridges public annotation renderer kinds onto the internal vector-layer
|
|
8596
|
+
* shape pipeline.
|
|
8597
|
+
*
|
|
8598
|
+
* Each shape-backed renderer kind resolves its own public instruction per
|
|
8599
|
+
* detection; this adapter lowers those instructions into the internal
|
|
8600
|
+
* renderer-neutral shape vocabulary and concatenates them into the single
|
|
8601
|
+
* shape style the vector layer consumes. Kinds stay independent: a kind that
|
|
8602
|
+
* resolves nothing for a detection simply contributes no instructions.
|
|
8603
|
+
*/
|
|
8604
|
+
function resolveAnnotationShapeStyle(styles) {
|
|
8605
|
+
const boxCornerStyle = styles.boxCornerStyle ?? null;
|
|
8606
|
+
const ellipseStyle = styles.ellipseStyle ?? null;
|
|
8607
|
+
const markerStyle = styles.markerStyle ?? null;
|
|
8608
|
+
if (!boxCornerStyle && !ellipseStyle && !markerStyle) {
|
|
8609
|
+
return null;
|
|
8610
|
+
}
|
|
8611
|
+
return {
|
|
8612
|
+
resolve(detection, context) {
|
|
8613
|
+
const instructions = [];
|
|
8614
|
+
const boxCorners = boxCornerStyle?.resolve(detection, context);
|
|
8615
|
+
if (boxCorners) {
|
|
8616
|
+
instructions.push({
|
|
8617
|
+
closed: false,
|
|
8618
|
+
kind: ShapeInstructionKind.Path,
|
|
8619
|
+
segments: boxCorners.segments,
|
|
8620
|
+
stroke: boxCorners.stroke,
|
|
8621
|
+
});
|
|
8622
|
+
}
|
|
8623
|
+
const marker = markerStyle?.resolve(detection, context);
|
|
8624
|
+
if (marker) {
|
|
8625
|
+
instructions.push({ kind: ShapeInstructionKind.Marker, ...marker });
|
|
8626
|
+
}
|
|
8627
|
+
const ellipse = ellipseStyle?.resolve(detection, context);
|
|
8628
|
+
if (ellipse) {
|
|
8629
|
+
instructions.push(lowerEllipseInstruction(ellipse));
|
|
8630
|
+
}
|
|
8631
|
+
return instructions.length > 0 ? instructions : undefined;
|
|
8632
|
+
},
|
|
8633
|
+
};
|
|
8634
|
+
}
|
|
8635
|
+
function lowerEllipseInstruction(instruction) {
|
|
8636
|
+
return { ...instruction, kind: ShapeInstructionKind.Ellipse };
|
|
8637
|
+
}
|
|
8638
|
+
|
|
7371
8639
|
function createPixiAnnotationOverlayLayer(editingEngine, initialStyle) {
|
|
7372
8640
|
let graphics;
|
|
7373
8641
|
let style = resolveAnnotationOverlayStyle(initialStyle);
|
|
@@ -7600,8 +8868,11 @@ function drawLoading(graphics, context, style) {
|
|
|
7600
8868
|
const loading = values instanceof Set ? values : new Set(values);
|
|
7601
8869
|
const angle = (context.now / 500) % (Math.PI * 2);
|
|
7602
8870
|
for (const detection of context.frame?.detections ?? []) {
|
|
7603
|
-
if (detection.id === undefined ||
|
|
8871
|
+
if (detection.id === undefined ||
|
|
8872
|
+
!loading.has(detection.id) ||
|
|
8873
|
+
resolveAnnotationStyleState(detection, context.visibility).hidden) {
|
|
7604
8874
|
continue;
|
|
8875
|
+
}
|
|
7605
8876
|
const rect = getDetectionRect(detection);
|
|
7606
8877
|
if (!rect)
|
|
7607
8878
|
continue;
|
|
@@ -7676,6 +8947,7 @@ var PixiSceneLayerKind;
|
|
|
7676
8947
|
PixiSceneLayerKind["Mask"] = "mask";
|
|
7677
8948
|
PixiSceneLayerKind["Box"] = "box";
|
|
7678
8949
|
PixiSceneLayerKind["Vector"] = "vector";
|
|
8950
|
+
PixiSceneLayerKind["Region"] = "region";
|
|
7679
8951
|
PixiSceneLayerKind["Focus"] = "focus";
|
|
7680
8952
|
PixiSceneLayerKind["Preview"] = "preview";
|
|
7681
8953
|
PixiSceneLayerKind["Guide"] = "guide";
|
|
@@ -7720,6 +8992,8 @@ function defaultLayerOrder(kind) {
|
|
|
7720
8992
|
return 200;
|
|
7721
8993
|
case PixiSceneLayerKind.Vector:
|
|
7722
8994
|
return 250;
|
|
8995
|
+
case PixiSceneLayerKind.Region:
|
|
8996
|
+
return 275;
|
|
7723
8997
|
case PixiSceneLayerKind.Focus:
|
|
7724
8998
|
return 300;
|
|
7725
8999
|
case PixiSceneLayerKind.Preview:
|
|
@@ -7775,14 +9049,42 @@ function observePixiContainerResize(container, resize) {
|
|
|
7775
9049
|
return () => observer.disconnect();
|
|
7776
9050
|
}
|
|
7777
9051
|
async function createPixiMediaScene(options) {
|
|
7778
|
-
const { Application, CanvasSource, Container, Graphics, ImageSource, Mesh, MeshGeometry, Rectangle, Sprite, Shader, Text, Texture, UniformGroup, } = await import('pixi.js');
|
|
9052
|
+
const { Application, Assets, BlurFilter, CanvasSource, Container, Graphics, ImageSource, Mesh, MeshGeometry, Rectangle, Sprite, Shader, Text, Texture, UniformGroup, } = await import('pixi.js');
|
|
9053
|
+
const { GifSprite } = await import('pixi.js/gif');
|
|
7779
9054
|
const app = new Application();
|
|
9055
|
+
let currentEllipseStyle = options.ellipseStyle ?? null;
|
|
9056
|
+
let currentBoxCornerStyle = options.boxCornerStyle ?? null;
|
|
7780
9057
|
let currentLabelStyle = options.labelStyle ?? null;
|
|
7781
9058
|
let currentMaskStyle = options.maskStyle ?? null;
|
|
9059
|
+
let currentMarkerStyle = options.markerStyle ?? null;
|
|
9060
|
+
let currentMaskHaloStyle = options.maskHaloStyle ?? null;
|
|
9061
|
+
// Halo-only presentations still need prepared id-mask artifacts; this
|
|
9062
|
+
// invisible style drives preparation without drawing any fill or border.
|
|
9063
|
+
const haloOnlyMaskStyle = {
|
|
9064
|
+
artifactKey: "mask-halo-only:v1",
|
|
9065
|
+
resolve: (detection) => detection.mask
|
|
9066
|
+
? { alpha: 0, color: 0x000000, mask: detection.mask }
|
|
9067
|
+
: undefined,
|
|
9068
|
+
};
|
|
9069
|
+
const createVisibilityMaskHaloStyle = (style) => ({
|
|
9070
|
+
resolve(detection, context) {
|
|
9071
|
+
const state = resolveContextState(detection);
|
|
9072
|
+
if (state.hidden) {
|
|
9073
|
+
return undefined;
|
|
9074
|
+
}
|
|
9075
|
+
return style.resolve(detection, { ...context, ...state });
|
|
9076
|
+
},
|
|
9077
|
+
});
|
|
9078
|
+
const resolveArtifactMaskStyle = () => currentMaskStyle ?? (currentMaskHaloStyle ? haloOnlyMaskStyle : null);
|
|
7782
9079
|
let currentPolygonStyle = options.polygonStyle === undefined
|
|
7783
9080
|
? new BasePolygonStyle()
|
|
7784
9081
|
: options.polygonStyle;
|
|
7785
9082
|
let currentVisibility = options.visibility;
|
|
9083
|
+
let currentMediaTime = 0;
|
|
9084
|
+
let viewportScale = 1;
|
|
9085
|
+
let hasPresentedSample = false;
|
|
9086
|
+
let mediaHeight = 0;
|
|
9087
|
+
let mediaWidth = 0;
|
|
7786
9088
|
let visibilityVersion = 0;
|
|
7787
9089
|
let visibilityMaskStyleCache = null;
|
|
7788
9090
|
const resolveContextState = (detection) => resolveAnnotationStyleState(detection, currentVisibility);
|
|
@@ -7791,6 +9093,28 @@ async function createPixiMediaScene(options) {
|
|
|
7791
9093
|
hidden: currentVisibility?.labelsHidden === true ||
|
|
7792
9094
|
resolveContextState(detection).hidden,
|
|
7793
9095
|
});
|
|
9096
|
+
// The vector layer consumes one shape style; renderer kinds that lower to
|
|
9097
|
+
// shape instructions compose with the internal hook here.
|
|
9098
|
+
function resolveVectorShapeStyle() {
|
|
9099
|
+
const kindShapeStyle = resolveAnnotationShapeStyle({
|
|
9100
|
+
boxCornerStyle: currentBoxCornerStyle,
|
|
9101
|
+
ellipseStyle: currentEllipseStyle,
|
|
9102
|
+
markerStyle: currentMarkerStyle,
|
|
9103
|
+
});
|
|
9104
|
+
const baseShapeStyle = options.shapeStyle ?? null;
|
|
9105
|
+
if (!kindShapeStyle || !baseShapeStyle) {
|
|
9106
|
+
return kindShapeStyle ?? baseShapeStyle;
|
|
9107
|
+
}
|
|
9108
|
+
return {
|
|
9109
|
+
resolve(detection, context) {
|
|
9110
|
+
const combined = [
|
|
9111
|
+
...(baseShapeStyle.resolve(detection, context) ?? []),
|
|
9112
|
+
...(kindShapeStyle.resolve(detection, context) ?? []),
|
|
9113
|
+
];
|
|
9114
|
+
return combined.length > 0 ? combined : undefined;
|
|
9115
|
+
},
|
|
9116
|
+
};
|
|
9117
|
+
}
|
|
7794
9118
|
const boxLayer = createPixiBoxLayer({
|
|
7795
9119
|
boxStyle: options.boxStyle,
|
|
7796
9120
|
Container: options.editingEngine ? Container : undefined,
|
|
@@ -7823,6 +9147,24 @@ async function createPixiMediaScene(options) {
|
|
|
7823
9147
|
polygonStyle: polygonLayer?.getVectorFallbackStyle() ?? currentPolygonStyle,
|
|
7824
9148
|
polylineStyle: options.polylineStyle,
|
|
7825
9149
|
keypointStyle: options.keypointStyle,
|
|
9150
|
+
shapeStyle: resolveVectorShapeStyle(),
|
|
9151
|
+
resolveContextState,
|
|
9152
|
+
});
|
|
9153
|
+
const regionLayer = createPixiRegionLayer({
|
|
9154
|
+
Assets,
|
|
9155
|
+
Container,
|
|
9156
|
+
GifSprite,
|
|
9157
|
+
Sprite,
|
|
9158
|
+
detectionTimeline: options.detectionTimeline,
|
|
9159
|
+
onInvalidate: () => {
|
|
9160
|
+
if (!hasPresentedSample || mediaWidth <= 0 || mediaHeight <= 0)
|
|
9161
|
+
return;
|
|
9162
|
+
const boxState = boxLayer.drawFrame(currentMediaTime, viewportScale);
|
|
9163
|
+
const regionState = regionLayer.drawFrame(currentMediaTime, viewportScale);
|
|
9164
|
+
options.onPresentationUpdate?.(createPresentedSampleState(currentMediaTime, boxState, regionState));
|
|
9165
|
+
},
|
|
9166
|
+
onAssetError: options.diagnostics?.onAssetError,
|
|
9167
|
+
regionRenderers: options.regionRenderers,
|
|
7826
9168
|
resolveContextState,
|
|
7827
9169
|
});
|
|
7828
9170
|
const annotationOverlayLayer = createPixiAnnotationOverlayLayer(options.editingEngine, options.annotationOverlayStyle);
|
|
@@ -7836,9 +9178,11 @@ async function createPixiMediaScene(options) {
|
|
|
7836
9178
|
preview: options.maskBrush,
|
|
7837
9179
|
})
|
|
7838
9180
|
: undefined;
|
|
7839
|
-
let maskLayer =
|
|
9181
|
+
let maskLayer = resolveArtifactMaskStyle()
|
|
7840
9182
|
? createPixiMaskLayer({
|
|
9183
|
+
BlurFilter,
|
|
7841
9184
|
Container,
|
|
9185
|
+
Rectangle,
|
|
7842
9186
|
ImageSource,
|
|
7843
9187
|
Mesh,
|
|
7844
9188
|
MeshGeometry,
|
|
@@ -7847,7 +9191,10 @@ async function createPixiMediaScene(options) {
|
|
|
7847
9191
|
Texture,
|
|
7848
9192
|
UniformGroup,
|
|
7849
9193
|
detectionTimeline: options.detectionTimeline,
|
|
7850
|
-
|
|
9194
|
+
maskHaloStyle: currentMaskHaloStyle
|
|
9195
|
+
? createVisibilityMaskHaloStyle(currentMaskHaloStyle)
|
|
9196
|
+
: null,
|
|
9197
|
+
maskStyle: createVisibilityMaskStyle(resolveArtifactMaskStyle()),
|
|
7851
9198
|
onActiveIdMaskFramePresented: handleActiveIdMaskFramePresented,
|
|
7852
9199
|
renderPreparation: options.renderPreparation,
|
|
7853
9200
|
})
|
|
@@ -7879,8 +9226,6 @@ async function createPixiMediaScene(options) {
|
|
|
7879
9226
|
if (!stagingContext) {
|
|
7880
9227
|
throw new Error("Unable to create staging canvas context.");
|
|
7881
9228
|
}
|
|
7882
|
-
let mediaHeight = 0;
|
|
7883
|
-
let mediaWidth = 0;
|
|
7884
9229
|
let mediaScene;
|
|
7885
9230
|
let vectorDisplay;
|
|
7886
9231
|
let polygonDisplay;
|
|
@@ -7889,6 +9234,7 @@ async function createPixiMediaScene(options) {
|
|
|
7889
9234
|
const maskSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Mask);
|
|
7890
9235
|
const boxSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Box);
|
|
7891
9236
|
const vectorSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Vector);
|
|
9237
|
+
const regionSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Region);
|
|
7892
9238
|
const focusSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Focus);
|
|
7893
9239
|
const previewSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Preview);
|
|
7894
9240
|
const interactionSlot = createPixiSceneLayerSlot(PixiSceneLayerKind.Interaction);
|
|
@@ -7899,16 +9245,14 @@ async function createPixiMediaScene(options) {
|
|
|
7899
9245
|
maskSlot,
|
|
7900
9246
|
boxSlot,
|
|
7901
9247
|
vectorSlot,
|
|
9248
|
+
regionSlot,
|
|
7902
9249
|
focusSlot,
|
|
7903
9250
|
previewSlot,
|
|
7904
9251
|
handleSlot,
|
|
7905
9252
|
interactionSlot,
|
|
7906
9253
|
labelSlot,
|
|
7907
9254
|
];
|
|
7908
|
-
let currentMediaTime = 0;
|
|
7909
|
-
let viewportScale = 1;
|
|
7910
9255
|
const viewport = createViewportController({ scale: 1 });
|
|
7911
|
-
let hasPresentedSample = false;
|
|
7912
9256
|
/**
|
|
7913
9257
|
* Timestamp of the sample whose pixels are on the staging canvas. Unlike
|
|
7914
9258
|
* `currentMediaTime`, presentation and selection updates never move it.
|
|
@@ -7924,6 +9268,7 @@ async function createPixiMediaScene(options) {
|
|
|
7924
9268
|
interaction: options.interaction ?? {
|
|
7925
9269
|
mode: MediaInteractionMode.Always,
|
|
7926
9270
|
},
|
|
9271
|
+
canPickDetection: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden,
|
|
7927
9272
|
editingEngine: options.editingEngine,
|
|
7928
9273
|
capturePointer: (pointerId) => {
|
|
7929
9274
|
if (!rendererCanvas.hasPointerCapture?.(pointerId)) {
|
|
@@ -7960,6 +9305,7 @@ async function createPixiMediaScene(options) {
|
|
|
7960
9305
|
Text,
|
|
7961
9306
|
UniformGroup,
|
|
7962
9307
|
interactionStyle: options.interactionStyle,
|
|
9308
|
+
isDetectionVisible: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden,
|
|
7963
9309
|
})
|
|
7964
9310
|
: undefined;
|
|
7965
9311
|
let fastTranslatedDetectionId = null;
|
|
@@ -7967,6 +9313,7 @@ async function createPixiMediaScene(options) {
|
|
|
7967
9313
|
fastTranslatedDetectionId = id;
|
|
7968
9314
|
boxLayer.translateDetection(id, dx, dy);
|
|
7969
9315
|
vectorLayer.translateDetection(id, dx, dy);
|
|
9316
|
+
regionLayer.translateDetection(id, dx, dy);
|
|
7970
9317
|
labelLayer?.translateDetection(id, dx, dy);
|
|
7971
9318
|
});
|
|
7972
9319
|
const unsubscribeEditingState = options.editingEngine?.subscribe((state) => {
|
|
@@ -7978,11 +9325,13 @@ async function createPixiMediaScene(options) {
|
|
|
7978
9325
|
fastTranslatedDetectionId = null;
|
|
7979
9326
|
boxLayer.translateDetection(id, 0, 0);
|
|
7980
9327
|
vectorLayer.translateDetection(id, 0, 0);
|
|
9328
|
+
regionLayer.translateDetection(id, 0, 0);
|
|
7981
9329
|
labelLayer?.translateDetection(id, 0, 0);
|
|
7982
9330
|
boxLayer.invalidateDetection(id);
|
|
7983
9331
|
vectorLayer.invalidateDetection(id);
|
|
7984
9332
|
boxLayer.drawFrame(currentMediaTime, viewportScale);
|
|
7985
9333
|
vectorLayer.drawFrame(currentMediaTime, viewportScale);
|
|
9334
|
+
regionLayer.drawFrame(currentMediaTime, viewportScale);
|
|
7986
9335
|
labelLayer?.drawFrame(currentMediaTime, viewportScale);
|
|
7987
9336
|
});
|
|
7988
9337
|
const handleKeyDown = (event) => {
|
|
@@ -8017,6 +9366,7 @@ async function createPixiMediaScene(options) {
|
|
|
8017
9366
|
Shader,
|
|
8018
9367
|
UniformGroup,
|
|
8019
9368
|
focusStyle: options.focusStyle,
|
|
9369
|
+
isDetectionVisible: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden,
|
|
8020
9370
|
})
|
|
8021
9371
|
: undefined;
|
|
8022
9372
|
let mediaSprite;
|
|
@@ -8126,6 +9476,7 @@ async function createPixiMediaScene(options) {
|
|
|
8126
9476
|
attachPolygonLayerDisplay();
|
|
8127
9477
|
vectorDisplay.addChild(vectorLayer.createContainer());
|
|
8128
9478
|
vectorSlot.setDisplay(vectorDisplay);
|
|
9479
|
+
regionSlot.setDisplay(regionLayer.createContainer());
|
|
8129
9480
|
previewSlot.setDisplay(maskBrushPreview?.display);
|
|
8130
9481
|
if (annotationOverlay) {
|
|
8131
9482
|
annotationOverlayLayer.attachGraphics(annotationOverlay);
|
|
@@ -8179,13 +9530,14 @@ async function createPixiMediaScene(options) {
|
|
|
8179
9530
|
const boxState = boxLayer.drawFrame(sample.timestamp, viewportScale);
|
|
8180
9531
|
polygonLayer?.drawFrame(sample.timestamp, viewportScale);
|
|
8181
9532
|
vectorLayer.drawFrame(sample.timestamp, viewportScale);
|
|
9533
|
+
const regionState = regionLayer.drawFrame(sample.timestamp, viewportScale);
|
|
8182
9534
|
interactionLayer?.drawFrame(sample.timestamp);
|
|
8183
9535
|
drawFocusLayer(sample.timestamp);
|
|
8184
9536
|
drawInteractionPresentationLayer(sample.timestamp);
|
|
8185
9537
|
labelLayer?.drawFrame(sample.timestamp, viewportScale);
|
|
8186
9538
|
updateMediaSceneFit();
|
|
8187
9539
|
return {
|
|
8188
|
-
...createPresentedSampleState(sample.timestamp, boxState),
|
|
9540
|
+
...createPresentedSampleState(sample.timestamp, boxState, regionState),
|
|
8189
9541
|
duration: sample.duration,
|
|
8190
9542
|
};
|
|
8191
9543
|
}
|
|
@@ -8207,10 +9559,12 @@ async function createPixiMediaScene(options) {
|
|
|
8207
9559
|
maskLayer?.drawFrame(sample.timestamp);
|
|
8208
9560
|
});
|
|
8209
9561
|
let boxState;
|
|
9562
|
+
let regionState;
|
|
8210
9563
|
boxMs = measure(() => {
|
|
8211
9564
|
boxState = boxLayer.drawFrame(sample.timestamp, viewportScale);
|
|
8212
9565
|
polygonLayer?.drawFrame(sample.timestamp, viewportScale);
|
|
8213
9566
|
vectorLayer.drawFrame(sample.timestamp, viewportScale);
|
|
9567
|
+
regionState = regionLayer.drawFrame(sample.timestamp, viewportScale);
|
|
8214
9568
|
});
|
|
8215
9569
|
interactionMs = measure(() => {
|
|
8216
9570
|
interactionLayer?.drawFrame(sample.timestamp);
|
|
@@ -8237,7 +9591,7 @@ async function createPixiMediaScene(options) {
|
|
|
8237
9591
|
totalMs: elapsedSince(totalStart),
|
|
8238
9592
|
};
|
|
8239
9593
|
return {
|
|
8240
|
-
...createPresentedSampleState(sample.timestamp, boxState),
|
|
9594
|
+
...createPresentedSampleState(sample.timestamp, boxState, regionState),
|
|
8241
9595
|
duration: sample.duration,
|
|
8242
9596
|
renderTimings,
|
|
8243
9597
|
};
|
|
@@ -8386,6 +9740,7 @@ async function createPixiMediaScene(options) {
|
|
|
8386
9740
|
visibilityVersion += 1;
|
|
8387
9741
|
maskLayer?.setMaskStyle(createVisibilityMaskStyle(currentMaskStyle));
|
|
8388
9742
|
}
|
|
9743
|
+
interactionLayer?.drawFrame(mediaTime);
|
|
8389
9744
|
}
|
|
8390
9745
|
boxLayer.setBoxStyle(presentation.boxStyle);
|
|
8391
9746
|
if (presentation.polygonStyle !== undefined) {
|
|
@@ -8406,15 +9761,38 @@ async function createPixiMediaScene(options) {
|
|
|
8406
9761
|
polylineStyle: presentation.polylineStyle,
|
|
8407
9762
|
keypointStyle: presentation.keypointStyle,
|
|
8408
9763
|
});
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
9764
|
+
regionLayer.setRenderers(presentation.renderers?.filter((renderer) => renderer.kind === "region") ?? []);
|
|
9765
|
+
if (presentation.boxCornerStyle !== undefined ||
|
|
9766
|
+
presentation.ellipseStyle !== undefined ||
|
|
9767
|
+
presentation.markerStyle !== undefined) {
|
|
9768
|
+
if (presentation.boxCornerStyle !== undefined) {
|
|
9769
|
+
currentBoxCornerStyle = presentation.boxCornerStyle;
|
|
9770
|
+
}
|
|
9771
|
+
if (presentation.ellipseStyle !== undefined) {
|
|
9772
|
+
currentEllipseStyle = presentation.ellipseStyle;
|
|
9773
|
+
}
|
|
9774
|
+
if (presentation.markerStyle !== undefined) {
|
|
9775
|
+
currentMarkerStyle = presentation.markerStyle;
|
|
9776
|
+
}
|
|
9777
|
+
vectorLayer.setStyles({ shapeStyle: resolveVectorShapeStyle() });
|
|
9778
|
+
}
|
|
9779
|
+
if (presentation.maskStyle !== undefined ||
|
|
9780
|
+
presentation.maskHaloStyle !== undefined) {
|
|
9781
|
+
if (presentation.maskStyle !== undefined) {
|
|
9782
|
+
currentMaskStyle = presentation.maskStyle;
|
|
9783
|
+
}
|
|
9784
|
+
if (presentation.maskHaloStyle !== undefined) {
|
|
9785
|
+
currentMaskHaloStyle = presentation.maskHaloStyle;
|
|
9786
|
+
}
|
|
9787
|
+
const artifactStyle = resolveArtifactMaskStyle();
|
|
9788
|
+
const nextMaskLayer = artifactStyle
|
|
9789
|
+
? ensureMaskLayer(artifactStyle)
|
|
8413
9790
|
: maskLayer;
|
|
8414
|
-
nextMaskLayer?.setMaskStyle(
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
9791
|
+
nextMaskLayer?.setMaskStyle(artifactStyle ? createVisibilityMaskStyle(artifactStyle) : null);
|
|
9792
|
+
nextMaskLayer?.setMaskHaloStyle(currentMaskHaloStyle
|
|
9793
|
+
? createVisibilityMaskHaloStyle(currentMaskHaloStyle)
|
|
9794
|
+
: null);
|
|
9795
|
+
if (artifactStyle) {
|
|
8418
9796
|
attachMaskLayerDisplay();
|
|
8419
9797
|
syncSceneChildren();
|
|
8420
9798
|
}
|
|
@@ -8448,11 +9826,12 @@ async function createPixiMediaScene(options) {
|
|
|
8448
9826
|
const boxState = boxLayer.drawFrame(mediaTime, viewportScale);
|
|
8449
9827
|
polygonLayer?.drawFrame(mediaTime, viewportScale);
|
|
8450
9828
|
vectorLayer.drawFrame(mediaTime, viewportScale);
|
|
9829
|
+
const regionState = regionLayer.drawFrame(mediaTime, viewportScale);
|
|
8451
9830
|
interactionLayer?.drawFrame(mediaTime);
|
|
8452
9831
|
drawFocusLayer(mediaTime);
|
|
8453
9832
|
drawInteractionPresentationLayer(mediaTime);
|
|
8454
9833
|
labelLayer?.drawFrame(mediaTime, viewportScale);
|
|
8455
|
-
return createPresentedSampleState(mediaTime, boxState);
|
|
9834
|
+
return createPresentedSampleState(mediaTime, boxState, regionState);
|
|
8456
9835
|
},
|
|
8457
9836
|
setSelectedDetection(selection, mediaTime) {
|
|
8458
9837
|
currentMediaTime = selection?.mediaTime ?? mediaTime;
|
|
@@ -8479,6 +9858,7 @@ async function createPixiMediaScene(options) {
|
|
|
8479
9858
|
polygonLayer?.destroy();
|
|
8480
9859
|
labelLayer?.destroy();
|
|
8481
9860
|
vectorLayer.destroy();
|
|
9861
|
+
regionLayer.destroy();
|
|
8482
9862
|
maskBrushPreview?.destroy();
|
|
8483
9863
|
unsubscribeFastTranslate?.();
|
|
8484
9864
|
unsubscribeEditingState?.();
|
|
@@ -8495,21 +9875,24 @@ async function createPixiMediaScene(options) {
|
|
|
8495
9875
|
});
|
|
8496
9876
|
},
|
|
8497
9877
|
};
|
|
8498
|
-
function createPresentedSampleState(mediaTime, boxState) {
|
|
9878
|
+
function createPresentedSampleState(mediaTime, boxState, regionState) {
|
|
8499
9879
|
const detectionFrame = options.detectionTimeline.selectFrame(mediaTime);
|
|
8500
9880
|
return {
|
|
8501
|
-
activeDetectionCount: countPresentedDetections(detectionFrame, mediaTime, boxState),
|
|
9881
|
+
activeDetectionCount: countPresentedDetections(detectionFrame, mediaTime, boxState, regionState),
|
|
8502
9882
|
activeDetectionFrameIndex: detectionFrame?.frameIndex ?? null,
|
|
8503
9883
|
activeDetectionFrameTime: detectionFrame?.mediaTime ?? null,
|
|
8504
9884
|
detectionBuffer: options.detectionTimeline.getState(),
|
|
8505
9885
|
mediaTime,
|
|
8506
9886
|
};
|
|
8507
9887
|
}
|
|
8508
|
-
function countPresentedDetections(frame, mediaTime, boxState) {
|
|
9888
|
+
function countPresentedDetections(frame, mediaTime, boxState, regionState) {
|
|
8509
9889
|
if (!frame) {
|
|
8510
9890
|
return 0;
|
|
8511
9891
|
}
|
|
8512
9892
|
const visibleDetectionIndexes = new Set(boxState.activeDetectionIndexes);
|
|
9893
|
+
for (const detectionIndex of regionState?.activeDetectionIndexes ?? []) {
|
|
9894
|
+
visibleDetectionIndexes.add(detectionIndex);
|
|
9895
|
+
}
|
|
8513
9896
|
for (const [detectionIndex, detection] of frame.detections.entries()) {
|
|
8514
9897
|
if (visibleDetectionIndexes.has(detectionIndex)) {
|
|
8515
9898
|
continue;
|
|
@@ -8536,7 +9919,9 @@ async function createPixiMediaScene(options) {
|
|
|
8536
9919
|
function ensureMaskLayer(maskStyle) {
|
|
8537
9920
|
if (!maskLayer) {
|
|
8538
9921
|
maskLayer = createPixiMaskLayer({
|
|
9922
|
+
BlurFilter,
|
|
8539
9923
|
Container,
|
|
9924
|
+
Rectangle,
|
|
8540
9925
|
ImageSource,
|
|
8541
9926
|
Mesh,
|
|
8542
9927
|
MeshGeometry,
|
|
@@ -8545,6 +9930,9 @@ async function createPixiMediaScene(options) {
|
|
|
8545
9930
|
Texture,
|
|
8546
9931
|
UniformGroup,
|
|
8547
9932
|
detectionTimeline: options.detectionTimeline,
|
|
9933
|
+
maskHaloStyle: currentMaskHaloStyle
|
|
9934
|
+
? createVisibilityMaskHaloStyle(currentMaskHaloStyle)
|
|
9935
|
+
: null,
|
|
8548
9936
|
maskStyle: createVisibilityMaskStyle(maskStyle),
|
|
8549
9937
|
onActiveIdMaskFramePresented: handleActiveIdMaskFramePresented,
|
|
8550
9938
|
renderPreparation: options.renderPreparation,
|
|
@@ -8622,6 +10010,7 @@ async function createPixiMediaScene(options) {
|
|
|
8622
10010
|
Shader,
|
|
8623
10011
|
UniformGroup,
|
|
8624
10012
|
focusStyle,
|
|
10013
|
+
isDetectionVisible: (detection) => !resolveAnnotationStyleState(detection, currentVisibility).hidden,
|
|
8625
10014
|
});
|
|
8626
10015
|
}
|
|
8627
10016
|
return focusLayer;
|
|
@@ -8657,10 +10046,10 @@ async function createPixiMediaScene(options) {
|
|
|
8657
10046
|
const interactionState = interactionLayer?.getState();
|
|
8658
10047
|
focusLayer.drawFrame({
|
|
8659
10048
|
frame,
|
|
8660
|
-
hoveredPick: interactionState?.hoveredPick ?? null,
|
|
10049
|
+
hoveredPick: filterVisiblePick(interactionState?.hoveredPick ?? null),
|
|
8661
10050
|
idMaskArtifact: maskLayer?.getActiveIdMaskFrameTexture() ?? null,
|
|
8662
10051
|
mediaTime,
|
|
8663
|
-
selectedPick: interactionState?.selectedPick ?? null,
|
|
10052
|
+
selectedPick: filterVisiblePick(interactionState?.selectedPick ?? null),
|
|
8664
10053
|
viewportScale,
|
|
8665
10054
|
});
|
|
8666
10055
|
}
|
|
@@ -8672,11 +10061,11 @@ async function createPixiMediaScene(options) {
|
|
|
8672
10061
|
const interactionState = interactionLayer?.getState();
|
|
8673
10062
|
interactionPresentationLayer.drawFrame({
|
|
8674
10063
|
frame,
|
|
8675
|
-
hoveredPick: interactionState?.hoveredPick ?? null,
|
|
10064
|
+
hoveredPick: filterVisiblePick(interactionState?.hoveredPick ?? null),
|
|
8676
10065
|
idMaskArtifact: maskLayer?.getActiveIdMaskFrameTexture() ?? null,
|
|
8677
10066
|
mediaTime,
|
|
8678
|
-
selectedPick: interactionState?.selectedPick ?? null,
|
|
8679
|
-
selectedPicks: interactionState?.selectedPicks,
|
|
10067
|
+
selectedPick: filterVisiblePick(interactionState?.selectedPick ?? null),
|
|
10068
|
+
selectedPicks: interactionState?.selectedPicks.filter(({ detection }) => !resolveAnnotationStyleState(detection, currentVisibility).hidden),
|
|
8680
10069
|
viewportScale,
|
|
8681
10070
|
});
|
|
8682
10071
|
}
|
|
@@ -8747,6 +10136,12 @@ async function createPixiMediaScene(options) {
|
|
|
8747
10136
|
};
|
|
8748
10137
|
return visibilityMaskStyle;
|
|
8749
10138
|
}
|
|
10139
|
+
function filterVisiblePick(pick) {
|
|
10140
|
+
return pick &&
|
|
10141
|
+
!resolveAnnotationStyleState(pick.detection, currentVisibility).hidden
|
|
10142
|
+
? pick
|
|
10143
|
+
: null;
|
|
10144
|
+
}
|
|
8750
10145
|
}
|
|
8751
10146
|
function canReuseMaskVisibilityArtifacts(previous, next) {
|
|
8752
10147
|
return ((previous?.annotationsHidden === true) ===
|
|
@@ -9393,10 +10788,20 @@ async function createMediaSession(options) {
|
|
|
9393
10788
|
? undefined
|
|
9394
10789
|
: { enabledRendererKinds: renderers.map(({ kind }) => kind) });
|
|
9395
10790
|
};
|
|
9396
|
-
const resolveRendererPresentation = (presentation) =>
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
10791
|
+
const resolveRendererPresentation = (presentation) => {
|
|
10792
|
+
const resolved = resolvePresentation(presentation);
|
|
10793
|
+
return {
|
|
10794
|
+
...resolved,
|
|
10795
|
+
// Style-backed renderers have already been lowered into their
|
|
10796
|
+
// specialized fields, including source overrides. Keep their kinds in
|
|
10797
|
+
// the authoritative list so the renderer core does not clear those
|
|
10798
|
+
// fields when direct renderers are also present, but let the resolved
|
|
10799
|
+
// fields win over the descriptors' original global styles.
|
|
10800
|
+
renderers: resolved.renderers?.map((renderer) => renderer.kind === "region"
|
|
10801
|
+
? renderer
|
|
10802
|
+
: { ...renderer, style: undefined }),
|
|
10803
|
+
};
|
|
10804
|
+
};
|
|
9400
10805
|
const initialPresentation = resolveRendererPresentation(currentPresentation);
|
|
9401
10806
|
const renderer = await createMediaRenderer({
|
|
9402
10807
|
...options.renderer,
|
|
@@ -9404,6 +10809,8 @@ async function createMediaSession(options) {
|
|
|
9404
10809
|
backgroundColor: initialPresentation.backgroundColor,
|
|
9405
10810
|
annotationOverlayStyle: initialPresentation.annotationOverlayStyle,
|
|
9406
10811
|
boxStyle: initialPresentation.boxStyle,
|
|
10812
|
+
boxCornerStyle: initialPresentation.boxCornerStyle,
|
|
10813
|
+
ellipseStyle: initialPresentation.ellipseStyle,
|
|
9407
10814
|
container: options.container,
|
|
9408
10815
|
detectionBuffer: sessionDefaults.detectionBuffer,
|
|
9409
10816
|
detectionFrames: sessionDetections.detectionFrames,
|
|
@@ -9412,9 +10819,12 @@ async function createMediaSession(options) {
|
|
|
9412
10819
|
focusStyle: initialPresentation.focusStyle,
|
|
9413
10820
|
interactionStyle: initialPresentation.interactionStyle,
|
|
9414
10821
|
labelStyle: initialPresentation.labelStyle,
|
|
10822
|
+
maskHaloStyle: initialPresentation.maskHaloStyle,
|
|
9415
10823
|
maskStyle: initialPresentation.maskStyle,
|
|
10824
|
+
markerStyle: initialPresentation.markerStyle,
|
|
9416
10825
|
polygonStyle: initialPresentation.polygonStyle,
|
|
9417
10826
|
polylineStyle: initialPresentation.polylineStyle,
|
|
10827
|
+
renderers: initialPresentation.renderers,
|
|
9418
10828
|
keypointStyle: initialPresentation.keypointStyle,
|
|
9419
10829
|
visibility: initialPresentation.visibility,
|
|
9420
10830
|
onState(state) {
|
|
@@ -9569,5 +10979,5 @@ function getErrorMessage(error, fallback) {
|
|
|
9569
10979
|
return error instanceof Error ? error.message : fallback;
|
|
9570
10980
|
}
|
|
9571
10981
|
|
|
9572
|
-
export { DetectionTimelineOrigin, MediaNormalizationAudioCodec, MediaNormalizationContainer, MediaNormalizationFit, MediaNormalizationVideoCodec, MediaPreparationError, MediaProbeIssueCode, MediaProbeStatus, RenderPreparationArtifactFrameStatus, RenderPreparationArtifactKind, RenderPreparationExecutionMode, RenderPreparationMode, RenderPreparationWorkerStatus, createBrowserColdDetectionFrameStore, createChunkedDetectionFrameSource, createImageUrlMediaSource, createMediaRenderer, createMediaSession, createMediaStreamRendererSource, createStaticImageMediaSource, normalizeMedia, normalizeMediaProgressively, prepareMedia, prepareMediaProgressively, probeMedia };
|
|
10982
|
+
export { DetectionPostProcessingMode, DetectionTimelineOrigin, MediaNormalizationAudioCodec, MediaNormalizationContainer, MediaNormalizationFit, MediaNormalizationVideoCodec, MediaPreparationError, MediaProbeIssueCode, MediaProbeStatus, RenderPreparationArtifactFrameStatus, RenderPreparationArtifactKind, RenderPreparationExecutionMode, RenderPreparationMode, RenderPreparationWorkerStatus, createBrowserColdDetectionFrameStore, createChunkedDetectionFrameSource, createDefaultDetectionPostProcessingWorkerFactory, createDetectionPostProcessingPipeline, createImageUrlMediaSource, createMediaRenderer, createMediaSession, createMediaStreamRendererSource, createStaticImageMediaSource, normalizeMedia, normalizeMediaProgressively, prepareMedia, prepareMediaProgressively, probeMedia };
|
|
9573
10983
|
//# sourceMappingURL=index.js.map
|