supervision 0.1.2 → 0.1.4
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 +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1232 -107
- package/dist/index.js.map +1 -1
- package/dist/mask-preparation.worker.js +43 -9
- package/dist/mask-preparation.worker.js.map +1 -1
- 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/types/media-renderer.d.ts +1 -1
- package/dist/types/media-renderer.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.d.ts +19 -3
- package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.js +944 -157
- 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/styles/annotation-renderer-presentation.d.ts +9 -0
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-presentation.d.ts.map +1 -0
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts +47 -0
- package/node_modules/supervision-js-core/dist/styles/annotation-renderer-registry.d.ts.map +1 -0
- 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 +38 -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 +18 -2
- 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 +197 -0
- package/node_modules/supervision-js-core/dist/types/annotation-renderer.d.ts.map +1 -0
- 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/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 +26 -1
- 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/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/shape-geometry.d.ts +50 -0
- package/node_modules/supervision-js-core/dist/utils/shape-geometry.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -1928,6 +1928,29 @@ function createDetectionPickKey(pick) {
|
|
|
1928
1928
|
pick.geometryIndex ?? "geometry",
|
|
1929
1929
|
].join(":");
|
|
1930
1930
|
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Compares the frame-independent membership of two ordered selections.
|
|
1933
|
+
*
|
|
1934
|
+
* Detection ids retain their runtime type, so numeric `1` and string `"1"`
|
|
1935
|
+
* remain distinct identities. Anonymous detections keep their frame-local
|
|
1936
|
+
* index identity.
|
|
1937
|
+
*/
|
|
1938
|
+
function haveSameDetectionPickIdentities(current, next) {
|
|
1939
|
+
if (current.length !== next.length) {
|
|
1940
|
+
return false;
|
|
1941
|
+
}
|
|
1942
|
+
return current.every((pick, index) => {
|
|
1943
|
+
const nextPick = next[index];
|
|
1944
|
+
if (!nextPick) {
|
|
1945
|
+
return false;
|
|
1946
|
+
}
|
|
1947
|
+
const id = pick.detection.id;
|
|
1948
|
+
const nextId = nextPick.detection.id;
|
|
1949
|
+
return id === undefined || nextId === undefined
|
|
1950
|
+
? id === nextId && pick.detectionIndex === nextPick.detectionIndex
|
|
1951
|
+
: typeof id === typeof nextId && id === nextId;
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1931
1954
|
function rebaseDetectionPickToFrame(pick, frame) {
|
|
1932
1955
|
if (!pick || !frame) {
|
|
1933
1956
|
return null;
|
|
@@ -1953,13 +1976,55 @@ function rebaseDetectionPickToFrame(pick, frame) {
|
|
|
1953
1976
|
? rebasedPick
|
|
1954
1977
|
: null;
|
|
1955
1978
|
}
|
|
1979
|
+
/**
|
|
1980
|
+
* Carries a pick forward onto a different frame of the same timeline.
|
|
1981
|
+
*
|
|
1982
|
+
* Same-frame rebases behave exactly like `rebaseDetectionPickToFrame`. Across
|
|
1983
|
+
* frames, the pick follows its detection only when the detection id is defined
|
|
1984
|
+
* and unique in both frames, so anonymous and duplicate-id detections keep the
|
|
1985
|
+
* per-frame lifetime they have today. Returns `null` when the detection has no
|
|
1986
|
+
* followable identity or has left the frame.
|
|
1987
|
+
*/
|
|
1988
|
+
function followDetectionPickAcrossFrames(pick, frame) {
|
|
1989
|
+
const rebasedPick = rebaseDetectionPickToFrame(pick, frame);
|
|
1990
|
+
if (rebasedPick || !pick || !frame) {
|
|
1991
|
+
return rebasedPick;
|
|
1992
|
+
}
|
|
1993
|
+
const id = pick.detection.id;
|
|
1994
|
+
if (id === undefined ||
|
|
1995
|
+
!hasUniqueDetectionId(pick.frame, id) ||
|
|
1996
|
+
!hasUniqueDetectionId(frame, id)) {
|
|
1997
|
+
return null;
|
|
1998
|
+
}
|
|
1999
|
+
const detectionIndex = frame.detections.findIndex((detection) => detection.id === id);
|
|
2000
|
+
const detection = frame.detections[detectionIndex];
|
|
2001
|
+
if (!detection) {
|
|
2002
|
+
return null;
|
|
2003
|
+
}
|
|
2004
|
+
return {
|
|
2005
|
+
detection,
|
|
2006
|
+
detectionIndex,
|
|
2007
|
+
frame,
|
|
2008
|
+
geometryIndex: pick.geometryIndex,
|
|
2009
|
+
mediaTime: frame.mediaTime,
|
|
2010
|
+
point: pick.point,
|
|
2011
|
+
target: pick.target,
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
1956
2014
|
function detectionPickKey(pick) {
|
|
1957
2015
|
if (pick.detection.id === undefined) {
|
|
1958
2016
|
return ["anonymous", pick.detectionIndex];
|
|
1959
2017
|
}
|
|
2018
|
+
const idType = typeof pick.detection.id;
|
|
1960
2019
|
return hasUniqueDetectionId(pick.frame, pick.detection.id)
|
|
1961
|
-
? ["id", String(pick.detection.id)]
|
|
1962
|
-
: [
|
|
2020
|
+
? ["id", idType, String(pick.detection.id)]
|
|
2021
|
+
: [
|
|
2022
|
+
"duplicate-id",
|
|
2023
|
+
idType,
|
|
2024
|
+
String(pick.detection.id),
|
|
2025
|
+
"index",
|
|
2026
|
+
pick.detectionIndex,
|
|
2027
|
+
];
|
|
1963
2028
|
}
|
|
1964
2029
|
function hasUniqueDetectionId(frame, id) {
|
|
1965
2030
|
let count = 0;
|
|
@@ -2652,17 +2717,46 @@ function resolveStyleValue(value, detection, context) {
|
|
|
2652
2717
|
: value;
|
|
2653
2718
|
}
|
|
2654
2719
|
|
|
2720
|
+
/**
|
|
2721
|
+
* Resolves the renderer-neutral stroke contract without dropping optional
|
|
2722
|
+
* geometry controls as styles move from public configuration to draw
|
|
2723
|
+
* instructions.
|
|
2724
|
+
*
|
|
2725
|
+
* Keep this as the single normalization point for every built-in style. That
|
|
2726
|
+
* makes additions to `StrokeStyle` deliberate instead of relying on each style
|
|
2727
|
+
* implementation to copy a growing property list correctly.
|
|
2728
|
+
*/
|
|
2729
|
+
function resolveStrokeStyle(stroke, defaults) {
|
|
2730
|
+
const alignment = stroke?.alignment ?? defaults.alignment;
|
|
2731
|
+
const cap = stroke?.cap ?? defaults.cap;
|
|
2732
|
+
const dash = stroke?.dash ?? defaults.dash;
|
|
2733
|
+
const join = stroke?.join ?? defaults.join;
|
|
2734
|
+
const miterLimit = stroke?.miterLimit ?? defaults.miterLimit;
|
|
2735
|
+
return {
|
|
2736
|
+
alpha: stroke?.alpha ?? defaults.alpha,
|
|
2737
|
+
color: stroke?.color ?? defaults.color,
|
|
2738
|
+
width: stroke?.width ?? defaults.width,
|
|
2739
|
+
...(alignment === undefined ? {} : { alignment }),
|
|
2740
|
+
...(cap === undefined ? {} : { cap }),
|
|
2741
|
+
...(dash === undefined ? {} : { dash }),
|
|
2742
|
+
...(join === undefined ? {} : { join }),
|
|
2743
|
+
...(miterLimit === undefined ? {} : { miterLimit }),
|
|
2744
|
+
};
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
/** Alignment for strokes applied to closed rendered geometry. */
|
|
2748
|
+
var StrokeAlignment;
|
|
2749
|
+
(function (StrokeAlignment) {
|
|
2750
|
+
StrokeAlignment["Inside"] = "inside";
|
|
2751
|
+
StrokeAlignment["Center"] = "center";
|
|
2752
|
+
StrokeAlignment["Outside"] = "outside";
|
|
2753
|
+
})(StrokeAlignment || (StrokeAlignment = {}));
|
|
2754
|
+
|
|
2655
2755
|
var BoxShape;
|
|
2656
2756
|
(function (BoxShape) {
|
|
2657
2757
|
BoxShape["Rect"] = "rect";
|
|
2658
2758
|
BoxShape["RoundedRect"] = "roundedRect";
|
|
2659
2759
|
})(BoxShape || (BoxShape = {}));
|
|
2660
|
-
var BoxStrokeAlignment;
|
|
2661
|
-
(function (BoxStrokeAlignment) {
|
|
2662
|
-
BoxStrokeAlignment["Inside"] = "inside";
|
|
2663
|
-
BoxStrokeAlignment["Center"] = "center";
|
|
2664
|
-
BoxStrokeAlignment["Outside"] = "outside";
|
|
2665
|
-
})(BoxStrokeAlignment || (BoxStrokeAlignment = {}));
|
|
2666
2760
|
|
|
2667
2761
|
const DEFAULT_BOX_STROKE_ALPHA = 1;
|
|
2668
2762
|
const DEFAULT_BOX_STROKE_COLOR = 0x00ff66;
|
|
@@ -2716,22 +2810,12 @@ class BaseBoxStyle {
|
|
|
2716
2810
|
if (stroke === null) {
|
|
2717
2811
|
return undefined;
|
|
2718
2812
|
}
|
|
2719
|
-
|
|
2720
|
-
alpha:
|
|
2721
|
-
color:
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
...resolvedStroke,
|
|
2726
|
-
...(stroke?.alignment === undefined
|
|
2727
|
-
? {}
|
|
2728
|
-
: { alignment: stroke.alignment }),
|
|
2729
|
-
...(stroke?.dash === undefined
|
|
2730
|
-
? context.ephemeral
|
|
2731
|
-
? { dash: [6, 4] }
|
|
2732
|
-
: {}
|
|
2733
|
-
: { dash: stroke.dash }),
|
|
2734
|
-
};
|
|
2813
|
+
return resolveStrokeStyle(stroke, {
|
|
2814
|
+
alpha: DEFAULT_BOX_STROKE_ALPHA,
|
|
2815
|
+
color: DEFAULT_BOX_STROKE_COLOR,
|
|
2816
|
+
...(context.ephemeral ? { dash: [6, 4] } : {}),
|
|
2817
|
+
width: DEFAULT_BOX_STROKE_WIDTH,
|
|
2818
|
+
});
|
|
2735
2819
|
}
|
|
2736
2820
|
resolveFill(detection, context) {
|
|
2737
2821
|
const fill = resolveStyleValue(this.options.fill, detection, context);
|
|
@@ -2745,6 +2829,83 @@ class BaseBoxStyle {
|
|
|
2745
2829
|
}
|
|
2746
2830
|
}
|
|
2747
2831
|
|
|
2832
|
+
const DEFAULT_CORNER_LENGTH = 20;
|
|
2833
|
+
const DEFAULT_CORNER_STROKE = {
|
|
2834
|
+
alpha: 1,
|
|
2835
|
+
cap: "round",
|
|
2836
|
+
color: 0x00ff66,
|
|
2837
|
+
join: "round",
|
|
2838
|
+
width: 2,
|
|
2839
|
+
};
|
|
2840
|
+
/**
|
|
2841
|
+
* Configurable four-corner decoration derived from a detection rectangle.
|
|
2842
|
+
*
|
|
2843
|
+
* This is the BoxCornerAnnotator-style facade: it keeps the rectangle semantic
|
|
2844
|
+
* and draws only its four open corner segments.
|
|
2845
|
+
*/
|
|
2846
|
+
class BaseBoxCornerStyle {
|
|
2847
|
+
options;
|
|
2848
|
+
constructor(options = {}) {
|
|
2849
|
+
this.options = options;
|
|
2850
|
+
}
|
|
2851
|
+
resolve(detection, context) {
|
|
2852
|
+
if (!detection.rect ||
|
|
2853
|
+
context.hidden ||
|
|
2854
|
+
this.options.shouldRender?.(detection, context) === false) {
|
|
2855
|
+
return undefined;
|
|
2856
|
+
}
|
|
2857
|
+
const resolvedLength = resolveStyleValue(this.options.length, detection, context) ??
|
|
2858
|
+
DEFAULT_CORNER_LENGTH;
|
|
2859
|
+
const viewportScale = context.viewportScale && context.viewportScale > 0
|
|
2860
|
+
? context.viewportScale
|
|
2861
|
+
: 1;
|
|
2862
|
+
const length = Math.min(Math.max(0, resolvedLength / viewportScale), detection.rect.width / 2, detection.rect.height / 2);
|
|
2863
|
+
const stroke = resolveStyleValue(this.options.stroke, detection, context);
|
|
2864
|
+
if (length <= 0 || stroke === null)
|
|
2865
|
+
return undefined;
|
|
2866
|
+
const { height, width, x, y } = detection.rect;
|
|
2867
|
+
const left = x - width / 2;
|
|
2868
|
+
const right = x + width / 2;
|
|
2869
|
+
const top = y - height / 2;
|
|
2870
|
+
const bottom = y + height / 2;
|
|
2871
|
+
const resolvedStroke = resolveStrokeStyle(stroke, DEFAULT_CORNER_STROKE);
|
|
2872
|
+
const openStroke = {
|
|
2873
|
+
alpha: resolvedStroke.alpha,
|
|
2874
|
+
cap: resolvedStroke.cap,
|
|
2875
|
+
color: resolvedStroke.color,
|
|
2876
|
+
dash: resolvedStroke.dash,
|
|
2877
|
+
join: resolvedStroke.join,
|
|
2878
|
+
miterLimit: resolvedStroke.miterLimit,
|
|
2879
|
+
width: resolvedStroke.width,
|
|
2880
|
+
};
|
|
2881
|
+
return {
|
|
2882
|
+
segments: [
|
|
2883
|
+
[
|
|
2884
|
+
{ x: left + length, y: top },
|
|
2885
|
+
{ x: left, y: top },
|
|
2886
|
+
{ x: left, y: top + length },
|
|
2887
|
+
],
|
|
2888
|
+
[
|
|
2889
|
+
{ x: right - length, y: top },
|
|
2890
|
+
{ x: right, y: top },
|
|
2891
|
+
{ x: right, y: top + length },
|
|
2892
|
+
],
|
|
2893
|
+
[
|
|
2894
|
+
{ x: left + length, y: bottom },
|
|
2895
|
+
{ x: left, y: bottom },
|
|
2896
|
+
{ x: left, y: bottom - length },
|
|
2897
|
+
],
|
|
2898
|
+
[
|
|
2899
|
+
{ x: right - length, y: bottom },
|
|
2900
|
+
{ x: right, y: bottom },
|
|
2901
|
+
{ x: right, y: bottom - length },
|
|
2902
|
+
],
|
|
2903
|
+
],
|
|
2904
|
+
stroke: openStroke,
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2748
2909
|
var FocusTargetMode;
|
|
2749
2910
|
(function (FocusTargetMode) {
|
|
2750
2911
|
FocusTargetMode["Hovered"] = "hovered";
|
|
@@ -2943,18 +3104,11 @@ class BaseInteractionStyle {
|
|
|
2943
3104
|
return undefined;
|
|
2944
3105
|
}
|
|
2945
3106
|
const defaults = getStateDefaults(context.state);
|
|
2946
|
-
|
|
2947
|
-
alpha:
|
|
2948
|
-
color:
|
|
2949
|
-
width:
|
|
2950
|
-
};
|
|
2951
|
-
if (stroke?.alignment !== undefined) {
|
|
2952
|
-
return {
|
|
2953
|
-
...resolvedStroke,
|
|
2954
|
-
alignment: stroke.alignment,
|
|
2955
|
-
};
|
|
2956
|
-
}
|
|
2957
|
-
return resolvedStroke;
|
|
3107
|
+
return resolveStrokeStyle(stroke, {
|
|
3108
|
+
alpha: defaults.strokeAlpha,
|
|
3109
|
+
color: defaults.strokeColor,
|
|
3110
|
+
width: defaults.strokeWidth,
|
|
3111
|
+
});
|
|
2958
3112
|
}
|
|
2959
3113
|
resolveFill(detection, context) {
|
|
2960
3114
|
const fill = resolveStyleValue(this.options.fill, detection, context);
|
|
@@ -3206,6 +3360,115 @@ function createArtifactKey(options) {
|
|
|
3206
3360
|
return `base:${color}:${fillAlpha}:${mode}:${serializeStroke(resolveStroke(options.stroke, color, mode))}`;
|
|
3207
3361
|
}
|
|
3208
3362
|
|
|
3363
|
+
/**
|
|
3364
|
+
* Discriminator for renderer-neutral shape draw instructions.
|
|
3365
|
+
*/
|
|
3366
|
+
var ShapeInstructionKind;
|
|
3367
|
+
(function (ShapeInstructionKind) {
|
|
3368
|
+
ShapeInstructionKind["Ellipse"] = "ellipse";
|
|
3369
|
+
ShapeInstructionKind["Marker"] = "marker";
|
|
3370
|
+
ShapeInstructionKind["Path"] = "path";
|
|
3371
|
+
})(ShapeInstructionKind || (ShapeInstructionKind = {}));
|
|
3372
|
+
var MarkerShape;
|
|
3373
|
+
(function (MarkerShape) {
|
|
3374
|
+
MarkerShape["Circle"] = "circle";
|
|
3375
|
+
MarkerShape["Cross"] = "cross";
|
|
3376
|
+
MarkerShape["Square"] = "square";
|
|
3377
|
+
MarkerShape["Triangle"] = "triangle";
|
|
3378
|
+
})(MarkerShape || (MarkerShape = {}));
|
|
3379
|
+
/**
|
|
3380
|
+
* Coordinate space for marker sizing. Media sizes scale with the viewport;
|
|
3381
|
+
* screen sizes stay constant on screen, matching stroke-width semantics.
|
|
3382
|
+
*/
|
|
3383
|
+
var MarkerSizeSpace;
|
|
3384
|
+
(function (MarkerSizeSpace) {
|
|
3385
|
+
MarkerSizeSpace["Media"] = "media";
|
|
3386
|
+
MarkerSizeSpace["Screen"] = "screen";
|
|
3387
|
+
})(MarkerSizeSpace || (MarkerSizeSpace = {}));
|
|
3388
|
+
|
|
3389
|
+
const DEFAULT_FILL = { alpha: 1, color: 0x00ff66 };
|
|
3390
|
+
const DEFAULT_STROKE = { alpha: 1, color: 0xffffff, width: 1 };
|
|
3391
|
+
const DEFAULT_SIZE = 12;
|
|
3392
|
+
/**
|
|
3393
|
+
* Configurable marker renderer style anchored to semantic detection geometry.
|
|
3394
|
+
*
|
|
3395
|
+
* Use `center` for any domain-specific anchor (keypoint, centroid, or custom
|
|
3396
|
+
* metadata). Without it, the marker lands at the detection rectangle center.
|
|
3397
|
+
*/
|
|
3398
|
+
class BaseMarkerStyle {
|
|
3399
|
+
options;
|
|
3400
|
+
constructor(options = {}) {
|
|
3401
|
+
this.options = options;
|
|
3402
|
+
}
|
|
3403
|
+
resolve(detection, context) {
|
|
3404
|
+
if (context.hidden ||
|
|
3405
|
+
this.options.shouldRender?.(detection, context) === false) {
|
|
3406
|
+
return undefined;
|
|
3407
|
+
}
|
|
3408
|
+
const center = resolveStyleValue(this.options.center, detection, context) ??
|
|
3409
|
+
(detection.rect
|
|
3410
|
+
? { x: detection.rect.x, y: detection.rect.y }
|
|
3411
|
+
: undefined);
|
|
3412
|
+
const shape = resolveStyleValue(this.options.shape, detection, context) ??
|
|
3413
|
+
MarkerShape.Circle;
|
|
3414
|
+
const size = resolveStyleValue(this.options.size, detection, context) ?? DEFAULT_SIZE;
|
|
3415
|
+
const sizeSpace = resolveStyleValue(this.options.sizeSpace, detection, context) ??
|
|
3416
|
+
MarkerSizeSpace.Screen;
|
|
3417
|
+
const rotation = resolveStyleValue(this.options.rotation, detection, context);
|
|
3418
|
+
const stroke = resolveStyleValue(this.options.stroke, detection, context);
|
|
3419
|
+
if (!center || !Number.isFinite(size) || size <= 0) {
|
|
3420
|
+
return undefined;
|
|
3421
|
+
}
|
|
3422
|
+
if (shape === MarkerShape.Cross) {
|
|
3423
|
+
if (stroke === null) {
|
|
3424
|
+
return undefined;
|
|
3425
|
+
}
|
|
3426
|
+
const resolvedStroke = resolveStrokeStyle(stroke, DEFAULT_STROKE);
|
|
3427
|
+
const openStroke = {
|
|
3428
|
+
alpha: resolvedStroke.alpha,
|
|
3429
|
+
cap: resolvedStroke.cap,
|
|
3430
|
+
color: resolvedStroke.color,
|
|
3431
|
+
dash: resolvedStroke.dash,
|
|
3432
|
+
join: resolvedStroke.join,
|
|
3433
|
+
miterLimit: resolvedStroke.miterLimit,
|
|
3434
|
+
width: resolvedStroke.width,
|
|
3435
|
+
};
|
|
3436
|
+
return {
|
|
3437
|
+
center,
|
|
3438
|
+
shape,
|
|
3439
|
+
size,
|
|
3440
|
+
sizeSpace,
|
|
3441
|
+
...(rotation === undefined ? {} : { rotation }),
|
|
3442
|
+
stroke: openStroke,
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
const fill = resolveStyleValue(this.options.fill, detection, context);
|
|
3446
|
+
if (fill === null && stroke === null) {
|
|
3447
|
+
return undefined;
|
|
3448
|
+
}
|
|
3449
|
+
return {
|
|
3450
|
+
center,
|
|
3451
|
+
shape,
|
|
3452
|
+
size,
|
|
3453
|
+
sizeSpace,
|
|
3454
|
+
...(rotation === undefined ? {} : { rotation }),
|
|
3455
|
+
...(fill === null
|
|
3456
|
+
? {}
|
|
3457
|
+
: {
|
|
3458
|
+
fill: {
|
|
3459
|
+
alpha: fill?.alpha ?? DEFAULT_FILL.alpha,
|
|
3460
|
+
color: fill?.color ?? DEFAULT_FILL.color,
|
|
3461
|
+
},
|
|
3462
|
+
}),
|
|
3463
|
+
...(stroke === null
|
|
3464
|
+
? {}
|
|
3465
|
+
: stroke === undefined
|
|
3466
|
+
? { stroke: DEFAULT_STROKE }
|
|
3467
|
+
: { stroke: resolveStrokeStyle(stroke, DEFAULT_STROKE) }),
|
|
3468
|
+
};
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
|
|
3209
3472
|
class BasePolygonStyle {
|
|
3210
3473
|
options;
|
|
3211
3474
|
constructor(options = {}) {
|
|
@@ -3233,15 +3496,11 @@ class BasePolygonStyle {
|
|
|
3233
3496
|
...(stroke === null
|
|
3234
3497
|
? {}
|
|
3235
3498
|
: {
|
|
3236
|
-
stroke: {
|
|
3237
|
-
alpha:
|
|
3238
|
-
color:
|
|
3239
|
-
width:
|
|
3240
|
-
|
|
3241
|
-
? {}
|
|
3242
|
-
: { alignment: stroke.alignment }),
|
|
3243
|
-
...(stroke?.dash === undefined ? {} : { dash: stroke.dash }),
|
|
3244
|
-
},
|
|
3499
|
+
stroke: resolveStrokeStyle(stroke, {
|
|
3500
|
+
alpha: 1,
|
|
3501
|
+
color: 0x00ff66,
|
|
3502
|
+
width: 2,
|
|
3503
|
+
}),
|
|
3245
3504
|
}),
|
|
3246
3505
|
};
|
|
3247
3506
|
}
|
|
@@ -3265,15 +3524,11 @@ class BasePolylineStyle {
|
|
|
3265
3524
|
}
|
|
3266
3525
|
return {
|
|
3267
3526
|
points: detection.polyline.points,
|
|
3268
|
-
stroke: {
|
|
3269
|
-
alpha:
|
|
3270
|
-
color:
|
|
3271
|
-
width:
|
|
3272
|
-
|
|
3273
|
-
? {}
|
|
3274
|
-
: { alignment: stroke.alignment }),
|
|
3275
|
-
...(stroke?.dash === undefined ? {} : { dash: stroke.dash }),
|
|
3276
|
-
},
|
|
3527
|
+
stroke: resolveStrokeStyle(stroke, {
|
|
3528
|
+
alpha: 1,
|
|
3529
|
+
color: 0x00ff66,
|
|
3530
|
+
width: 2,
|
|
3531
|
+
}),
|
|
3277
3532
|
};
|
|
3278
3533
|
}
|
|
3279
3534
|
}
|
|
@@ -3307,19 +3562,21 @@ class BaseKeypointStyle {
|
|
|
3307
3562
|
const edges = geometry.edges.map(([fromIndex, toIndex], edgeIndex) => ({
|
|
3308
3563
|
from: geometry.points[fromIndex],
|
|
3309
3564
|
to: geometry.points[toIndex],
|
|
3310
|
-
stroke:
|
|
3311
|
-
|
|
3312
|
-
color: definition
|
|
3313
|
-
|
|
3314
|
-
|
|
3565
|
+
stroke: resolveStrokeStyle(definition?.edges[edgeIndex]?.color === undefined
|
|
3566
|
+
? edgeStroke
|
|
3567
|
+
: { ...edgeStroke, color: definition.edges[edgeIndex].color }, {
|
|
3568
|
+
alpha: 1,
|
|
3569
|
+
color: 0x00ff66,
|
|
3570
|
+
width: 2,
|
|
3571
|
+
}),
|
|
3315
3572
|
...(shadowStroke === null
|
|
3316
3573
|
? {}
|
|
3317
3574
|
: {
|
|
3318
|
-
shadowStroke: {
|
|
3319
|
-
alpha:
|
|
3320
|
-
color:
|
|
3321
|
-
width:
|
|
3322
|
-
},
|
|
3575
|
+
shadowStroke: resolveStrokeStyle(shadowStroke, {
|
|
3576
|
+
alpha: 0.65,
|
|
3577
|
+
color: 0x000000,
|
|
3578
|
+
width: 4,
|
|
3579
|
+
}),
|
|
3323
3580
|
}),
|
|
3324
3581
|
}));
|
|
3325
3582
|
const markers = geometry.points.flatMap((point, index) => {
|
|
@@ -3341,11 +3598,11 @@ class BaseKeypointStyle {
|
|
|
3341
3598
|
shape: visibility === KeypointVisibility.Occluded
|
|
3342
3599
|
? KeypointMarkerShape.Cross
|
|
3343
3600
|
: KeypointMarkerShape.Circle,
|
|
3344
|
-
stroke: {
|
|
3345
|
-
alpha:
|
|
3346
|
-
color:
|
|
3347
|
-
width:
|
|
3348
|
-
},
|
|
3601
|
+
stroke: resolveStrokeStyle(markerStroke, {
|
|
3602
|
+
alpha: 1,
|
|
3603
|
+
color: 0xffffff,
|
|
3604
|
+
width: 2,
|
|
3605
|
+
}),
|
|
3349
3606
|
},
|
|
3350
3607
|
];
|
|
3351
3608
|
});
|
|
@@ -3426,6 +3683,9 @@ function createClassColorStyle(fill, labelBackground, labelText, stroke) {
|
|
|
3426
3683
|
}
|
|
3427
3684
|
|
|
3428
3685
|
const DEFAULT_BOX_CORNER_RADIUS = 1;
|
|
3686
|
+
const DEFAULT_ELLIPSE_AXIS_RATIO = 0.35;
|
|
3687
|
+
const DEFAULT_ELLIPSE_START_ANGLE = (-45 * Math.PI) / 180;
|
|
3688
|
+
const DEFAULT_ELLIPSE_END_ANGLE = (235 * Math.PI) / 180;
|
|
3429
3689
|
const DEFAULT_OUTLINE_WIDTH = 2;
|
|
3430
3690
|
const DEFAULT_FILL_ALPHA = 0.08;
|
|
3431
3691
|
const DEFAULT_MASK_FILL_ALPHA = 0.45;
|
|
@@ -3444,95 +3704,193 @@ const DEFAULT_LABEL_FONT_FAMILY = "ui-monospace, SFMono-Regular, Menlo, Monaco,
|
|
|
3444
3704
|
* editing, focus, and theme behaviour so consumers can compose those layers.
|
|
3445
3705
|
*/
|
|
3446
3706
|
function createDefaultAnnotationPresentation(options = {}) {
|
|
3447
|
-
const getClassColor = (detection) => resolveDefaultClassColor(detection, options.getClassColor);
|
|
3448
|
-
const shouldRenderBox = (detection) => !(detection.mask ||
|
|
3449
|
-
detection.polygon ||
|
|
3450
|
-
detection.polyline ||
|
|
3451
|
-
detection.keypoints);
|
|
3452
3707
|
return {
|
|
3453
|
-
boxStyle:
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3708
|
+
boxStyle: createDefaultBoxStyle(options),
|
|
3709
|
+
keypointStyle: createDefaultKeypointStyle(options),
|
|
3710
|
+
labelStyle: createDefaultLabelStyle(options),
|
|
3711
|
+
maskStyle: createDefaultMaskStyle(options),
|
|
3712
|
+
polygonStyle: createDefaultPolygonStyle(options),
|
|
3713
|
+
polylineStyle: createDefaultPolylineStyle(options),
|
|
3714
|
+
};
|
|
3715
|
+
}
|
|
3716
|
+
/**
|
|
3717
|
+
* The canonical default style of one built-in layer.
|
|
3718
|
+
*
|
|
3719
|
+
* These exist so a renderer kind can build only the style it needs instead of
|
|
3720
|
+
* the whole default presentation. Keep them equivalent to the matching field of
|
|
3721
|
+
* {@link createDefaultAnnotationPresentation}.
|
|
3722
|
+
*/
|
|
3723
|
+
function createDefaultBoxStyle(options = {}) {
|
|
3724
|
+
const getClassColor = createClassColorResolver(options);
|
|
3725
|
+
return new BaseBoxStyle({
|
|
3726
|
+
cornerRadius: DEFAULT_BOX_CORNER_RADIUS,
|
|
3727
|
+
fill: (detection) => ({
|
|
3728
|
+
alpha: DEFAULT_FILL_ALPHA,
|
|
3729
|
+
color: getClassColor(detection),
|
|
3466
3730
|
}),
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
edgeStroke: (detection) => ({
|
|
3477
|
-
alpha: 1,
|
|
3478
|
-
color: getClassColor(detection),
|
|
3479
|
-
width: DEFAULT_KEYPOINT_EDGE_WIDTH,
|
|
3480
|
-
}),
|
|
3481
|
-
markerFill: (detection) => ({
|
|
3482
|
-
alpha: 1,
|
|
3483
|
-
color: getClassColor(detection),
|
|
3484
|
-
}),
|
|
3485
|
-
markerStroke: { alpha: 1, color: 0xffffff, width: 1 },
|
|
3486
|
-
radius: DEFAULT_KEYPOINT_RADIUS,
|
|
3731
|
+
shape: BoxShape.RoundedRect,
|
|
3732
|
+
shouldRender: (detection) => !(detection.mask ||
|
|
3733
|
+
detection.polygon ||
|
|
3734
|
+
detection.polyline ||
|
|
3735
|
+
detection.keypoints),
|
|
3736
|
+
stroke: (detection) => ({
|
|
3737
|
+
alpha: 1,
|
|
3738
|
+
color: getClassColor(detection),
|
|
3739
|
+
width: DEFAULT_OUTLINE_WIDTH,
|
|
3487
3740
|
}),
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3741
|
+
});
|
|
3742
|
+
}
|
|
3743
|
+
/** Canonical opt-in BoxCornerAnnotator-style presentation. */
|
|
3744
|
+
function createDefaultBoxCornerStyle(options = {}) {
|
|
3745
|
+
const getClassColor = createClassColorResolver(options);
|
|
3746
|
+
return new BaseBoxCornerStyle({
|
|
3747
|
+
stroke: (detection) => ({ color: getClassColor(detection) }),
|
|
3748
|
+
});
|
|
3749
|
+
}
|
|
3750
|
+
/**
|
|
3751
|
+
* Matches the Python Supervision EllipseAnnotator: an elliptical footprint
|
|
3752
|
+
* arc under the detection box, swept from -45deg to 235deg, in the class
|
|
3753
|
+
* color.
|
|
3754
|
+
*/
|
|
3755
|
+
function createDefaultEllipseStyle(options = {}) {
|
|
3756
|
+
const getClassColor = createClassColorResolver(options);
|
|
3757
|
+
return {
|
|
3758
|
+
resolve(detection, context) {
|
|
3759
|
+
if (!detection.rect || context.hidden) {
|
|
3760
|
+
return undefined;
|
|
3761
|
+
}
|
|
3762
|
+
const radiusX = detection.rect.width / 2;
|
|
3763
|
+
const radiusY = radiusX * DEFAULT_ELLIPSE_AXIS_RATIO;
|
|
3764
|
+
return {
|
|
3765
|
+
center: {
|
|
3766
|
+
x: detection.rect.x,
|
|
3767
|
+
// Bottom-tangent so the arc hugs the detection instead of dipping
|
|
3768
|
+
// below its feet.
|
|
3769
|
+
y: detection.rect.y + detection.rect.height / 2 - radiusY,
|
|
3770
|
+
},
|
|
3771
|
+
endAngle: DEFAULT_ELLIPSE_END_ANGLE,
|
|
3772
|
+
radiusX,
|
|
3773
|
+
radiusY,
|
|
3774
|
+
startAngle: DEFAULT_ELLIPSE_START_ANGLE,
|
|
3775
|
+
stroke: {
|
|
3776
|
+
alpha: 1,
|
|
3777
|
+
color: getClassColor(detection),
|
|
3778
|
+
width: DEFAULT_OUTLINE_WIDTH,
|
|
3779
|
+
},
|
|
3780
|
+
};
|
|
3781
|
+
},
|
|
3782
|
+
};
|
|
3783
|
+
}
|
|
3784
|
+
function createDefaultKeypointStyle(options = {}) {
|
|
3785
|
+
const getClassColor = createClassColorResolver(options);
|
|
3786
|
+
return new BaseKeypointStyle({
|
|
3787
|
+
...(options.skeletonDefinitions === undefined
|
|
3788
|
+
? {}
|
|
3789
|
+
: { definitions: options.skeletonDefinitions }),
|
|
3790
|
+
edgeShadowStroke: {
|
|
3791
|
+
alpha: DEFAULT_KEYPOINT_SHADOW_ALPHA,
|
|
3792
|
+
color: 0x000000,
|
|
3793
|
+
width: DEFAULT_KEYPOINT_SHADOW_WIDTH,
|
|
3794
|
+
},
|
|
3795
|
+
edgeStroke: (detection) => ({
|
|
3796
|
+
alpha: 1,
|
|
3797
|
+
color: getClassColor(detection),
|
|
3798
|
+
width: DEFAULT_KEYPOINT_EDGE_WIDTH,
|
|
3504
3799
|
}),
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
mode: MaskRenderMode.FillAndStroke,
|
|
3509
|
-
opacity: 1,
|
|
3510
|
-
stroke: (detection) => ({
|
|
3511
|
-
alpha: 1,
|
|
3512
|
-
color: getClassColor(detection),
|
|
3513
|
-
width: DEFAULT_OUTLINE_WIDTH,
|
|
3514
|
-
}),
|
|
3800
|
+
markerFill: (detection) => ({
|
|
3801
|
+
alpha: 1,
|
|
3802
|
+
color: getClassColor(detection),
|
|
3515
3803
|
}),
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3804
|
+
markerStroke: { alpha: 1, color: 0xffffff, width: 1 },
|
|
3805
|
+
radius: DEFAULT_KEYPOINT_RADIUS,
|
|
3806
|
+
});
|
|
3807
|
+
}
|
|
3808
|
+
function createDefaultLabelStyle(options = {}) {
|
|
3809
|
+
const getClassColor = createClassColorResolver(options);
|
|
3810
|
+
return new BaseLabelStyle({
|
|
3811
|
+
background: (detection) => ({
|
|
3812
|
+
alpha: 1,
|
|
3813
|
+
color: getClassColor(detection),
|
|
3814
|
+
cornerRadius: DEFAULT_LABEL_CORNER_RADIUS,
|
|
3815
|
+
paddingX: DEFAULT_LABEL_PADDING_X,
|
|
3816
|
+
paddingY: DEFAULT_LABEL_PADDING_Y,
|
|
3817
|
+
topCornersOnly: true,
|
|
3526
3818
|
}),
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3819
|
+
includeConfidence: options.includeConfidence,
|
|
3820
|
+
placement: LabelPlacement.Top,
|
|
3821
|
+
textStyle: {
|
|
3822
|
+
fontFamily: DEFAULT_LABEL_FONT_FAMILY,
|
|
3823
|
+
fontSize: DEFAULT_LABEL_FONT_SIZE,
|
|
3824
|
+
fontWeight: "600",
|
|
3825
|
+
},
|
|
3826
|
+
});
|
|
3827
|
+
}
|
|
3828
|
+
function createDefaultMaskStyle(options = {}) {
|
|
3829
|
+
const getClassColor = createClassColorResolver(options);
|
|
3830
|
+
return new BaseMaskStyle({
|
|
3831
|
+
color: (detection) => getClassColor(detection),
|
|
3832
|
+
fillAlpha: DEFAULT_MASK_FILL_ALPHA,
|
|
3833
|
+
mode: MaskRenderMode.FillAndStroke,
|
|
3834
|
+
opacity: 1,
|
|
3835
|
+
stroke: (detection) => ({
|
|
3836
|
+
alpha: 1,
|
|
3837
|
+
color: getClassColor(detection),
|
|
3838
|
+
width: DEFAULT_OUTLINE_WIDTH,
|
|
3533
3839
|
}),
|
|
3840
|
+
});
|
|
3841
|
+
}
|
|
3842
|
+
/** Canonical opt-in marker presentation. */
|
|
3843
|
+
function createDefaultMarkerStyle() {
|
|
3844
|
+
return new BaseMarkerStyle();
|
|
3845
|
+
}
|
|
3846
|
+
const DEFAULT_MASK_HALO_ALPHA = 0.6;
|
|
3847
|
+
const DEFAULT_MASK_HALO_SPREAD = 12;
|
|
3848
|
+
/**
|
|
3849
|
+
* Canonical mask halo: a class-colored glow that follows the mask silhouette.
|
|
3850
|
+
* Used when the `maskHalo` renderer is listed without an explicit style.
|
|
3851
|
+
*/
|
|
3852
|
+
function createDefaultMaskHaloStyle(options = {}) {
|
|
3853
|
+
const getClassColor = createClassColorResolver(options);
|
|
3854
|
+
return {
|
|
3855
|
+
resolve(detection, context) {
|
|
3856
|
+
if (!detection.mask || context.hidden) {
|
|
3857
|
+
return undefined;
|
|
3858
|
+
}
|
|
3859
|
+
return {
|
|
3860
|
+
alpha: DEFAULT_MASK_HALO_ALPHA,
|
|
3861
|
+
color: getClassColor(detection),
|
|
3862
|
+
spread: DEFAULT_MASK_HALO_SPREAD,
|
|
3863
|
+
};
|
|
3864
|
+
},
|
|
3534
3865
|
};
|
|
3535
3866
|
}
|
|
3867
|
+
function createDefaultPolygonStyle(options = {}) {
|
|
3868
|
+
const getClassColor = createClassColorResolver(options);
|
|
3869
|
+
return new BasePolygonStyle({
|
|
3870
|
+
fill: (detection) => ({
|
|
3871
|
+
alpha: DEFAULT_FILL_ALPHA,
|
|
3872
|
+
color: getClassColor(detection),
|
|
3873
|
+
}),
|
|
3874
|
+
stroke: (detection) => ({
|
|
3875
|
+
alpha: 1,
|
|
3876
|
+
color: getClassColor(detection),
|
|
3877
|
+
width: DEFAULT_OUTLINE_WIDTH,
|
|
3878
|
+
}),
|
|
3879
|
+
});
|
|
3880
|
+
}
|
|
3881
|
+
function createDefaultPolylineStyle(options = {}) {
|
|
3882
|
+
const getClassColor = createClassColorResolver(options);
|
|
3883
|
+
return new BasePolylineStyle({
|
|
3884
|
+
stroke: (detection) => ({
|
|
3885
|
+
alpha: 1,
|
|
3886
|
+
color: getClassColor(detection),
|
|
3887
|
+
width: DEFAULT_OUTLINE_WIDTH,
|
|
3888
|
+
}),
|
|
3889
|
+
});
|
|
3890
|
+
}
|
|
3891
|
+
function createClassColorResolver(options) {
|
|
3892
|
+
return (detection) => resolveDefaultClassColor(detection, options.getClassColor);
|
|
3893
|
+
}
|
|
3536
3894
|
function resolveDefaultClassColor(detection, getClassColor) {
|
|
3537
3895
|
const color = getClassColor?.(detection.className);
|
|
3538
3896
|
return typeof color === "number" && Number.isFinite(color)
|
|
@@ -3540,33 +3898,246 @@ function resolveDefaultClassColor(detection, getClassColor) {
|
|
|
3540
3898
|
: resolveDetectionClassColorStyle(detection.className).fill;
|
|
3541
3899
|
}
|
|
3542
3900
|
|
|
3543
|
-
|
|
3901
|
+
/**
|
|
3902
|
+
* The built-in annotation renderer vocabulary.
|
|
3903
|
+
*
|
|
3904
|
+
* These stay plain string literals so `{ kind: "box" }` remains assignable to
|
|
3905
|
+
* the public descriptor union from JavaScript and TypeScript alike.
|
|
3906
|
+
*/
|
|
3907
|
+
const annotationRendererKinds = [
|
|
3908
|
+
"box",
|
|
3909
|
+
"box-corners",
|
|
3910
|
+
"ellipse",
|
|
3911
|
+
"keypoints",
|
|
3912
|
+
"label",
|
|
3913
|
+
"mask",
|
|
3914
|
+
"maskHalo",
|
|
3915
|
+
"marker",
|
|
3916
|
+
"polygon",
|
|
3917
|
+
"polyline",
|
|
3918
|
+
"region",
|
|
3919
|
+
];
|
|
3920
|
+
/** Sources currently supported by a region renderer. */
|
|
3921
|
+
const RegionRendererSourceKind = {
|
|
3922
|
+
Asset: "asset",
|
|
3923
|
+
};
|
|
3924
|
+
/** Media-space regions currently supported by a region renderer. */
|
|
3925
|
+
const RegionRendererRegionKind = {
|
|
3926
|
+
Bounds: "bounds",
|
|
3927
|
+
KeypointAnchor: "keypoint-anchor",
|
|
3928
|
+
};
|
|
3929
|
+
/** Composition modes currently supported by a region renderer. */
|
|
3930
|
+
const RegionRendererComposeMode = {
|
|
3931
|
+
Over: "over",
|
|
3932
|
+
};
|
|
3933
|
+
/**
|
|
3934
|
+
* Creates built-in annotation renderer descriptors for
|
|
3935
|
+
* `MediaRendererPresentation.renderers`.
|
|
3936
|
+
*
|
|
3937
|
+
* The currently supported renderers retain their established scene ordering:
|
|
3938
|
+
* masks, polygons, vectors, labels. A later renderer kind may add a new
|
|
3939
|
+
* composition capability without changing that ordering for existing scenes.
|
|
3940
|
+
*/
|
|
3941
|
+
const annotationRenderers = {
|
|
3942
|
+
box: (options) => createAnnotationRenderer("box", options),
|
|
3943
|
+
boxCorners: (options) => createAnnotationRenderer("box-corners", options),
|
|
3944
|
+
ellipse: (options) => createAnnotationRenderer("ellipse", options),
|
|
3945
|
+
keypoints: (options) => createAnnotationRenderer("keypoints", options),
|
|
3946
|
+
label: (options) => createAnnotationRenderer("label", options),
|
|
3947
|
+
mask: (options) => createAnnotationRenderer("mask", options),
|
|
3948
|
+
maskHalo: (options) => createAnnotationRenderer("maskHalo", options),
|
|
3949
|
+
marker: (options) => createAnnotationRenderer("marker", options),
|
|
3950
|
+
polygon: (options) => createAnnotationRenderer("polygon", options),
|
|
3951
|
+
polyline: (options) => createAnnotationRenderer("polyline", options),
|
|
3952
|
+
region: (options) => ({ kind: "region", ...options }),
|
|
3953
|
+
};
|
|
3954
|
+
function createAnnotationRenderer(kind, options) {
|
|
3955
|
+
// Every built-in renderer keeps its kind as its stable id. TypeScript cannot
|
|
3956
|
+
// narrow the descriptor union through the generic kind parameter, so the
|
|
3957
|
+
// descriptor shape is asserted here and covered by a focused test.
|
|
3958
|
+
return { id: kind, kind, ...options };
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
const annotationRendererRegistry = {
|
|
3962
|
+
box: {
|
|
3963
|
+
cardinality: "singleton",
|
|
3964
|
+
createCanonicalStyle: createDefaultBoxStyle,
|
|
3965
|
+
styleField: "boxStyle",
|
|
3966
|
+
},
|
|
3967
|
+
"box-corners": {
|
|
3968
|
+
cardinality: "singleton",
|
|
3969
|
+
createCanonicalStyle: createDefaultBoxCornerStyle,
|
|
3970
|
+
styleField: "boxCornerStyle",
|
|
3971
|
+
},
|
|
3972
|
+
ellipse: {
|
|
3973
|
+
cardinality: "singleton",
|
|
3974
|
+
createCanonicalStyle: createDefaultEllipseStyle,
|
|
3975
|
+
styleField: "ellipseStyle",
|
|
3976
|
+
},
|
|
3977
|
+
keypoints: {
|
|
3978
|
+
cardinality: "singleton",
|
|
3979
|
+
createCanonicalStyle: createDefaultKeypointStyle,
|
|
3980
|
+
styleField: "keypointStyle",
|
|
3981
|
+
},
|
|
3982
|
+
label: {
|
|
3983
|
+
cardinality: "singleton",
|
|
3984
|
+
createCanonicalStyle: createDefaultLabelStyle,
|
|
3985
|
+
styleField: "labelStyle",
|
|
3986
|
+
},
|
|
3987
|
+
mask: {
|
|
3988
|
+
cardinality: "singleton",
|
|
3989
|
+
createCanonicalStyle: createDefaultMaskStyle,
|
|
3990
|
+
styleField: "maskStyle",
|
|
3991
|
+
},
|
|
3992
|
+
maskHalo: {
|
|
3993
|
+
cardinality: "singleton",
|
|
3994
|
+
createCanonicalStyle: () => createDefaultMaskHaloStyle(),
|
|
3995
|
+
styleField: "maskHaloStyle",
|
|
3996
|
+
},
|
|
3997
|
+
marker: {
|
|
3998
|
+
cardinality: "singleton",
|
|
3999
|
+
createCanonicalStyle: createDefaultMarkerStyle,
|
|
4000
|
+
styleField: "markerStyle",
|
|
4001
|
+
},
|
|
4002
|
+
polygon: {
|
|
4003
|
+
cardinality: "singleton",
|
|
4004
|
+
createCanonicalStyle: createDefaultPolygonStyle,
|
|
4005
|
+
styleField: "polygonStyle",
|
|
4006
|
+
},
|
|
4007
|
+
polyline: {
|
|
4008
|
+
cardinality: "singleton",
|
|
4009
|
+
createCanonicalStyle: createDefaultPolylineStyle,
|
|
4010
|
+
styleField: "polylineStyle",
|
|
4011
|
+
},
|
|
4012
|
+
region: { cardinality: "multiple" },
|
|
4013
|
+
};
|
|
4014
|
+
function isStyleBackedAnnotationRendererKind(kind) {
|
|
4015
|
+
return annotationRendererRegistry[kind].cardinality === "singleton";
|
|
4016
|
+
}
|
|
4017
|
+
/** Presentation fields owned by the given renderer kinds, in the same order. */
|
|
4018
|
+
function resolveAnnotationRendererStyleFields(kinds) {
|
|
4019
|
+
return kinds.flatMap((kind) => {
|
|
4020
|
+
const metadata = annotationRendererRegistry[kind];
|
|
4021
|
+
return "styleField" in metadata ? [metadata.styleField] : [];
|
|
4022
|
+
});
|
|
4023
|
+
}
|
|
4024
|
+
|
|
4025
|
+
/**
|
|
4026
|
+
* Resolves built-in renderer descriptors into the existing specialized style
|
|
4027
|
+
* fields. The browser backend deliberately keeps ownership of its box, mask,
|
|
4028
|
+
* label, polygon, polyline, and keypoint pipelines; this normalizer only
|
|
4029
|
+
* supplies those pipelines with their configured style.
|
|
4030
|
+
*/
|
|
4031
|
+
function resolveAnnotationRendererPresentation(presentation) {
|
|
4032
|
+
const renderers = presentation.renderers;
|
|
4033
|
+
if (renderers === undefined) {
|
|
4034
|
+
return presentation;
|
|
4035
|
+
}
|
|
4036
|
+
const resolved = {
|
|
4037
|
+
boxStyle: null,
|
|
4038
|
+
boxCornerStyle: null,
|
|
4039
|
+
ellipseStyle: null,
|
|
4040
|
+
keypointStyle: null,
|
|
4041
|
+
labelStyle: null,
|
|
4042
|
+
maskHaloStyle: null,
|
|
4043
|
+
maskStyle: null,
|
|
4044
|
+
markerStyle: null,
|
|
4045
|
+
polygonStyle: null,
|
|
4046
|
+
polylineStyle: null,
|
|
4047
|
+
};
|
|
4048
|
+
const rendererIds = new Set();
|
|
4049
|
+
const rendererKinds = new Set();
|
|
4050
|
+
for (const renderer of renderers) {
|
|
4051
|
+
if (rendererIds.has(renderer.id)) {
|
|
4052
|
+
throw new RangeError(`MediaRendererPresentation.renderers contains duplicate renderer id "${renderer.id}".`);
|
|
4053
|
+
}
|
|
4054
|
+
rendererIds.add(renderer.id);
|
|
4055
|
+
if (annotationRendererRegistry[renderer.kind].cardinality === "singleton" &&
|
|
4056
|
+
rendererKinds.has(renderer.kind)) {
|
|
4057
|
+
throw new RangeError(`MediaRendererPresentation.renderers contains duplicate renderer kind "${renderer.kind}".`);
|
|
4058
|
+
}
|
|
4059
|
+
rendererKinds.add(renderer.kind);
|
|
4060
|
+
if (isStyleBackedAnnotationRenderer(renderer)) {
|
|
4061
|
+
applyRendererStyle(resolved, presentation, renderer);
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
return {
|
|
4065
|
+
...presentation,
|
|
4066
|
+
...resolved,
|
|
4067
|
+
};
|
|
4068
|
+
}
|
|
4069
|
+
function isStyleBackedAnnotationRenderer(renderer) {
|
|
4070
|
+
return isStyleBackedAnnotationRendererKind(renderer.kind);
|
|
4071
|
+
}
|
|
4072
|
+
function applyRendererStyle(resolved, configured, renderer) {
|
|
4073
|
+
const { createCanonicalStyle, styleField } = annotationRendererRegistry[renderer.kind];
|
|
4074
|
+
const configuredStyle = configured[styleField];
|
|
4075
|
+
const style = renderer.style !== undefined
|
|
4076
|
+
? renderer.style
|
|
4077
|
+
: configuredStyle !== undefined
|
|
4078
|
+
? configuredStyle
|
|
4079
|
+
: createCanonicalStyle();
|
|
4080
|
+
// The registry pairs each kind with the presentation field holding the same
|
|
4081
|
+
// style contract, but TypeScript cannot correlate that pairing across a
|
|
4082
|
+
// lookup on a union, so the write is asserted once here.
|
|
4083
|
+
resolved[styleField] = style;
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
function createSourceAwarePresentation(globalPresentation = {}, sources, options = {}) {
|
|
3544
4087
|
if (!sources.some((source) => source.presentation !== undefined)) {
|
|
3545
4088
|
return globalPresentation;
|
|
3546
4089
|
}
|
|
3547
4090
|
const sourcePresentations = new Map(sources.map((source) => [source.id, source.presentation]));
|
|
4091
|
+
const enabledStyleFields = options.enabledRendererKinds === undefined
|
|
4092
|
+
? undefined
|
|
4093
|
+
: new Set(resolveAnnotationRendererStyleFields(options.enabledRendererKinds));
|
|
4094
|
+
const shouldApplySourceStyle = (style) => hasSourceStyle(sources, style) &&
|
|
4095
|
+
(enabledStyleFields === undefined || enabledStyleFields.has(style));
|
|
3548
4096
|
return {
|
|
3549
4097
|
...globalPresentation,
|
|
3550
|
-
boxStyle:
|
|
4098
|
+
boxStyle: shouldApplySourceStyle("boxStyle")
|
|
3551
4099
|
? new SourceAwareBoxStyle(normalizeGlobalBoxStyle(globalPresentation.boxStyle), sourcePresentations)
|
|
3552
4100
|
: globalPresentation.boxStyle,
|
|
3553
|
-
|
|
4101
|
+
boxCornerStyle: shouldApplySourceStyle("boxCornerStyle")
|
|
4102
|
+
? new SourceAwareBoxCornerStyle(globalPresentation.boxCornerStyle ?? null, sourcePresentations)
|
|
4103
|
+
: globalPresentation.boxCornerStyle,
|
|
4104
|
+
ellipseStyle: shouldApplySourceStyle("ellipseStyle")
|
|
4105
|
+
? new SourceAwareEllipseStyle(globalPresentation.ellipseStyle ?? null, sourcePresentations)
|
|
4106
|
+
: globalPresentation.ellipseStyle,
|
|
4107
|
+
labelStyle: shouldApplySourceStyle("labelStyle")
|
|
3554
4108
|
? new SourceAwareLabelStyle(normalizeGlobalLabelStyle(globalPresentation.labelStyle), sourcePresentations)
|
|
3555
4109
|
: globalPresentation.labelStyle,
|
|
3556
|
-
maskStyle:
|
|
4110
|
+
maskStyle: shouldApplySourceStyle("maskStyle")
|
|
3557
4111
|
? new SourceAwareMaskStyle(normalizeGlobalMaskStyle(globalPresentation.maskStyle), sourcePresentations)
|
|
3558
4112
|
: globalPresentation.maskStyle,
|
|
3559
|
-
|
|
4113
|
+
markerStyle: shouldApplySourceStyle("markerStyle")
|
|
4114
|
+
? new SourceAwareMarkerStyle(normalizeGlobalMarkerStyle(globalPresentation.markerStyle), sourcePresentations)
|
|
4115
|
+
: globalPresentation.markerStyle,
|
|
4116
|
+
maskHaloStyle: shouldApplySourceStyle("maskHaloStyle")
|
|
4117
|
+
? new SourceAwareMaskHaloStyle(normalizeGlobalMaskHaloStyle(globalPresentation.maskHaloStyle), sourcePresentations)
|
|
4118
|
+
: globalPresentation.maskHaloStyle,
|
|
4119
|
+
polygonStyle: shouldApplySourceStyle("polygonStyle")
|
|
3560
4120
|
? new SourceAwarePolygonStyle(normalizeGlobalPolygonStyle(globalPresentation.polygonStyle), sourcePresentations)
|
|
3561
4121
|
: globalPresentation.polygonStyle,
|
|
3562
|
-
polylineStyle:
|
|
4122
|
+
polylineStyle: shouldApplySourceStyle("polylineStyle")
|
|
3563
4123
|
? new SourceAwarePolylineStyle(normalizeGlobalPolylineStyle(globalPresentation.polylineStyle), sourcePresentations)
|
|
3564
4124
|
: globalPresentation.polylineStyle,
|
|
3565
|
-
keypointStyle:
|
|
4125
|
+
keypointStyle: shouldApplySourceStyle("keypointStyle")
|
|
3566
4126
|
? new SourceAwareKeypointStyle(normalizeGlobalKeypointStyle(globalPresentation.keypointStyle), sourcePresentations)
|
|
3567
4127
|
: globalPresentation.keypointStyle,
|
|
3568
4128
|
};
|
|
3569
4129
|
}
|
|
4130
|
+
class SourceAwareBoxCornerStyle {
|
|
4131
|
+
globalStyle;
|
|
4132
|
+
sourcePresentations;
|
|
4133
|
+
constructor(globalStyle, sourcePresentations) {
|
|
4134
|
+
this.globalStyle = globalStyle;
|
|
4135
|
+
this.sourcePresentations = sourcePresentations;
|
|
4136
|
+
}
|
|
4137
|
+
resolve(detection, context) {
|
|
4138
|
+
return resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "boxCornerStyle")?.resolve(detection, context);
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
3570
4141
|
class SourceAwareBoxStyle {
|
|
3571
4142
|
globalStyle;
|
|
3572
4143
|
sourcePresentations;
|
|
@@ -3579,6 +4150,18 @@ class SourceAwareBoxStyle {
|
|
|
3579
4150
|
return style?.resolve(detection, context);
|
|
3580
4151
|
}
|
|
3581
4152
|
}
|
|
4153
|
+
class SourceAwareEllipseStyle {
|
|
4154
|
+
globalStyle;
|
|
4155
|
+
sourcePresentations;
|
|
4156
|
+
constructor(globalStyle, sourcePresentations) {
|
|
4157
|
+
this.globalStyle = globalStyle;
|
|
4158
|
+
this.sourcePresentations = sourcePresentations;
|
|
4159
|
+
}
|
|
4160
|
+
resolve(detection, context) {
|
|
4161
|
+
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "ellipseStyle");
|
|
4162
|
+
return style?.resolve(detection, context);
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
3582
4165
|
class SourceAwareLabelStyle {
|
|
3583
4166
|
globalStyle;
|
|
3584
4167
|
sourcePresentations;
|
|
@@ -3606,6 +4189,30 @@ class SourceAwareMaskStyle {
|
|
|
3606
4189
|
return style?.resolve(detection, context);
|
|
3607
4190
|
}
|
|
3608
4191
|
}
|
|
4192
|
+
class SourceAwareMaskHaloStyle {
|
|
4193
|
+
globalStyle;
|
|
4194
|
+
sourcePresentations;
|
|
4195
|
+
constructor(globalStyle, sourcePresentations) {
|
|
4196
|
+
this.globalStyle = globalStyle;
|
|
4197
|
+
this.sourcePresentations = sourcePresentations;
|
|
4198
|
+
}
|
|
4199
|
+
resolve(detection, context) {
|
|
4200
|
+
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "maskHaloStyle");
|
|
4201
|
+
return style?.resolve(detection, context);
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
class SourceAwareMarkerStyle {
|
|
4205
|
+
globalStyle;
|
|
4206
|
+
sourcePresentations;
|
|
4207
|
+
constructor(globalStyle, sourcePresentations) {
|
|
4208
|
+
this.globalStyle = globalStyle;
|
|
4209
|
+
this.sourcePresentations = sourcePresentations;
|
|
4210
|
+
}
|
|
4211
|
+
resolve(detection, context) {
|
|
4212
|
+
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "markerStyle");
|
|
4213
|
+
return style?.resolve(detection, context);
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
3609
4216
|
class SourceAwarePolygonStyle {
|
|
3610
4217
|
globalStyle;
|
|
3611
4218
|
sourcePresentations;
|
|
@@ -3661,6 +4268,12 @@ function normalizeGlobalLabelStyle(style) {
|
|
|
3661
4268
|
function normalizeGlobalMaskStyle(style) {
|
|
3662
4269
|
return style === undefined ? new BaseMaskStyle() : style;
|
|
3663
4270
|
}
|
|
4271
|
+
function normalizeGlobalMaskHaloStyle(style) {
|
|
4272
|
+
return style === undefined ? createDefaultMaskHaloStyle() : style;
|
|
4273
|
+
}
|
|
4274
|
+
function normalizeGlobalMarkerStyle(style) {
|
|
4275
|
+
return style === undefined ? new BaseMarkerStyle() : style;
|
|
4276
|
+
}
|
|
3664
4277
|
function normalizeGlobalPolygonStyle(style) {
|
|
3665
4278
|
return style === undefined ? new BasePolygonStyle() : style;
|
|
3666
4279
|
}
|
|
@@ -4157,6 +4770,180 @@ function writePaletteEntry(palette, id, color, alpha) {
|
|
|
4157
4770
|
palette[offset + 3] = Math.max(0, Math.min(alpha, 1));
|
|
4158
4771
|
}
|
|
4159
4772
|
|
|
4773
|
+
const DEFAULT_ELLIPSE_SEGMENT_COUNT = 48;
|
|
4774
|
+
const FULL_TURN = Math.PI * 2;
|
|
4775
|
+
const GEOMETRY_EPSILON = 1e-9;
|
|
4776
|
+
const MIN_ELLIPSE_SEGMENT_COUNT = 4;
|
|
4777
|
+
const MAX_ELLIPSE_SEGMENT_COUNT = 512;
|
|
4778
|
+
/**
|
|
4779
|
+
* Samples an ellipse or elliptical arc into a deterministic polyline so every
|
|
4780
|
+
* backend rasterizes the same structural geometry, including dashed strokes.
|
|
4781
|
+
*
|
|
4782
|
+
* A closed ellipse returns `segmentCount` points without repeating the first
|
|
4783
|
+
* point; an open arc returns `segmentCount + 1` points including both
|
|
4784
|
+
* endpoints.
|
|
4785
|
+
*/
|
|
4786
|
+
function sampleEllipseArc(ellipse, segmentCount = DEFAULT_ELLIPSE_SEGMENT_COUNT) {
|
|
4787
|
+
validateEllipseGeometry(ellipse);
|
|
4788
|
+
assertIntegerAtLeast(segmentCount, MIN_ELLIPSE_SEGMENT_COUNT, "segmentCount");
|
|
4789
|
+
const rotation = ellipse.rotation ?? 0;
|
|
4790
|
+
const startAngle = ellipse.startAngle ?? 0;
|
|
4791
|
+
const endAngle = ellipse.endAngle ?? startAngle + FULL_TURN;
|
|
4792
|
+
const range = endAngle - startAngle;
|
|
4793
|
+
const closed = ellipse.startAngle === undefined;
|
|
4794
|
+
const pointCount = closed ? segmentCount : segmentCount + 1;
|
|
4795
|
+
const cosRotation = Math.cos(rotation);
|
|
4796
|
+
const sinRotation = Math.sin(rotation);
|
|
4797
|
+
const points = [];
|
|
4798
|
+
for (let index = 0; index < pointCount; index += 1) {
|
|
4799
|
+
const angle = startAngle + (range * index) / segmentCount;
|
|
4800
|
+
const localX = Math.cos(angle) * ellipse.radiusX;
|
|
4801
|
+
const localY = Math.sin(angle) * ellipse.radiusY;
|
|
4802
|
+
points.push({
|
|
4803
|
+
x: ellipse.center.x + localX * cosRotation - localY * sinRotation,
|
|
4804
|
+
y: ellipse.center.y + localX * sinRotation + localY * cosRotation,
|
|
4805
|
+
});
|
|
4806
|
+
}
|
|
4807
|
+
return { closed, points };
|
|
4808
|
+
}
|
|
4809
|
+
/**
|
|
4810
|
+
* Chooses a bounded segment count from the ellipse's on-screen radius. The
|
|
4811
|
+
* result targets sub-pixel chord error without making tiny markers expensive.
|
|
4812
|
+
*/
|
|
4813
|
+
function resolveEllipseSegmentCount(ellipse, viewportScale = 1, maxErrorPixels = 0.5) {
|
|
4814
|
+
validateEllipseGeometry(ellipse);
|
|
4815
|
+
assertPositiveFinite(viewportScale, "viewportScale");
|
|
4816
|
+
assertPositiveFinite(maxErrorPixels, "maxErrorPixels");
|
|
4817
|
+
const screenRadius = Math.max(ellipse.radiusX, ellipse.radiusY) * viewportScale;
|
|
4818
|
+
assertPositiveFinite(screenRadius, "screenRadius");
|
|
4819
|
+
const clampedError = Math.min(maxErrorPixels, screenRadius);
|
|
4820
|
+
const maxStep = clampedError === screenRadius
|
|
4821
|
+
? Math.PI / 2
|
|
4822
|
+
: 2 * Math.acos(1 - clampedError / screenRadius);
|
|
4823
|
+
const startAngle = ellipse.startAngle ?? 0;
|
|
4824
|
+
const endAngle = ellipse.endAngle ?? startAngle + FULL_TURN;
|
|
4825
|
+
const arcLength = Math.abs(endAngle - startAngle);
|
|
4826
|
+
const segmentCount = Math.ceil(arcLength / maxStep);
|
|
4827
|
+
return Math.min(MAX_ELLIPSE_SEGMENT_COUNT, Math.max(MIN_ELLIPSE_SEGMENT_COUNT, segmentCount));
|
|
4828
|
+
}
|
|
4829
|
+
/**
|
|
4830
|
+
* Resolves a marker instruction into media-space geometry. Screen-space sizes
|
|
4831
|
+
* are divided by the viewport scale, matching stroke-width semantics.
|
|
4832
|
+
*/
|
|
4833
|
+
function resolveMarkerGeometry(marker, viewportScale = 1) {
|
|
4834
|
+
assertFinitePoint(marker.center, "center");
|
|
4835
|
+
assertPositiveFinite(marker.size, "size");
|
|
4836
|
+
assertPositiveFinite(viewportScale, "viewportScale");
|
|
4837
|
+
if (marker.rotation !== undefined) {
|
|
4838
|
+
assertFinite(marker.rotation, "rotation");
|
|
4839
|
+
}
|
|
4840
|
+
const size = marker.sizeSpace === MarkerSizeSpace.Screen
|
|
4841
|
+
? marker.size / viewportScale
|
|
4842
|
+
: marker.size;
|
|
4843
|
+
const radius = size / 2;
|
|
4844
|
+
const rotation = marker.rotation ?? 0;
|
|
4845
|
+
if (marker.shape === MarkerShape.Circle) {
|
|
4846
|
+
return { center: marker.center, kind: "circle", radius };
|
|
4847
|
+
}
|
|
4848
|
+
if (marker.shape === MarkerShape.Cross) {
|
|
4849
|
+
return {
|
|
4850
|
+
closed: false,
|
|
4851
|
+
kind: "subpaths",
|
|
4852
|
+
subpaths: [
|
|
4853
|
+
[
|
|
4854
|
+
rotatePoint(marker.center, -radius, -radius, rotation),
|
|
4855
|
+
rotatePoint(marker.center, radius, radius, rotation),
|
|
4856
|
+
],
|
|
4857
|
+
[
|
|
4858
|
+
rotatePoint(marker.center, radius, -radius, rotation),
|
|
4859
|
+
rotatePoint(marker.center, -radius, radius, rotation),
|
|
4860
|
+
],
|
|
4861
|
+
],
|
|
4862
|
+
};
|
|
4863
|
+
}
|
|
4864
|
+
if (marker.shape === MarkerShape.Square) {
|
|
4865
|
+
return {
|
|
4866
|
+
closed: true,
|
|
4867
|
+
kind: "subpaths",
|
|
4868
|
+
subpaths: [
|
|
4869
|
+
[
|
|
4870
|
+
rotatePoint(marker.center, -radius, -radius, rotation),
|
|
4871
|
+
rotatePoint(marker.center, radius, -radius, rotation),
|
|
4872
|
+
rotatePoint(marker.center, radius, radius, rotation),
|
|
4873
|
+
rotatePoint(marker.center, -radius, radius, rotation),
|
|
4874
|
+
],
|
|
4875
|
+
],
|
|
4876
|
+
};
|
|
4877
|
+
}
|
|
4878
|
+
// Triangle: equilateral, inscribed in the marker circle, pointing toward
|
|
4879
|
+
// positive y at rotation 0.
|
|
4880
|
+
const baseOffsetY = -radius / 2;
|
|
4881
|
+
const baseOffsetX = radius * (Math.sqrt(3) / 2);
|
|
4882
|
+
return {
|
|
4883
|
+
closed: true,
|
|
4884
|
+
kind: "subpaths",
|
|
4885
|
+
subpaths: [
|
|
4886
|
+
[
|
|
4887
|
+
rotatePoint(marker.center, 0, radius, rotation),
|
|
4888
|
+
rotatePoint(marker.center, baseOffsetX, baseOffsetY, rotation),
|
|
4889
|
+
rotatePoint(marker.center, -baseOffsetX, baseOffsetY, rotation),
|
|
4890
|
+
],
|
|
4891
|
+
],
|
|
4892
|
+
};
|
|
4893
|
+
}
|
|
4894
|
+
function validateEllipseGeometry(ellipse) {
|
|
4895
|
+
assertFinitePoint(ellipse.center, "center");
|
|
4896
|
+
assertPositiveFinite(ellipse.radiusX, "radiusX");
|
|
4897
|
+
assertPositiveFinite(ellipse.radiusY, "radiusY");
|
|
4898
|
+
if (ellipse.rotation !== undefined) {
|
|
4899
|
+
assertFinite(ellipse.rotation, "rotation");
|
|
4900
|
+
}
|
|
4901
|
+
const hasStart = ellipse.startAngle !== undefined;
|
|
4902
|
+
const hasEnd = ellipse.endAngle !== undefined;
|
|
4903
|
+
if (hasStart !== hasEnd) {
|
|
4904
|
+
throw new RangeError("startAngle and endAngle must be provided together");
|
|
4905
|
+
}
|
|
4906
|
+
if (hasStart && hasEnd) {
|
|
4907
|
+
assertFinite(ellipse.startAngle, "startAngle");
|
|
4908
|
+
assertFinite(ellipse.endAngle, "endAngle");
|
|
4909
|
+
const range = Math.abs(ellipse.endAngle - ellipse.startAngle);
|
|
4910
|
+
if (range <= GEOMETRY_EPSILON || range >= FULL_TURN - GEOMETRY_EPSILON) {
|
|
4911
|
+
throw new RangeError("ellipse arc range must be greater than 0 and less than one full turn");
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
function assertFinitePoint(point, name) {
|
|
4916
|
+
assertFinite(point.x, `${name}.x`);
|
|
4917
|
+
assertFinite(point.y, `${name}.y`);
|
|
4918
|
+
}
|
|
4919
|
+
function assertFinite(value, name) {
|
|
4920
|
+
if (!Number.isFinite(value)) {
|
|
4921
|
+
throw new RangeError(`${name} must be finite`);
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4924
|
+
function assertPositiveFinite(value, name) {
|
|
4925
|
+
assertFinite(value, name);
|
|
4926
|
+
if (value <= 0) {
|
|
4927
|
+
throw new RangeError(`${name} must be greater than 0`);
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
function assertIntegerAtLeast(value, minimum, name) {
|
|
4931
|
+
if (!Number.isInteger(value) || value < minimum) {
|
|
4932
|
+
throw new RangeError(`${name} must be an integer of at least ${minimum}`);
|
|
4933
|
+
}
|
|
4934
|
+
}
|
|
4935
|
+
function rotatePoint(center, offsetX, offsetY, rotation) {
|
|
4936
|
+
if (rotation === 0) {
|
|
4937
|
+
return { x: center.x + offsetX, y: center.y + offsetY };
|
|
4938
|
+
}
|
|
4939
|
+
const cosRotation = Math.cos(rotation);
|
|
4940
|
+
const sinRotation = Math.sin(rotation);
|
|
4941
|
+
return {
|
|
4942
|
+
x: center.x + offsetX * cosRotation - offsetY * sinRotation,
|
|
4943
|
+
y: center.y + offsetX * sinRotation + offsetY * cosRotation,
|
|
4944
|
+
};
|
|
4945
|
+
}
|
|
4946
|
+
|
|
4160
4947
|
function includeDefined(values) {
|
|
4161
4948
|
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
|
|
4162
4949
|
}
|
|
@@ -4273,5 +5060,5 @@ var MediaSessionActivityStatus;
|
|
|
4273
5060
|
MediaSessionActivityStatus["Waiting"] = "waiting";
|
|
4274
5061
|
})(MediaSessionActivityStatus || (MediaSessionActivityStatus = {}));
|
|
4275
5062
|
|
|
4276
|
-
export { AnnotationFrameMutationKind, AnnotationGeometryKind, AnnotationGestureStateKind, AnnotationHandleKind, BaseBoxStyle, BaseFocusStyle, BaseInteractionStyle, BaseKeypointStyle, BaseLabelStyle, BaseMaskStyle, BasePolygonStyle, BasePolylineStyle, BoxShape, BoxStrokeAlignment, DEFAULT_DETECTION_CLASS_STYLES, DEFAULT_DETECTION_COLOR_SEQUENCE, DetectionBufferStatus, DetectionFrameRetentionMode, DetectionFrameSelectionMode, DetectionInteractionState, DetectionMaskEncoding, DetectionMaskPayloadFormat, DetectionPickTarget, FocusTargetMode, KeypointMarkerShape, KeypointVisibility, LabelPlacement, LabelVisibilityMode, MAX_ID_MASK_PALETTE_ENTRIES, MAX_ID_MASK_STROKE_WIDTH, MaskRenderMode, MediaInteractionMode, MediaRendererFit, MediaRendererPlaybackState, MediaSessionActivityKind, MediaSessionActivityStatus, MediaSessionMode, MediaSessionStatus, MediaSourceStatus, SUPERVISION_ROBOFLOW_COLOR, applyAnnotationHandleDrag, canReuseMaskStyleArtifacts, centerRectToTopLeftRect, computeDetectionMaskRect, computeMaskBounds, containsPoint, convertDetectionBoxToMask, convertDetectionBoxToPolygon, convertDetectionMaskToBox, convertDetectionMaskToPolygon, convertDetectionPolygonToBox, convertDetectionPolygonToMask, copySortedDetectionFrames, createAnnotationEditingEngine, createArrayDetectionFrameSource, createBufferedDetectionTimeline, createColdDetectionFrameSource, createCompositeDetectionFrameSource, createDefaultAnnotationPresentation, createDetectionPickKey, createEditableAnnotationFrameSession, createIdMaskFrame, createIdleDetectionBufferState, createMemoryColdDetectionFrameStore, createSourceAwarePresentation, createViewportController, createWritableDetectionFrameSource, decodeCompressedRleCounts, decodeCompressedRleMask, decodeDetectionMaskPayload, deleteAnnotationVertex, detectMaskBorders, detectionFrameOverlapsRange, distanceToSegment, encodeBinaryMask, encodeBinaryMaskWithBounds, encodeCompressedRleCounts, encodeDetectionMaskPayload, extractMaskContour, extractMaskRectRuns, filterDetectionFramesForRange, findClosestAnnotationSegment, getAnnotationHandles, getBufferedDetectionTimelineFrameSnapshot, getDetectionRect, getPointsRect, includeDefined, isDeflatedBase64DetectionMaskPayload, lightenColor, mediaToScreen, mergeDetectionMasks, mergeDetectionPolygonsByClass, normalizeDetectionClassName, offsetDetection, pickAnnotationHandle, pickDetectionAtPoint, pickDetectionByMaskId, pointInPolygon, polygonArea, polygonToRect, rasterizePolygonToMask, rasterizeRectToMask, rebaseDetectionPickToFrame, rectArea, rectToPolygon, resolveAnnotationStyleState, resolveContrastTextColor, resolveDetectionClassColorStyle, resolveMaskStyleOpacity, resolveStyleValue, screenToMedia, selectDetectionFrame, topLeftRectToCenterRect, validateDetectionFrames };
|
|
5063
|
+
export { AnnotationFrameMutationKind, AnnotationGeometryKind, AnnotationGestureStateKind, AnnotationHandleKind, BaseBoxCornerStyle, BaseBoxStyle, BaseFocusStyle, BaseInteractionStyle, BaseKeypointStyle, BaseLabelStyle, BaseMarkerStyle, BaseMaskStyle, BasePolygonStyle, BasePolylineStyle, BoxShape, StrokeAlignment as BoxStrokeAlignment, DEFAULT_DETECTION_CLASS_STYLES, DEFAULT_DETECTION_COLOR_SEQUENCE, DetectionBufferStatus, DetectionFrameRetentionMode, DetectionFrameSelectionMode, DetectionInteractionState, DetectionMaskEncoding, DetectionMaskPayloadFormat, DetectionPickTarget, FocusTargetMode, KeypointMarkerShape, KeypointVisibility, LabelPlacement, LabelVisibilityMode, MAX_ID_MASK_PALETTE_ENTRIES, MAX_ID_MASK_STROKE_WIDTH, MarkerShape, MarkerSizeSpace, MaskRenderMode, MediaInteractionMode, MediaRendererFit, MediaRendererPlaybackState, MediaSessionActivityKind, MediaSessionActivityStatus, MediaSessionMode, MediaSessionStatus, MediaSourceStatus, RegionRendererComposeMode, RegionRendererRegionKind, RegionRendererSourceKind, SUPERVISION_ROBOFLOW_COLOR, ShapeInstructionKind, StrokeAlignment, annotationRendererKinds, annotationRenderers, applyAnnotationHandleDrag, canReuseMaskStyleArtifacts, centerRectToTopLeftRect, computeDetectionMaskRect, computeMaskBounds, containsPoint, convertDetectionBoxToMask, convertDetectionBoxToPolygon, convertDetectionMaskToBox, convertDetectionMaskToPolygon, convertDetectionPolygonToBox, convertDetectionPolygonToMask, copySortedDetectionFrames, createAnnotationEditingEngine, createArrayDetectionFrameSource, createBufferedDetectionTimeline, createColdDetectionFrameSource, createCompositeDetectionFrameSource, createDefaultAnnotationPresentation, createDetectionPickKey, createEditableAnnotationFrameSession, createIdMaskFrame, createIdleDetectionBufferState, createMemoryColdDetectionFrameStore, createSourceAwarePresentation, createViewportController, createWritableDetectionFrameSource, decodeCompressedRleCounts, decodeCompressedRleMask, decodeDetectionMaskPayload, deleteAnnotationVertex, detectMaskBorders, detectionFrameOverlapsRange, distanceToSegment, encodeBinaryMask, encodeBinaryMaskWithBounds, encodeCompressedRleCounts, encodeDetectionMaskPayload, extractMaskContour, extractMaskRectRuns, filterDetectionFramesForRange, findClosestAnnotationSegment, followDetectionPickAcrossFrames, getAnnotationHandles, getBufferedDetectionTimelineFrameSnapshot, getDetectionRect, getPointsRect, haveSameDetectionPickIdentities, includeDefined, isDeflatedBase64DetectionMaskPayload, lightenColor, mediaToScreen, mergeDetectionMasks, mergeDetectionPolygonsByClass, normalizeDetectionClassName, offsetDetection, pickAnnotationHandle, pickDetectionAtPoint, pickDetectionByMaskId, pointInPolygon, polygonArea, polygonToRect, rasterizePolygonToMask, rasterizeRectToMask, rebaseDetectionPickToFrame, rectArea, rectToPolygon, resolveAnnotationRendererPresentation, resolveAnnotationStyleState, resolveContrastTextColor, resolveDetectionClassColorStyle, resolveEllipseSegmentCount, resolveMarkerGeometry, resolveMaskStyleOpacity, resolveStyleValue, sampleEllipseArc, screenToMedia, selectDetectionFrame, topLeftRectToCenterRect, validateDetectionFrames };
|
|
4277
5064
|
//# sourceMappingURL=index.js.map
|