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
|
@@ -140,6 +140,12 @@ function validateDetectionFrames(detectionFrames) {
|
|
|
140
140
|
if (detection.zIndex !== undefined) {
|
|
141
141
|
validateNumber(detection.zIndex, `${detectionPath}.zIndex`);
|
|
142
142
|
}
|
|
143
|
+
if (detection.trackerId !== undefined) {
|
|
144
|
+
validateNumber(detection.trackerId, `${detectionPath}.trackerId`, {
|
|
145
|
+
integer: true,
|
|
146
|
+
min: 0,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
143
149
|
if (detection.sourceId !== undefined &&
|
|
144
150
|
typeof detection.sourceId !== "string") {
|
|
145
151
|
throw new Error(`${detectionPath}.sourceId must be a string.`);
|
|
@@ -1657,221 +1663,1796 @@ function isRangeCovered(range, availableRanges) {
|
|
|
1657
1663
|
availableRange.endTime + RANGE_EPSILON_SECONDS >= range.endTime);
|
|
1658
1664
|
}
|
|
1659
1665
|
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1666
|
+
/**
|
|
1667
|
+
* Creates one stateful SORT tracker for a single ordered media sequence.
|
|
1668
|
+
*
|
|
1669
|
+
* Defaults, lifecycle semantics, and observation-only output mirror
|
|
1670
|
+
* roboflow/trackers SORT. Motion predictions remain internal to association.
|
|
1671
|
+
*/
|
|
1672
|
+
function createSortTracker$1(options = {}) {
|
|
1673
|
+
const lostTrackBuffer = normalizeNonNegativeInteger$1(options.lostTrackBuffer ?? 30, "lostTrackBuffer");
|
|
1674
|
+
const frameRate = options.frameRate ?? 30;
|
|
1675
|
+
const trackActivationThreshold = options.trackActivationThreshold ?? 0.25;
|
|
1676
|
+
const minimumConsecutiveFrames = normalizePositiveInteger$1(options.minimumConsecutiveFrames ?? 3, "minimumConsecutiveFrames");
|
|
1677
|
+
const minimumIouThreshold = options.minimumIouThreshold ?? 0.3;
|
|
1678
|
+
if (!Number.isFinite(frameRate) || frameRate <= 0) {
|
|
1679
|
+
throw new Error("frameRate must be a finite positive value.");
|
|
1680
|
+
}
|
|
1681
|
+
normalizeUnitInterval$1(trackActivationThreshold, "trackActivationThreshold");
|
|
1682
|
+
normalizeUnitInterval$1(minimumIouThreshold, "minimumIouThreshold");
|
|
1683
|
+
const scaledLostTrackBuffer = (frameRate / 30) * lostTrackBuffer;
|
|
1684
|
+
if (!Number.isFinite(scaledLostTrackBuffer)) {
|
|
1685
|
+
throw new Error("Scaled lostTrackBuffer overflows: frameRate / 30 * lostTrackBuffer must be finite.");
|
|
1686
|
+
}
|
|
1687
|
+
const maximumFramesWithoutUpdate = lostTrackBuffer === 0 ? 0 : Math.max(1, Math.ceil(scaledLostTrackBuffer));
|
|
1688
|
+
let tracks = [];
|
|
1689
|
+
let nextTrackerId = 0;
|
|
1690
|
+
let previousFrameIndex;
|
|
1678
1691
|
return {
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1692
|
+
reset() {
|
|
1693
|
+
tracks = [];
|
|
1694
|
+
nextTrackerId = 0;
|
|
1695
|
+
previousFrameIndex = undefined;
|
|
1696
|
+
},
|
|
1697
|
+
update(detections, frameIndex) {
|
|
1698
|
+
const frameStep = resolveFrameStep(frameIndex, previousFrameIndex);
|
|
1699
|
+
previousFrameIndex = frameIndex ?? previousFrameIndex;
|
|
1700
|
+
const predicted = tracks.map((track) => track.predict(frameStep, frameRate));
|
|
1701
|
+
const { matches, unmatchedDetections } = associateDetectionsToTracks(detections, predicted, minimumIouThreshold);
|
|
1702
|
+
const trackerIds = new Map();
|
|
1703
|
+
for (const match of matches) {
|
|
1704
|
+
const track = tracks[match.trackIndex];
|
|
1705
|
+
const detection = detections[match.detectionIndex];
|
|
1706
|
+
track.update(detection);
|
|
1707
|
+
if (track.trackerId === undefined &&
|
|
1708
|
+
track.successfulUpdates >= minimumConsecutiveFrames) {
|
|
1709
|
+
track.trackerId = nextTrackerId;
|
|
1710
|
+
nextTrackerId += 1;
|
|
1711
|
+
}
|
|
1712
|
+
if (track.trackerId !== undefined) {
|
|
1713
|
+
trackerIds.set(detection.detectionIndex, track.trackerId);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
for (const detectionIndex of unmatchedDetections) {
|
|
1717
|
+
const detection = detections[detectionIndex];
|
|
1718
|
+
if ((detection.confidence ?? 1) >= trackActivationThreshold) {
|
|
1719
|
+
tracks.push(new KalmanBoxTrack(detection));
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
tracks = tracks.filter((track) => track.timeSinceUpdate <= maximumFramesWithoutUpdate &&
|
|
1723
|
+
(track.successfulUpdates >= minimumConsecutiveFrames ||
|
|
1724
|
+
track.timeSinceUpdate === 0));
|
|
1725
|
+
return {
|
|
1726
|
+
activeTrackCount: tracks.length,
|
|
1727
|
+
assignments: detections.flatMap((detection) => {
|
|
1728
|
+
const trackerId = trackerIds.get(detection.detectionIndex);
|
|
1729
|
+
return trackerId === undefined
|
|
1730
|
+
? []
|
|
1731
|
+
: [{ detectionIndex: detection.detectionIndex, trackerId }];
|
|
1732
|
+
}),
|
|
1733
|
+
confirmedTrackCount: tracks.filter((track) => track.trackerId !== undefined).length,
|
|
1734
|
+
};
|
|
1735
|
+
},
|
|
1683
1736
|
};
|
|
1684
1737
|
}
|
|
1685
|
-
|
|
1738
|
+
class KalmanBoxTrack {
|
|
1739
|
+
consecutiveUpdates;
|
|
1740
|
+
trackerId;
|
|
1741
|
+
successfulUpdates = 1;
|
|
1742
|
+
timeSinceUpdate = 0;
|
|
1743
|
+
state;
|
|
1744
|
+
covariance = identity(8);
|
|
1745
|
+
constructor(detection, consecutiveUpdates = false) {
|
|
1746
|
+
this.consecutiveUpdates = consecutiveUpdates;
|
|
1747
|
+
this.state = [...rectToXyxy(detection.rect), 0, 0, 0, 0].map((value) => [
|
|
1748
|
+
value,
|
|
1749
|
+
]);
|
|
1750
|
+
}
|
|
1751
|
+
predict(frameStep, frameRate) {
|
|
1752
|
+
if (this.consecutiveUpdates && this.timeSinceUpdate > 0) {
|
|
1753
|
+
this.successfulUpdates = 0;
|
|
1754
|
+
}
|
|
1755
|
+
const transition = identity(8);
|
|
1756
|
+
for (let index = 0; index < 4; index += 1) {
|
|
1757
|
+
transition[index][index + 4] = frameStep;
|
|
1758
|
+
}
|
|
1759
|
+
this.state = multiply(transition, this.state);
|
|
1760
|
+
this.covariance = add(multiply(multiply(transition, this.covariance), transpose(transition)), createProcessNoise(frameStep, frameRate));
|
|
1761
|
+
// Python SORT counts update calls for the fixed-rate lost-track budget.
|
|
1762
|
+
this.timeSinceUpdate += 1;
|
|
1763
|
+
return stateToRect(this.state);
|
|
1764
|
+
}
|
|
1765
|
+
update(detection) {
|
|
1766
|
+
const measurement = rectToXyxy(detection.rect).map((value) => [value]);
|
|
1767
|
+
const observation = [
|
|
1768
|
+
[1, 0, 0, 0, 0, 0, 0, 0],
|
|
1769
|
+
[0, 1, 0, 0, 0, 0, 0, 0],
|
|
1770
|
+
[0, 0, 1, 0, 0, 0, 0, 0],
|
|
1771
|
+
[0, 0, 0, 1, 0, 0, 0, 0],
|
|
1772
|
+
];
|
|
1773
|
+
const measurementNoise = scale(identity(4), 0.1);
|
|
1774
|
+
const innovation = subtract(measurement, multiply(observation, this.state));
|
|
1775
|
+
const covarianceObservationTranspose = multiply(this.covariance, transpose(observation));
|
|
1776
|
+
const innovationCovariance = add(multiply(observation, covarianceObservationTranspose), measurementNoise);
|
|
1777
|
+
const gain = multiply(covarianceObservationTranspose, inverse(innovationCovariance));
|
|
1778
|
+
this.state = add(this.state, multiply(gain, innovation));
|
|
1779
|
+
const identityMinusGainObservation = subtract(identity(8), multiply(gain, observation));
|
|
1780
|
+
// Joseph form matches the Python implementation and is more stable.
|
|
1781
|
+
this.covariance = add(multiply(multiply(identityMinusGainObservation, this.covariance), transpose(identityMinusGainObservation)), multiply(multiply(gain, measurementNoise), transpose(gain)));
|
|
1782
|
+
this.timeSinceUpdate = 0;
|
|
1783
|
+
this.successfulUpdates += 1;
|
|
1784
|
+
}
|
|
1785
|
+
getStateRect() {
|
|
1786
|
+
return stateToRect(this.state);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
function associateDetectionsToTracks(detections, predicted, minimumIouThreshold) {
|
|
1790
|
+
if (predicted.length === 0 || detections.length === 0) {
|
|
1791
|
+
return {
|
|
1792
|
+
matches: [],
|
|
1793
|
+
unmatchedDetections: detections.map((_, index) => index),
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
// Python SORT associates all detections class-agnostically using standard IoU.
|
|
1797
|
+
const scores = predicted.map((rect) => detections.map((detection) => intersectionOverUnion(rect, detection.rect)));
|
|
1798
|
+
const candidateMatches = maximizeAssignment(scores);
|
|
1799
|
+
const matches = [];
|
|
1800
|
+
const matchedDetections = new Set();
|
|
1801
|
+
for (const [trackIndex, detectionIndex] of candidateMatches) {
|
|
1802
|
+
if (scores[trackIndex][detectionIndex] < minimumIouThreshold)
|
|
1803
|
+
continue;
|
|
1804
|
+
matches.push({ detectionIndex, trackIndex });
|
|
1805
|
+
matchedDetections.add(detectionIndex);
|
|
1806
|
+
}
|
|
1807
|
+
matches.sort((left, right) => left.trackIndex - right.trackIndex);
|
|
1686
1808
|
return {
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
x: rect.x + rect.width / 2,
|
|
1690
|
-
y: rect.y + rect.height / 2,
|
|
1809
|
+
matches,
|
|
1810
|
+
unmatchedDetections: detections.flatMap((_, index) => matchedDetections.has(index) ? [] : [index]),
|
|
1691
1811
|
};
|
|
1692
1812
|
}
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1813
|
+
/** Hungarian assignment for a rectangular score matrix. */
|
|
1814
|
+
function maximizeAssignment(scores) {
|
|
1815
|
+
if (scores.length === 0 || scores[0]?.length === 0) {
|
|
1816
|
+
return [];
|
|
1696
1817
|
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1818
|
+
const rowCount = scores.length;
|
|
1819
|
+
const columnCount = scores[0].length;
|
|
1820
|
+
const transposed = rowCount > columnCount;
|
|
1821
|
+
const costs = transposed
|
|
1822
|
+
? Array.from({ length: columnCount }, (_, row) => Array.from({ length: rowCount }, (_, column) => 1 - scores[column][row]))
|
|
1823
|
+
: scores.map((row) => row.map((score) => 1 - score));
|
|
1824
|
+
const rows = costs.length;
|
|
1825
|
+
const columns = costs[0].length;
|
|
1826
|
+
const u = new Array(rows + 1).fill(0);
|
|
1827
|
+
const v = new Array(columns + 1).fill(0);
|
|
1828
|
+
const p = new Array(columns + 1).fill(0);
|
|
1829
|
+
const way = new Array(columns + 1).fill(0);
|
|
1830
|
+
for (let row = 1; row <= rows; row += 1) {
|
|
1831
|
+
p[0] = row;
|
|
1832
|
+
let column0 = 0;
|
|
1833
|
+
const minValue = new Array(columns + 1).fill(Number.POSITIVE_INFINITY);
|
|
1834
|
+
const used = new Array(columns + 1).fill(false);
|
|
1835
|
+
do {
|
|
1836
|
+
used[column0] = true;
|
|
1837
|
+
const row0 = p[column0];
|
|
1838
|
+
let delta = Number.POSITIVE_INFINITY;
|
|
1839
|
+
let column1 = 0;
|
|
1840
|
+
for (let column = 1; column <= columns; column += 1) {
|
|
1841
|
+
if (used[column])
|
|
1842
|
+
continue;
|
|
1843
|
+
const current = costs[row0 - 1][column - 1] - u[row0] - v[column];
|
|
1844
|
+
if (current < minValue[column]) {
|
|
1845
|
+
minValue[column] = current;
|
|
1846
|
+
way[column] = column0;
|
|
1847
|
+
}
|
|
1848
|
+
if (minValue[column] < delta) {
|
|
1849
|
+
delta = minValue[column];
|
|
1850
|
+
column1 = column;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
for (let column = 0; column <= columns; column += 1) {
|
|
1854
|
+
if (used[column]) {
|
|
1855
|
+
u[p[column]] += delta;
|
|
1856
|
+
v[column] -= delta;
|
|
1857
|
+
}
|
|
1858
|
+
else {
|
|
1859
|
+
minValue[column] -= delta;
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
column0 = column1;
|
|
1863
|
+
} while (p[column0] !== 0);
|
|
1864
|
+
do {
|
|
1865
|
+
const column1 = way[column0];
|
|
1866
|
+
p[column0] = p[column1];
|
|
1867
|
+
column0 = column1;
|
|
1868
|
+
} while (column0 !== 0);
|
|
1869
|
+
}
|
|
1870
|
+
const assignments = [];
|
|
1871
|
+
for (let column = 1; column <= columns; column += 1) {
|
|
1872
|
+
if (p[column] === 0)
|
|
1873
|
+
continue;
|
|
1874
|
+
const row = p[column] - 1;
|
|
1875
|
+
assignments.push(transposed ? [column - 1, row] : [row, column - 1]);
|
|
1706
1876
|
}
|
|
1877
|
+
return assignments;
|
|
1878
|
+
}
|
|
1879
|
+
function rectToXyxy(rect) {
|
|
1880
|
+
return [
|
|
1881
|
+
rect.x - rect.width / 2,
|
|
1882
|
+
rect.y - rect.height / 2,
|
|
1883
|
+
rect.x + rect.width / 2,
|
|
1884
|
+
rect.y + rect.height / 2,
|
|
1885
|
+
];
|
|
1886
|
+
}
|
|
1887
|
+
function stateToRect(state) {
|
|
1888
|
+
// The Python XYXY estimator intentionally leaves corner velocities
|
|
1889
|
+
// unconstrained. Normalize only at the browser Rect boundary so a crossing
|
|
1890
|
+
// prediction cannot violate the positive-width/height storage contract.
|
|
1891
|
+
const x1 = Math.min(state[0][0], state[2][0]);
|
|
1892
|
+
const y1 = Math.min(state[1][0], state[3][0]);
|
|
1893
|
+
const x2 = Math.max(state[0][0], state[2][0]);
|
|
1894
|
+
const y2 = Math.max(state[1][0], state[3][0]);
|
|
1707
1895
|
return {
|
|
1708
|
-
height:
|
|
1709
|
-
width:
|
|
1710
|
-
x: (
|
|
1711
|
-
y: (
|
|
1896
|
+
height: Math.max(Number.EPSILON, y2 - y1),
|
|
1897
|
+
width: Math.max(Number.EPSILON, x2 - x1),
|
|
1898
|
+
x: (x1 + x2) / 2,
|
|
1899
|
+
y: (y1 + y2) / 2,
|
|
1712
1900
|
};
|
|
1713
1901
|
}
|
|
1714
|
-
function
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1902
|
+
function createProcessNoise(frameStep, frameRate) {
|
|
1903
|
+
if (Math.abs(frameStep - 1) <= 0.004 * frameRate) {
|
|
1904
|
+
return scale(identity(8), 0.01);
|
|
1905
|
+
}
|
|
1906
|
+
const result = Array.from({ length: 8 }, () => new Array(8).fill(0));
|
|
1907
|
+
const dt2 = frameStep * frameStep;
|
|
1908
|
+
const dt3 = dt2 * frameStep;
|
|
1909
|
+
const dt4 = dt2 * dt2;
|
|
1910
|
+
for (let index = 0; index < 4; index += 1) {
|
|
1911
|
+
const velocityIndex = index + 4;
|
|
1912
|
+
result[index][index] = (0.01 * dt4) / 4;
|
|
1913
|
+
result[index][velocityIndex] = (0.01 * dt3) / 2;
|
|
1914
|
+
result[velocityIndex][index] = (0.01 * dt3) / 2;
|
|
1915
|
+
result[velocityIndex][velocityIndex] = 0.01 * dt2;
|
|
1916
|
+
}
|
|
1917
|
+
return result;
|
|
1719
1918
|
}
|
|
1720
|
-
function
|
|
1721
|
-
const
|
|
1722
|
-
const
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1919
|
+
function intersectionOverUnion(left, right) {
|
|
1920
|
+
const leftX = left.x - left.width / 2;
|
|
1921
|
+
const leftY = left.y - left.height / 2;
|
|
1922
|
+
const rightX = right.x - right.width / 2;
|
|
1923
|
+
const rightY = right.y - right.height / 2;
|
|
1924
|
+
const intersectionWidth = Math.max(0, Math.min(leftX + left.width, rightX + right.width) -
|
|
1925
|
+
Math.max(leftX, rightX));
|
|
1926
|
+
const intersectionHeight = Math.max(0, Math.min(leftY + left.height, rightY + right.height) -
|
|
1927
|
+
Math.max(leftY, rightY));
|
|
1928
|
+
const intersection = intersectionWidth * intersectionHeight;
|
|
1929
|
+
const union = left.width * left.height + right.width * right.height - intersection;
|
|
1930
|
+
return union <= 0 ? 0 : intersection / union;
|
|
1931
|
+
}
|
|
1932
|
+
function resolveFrameStep(current, previous) {
|
|
1933
|
+
if (current === undefined || previous === undefined)
|
|
1934
|
+
return 1;
|
|
1935
|
+
return Math.max(1, current - previous);
|
|
1727
1936
|
}
|
|
1728
|
-
function
|
|
1729
|
-
if (
|
|
1730
|
-
|
|
1731
|
-
}
|
|
1732
|
-
let inside = false;
|
|
1733
|
-
for (let currentIndex = 0, previousIndex = polygon.length - 1; currentIndex < polygon.length; previousIndex = currentIndex, currentIndex += 1) {
|
|
1734
|
-
const current = polygon[currentIndex];
|
|
1735
|
-
const previous = polygon[previousIndex];
|
|
1736
|
-
const crossesRay = current.y > point.y !== previous.y > point.y &&
|
|
1737
|
-
point.x <
|
|
1738
|
-
((previous.x - current.x) * (point.y - current.y)) /
|
|
1739
|
-
(previous.y - current.y) +
|
|
1740
|
-
current.x;
|
|
1741
|
-
if (crossesRay) {
|
|
1742
|
-
inside = !inside;
|
|
1743
|
-
}
|
|
1937
|
+
function normalizePositiveInteger$1(value, label) {
|
|
1938
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
1939
|
+
throw new Error(`${label} must be a positive integer.`);
|
|
1744
1940
|
}
|
|
1745
|
-
return
|
|
1941
|
+
return value;
|
|
1746
1942
|
}
|
|
1747
|
-
function
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
const lengthSquared = deltaX * deltaX + deltaY * deltaY;
|
|
1751
|
-
if (lengthSquared === 0) {
|
|
1752
|
-
return Math.hypot(point.x - start.x, point.y - start.y);
|
|
1943
|
+
function normalizeNonNegativeInteger$1(value, label) {
|
|
1944
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
1945
|
+
throw new Error(`${label} must be a non-negative integer.`);
|
|
1753
1946
|
}
|
|
1754
|
-
|
|
1755
|
-
lengthSquared));
|
|
1756
|
-
const closestX = start.x + projection * deltaX;
|
|
1757
|
-
const closestY = start.y + projection * deltaY;
|
|
1758
|
-
return Math.hypot(point.x - closestX, point.y - closestY);
|
|
1947
|
+
return value;
|
|
1759
1948
|
}
|
|
1760
|
-
function
|
|
1761
|
-
if (
|
|
1762
|
-
|
|
1949
|
+
function normalizeUnitInterval$1(value, label) {
|
|
1950
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
1951
|
+
throw new Error(`${label} must be between 0 and 1.`);
|
|
1763
1952
|
}
|
|
1764
|
-
let twiceArea = 0;
|
|
1765
|
-
for (let index = 0; index < points.length; index += 1) {
|
|
1766
|
-
const current = points[index];
|
|
1767
|
-
const next = points[(index + 1) % points.length];
|
|
1768
|
-
twiceArea += current.x * next.y - next.x * current.y;
|
|
1769
|
-
}
|
|
1770
|
-
return Math.abs(twiceArea) / 2;
|
|
1771
1953
|
}
|
|
1772
|
-
function
|
|
1773
|
-
return
|
|
1954
|
+
function identity(size) {
|
|
1955
|
+
return Array.from({ length: size }, (_, row) => Array.from({ length: size }, (_, column) => (row === column ? 1 : 0)));
|
|
1774
1956
|
}
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1957
|
+
function scale(matrix, factor) {
|
|
1958
|
+
return matrix.map((row) => row.map((value) => value * factor));
|
|
1959
|
+
}
|
|
1960
|
+
function transpose(matrix) {
|
|
1961
|
+
return matrix[0].map((_, column) => matrix.map((row) => row[column]));
|
|
1962
|
+
}
|
|
1963
|
+
function add(left, right) {
|
|
1964
|
+
return left.map((row, rowIndex) => row.map((value, columnIndex) => value + right[rowIndex][columnIndex]));
|
|
1965
|
+
}
|
|
1966
|
+
function subtract(left, right) {
|
|
1967
|
+
return left.map((row, rowIndex) => row.map((value, columnIndex) => value - right[rowIndex][columnIndex]));
|
|
1968
|
+
}
|
|
1969
|
+
function multiply(left, right) {
|
|
1970
|
+
return left.map((row) => right[0].map((_, column) => row.reduce((sum, value, index) => sum + value * right[index][column], 0)));
|
|
1971
|
+
}
|
|
1972
|
+
function inverse(matrix) {
|
|
1973
|
+
const size = matrix.length;
|
|
1974
|
+
const augmented = matrix.map((row, index) => [
|
|
1975
|
+
...row,
|
|
1976
|
+
...identity(size)[index],
|
|
1977
|
+
]);
|
|
1978
|
+
for (let column = 0; column < size; column += 1) {
|
|
1979
|
+
let pivot = column;
|
|
1980
|
+
for (let row = column + 1; row < size; row += 1) {
|
|
1981
|
+
if (Math.abs(augmented[row][column]) >
|
|
1982
|
+
Math.abs(augmented[pivot][column])) {
|
|
1983
|
+
pivot = row;
|
|
1984
|
+
}
|
|
1792
1985
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
candidates.push({
|
|
1796
|
-
area,
|
|
1797
|
-
priority: getPickTargetPriority(target),
|
|
1798
|
-
result: {
|
|
1799
|
-
detection,
|
|
1800
|
-
detectionIndex,
|
|
1801
|
-
frame,
|
|
1802
|
-
geometryIndex,
|
|
1803
|
-
mediaTime: frame.mediaTime,
|
|
1804
|
-
point,
|
|
1805
|
-
target,
|
|
1806
|
-
},
|
|
1807
|
-
zIndex: detection.zIndex ?? detectionIndex,
|
|
1808
|
-
});
|
|
1809
|
-
};
|
|
1810
|
-
if (detection.rect && containsPoint(detection.rect, point, padding)) {
|
|
1811
|
-
pushCandidate(DetectionPickTarget.Box, rectArea(detection.rect));
|
|
1986
|
+
if (Math.abs(augmented[pivot][column]) < 1e-12) {
|
|
1987
|
+
throw new Error("SORT Kalman covariance is singular.");
|
|
1812
1988
|
}
|
|
1813
|
-
|
|
1814
|
-
|
|
1989
|
+
[augmented[column], augmented[pivot]] = [
|
|
1990
|
+
augmented[pivot],
|
|
1991
|
+
augmented[column],
|
|
1992
|
+
];
|
|
1993
|
+
const divisor = augmented[column][column];
|
|
1994
|
+
augmented[column] = augmented[column].map((value) => value / divisor);
|
|
1995
|
+
for (let row = 0; row < size; row += 1) {
|
|
1996
|
+
if (row === column)
|
|
1997
|
+
continue;
|
|
1998
|
+
const factor = augmented[row][column];
|
|
1999
|
+
augmented[row] = augmented[row].map((value, index) => value - factor * augmented[column][index]);
|
|
1815
2000
|
}
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2001
|
+
}
|
|
2002
|
+
return augmented.map((row) => row.slice(size));
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
/**
|
|
2006
|
+
* Creates one stateful ByteTrack tracker for a single ordered media sequence.
|
|
2007
|
+
*
|
|
2008
|
+
* Defaults and two-stage association mirror roboflow/trackers ByteTrack at
|
|
2009
|
+
* source commit 60b21c8a48676784085fbee455559f16b75a7c9a.
|
|
2010
|
+
* Motion predictions remain internal; only observed detections receive IDs.
|
|
2011
|
+
*/
|
|
2012
|
+
function createByteTrackTracker$1(options = {}) {
|
|
2013
|
+
const lostTrackBuffer = normalizeNonNegativeInteger$1(options.lostTrackBuffer ?? 30, "lostTrackBuffer");
|
|
2014
|
+
const frameRate = options.frameRate ?? 30;
|
|
2015
|
+
const trackActivationThreshold = options.trackActivationThreshold ?? 0.7;
|
|
2016
|
+
const minimumConsecutiveFrames = normalizePositiveInteger$1(options.minimumConsecutiveFrames ?? 2, "minimumConsecutiveFrames");
|
|
2017
|
+
const minimumIouThreshold = options.minimumIouThreshold ?? 0.1;
|
|
2018
|
+
const highConfidenceDetectionThreshold = options.highConfidenceDetectionThreshold ?? 0.6;
|
|
2019
|
+
if (!Number.isFinite(frameRate) || frameRate <= 0) {
|
|
2020
|
+
throw new Error("frameRate must be a finite positive value.");
|
|
2021
|
+
}
|
|
2022
|
+
normalizeUnitInterval$1(trackActivationThreshold, "trackActivationThreshold");
|
|
2023
|
+
normalizeUnitInterval$1(minimumIouThreshold, "minimumIouThreshold");
|
|
2024
|
+
normalizeUnitInterval$1(highConfidenceDetectionThreshold, "highConfidenceDetectionThreshold");
|
|
2025
|
+
const scaledLostTrackBuffer = (frameRate / 30) * lostTrackBuffer;
|
|
2026
|
+
if (!Number.isFinite(scaledLostTrackBuffer)) {
|
|
2027
|
+
throw new Error("Scaled lostTrackBuffer overflows: frameRate / 30 * lostTrackBuffer must be finite.");
|
|
2028
|
+
}
|
|
2029
|
+
const maximumFramesWithoutUpdate = lostTrackBuffer === 0 ? 0 : Math.max(1, Math.ceil(scaledLostTrackBuffer));
|
|
2030
|
+
let tracks = [];
|
|
2031
|
+
let nextTrackerId = 0;
|
|
2032
|
+
let previousFrameIndex;
|
|
2033
|
+
return {
|
|
2034
|
+
reset() {
|
|
2035
|
+
tracks = [];
|
|
2036
|
+
nextTrackerId = 0;
|
|
2037
|
+
previousFrameIndex = undefined;
|
|
2038
|
+
},
|
|
2039
|
+
update(detections, frameIndex) {
|
|
2040
|
+
const frameStep = resolveFrameStep(frameIndex, previousFrameIndex);
|
|
2041
|
+
previousFrameIndex = frameIndex ?? previousFrameIndex;
|
|
2042
|
+
tracks.forEach((track) => track.predict(frameStep, frameRate));
|
|
2043
|
+
const highDetectionIndexes = [];
|
|
2044
|
+
const lowDetectionIndexes = [];
|
|
2045
|
+
detections.forEach((detection, index) => {
|
|
2046
|
+
if ((detection.confidence ?? 1) >= highConfidenceDetectionThreshold) {
|
|
2047
|
+
highDetectionIndexes.push(index);
|
|
2048
|
+
}
|
|
2049
|
+
else {
|
|
2050
|
+
lowDetectionIndexes.push(index);
|
|
2051
|
+
}
|
|
2052
|
+
});
|
|
2053
|
+
const assignments = new Map();
|
|
2054
|
+
const firstStage = associate(tracks, detections, highDetectionIndexes, minimumIouThreshold);
|
|
2055
|
+
for (const match of firstStage.matches) {
|
|
2056
|
+
updateMatchedTrack(tracks[match.trackIndex], detections[match.detectionIndex], assignments);
|
|
1820
2057
|
}
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
const
|
|
1824
|
-
|
|
1825
|
-
Math.hypot(point.x - keypoint.x, point.y - keypoint.y) <=
|
|
1826
|
-
keypointPadding);
|
|
1827
|
-
if (keypointIndex !== -1) {
|
|
1828
|
-
pushCandidate(DetectionPickTarget.Keypoint, Math.PI * keypointPadding * keypointPadding, keypointIndex);
|
|
2058
|
+
const remainingTracks = firstStage.unmatchedTrackIndexes.map((index) => tracks[index]);
|
|
2059
|
+
const secondStage = associate(remainingTracks, detections, lowDetectionIndexes, minimumIouThreshold);
|
|
2060
|
+
for (const match of secondStage.matches) {
|
|
2061
|
+
updateMatchedTrack(remainingTracks[match.trackIndex], detections[match.detectionIndex], assignments);
|
|
1829
2062
|
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
to &&
|
|
1836
|
-
detection.keypoints?.visibility?.[fromIndex] !==
|
|
1837
|
-
KeypointVisibility.NotLabeled &&
|
|
1838
|
-
detection.keypoints?.visibility?.[toIndex] !==
|
|
1839
|
-
KeypointVisibility.NotLabeled &&
|
|
1840
|
-
distanceToSegment(point, from, to) <= edgePadding);
|
|
1841
|
-
});
|
|
1842
|
-
if (edgeIndex !== -1) {
|
|
1843
|
-
const [fromIndex, toIndex] = detection.keypoints.edges[edgeIndex];
|
|
1844
|
-
const from = detection.keypoints.points[fromIndex];
|
|
1845
|
-
const to = detection.keypoints.points[toIndex];
|
|
1846
|
-
const edgeArea = Math.max(1, Math.hypot(to.x - from.x, to.y - from.y) * edgePadding * 2);
|
|
1847
|
-
pushCandidate(DetectionPickTarget.Edge, edgeArea, edgeIndex);
|
|
2063
|
+
// Only unmatched high-confidence observations can start a track.
|
|
2064
|
+
for (const detectionIndex of firstStage.unmatchedDetectionIndexes) {
|
|
2065
|
+
const detection = detections[detectionIndex];
|
|
2066
|
+
if ((detection.confidence ?? 1) >= trackActivationThreshold) {
|
|
2067
|
+
tracks.push(new KalmanBoxTrack(detection, true));
|
|
1848
2068
|
}
|
|
1849
2069
|
}
|
|
2070
|
+
// Confirmation is sticky once an ID has been allocated. An unmatched
|
|
2071
|
+
// unconfirmed track is discarded immediately, as in Python ByteTrack.
|
|
2072
|
+
tracks = tracks.filter((track) => track.timeSinceUpdate <= maximumFramesWithoutUpdate &&
|
|
2073
|
+
(track.trackerId !== undefined ||
|
|
2074
|
+
track.successfulUpdates >= minimumConsecutiveFrames ||
|
|
2075
|
+
track.timeSinceUpdate === 0));
|
|
2076
|
+
return {
|
|
2077
|
+
activeTrackCount: tracks.length,
|
|
2078
|
+
assignments: detections.flatMap((detection) => {
|
|
2079
|
+
const trackerId = assignments.get(detection.detectionIndex);
|
|
2080
|
+
return trackerId === undefined
|
|
2081
|
+
? []
|
|
2082
|
+
: [{ detectionIndex: detection.detectionIndex, trackerId }];
|
|
2083
|
+
}),
|
|
2084
|
+
confirmedTrackCount: tracks.filter((track) => track.trackerId !== undefined).length,
|
|
2085
|
+
};
|
|
2086
|
+
},
|
|
2087
|
+
};
|
|
2088
|
+
function updateMatchedTrack(track, detection, assignments) {
|
|
2089
|
+
track.update(detection);
|
|
2090
|
+
if (track.trackerId === undefined &&
|
|
2091
|
+
track.successfulUpdates >= minimumConsecutiveFrames) {
|
|
2092
|
+
track.trackerId = nextTrackerId;
|
|
2093
|
+
nextTrackerId += 1;
|
|
1850
2094
|
}
|
|
1851
|
-
if (
|
|
1852
|
-
|
|
1853
|
-
const mediaDimensions = options.maskMediaDimensions;
|
|
1854
|
-
const x = Math.floor(mediaDimensions && mediaDimensions.width > 0
|
|
1855
|
-
? (point.x / mediaDimensions.width) * decoded.width
|
|
1856
|
-
: point.x);
|
|
1857
|
-
const y = Math.floor(mediaDimensions && mediaDimensions.height > 0
|
|
1858
|
-
? (point.y / mediaDimensions.height) * decoded.height
|
|
1859
|
-
: point.y);
|
|
1860
|
-
if (x >= 0 &&
|
|
1861
|
-
y >= 0 &&
|
|
1862
|
-
x < decoded.width &&
|
|
1863
|
-
y < decoded.height &&
|
|
1864
|
-
decoded.data[y * decoded.width + x]) {
|
|
1865
|
-
pushCandidate(DetectionPickTarget.Mask, getMaskMediaArea(decoded, mediaDimensions));
|
|
1866
|
-
}
|
|
2095
|
+
if (track.trackerId !== undefined) {
|
|
2096
|
+
assignments.set(detection.detectionIndex, track.trackerId);
|
|
1867
2097
|
}
|
|
1868
2098
|
}
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
2099
|
+
}
|
|
2100
|
+
function associate(tracks, detections, detectionIndexes, minimumIouThreshold) {
|
|
2101
|
+
if (tracks.length === 0 || detectionIndexes.length === 0) {
|
|
2102
|
+
return {
|
|
2103
|
+
matches: [],
|
|
2104
|
+
unmatchedDetectionIndexes: [...detectionIndexes],
|
|
2105
|
+
unmatchedTrackIndexes: tracks.map((_, index) => index),
|
|
2106
|
+
};
|
|
2107
|
+
}
|
|
2108
|
+
const scores = tracks.map((track) => {
|
|
2109
|
+
const predictedRect = track.getStateRect();
|
|
2110
|
+
return detectionIndexes.map((detectionIndex) => intersectionOverUnion(predictedRect, detections[detectionIndex].rect));
|
|
2111
|
+
});
|
|
2112
|
+
const matches = [];
|
|
2113
|
+
const matchedTracks = new Set();
|
|
2114
|
+
const matchedDetections = new Set();
|
|
2115
|
+
for (const [trackIndex, localDetectionIndex] of maximizeAssignment(scores)) {
|
|
2116
|
+
if (scores[trackIndex][localDetectionIndex] < minimumIouThreshold) {
|
|
2117
|
+
continue;
|
|
1873
2118
|
}
|
|
1874
|
-
const
|
|
2119
|
+
const detectionIndex = detectionIndexes[localDetectionIndex];
|
|
2120
|
+
matches.push({ detectionIndex, trackIndex });
|
|
2121
|
+
matchedTracks.add(trackIndex);
|
|
2122
|
+
matchedDetections.add(detectionIndex);
|
|
2123
|
+
}
|
|
2124
|
+
matches.sort((left, right) => left.trackIndex - right.trackIndex);
|
|
2125
|
+
return {
|
|
2126
|
+
matches,
|
|
2127
|
+
unmatchedDetectionIndexes: detectionIndexes.filter((index) => !matchedDetections.has(index)),
|
|
2128
|
+
unmatchedTrackIndexes: tracks.flatMap((_, index) => matchedTracks.has(index) ? [] : [index]),
|
|
2129
|
+
};
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
function associateTrackingScores(scores, trackCount, detectionCount, minimumScore, acceptanceScores = scores) {
|
|
2133
|
+
const matches = [];
|
|
2134
|
+
const matchedTracks = new Set();
|
|
2135
|
+
const matchedDetections = new Set();
|
|
2136
|
+
for (const [trackIndex, detectionIndex] of maximizeAssignment(scores)) {
|
|
2137
|
+
if (acceptanceScores[trackIndex][detectionIndex] < minimumScore)
|
|
2138
|
+
continue;
|
|
2139
|
+
matches.push({ detectionIndex, trackIndex });
|
|
2140
|
+
matchedTracks.add(trackIndex);
|
|
2141
|
+
matchedDetections.add(detectionIndex);
|
|
2142
|
+
}
|
|
2143
|
+
matches.sort((left, right) => left.trackIndex - right.trackIndex);
|
|
2144
|
+
return {
|
|
2145
|
+
matches,
|
|
2146
|
+
unmatchedDetectionIndexes: Array.from({ length: detectionCount }, (_, index) => index).filter((index) => !matchedDetections.has(index)),
|
|
2147
|
+
unmatchedTrackIndexes: Array.from({ length: trackCount }, (_, index) => index).filter((index) => !matchedTracks.has(index)),
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2150
|
+
function pairwiseIou(tracks, detections, bufferRatio = 0) {
|
|
2151
|
+
return tracks.map((track) => detections.map((detection) => bufferedIntersectionOverUnion(track, detection, bufferRatio)));
|
|
2152
|
+
}
|
|
2153
|
+
function bufferedIntersectionOverUnion(left, right, bufferRatio) {
|
|
2154
|
+
const leftWidth = left.width * (1 + 2 * bufferRatio);
|
|
2155
|
+
const leftHeight = left.height * (1 + 2 * bufferRatio);
|
|
2156
|
+
const rightWidth = right.width * (1 + 2 * bufferRatio);
|
|
2157
|
+
const rightHeight = right.height * (1 + 2 * bufferRatio);
|
|
2158
|
+
const leftX = left.x - leftWidth / 2;
|
|
2159
|
+
const leftY = left.y - leftHeight / 2;
|
|
2160
|
+
const rightX = right.x - rightWidth / 2;
|
|
2161
|
+
const rightY = right.y - rightHeight / 2;
|
|
2162
|
+
const intersectionWidth = Math.max(0, Math.min(leftX + leftWidth, rightX + rightWidth) - Math.max(leftX, rightX));
|
|
2163
|
+
const intersectionHeight = Math.max(0, Math.min(leftY + leftHeight, rightY + rightHeight) -
|
|
2164
|
+
Math.max(leftY, rightY));
|
|
2165
|
+
const intersection = intersectionWidth * intersectionHeight;
|
|
2166
|
+
const union = leftWidth * leftHeight + rightWidth * rightHeight - intersection;
|
|
2167
|
+
return union <= 0 ? 0 : intersection / union;
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
/**
|
|
2171
|
+
* Kalman state estimator shared by the browser C-BIoU and OC-SORT ports.
|
|
2172
|
+
* Its layouts, covariance update, and gap-scaled constant-velocity model
|
|
2173
|
+
* mirror roboflow/trackers at 60b21c8.
|
|
2174
|
+
*/
|
|
2175
|
+
class TrackingKalmanEstimator {
|
|
2176
|
+
representation;
|
|
2177
|
+
dimension;
|
|
2178
|
+
measurementDimension = 4;
|
|
2179
|
+
covariance;
|
|
2180
|
+
measurementNoise = identity(4);
|
|
2181
|
+
processNoise;
|
|
2182
|
+
state;
|
|
2183
|
+
baselineProcessNoise;
|
|
2184
|
+
positionIndexes;
|
|
2185
|
+
velocityIndexes;
|
|
2186
|
+
constructor(initialRect, representation) {
|
|
2187
|
+
this.representation = representation;
|
|
2188
|
+
const measurement = rectToMeasurement(initialRect, representation);
|
|
2189
|
+
this.dimension = representation === "xcycsr" ? 7 : 8;
|
|
2190
|
+
this.positionIndexes =
|
|
2191
|
+
representation === "xcycsr" ? [0, 1, 2] : [0, 1, 2, 3];
|
|
2192
|
+
this.velocityIndexes =
|
|
2193
|
+
representation === "xcycsr" ? [4, 5, 6] : [4, 5, 6, 7];
|
|
2194
|
+
this.state = Array.from({ length: this.dimension }, (_, index) => [
|
|
2195
|
+
measurement[index] ?? 0,
|
|
2196
|
+
]);
|
|
2197
|
+
this.covariance = identity(this.dimension);
|
|
2198
|
+
this.processNoise = identity(this.dimension);
|
|
2199
|
+
this.baselineProcessNoise = identity(this.dimension);
|
|
2200
|
+
}
|
|
2201
|
+
predict(frameStep, frameRate) {
|
|
2202
|
+
if (this.representation === "xcycsr" &&
|
|
2203
|
+
this.state[2][0] + frameStep * this.state[6][0] <= 0) {
|
|
2204
|
+
this.state[6][0] = 0;
|
|
2205
|
+
}
|
|
2206
|
+
const transition = identity(this.dimension);
|
|
2207
|
+
this.positionIndexes.forEach((positionIndex, index) => {
|
|
2208
|
+
transition[positionIndex][this.velocityIndexes[index]] = frameStep;
|
|
2209
|
+
});
|
|
2210
|
+
this.processNoise = this.createProcessNoise(frameStep, frameRate);
|
|
2211
|
+
this.state = multiply(transition, this.state);
|
|
2212
|
+
this.covariance = add(multiply(multiply(transition, this.covariance), transpose(transition)), this.processNoise);
|
|
2213
|
+
}
|
|
2214
|
+
update(rect) {
|
|
2215
|
+
this.updateMeasurement(rectToMeasurement(rect, this.representation));
|
|
2216
|
+
}
|
|
2217
|
+
updateMeasurement(measurement) {
|
|
2218
|
+
const observation = Array.from({ length: 4 }, (_, row) => Array.from({ length: this.dimension }, (_, column) => row === column ? 1 : 0));
|
|
2219
|
+
const measurementColumn = measurement.map((value) => [value]);
|
|
2220
|
+
const innovation = subtract(measurementColumn, multiply(observation, this.state));
|
|
2221
|
+
const covarianceObservationTranspose = multiply(this.covariance, transpose(observation));
|
|
2222
|
+
const innovationCovariance = add(multiply(observation, covarianceObservationTranspose), this.measurementNoise);
|
|
2223
|
+
const gain = multiply(covarianceObservationTranspose, inverse(innovationCovariance));
|
|
2224
|
+
this.state = add(this.state, multiply(gain, innovation));
|
|
2225
|
+
const identityMinusGainObservation = subtract(identity(this.dimension), multiply(gain, observation));
|
|
2226
|
+
this.covariance = add(multiply(multiply(identityMinusGainObservation, this.covariance), transpose(identityMinusGainObservation)), multiply(multiply(gain, this.measurementNoise), transpose(gain)));
|
|
2227
|
+
}
|
|
2228
|
+
getRect() {
|
|
2229
|
+
return measurementToRect(this.state.slice(0, 4).map((row) => row[0]), this.representation);
|
|
2230
|
+
}
|
|
2231
|
+
setCovariances(options) {
|
|
2232
|
+
if (options.covariance)
|
|
2233
|
+
this.covariance = clone(options.covariance);
|
|
2234
|
+
if (options.measurementNoise) {
|
|
2235
|
+
this.measurementNoise = clone(options.measurementNoise);
|
|
2236
|
+
}
|
|
2237
|
+
if (options.processNoise) {
|
|
2238
|
+
this.processNoise = clone(options.processNoise);
|
|
2239
|
+
this.baselineProcessNoise = clone(options.processNoise);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
snapshot() {
|
|
2243
|
+
return {
|
|
2244
|
+
covariance: clone(this.covariance),
|
|
2245
|
+
measurementNoise: clone(this.measurementNoise),
|
|
2246
|
+
processNoise: clone(this.processNoise),
|
|
2247
|
+
state: clone(this.state),
|
|
2248
|
+
};
|
|
2249
|
+
}
|
|
2250
|
+
restore(snapshot) {
|
|
2251
|
+
this.covariance = clone(snapshot.covariance);
|
|
2252
|
+
this.measurementNoise = clone(snapshot.measurementNoise);
|
|
2253
|
+
this.processNoise = clone(snapshot.processNoise);
|
|
2254
|
+
this.state = clone(snapshot.state);
|
|
2255
|
+
}
|
|
2256
|
+
createProcessNoise(frameStep, frameRate) {
|
|
2257
|
+
if (Math.abs(frameStep - 1) <= 0.004 * frameRate) {
|
|
2258
|
+
return clone(this.baselineProcessNoise);
|
|
2259
|
+
}
|
|
2260
|
+
const result = Array.from({ length: this.dimension }, () => new Array(this.dimension).fill(0));
|
|
2261
|
+
const dt2 = frameStep * frameStep;
|
|
2262
|
+
const dt3 = dt2 * frameStep;
|
|
2263
|
+
const dt4 = dt2 * dt2;
|
|
2264
|
+
const kinematicIndexes = new Set([
|
|
2265
|
+
...this.positionIndexes,
|
|
2266
|
+
...this.velocityIndexes,
|
|
2267
|
+
]);
|
|
2268
|
+
this.positionIndexes.forEach((positionIndex, index) => {
|
|
2269
|
+
const velocityIndex = this.velocityIndexes[index];
|
|
2270
|
+
const accelerationVariance = this.baselineProcessNoise[velocityIndex][velocityIndex];
|
|
2271
|
+
result[positionIndex][positionIndex] = (accelerationVariance * dt4) / 4;
|
|
2272
|
+
result[positionIndex][velocityIndex] = (accelerationVariance * dt3) / 2;
|
|
2273
|
+
result[velocityIndex][positionIndex] = (accelerationVariance * dt3) / 2;
|
|
2274
|
+
result[velocityIndex][velocityIndex] = accelerationVariance * dt2;
|
|
2275
|
+
});
|
|
2276
|
+
for (let index = 0; index < this.dimension; index += 1) {
|
|
2277
|
+
if (!kinematicIndexes.has(index)) {
|
|
2278
|
+
result[index][index] = this.baselineProcessNoise[index][index];
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
return result;
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
function rectToMeasurement(rect, representation) {
|
|
2285
|
+
if (representation === "xcycwh") {
|
|
2286
|
+
return [rect.x, rect.y, rect.width, rect.height];
|
|
2287
|
+
}
|
|
2288
|
+
return [
|
|
2289
|
+
rect.x,
|
|
2290
|
+
rect.y,
|
|
2291
|
+
rect.width * rect.height,
|
|
2292
|
+
rect.width / (rect.height + 1e-6),
|
|
2293
|
+
];
|
|
2294
|
+
}
|
|
2295
|
+
function measurementToRect(measurement, representation) {
|
|
2296
|
+
const [x, y, third, fourth] = measurement;
|
|
2297
|
+
if (representation === "xcycwh") {
|
|
2298
|
+
return {
|
|
2299
|
+
height: Math.max(1e-3, fourth),
|
|
2300
|
+
width: Math.max(1e-3, third),
|
|
2301
|
+
x,
|
|
2302
|
+
y,
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
const width = Math.sqrt(third * fourth);
|
|
2306
|
+
const height = width === 0 ? 0 : third / width;
|
|
2307
|
+
return {
|
|
2308
|
+
height: Math.max(Number.EPSILON, height),
|
|
2309
|
+
width: Math.max(Number.EPSILON, width),
|
|
2310
|
+
x,
|
|
2311
|
+
y,
|
|
2312
|
+
};
|
|
2313
|
+
}
|
|
2314
|
+
function diagonal(values) {
|
|
2315
|
+
return values.map((value, row) => values.map((_, column) => (row === column ? value : 0)));
|
|
2316
|
+
}
|
|
2317
|
+
function scaleMatrix(matrix, factor) {
|
|
2318
|
+
return scale(matrix, factor);
|
|
2319
|
+
}
|
|
2320
|
+
function clone(matrix) {
|
|
2321
|
+
return matrix.map((row) => [...row]);
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
const MINIMUM_DETECTION_CONFIDENCE = 0.1;
|
|
2325
|
+
class CBIoUTrack {
|
|
2326
|
+
trackerId;
|
|
2327
|
+
successfulUpdates = 1;
|
|
2328
|
+
timeSinceUpdate = 0;
|
|
2329
|
+
estimator;
|
|
2330
|
+
constructor(initial) {
|
|
2331
|
+
this.estimator = new TrackingKalmanEstimator(initial.rect, "xcycwh");
|
|
2332
|
+
this.setInitialNoise(initial.rect.width, initial.rect.height);
|
|
2333
|
+
}
|
|
2334
|
+
predict(frameStep, frameRate) {
|
|
2335
|
+
const current = this.estimator.getRect();
|
|
2336
|
+
this.estimator.setCovariances({
|
|
2337
|
+
processNoise: this.buildProcessNoise(Math.max(current.width, 1e-3), Math.max(current.height, 1e-3)),
|
|
2338
|
+
});
|
|
2339
|
+
this.estimator.predict(frameStep, frameRate);
|
|
2340
|
+
this.clampState();
|
|
2341
|
+
this.timeSinceUpdate += 1;
|
|
2342
|
+
}
|
|
2343
|
+
update(detection) {
|
|
2344
|
+
const current = this.estimator.getRect();
|
|
2345
|
+
this.estimator.setCovariances({
|
|
2346
|
+
measurementNoise: this.buildMeasurementNoise(Math.max(current.width, 1e-3), Math.max(current.height, 1e-3)),
|
|
2347
|
+
});
|
|
2348
|
+
this.estimator.update(detection.rect);
|
|
2349
|
+
this.clampState();
|
|
2350
|
+
this.timeSinceUpdate = 0;
|
|
2351
|
+
this.successfulUpdates += 1;
|
|
2352
|
+
}
|
|
2353
|
+
getRect() {
|
|
2354
|
+
return this.estimator.getRect();
|
|
2355
|
+
}
|
|
2356
|
+
setInitialNoise(width, height) {
|
|
2357
|
+
const sigmaPosition = 0.05;
|
|
2358
|
+
const sigmaVelocity = 0.00625;
|
|
2359
|
+
this.estimator.setCovariances({
|
|
2360
|
+
covariance: diagonal([
|
|
2361
|
+
(2 * sigmaPosition * width) ** 2,
|
|
2362
|
+
(2 * sigmaPosition * height) ** 2,
|
|
2363
|
+
(2 * sigmaPosition * width) ** 2,
|
|
2364
|
+
(2 * sigmaPosition * height) ** 2,
|
|
2365
|
+
(10 * sigmaVelocity * width) ** 2,
|
|
2366
|
+
(10 * sigmaVelocity * height) ** 2,
|
|
2367
|
+
(10 * sigmaVelocity * width) ** 2,
|
|
2368
|
+
(10 * sigmaVelocity * height) ** 2,
|
|
2369
|
+
]),
|
|
2370
|
+
measurementNoise: this.buildMeasurementNoise(width, height),
|
|
2371
|
+
processNoise: this.buildProcessNoise(width, height),
|
|
2372
|
+
});
|
|
2373
|
+
}
|
|
2374
|
+
buildProcessNoise(width, height) {
|
|
2375
|
+
const sigmaPosition = 0.05;
|
|
2376
|
+
const sigmaVelocity = 0.00625;
|
|
2377
|
+
return diagonal([
|
|
2378
|
+
(sigmaPosition * width) ** 2,
|
|
2379
|
+
(sigmaPosition * height) ** 2,
|
|
2380
|
+
(sigmaPosition * width) ** 2,
|
|
2381
|
+
(sigmaPosition * height) ** 2,
|
|
2382
|
+
(sigmaVelocity * width) ** 2,
|
|
2383
|
+
(sigmaVelocity * height) ** 2,
|
|
2384
|
+
(sigmaVelocity * width) ** 2,
|
|
2385
|
+
(sigmaVelocity * height) ** 2,
|
|
2386
|
+
]);
|
|
2387
|
+
}
|
|
2388
|
+
buildMeasurementNoise(width, height) {
|
|
2389
|
+
const sigmaMeasurement = 0.05;
|
|
2390
|
+
return diagonal([
|
|
2391
|
+
(sigmaMeasurement * width) ** 2,
|
|
2392
|
+
(sigmaMeasurement * height) ** 2,
|
|
2393
|
+
(sigmaMeasurement * width) ** 2,
|
|
2394
|
+
(sigmaMeasurement * height) ** 2,
|
|
2395
|
+
]);
|
|
2396
|
+
}
|
|
2397
|
+
clampState() {
|
|
2398
|
+
this.estimator.state[2][0] = Math.max(this.estimator.state[2][0], 1e-3);
|
|
2399
|
+
this.estimator.state[3][0] = Math.max(this.estimator.state[3][0], 1e-3);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
/** Creates the detection-only C-BIoU implementation from roboflow/trackers. */
|
|
2403
|
+
function createCBIoUTracker$1(options = {}) {
|
|
2404
|
+
const lostTrackBuffer = normalizeNonNegativeInteger$1(options.lostTrackBuffer ?? 30, "lostTrackBuffer");
|
|
2405
|
+
const frameRate = options.frameRate ?? 30;
|
|
2406
|
+
const trackActivationThreshold = options.trackActivationThreshold ?? 0.7;
|
|
2407
|
+
const minimumConsecutiveFrames = normalizePositiveInteger$1(options.minimumConsecutiveFrames ?? 2, "minimumConsecutiveFrames");
|
|
2408
|
+
const minimumIouThresholdFirstAssociation = options.minimumIouThresholdFirstAssociation ?? 0.2;
|
|
2409
|
+
const minimumIouThresholdSecondAssociation = options.minimumIouThresholdSecondAssociation ?? 0.5;
|
|
2410
|
+
const minimumIouThresholdUnconfirmedAssociation = options.minimumIouThresholdUnconfirmedAssociation ?? 0.3;
|
|
2411
|
+
const highConfidenceDetectionThreshold = options.highConfidenceDetectionThreshold ?? 0.6;
|
|
2412
|
+
const instantFirstFrameActivation = options.instantFirstFrameActivation ?? true;
|
|
2413
|
+
const bufferRatioFirst = options.bufferRatioFirst ?? 0.3;
|
|
2414
|
+
const bufferRatioSecond = options.bufferRatioSecond ?? 0.5;
|
|
2415
|
+
if (!Number.isFinite(frameRate) || frameRate <= 0) {
|
|
2416
|
+
throw new Error("frameRate must be a finite positive value.");
|
|
2417
|
+
}
|
|
2418
|
+
normalizeUnitInterval$1(trackActivationThreshold, "trackActivationThreshold");
|
|
2419
|
+
normalizeUnitInterval$1(minimumIouThresholdFirstAssociation, "minimumIouThresholdFirstAssociation");
|
|
2420
|
+
normalizeUnitInterval$1(minimumIouThresholdSecondAssociation, "minimumIouThresholdSecondAssociation");
|
|
2421
|
+
normalizeUnitInterval$1(minimumIouThresholdUnconfirmedAssociation, "minimumIouThresholdUnconfirmedAssociation");
|
|
2422
|
+
normalizeUnitInterval$1(highConfidenceDetectionThreshold, "highConfidenceDetectionThreshold");
|
|
2423
|
+
if (!Number.isFinite(bufferRatioFirst) || bufferRatioFirst < 0) {
|
|
2424
|
+
throw new Error("bufferRatioFirst must be a finite non-negative value.");
|
|
2425
|
+
}
|
|
2426
|
+
if (!Number.isFinite(bufferRatioSecond) || bufferRatioSecond < 0) {
|
|
2427
|
+
throw new Error("bufferRatioSecond must be a finite non-negative value.");
|
|
2428
|
+
}
|
|
2429
|
+
const scaledLostTrackBuffer = (frameRate / 30) * lostTrackBuffer;
|
|
2430
|
+
if (!Number.isFinite(scaledLostTrackBuffer)) {
|
|
2431
|
+
throw new Error("Scaled lostTrackBuffer overflows: frameRate / 30 * lostTrackBuffer must be finite.");
|
|
2432
|
+
}
|
|
2433
|
+
const maximumFramesWithoutUpdate = lostTrackBuffer === 0 ? 0 : Math.max(1, Math.ceil(scaledLostTrackBuffer));
|
|
2434
|
+
let tracks = [];
|
|
2435
|
+
let nextTrackerId = 0;
|
|
2436
|
+
let frameId = 0;
|
|
2437
|
+
let previousFrameIndex;
|
|
2438
|
+
return {
|
|
2439
|
+
reset() {
|
|
2440
|
+
tracks = [];
|
|
2441
|
+
nextTrackerId = 0;
|
|
2442
|
+
frameId = 0;
|
|
2443
|
+
previousFrameIndex = undefined;
|
|
2444
|
+
},
|
|
2445
|
+
update(detections, frameIndex) {
|
|
2446
|
+
const frameStep = resolveFrameStep(frameIndex, previousFrameIndex);
|
|
2447
|
+
previousFrameIndex = frameIndex ?? previousFrameIndex;
|
|
2448
|
+
frameId += 1;
|
|
2449
|
+
tracks.forEach((track) => track.predict(frameStep, frameRate));
|
|
2450
|
+
const highDetectionIndexes = [];
|
|
2451
|
+
const lowDetectionIndexes = [];
|
|
2452
|
+
detections.forEach((detection, index) => {
|
|
2453
|
+
const confidence = detection.confidence ?? 1;
|
|
2454
|
+
if (confidence >= highConfidenceDetectionThreshold) {
|
|
2455
|
+
highDetectionIndexes.push(index);
|
|
2456
|
+
}
|
|
2457
|
+
else if (confidence > MINIMUM_DETECTION_CONFIDENCE) {
|
|
2458
|
+
lowDetectionIndexes.push(index);
|
|
2459
|
+
}
|
|
2460
|
+
});
|
|
2461
|
+
const confirmed = [];
|
|
2462
|
+
const unconfirmed = [];
|
|
2463
|
+
const lost = [];
|
|
2464
|
+
for (const track of tracks) {
|
|
2465
|
+
if (track.timeSinceUpdate > 1)
|
|
2466
|
+
lost.push(track);
|
|
2467
|
+
else if (track.trackerId !== undefined ||
|
|
2468
|
+
track.successfulUpdates >= minimumConsecutiveFrames) {
|
|
2469
|
+
confirmed.push(track);
|
|
2470
|
+
}
|
|
2471
|
+
else
|
|
2472
|
+
unconfirmed.push(track);
|
|
2473
|
+
}
|
|
2474
|
+
const assignments = new Map();
|
|
2475
|
+
const pool = [...confirmed, ...lost];
|
|
2476
|
+
const highDetections = highDetectionIndexes.map((index) => detections[index]);
|
|
2477
|
+
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)));
|
|
2478
|
+
const firstAssociation = associateTrackingScores(firstScores, pool.length, highDetections.length, minimumIouThresholdFirstAssociation);
|
|
2479
|
+
for (const match of firstAssociation.matches) {
|
|
2480
|
+
updateMatchedTrack(pool[match.trackIndex], highDetections[match.detectionIndex], assignments);
|
|
2481
|
+
}
|
|
2482
|
+
const remainingTracked = firstAssociation.unmatchedTrackIndexes
|
|
2483
|
+
.map((index) => pool[index])
|
|
2484
|
+
.filter((track) => track.timeSinceUpdate === 1);
|
|
2485
|
+
const lowDetections = lowDetectionIndexes.map((index) => detections[index]);
|
|
2486
|
+
const secondScores = pairwiseIou(remainingTracked.map((track) => track.getRect()), lowDetections.map((detection) => detection.rect), bufferRatioSecond);
|
|
2487
|
+
const secondAssociation = associateTrackingScores(secondScores, remainingTracked.length, lowDetections.length, minimumIouThresholdSecondAssociation);
|
|
2488
|
+
for (const match of secondAssociation.matches) {
|
|
2489
|
+
updateMatchedTrack(remainingTracked[match.trackIndex], lowDetections[match.detectionIndex], assignments);
|
|
2490
|
+
}
|
|
2491
|
+
let unmatchedHighLocal = [...firstAssociation.unmatchedDetectionIndexes];
|
|
2492
|
+
let unmatchedUnconfirmed = unconfirmed.map((_, index) => index);
|
|
2493
|
+
if (unconfirmed.length > 0 && unmatchedHighLocal.length > 0) {
|
|
2494
|
+
const remainingHigh = unmatchedHighLocal.map((index) => highDetections[index]);
|
|
2495
|
+
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)));
|
|
2496
|
+
const unconfirmedAssociation = associateTrackingScores(unconfirmedScores, unconfirmed.length, remainingHigh.length, minimumIouThresholdUnconfirmedAssociation);
|
|
2497
|
+
unmatchedUnconfirmed = unconfirmedAssociation.unmatchedTrackIndexes;
|
|
2498
|
+
for (const match of unconfirmedAssociation.matches) {
|
|
2499
|
+
updateMatchedTrack(unconfirmed[match.trackIndex], remainingHigh[match.detectionIndex], assignments);
|
|
2500
|
+
}
|
|
2501
|
+
unmatchedHighLocal =
|
|
2502
|
+
unconfirmedAssociation.unmatchedDetectionIndexes.map((index) => unmatchedHighLocal[index]);
|
|
2503
|
+
}
|
|
2504
|
+
const unmatchedUnconfirmedTracks = new Set(unmatchedUnconfirmed.map((index) => unconfirmed[index]));
|
|
2505
|
+
tracks = tracks.filter((track) => !unmatchedUnconfirmedTracks.has(track));
|
|
2506
|
+
for (const localIndex of unmatchedHighLocal) {
|
|
2507
|
+
const detection = highDetections[localIndex];
|
|
2508
|
+
if ((detection.confidence ?? 1) < trackActivationThreshold)
|
|
2509
|
+
continue;
|
|
2510
|
+
const track = new CBIoUTrack(detection);
|
|
2511
|
+
if (frameId === 1 && instantFirstFrameActivation) {
|
|
2512
|
+
track.trackerId = nextTrackerId++;
|
|
2513
|
+
assignments.set(detection.detectionIndex, track.trackerId);
|
|
2514
|
+
}
|
|
2515
|
+
tracks.push(track);
|
|
2516
|
+
}
|
|
2517
|
+
tracks = tracks.filter((track) => track.timeSinceUpdate <= maximumFramesWithoutUpdate &&
|
|
2518
|
+
(track.timeSinceUpdate === 0 ||
|
|
2519
|
+
track.trackerId !== undefined ||
|
|
2520
|
+
track.successfulUpdates >= minimumConsecutiveFrames));
|
|
2521
|
+
return createUpdate(detections, assignments, tracks);
|
|
2522
|
+
},
|
|
2523
|
+
};
|
|
2524
|
+
function updateMatchedTrack(track, detection, assignments) {
|
|
2525
|
+
track.update(detection);
|
|
2526
|
+
if (track.trackerId === undefined &&
|
|
2527
|
+
track.successfulUpdates >= minimumConsecutiveFrames) {
|
|
2528
|
+
track.trackerId = nextTrackerId++;
|
|
2529
|
+
}
|
|
2530
|
+
if (track.trackerId !== undefined) {
|
|
2531
|
+
assignments.set(detection.detectionIndex, track.trackerId);
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
function createUpdate(detections, assignments, tracks) {
|
|
2536
|
+
const resolvedAssignments = detections.flatMap((detection) => {
|
|
2537
|
+
const trackerId = assignments.get(detection.detectionIndex);
|
|
2538
|
+
return trackerId === undefined
|
|
2539
|
+
? []
|
|
2540
|
+
: [{ detectionIndex: detection.detectionIndex, trackerId }];
|
|
2541
|
+
});
|
|
2542
|
+
return {
|
|
2543
|
+
activeTrackCount: tracks.length,
|
|
2544
|
+
assignments: resolvedAssignments,
|
|
2545
|
+
confirmedTrackCount: tracks.filter((track) => track.trackerId !== undefined)
|
|
2546
|
+
.length,
|
|
2547
|
+
};
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
class OCSortTrack {
|
|
2551
|
+
deltaT;
|
|
2552
|
+
age = 0;
|
|
2553
|
+
lastObservation;
|
|
2554
|
+
trackerId;
|
|
2555
|
+
successfulConsecutiveUpdates = 0;
|
|
2556
|
+
timeSinceUpdate = 0;
|
|
2557
|
+
velocity;
|
|
2558
|
+
frozenState;
|
|
2559
|
+
observed = true;
|
|
2560
|
+
observations = new Map();
|
|
2561
|
+
estimator;
|
|
2562
|
+
constructor(initial, deltaT) {
|
|
2563
|
+
this.deltaT = deltaT;
|
|
2564
|
+
this.lastObservation = initial;
|
|
2565
|
+
this.estimator = new TrackingKalmanEstimator(initial.rect, "xcycsr");
|
|
2566
|
+
this.configureNoise();
|
|
2567
|
+
}
|
|
2568
|
+
predict(frameStep, frameRate) {
|
|
2569
|
+
if (this.observed && this.timeSinceUpdate > 0) {
|
|
2570
|
+
this.frozenState = this.estimator.snapshot();
|
|
2571
|
+
this.observed = false;
|
|
2572
|
+
}
|
|
2573
|
+
this.estimator.predict(frameStep, frameRate);
|
|
2574
|
+
if (this.timeSinceUpdate > 0) {
|
|
2575
|
+
this.successfulConsecutiveUpdates = 0;
|
|
2576
|
+
}
|
|
2577
|
+
this.timeSinceUpdate += 1;
|
|
2578
|
+
this.age += 1;
|
|
2579
|
+
}
|
|
2580
|
+
update(detection, frameStep, frameRate) {
|
|
2581
|
+
const previous = this.getPreviousObservation();
|
|
2582
|
+
if (previous) {
|
|
2583
|
+
this.velocity = computeVelocity(previous, detection);
|
|
2584
|
+
}
|
|
2585
|
+
if (!this.observed && this.frozenState) {
|
|
2586
|
+
this.unfreeze(detection, frameStep, frameRate);
|
|
2587
|
+
}
|
|
2588
|
+
this.estimator.update(detection.rect);
|
|
2589
|
+
this.observed = true;
|
|
2590
|
+
this.timeSinceUpdate = 0;
|
|
2591
|
+
this.successfulConsecutiveUpdates += 1;
|
|
2592
|
+
this.lastObservation = detection;
|
|
2593
|
+
this.observations.set(this.age, detection);
|
|
2594
|
+
const cutoff = this.age - this.deltaT;
|
|
2595
|
+
for (const age of this.observations.keys()) {
|
|
2596
|
+
if (age < cutoff)
|
|
2597
|
+
this.observations.delete(age);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
getRect() {
|
|
2601
|
+
return this.estimator.getRect();
|
|
2602
|
+
}
|
|
2603
|
+
getPreviousObservation() {
|
|
2604
|
+
if (this.observations.size === 0)
|
|
2605
|
+
return undefined;
|
|
2606
|
+
for (let index = 0; index < this.deltaT; index += 1) {
|
|
2607
|
+
const delta = this.deltaT - index;
|
|
2608
|
+
const observation = this.observations.get(this.age - delta);
|
|
2609
|
+
if (observation)
|
|
2610
|
+
return observation;
|
|
2611
|
+
}
|
|
2612
|
+
const latestAge = Math.max(...this.observations.keys());
|
|
2613
|
+
return this.observations.get(latestAge);
|
|
2614
|
+
}
|
|
2615
|
+
configureNoise() {
|
|
2616
|
+
const measurementNoise = identity(4);
|
|
2617
|
+
for (let index = 2; index < 4; index += 1) {
|
|
2618
|
+
measurementNoise[index][index] *= 10;
|
|
2619
|
+
}
|
|
2620
|
+
const covariance = scaleMatrix(identity(7), 10);
|
|
2621
|
+
for (let index = 4; index < 7; index += 1) {
|
|
2622
|
+
covariance[index][index] *= 1000;
|
|
2623
|
+
}
|
|
2624
|
+
const processNoise = identity(7);
|
|
2625
|
+
processNoise[6][6] *= 0.01;
|
|
2626
|
+
for (let index = 4; index < 7; index += 1) {
|
|
2627
|
+
processNoise[index][index] *= 0.01;
|
|
2628
|
+
}
|
|
2629
|
+
this.estimator.setCovariances({
|
|
2630
|
+
covariance,
|
|
2631
|
+
measurementNoise,
|
|
2632
|
+
processNoise,
|
|
2633
|
+
});
|
|
2634
|
+
}
|
|
2635
|
+
unfreeze(detection, frameStep, frameRate) {
|
|
2636
|
+
if (!this.frozenState || this.timeSinceUpdate === 0)
|
|
2637
|
+
return;
|
|
2638
|
+
this.estimator.restore(this.frozenState);
|
|
2639
|
+
const timeGap = this.timeSinceUpdate;
|
|
2640
|
+
const start = rectToMeasurement(this.lastObservation.rect, "xcycsr");
|
|
2641
|
+
const end = rectToMeasurement(detection.rect, "xcycsr");
|
|
2642
|
+
const startWidth = Math.sqrt(start[2] * start[3]);
|
|
2643
|
+
const startHeight = start[3] === 0 ? 0 : Math.sqrt(start[2] / start[3]);
|
|
2644
|
+
const endWidth = Math.sqrt(end[2] * end[3]);
|
|
2645
|
+
const endHeight = end[3] === 0 ? 0 : Math.sqrt(end[2] / end[3]);
|
|
2646
|
+
for (let index = 0; index < timeGap; index += 1) {
|
|
2647
|
+
const progress = (index + 1) / timeGap;
|
|
2648
|
+
const x = start[0] + progress * (end[0] - start[0]);
|
|
2649
|
+
const y = start[1] + progress * (end[1] - start[1]);
|
|
2650
|
+
const width = startWidth + progress * (endWidth - startWidth);
|
|
2651
|
+
const height = startHeight + progress * (endHeight - startHeight);
|
|
2652
|
+
this.estimator.updateMeasurement([x, y, width * height, width / height]);
|
|
2653
|
+
if (index < timeGap - 1) {
|
|
2654
|
+
this.estimator.predict(frameStep, frameRate);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
this.frozenState = undefined;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
/** Creates the observation-centric SORT implementation from roboflow/trackers. */
|
|
2661
|
+
function createOCSortTracker$1(options = {}) {
|
|
2662
|
+
const lostTrackBuffer = normalizeNonNegativeInteger$1(options.lostTrackBuffer ?? 30, "lostTrackBuffer");
|
|
2663
|
+
const frameRate = options.frameRate ?? 30;
|
|
2664
|
+
const minimumConsecutiveFrames = normalizePositiveInteger$1(options.minimumConsecutiveFrames ?? 3, "minimumConsecutiveFrames");
|
|
2665
|
+
const minimumIouThreshold = options.minimumIouThreshold ?? 0.3;
|
|
2666
|
+
const directionConsistencyWeight = options.directionConsistencyWeight ?? 0.2;
|
|
2667
|
+
const highConfidenceDetectionThreshold = options.highConfidenceDetectionThreshold ?? 0.6;
|
|
2668
|
+
const deltaT = normalizePositiveInteger$1(options.deltaT ?? 3, "deltaT");
|
|
2669
|
+
if (!Number.isFinite(frameRate) || frameRate <= 0) {
|
|
2670
|
+
throw new Error("frameRate must be a finite positive value.");
|
|
2671
|
+
}
|
|
2672
|
+
normalizeUnitInterval$1(minimumIouThreshold, "minimumIouThreshold");
|
|
2673
|
+
normalizeUnitInterval$1(directionConsistencyWeight, "directionConsistencyWeight");
|
|
2674
|
+
normalizeUnitInterval$1(highConfidenceDetectionThreshold, "highConfidenceDetectionThreshold");
|
|
2675
|
+
const scaledLostTrackBuffer = (frameRate / 30) * lostTrackBuffer;
|
|
2676
|
+
if (!Number.isFinite(scaledLostTrackBuffer)) {
|
|
2677
|
+
throw new Error("Scaled lostTrackBuffer overflows: frameRate / 30 * lostTrackBuffer must be finite.");
|
|
2678
|
+
}
|
|
2679
|
+
const maximumFramesWithoutUpdate = lostTrackBuffer === 0 ? 0 : Math.max(1, Math.ceil(scaledLostTrackBuffer));
|
|
2680
|
+
let tracks = [];
|
|
2681
|
+
let frameCount = 0;
|
|
2682
|
+
let nextTrackerId = 0;
|
|
2683
|
+
let previousFrameIndex;
|
|
2684
|
+
return {
|
|
2685
|
+
reset() {
|
|
2686
|
+
tracks = [];
|
|
2687
|
+
frameCount = 0;
|
|
2688
|
+
nextTrackerId = 0;
|
|
2689
|
+
previousFrameIndex = undefined;
|
|
2690
|
+
},
|
|
2691
|
+
update(detections, frameIndex) {
|
|
2692
|
+
const frameStep = resolveFrameStep(frameIndex, previousFrameIndex);
|
|
2693
|
+
previousFrameIndex = frameIndex ?? previousFrameIndex;
|
|
2694
|
+
// Match roboflow/trackers: an empty stream before any track exists is
|
|
2695
|
+
// not part of OC-SORT's early-sequence activation window.
|
|
2696
|
+
if (tracks.length === 0 && detections.length === 0) {
|
|
2697
|
+
return {
|
|
2698
|
+
activeTrackCount: 0,
|
|
2699
|
+
assignments: [],
|
|
2700
|
+
confirmedTrackCount: 0,
|
|
2701
|
+
};
|
|
2702
|
+
}
|
|
2703
|
+
tracks.forEach((track) => track.predict(frameStep, frameRate));
|
|
2704
|
+
const highDetectionIndexes = detections.flatMap((detection, index) => detection.confidence === undefined ||
|
|
2705
|
+
detection.confidence >= highConfidenceDetectionThreshold
|
|
2706
|
+
? [index]
|
|
2707
|
+
: []);
|
|
2708
|
+
const highDetections = highDetectionIndexes.map((index) => detections[index]);
|
|
2709
|
+
const iouScores = pairwiseIou(tracks.map((track) => track.getRect()), highDetections.map((detection) => detection.rect));
|
|
2710
|
+
const combinedScores = iouScores.map((row, trackIndex) => row.map((iou, detectionIndex) => {
|
|
2711
|
+
if (directionConsistencyWeight === 0)
|
|
2712
|
+
return iou;
|
|
2713
|
+
return (iou +
|
|
2714
|
+
directionConsistencyWeight *
|
|
2715
|
+
directionConsistency(tracks[trackIndex], highDetections[detectionIndex]) *
|
|
2716
|
+
(highDetections[detectionIndex].confidence ?? 1));
|
|
2717
|
+
}));
|
|
2718
|
+
const primary = associateTrackingScores(combinedScores, tracks.length, highDetections.length, minimumIouThreshold, iouScores);
|
|
2719
|
+
const assignments = new Map();
|
|
2720
|
+
for (const match of primary.matches) {
|
|
2721
|
+
updateMatchedTrack(tracks[match.trackIndex], highDetections[match.detectionIndex], assignments, frameStep);
|
|
2722
|
+
}
|
|
2723
|
+
let remainingHigh = [...primary.unmatchedDetectionIndexes];
|
|
2724
|
+
if (primary.unmatchedTrackIndexes.length > 0 &&
|
|
2725
|
+
remainingHigh.length > 0) {
|
|
2726
|
+
const unmatchedTracks = primary.unmatchedTrackIndexes.map((index) => tracks[index]);
|
|
2727
|
+
const unmatchedDetections = remainingHigh.map((index) => highDetections[index]);
|
|
2728
|
+
const recoveryScores = pairwiseIou(unmatchedTracks.map((track) => track.lastObservation.rect), unmatchedDetections.map((detection) => detection.rect));
|
|
2729
|
+
const recovery = associateTrackingScores(recoveryScores, unmatchedTracks.length, unmatchedDetections.length, minimumIouThreshold);
|
|
2730
|
+
for (const match of recovery.matches) {
|
|
2731
|
+
updateMatchedTrack(unmatchedTracks[match.trackIndex], unmatchedDetections[match.detectionIndex], assignments, frameStep);
|
|
2732
|
+
}
|
|
2733
|
+
remainingHigh = recovery.unmatchedDetectionIndexes.map((index) => remainingHigh[index]);
|
|
2734
|
+
}
|
|
2735
|
+
for (const localIndex of remainingHigh) {
|
|
2736
|
+
tracks.push(new OCSortTrack(highDetections[localIndex], deltaT));
|
|
2737
|
+
}
|
|
2738
|
+
tracks = tracks.filter((track) => track.timeSinceUpdate <= maximumFramesWithoutUpdate);
|
|
2739
|
+
frameCount += 1;
|
|
2740
|
+
const resolvedAssignments = detections.flatMap((detection) => {
|
|
2741
|
+
const trackerId = assignments.get(detection.detectionIndex);
|
|
2742
|
+
return trackerId === undefined
|
|
2743
|
+
? []
|
|
2744
|
+
: [{ detectionIndex: detection.detectionIndex, trackerId }];
|
|
2745
|
+
});
|
|
2746
|
+
return {
|
|
2747
|
+
activeTrackCount: tracks.length,
|
|
2748
|
+
assignments: resolvedAssignments,
|
|
2749
|
+
confirmedTrackCount: tracks.filter((track) => track.trackerId !== undefined).length,
|
|
2750
|
+
};
|
|
2751
|
+
},
|
|
2752
|
+
};
|
|
2753
|
+
function updateMatchedTrack(track, detection, assignments, frameStep) {
|
|
2754
|
+
track.update(detection, frameStep, frameRate);
|
|
2755
|
+
const earlySequence = frameCount <= minimumConsecutiveFrames;
|
|
2756
|
+
const shouldEmit = (earlySequence && track.timeSinceUpdate === 0) ||
|
|
2757
|
+
track.successfulConsecutiveUpdates >= minimumConsecutiveFrames;
|
|
2758
|
+
if (shouldEmit) {
|
|
2759
|
+
if (track.trackerId === undefined) {
|
|
2760
|
+
track.trackerId = nextTrackerId++;
|
|
2761
|
+
}
|
|
2762
|
+
assignments.set(detection.detectionIndex, track.trackerId);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
function computeVelocity(previous, current) {
|
|
2767
|
+
const deltaX = current.rect.x - previous.rect.x;
|
|
2768
|
+
const deltaY = current.rect.y - previous.rect.y;
|
|
2769
|
+
const norm = Math.sqrt(deltaX * deltaX + deltaY * deltaY) + 1e-6;
|
|
2770
|
+
return { x: deltaX / norm, y: deltaY / norm };
|
|
2771
|
+
}
|
|
2772
|
+
function directionConsistency(track, detection) {
|
|
2773
|
+
if (!track.velocity)
|
|
2774
|
+
return 0;
|
|
2775
|
+
const reference = track.getPreviousObservation() ?? track.lastObservation;
|
|
2776
|
+
const deltaX = detection.rect.x - reference.rect.x;
|
|
2777
|
+
const deltaY = detection.rect.y - reference.rect.y;
|
|
2778
|
+
const norm = Math.sqrt(deltaX * deltaX + deltaY * deltaY) + 1e-6;
|
|
2779
|
+
const cosine = Math.max(-1, Math.min(1, track.velocity.x * (deltaX / norm) + track.velocity.y * (deltaY / norm)));
|
|
2780
|
+
const angle = Math.acos(cosine);
|
|
2781
|
+
return (Math.PI / 2 - Math.abs(angle)) / Math.PI;
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
/** Public core facade for the internal tracker engine workspace. */
|
|
2785
|
+
function createSortTracker(options = {}) {
|
|
2786
|
+
return createSortTracker$1(options);
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
/** Public core facade for the internal tracker engine workspace. */
|
|
2790
|
+
function createByteTrackTracker(options = {}) {
|
|
2791
|
+
return createByteTrackTracker$1(options);
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
/** Public core facade for the internal tracker engine workspace. */
|
|
2795
|
+
function createCBIoUTracker(options = {}) {
|
|
2796
|
+
return createCBIoUTracker$1(options);
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
/** Public core facade for the internal tracker engine workspace. */
|
|
2800
|
+
function createOCSortTracker(options = {}) {
|
|
2801
|
+
return createOCSortTracker$1(options);
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
/** Geometry projected into a tracker. The original detection payload is retained. */
|
|
2805
|
+
var TrackingGeometry;
|
|
2806
|
+
(function (TrackingGeometry) {
|
|
2807
|
+
TrackingGeometry["Box"] = "box";
|
|
2808
|
+
TrackingGeometry["Mask"] = "mask";
|
|
2809
|
+
TrackingGeometry["Keypoints"] = "keypoints";
|
|
2810
|
+
})(TrackingGeometry || (TrackingGeometry = {}));
|
|
2811
|
+
|
|
2812
|
+
var DetectionMaskPayloadFormat;
|
|
2813
|
+
(function (DetectionMaskPayloadFormat) {
|
|
2814
|
+
DetectionMaskPayloadFormat["RawCocoRle"] = "rawCocoRle";
|
|
2815
|
+
DetectionMaskPayloadFormat["DeflatedBase64"] = "deflatedBase64";
|
|
2816
|
+
})(DetectionMaskPayloadFormat || (DetectionMaskPayloadFormat = {}));
|
|
2817
|
+
function encodeBinaryMask(data, width, height) {
|
|
2818
|
+
assertMaskDimensions(data, width, height);
|
|
2819
|
+
const runs = [];
|
|
2820
|
+
let currentValue = 0;
|
|
2821
|
+
let runLength = 0;
|
|
2822
|
+
for (let x = 0; x < width; x += 1) {
|
|
2823
|
+
for (let y = 0; y < height; y += 1) {
|
|
2824
|
+
const value = data[y * width + x] ? 1 : 0;
|
|
2825
|
+
if (value === currentValue) {
|
|
2826
|
+
runLength += 1;
|
|
2827
|
+
}
|
|
2828
|
+
else {
|
|
2829
|
+
runs.push(runLength);
|
|
2830
|
+
currentValue = value;
|
|
2831
|
+
runLength = 1;
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
runs.push(runLength);
|
|
2836
|
+
return {
|
|
2837
|
+
counts: encodeCompressedRleCounts(runs),
|
|
2838
|
+
encoding: DetectionMaskEncoding.CompressedRle,
|
|
2839
|
+
height,
|
|
2840
|
+
width,
|
|
2841
|
+
};
|
|
2842
|
+
}
|
|
2843
|
+
/** Encodes a binary mask and derives its bounds in the same raster traversal. */
|
|
2844
|
+
function encodeBinaryMaskWithBounds(data, width, height) {
|
|
2845
|
+
assertMaskDimensions(data, width, height);
|
|
2846
|
+
const runs = [];
|
|
2847
|
+
let currentValue = 0;
|
|
2848
|
+
let runLength = 0;
|
|
2849
|
+
let minX = width;
|
|
2850
|
+
let minY = height;
|
|
2851
|
+
let maxX = -1;
|
|
2852
|
+
let maxY = -1;
|
|
2853
|
+
for (let x = 0; x < width; x += 1) {
|
|
2854
|
+
for (let y = 0; y < height; y += 1) {
|
|
2855
|
+
const value = data[y * width + x] ? 1 : 0;
|
|
2856
|
+
if (value) {
|
|
2857
|
+
minX = Math.min(minX, x);
|
|
2858
|
+
minY = Math.min(minY, y);
|
|
2859
|
+
maxX = Math.max(maxX, x);
|
|
2860
|
+
maxY = Math.max(maxY, y);
|
|
2861
|
+
}
|
|
2862
|
+
if (value === currentValue) {
|
|
2863
|
+
runLength += 1;
|
|
2864
|
+
}
|
|
2865
|
+
else {
|
|
2866
|
+
runs.push(runLength);
|
|
2867
|
+
currentValue = value;
|
|
2868
|
+
runLength = 1;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
}
|
|
2872
|
+
runs.push(runLength);
|
|
2873
|
+
return {
|
|
2874
|
+
bounds: maxX < 0
|
|
2875
|
+
? null
|
|
2876
|
+
: {
|
|
2877
|
+
height: maxY - minY + 1,
|
|
2878
|
+
width: maxX - minX + 1,
|
|
2879
|
+
x: minX + (maxX - minX + 1) / 2,
|
|
2880
|
+
y: minY + (maxY - minY + 1) / 2,
|
|
2881
|
+
},
|
|
2882
|
+
mask: {
|
|
2883
|
+
counts: encodeCompressedRleCounts(runs),
|
|
2884
|
+
encoding: DetectionMaskEncoding.CompressedRle,
|
|
2885
|
+
height,
|
|
2886
|
+
width,
|
|
2887
|
+
},
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
function encodeDetectionMaskPayload(mask, codec) {
|
|
2891
|
+
return codec ? codec.deflate(mask.counts) : mask.counts;
|
|
2892
|
+
}
|
|
2893
|
+
function decodeDetectionMaskPayload(payload, width, height, options = {}) {
|
|
2894
|
+
const format = options.format ??
|
|
2895
|
+
(isDeflatedBase64DetectionMaskPayload(payload)
|
|
2896
|
+
? DetectionMaskPayloadFormat.DeflatedBase64
|
|
2897
|
+
: DetectionMaskPayloadFormat.RawCocoRle);
|
|
2898
|
+
if (format === DetectionMaskPayloadFormat.DeflatedBase64 && !options.codec) {
|
|
2899
|
+
throw new Error("A detection mask compression codec is required for deflated payloads.");
|
|
2900
|
+
}
|
|
2901
|
+
return {
|
|
2902
|
+
counts: format === DetectionMaskPayloadFormat.DeflatedBase64
|
|
2903
|
+
? options.codec.inflate(payload)
|
|
2904
|
+
: payload,
|
|
2905
|
+
encoding: DetectionMaskEncoding.CompressedRle,
|
|
2906
|
+
height,
|
|
2907
|
+
width,
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
/** Matches the annotation editor's legacy transport-format heuristic. */
|
|
2911
|
+
function isDeflatedBase64DetectionMaskPayload(value) {
|
|
2912
|
+
return value.length > 100 && /^[A-Za-z0-9+/]+={0,2}$/.test(value);
|
|
2913
|
+
}
|
|
2914
|
+
function computeMaskBounds(data, width, height) {
|
|
2915
|
+
assertMaskDimensions(data, width, height);
|
|
2916
|
+
let minX = width;
|
|
2917
|
+
let minY = height;
|
|
2918
|
+
let maxX = -1;
|
|
2919
|
+
let maxY = -1;
|
|
2920
|
+
for (let y = 0; y < height; y += 1) {
|
|
2921
|
+
for (let x = 0; x < width; x += 1) {
|
|
2922
|
+
if (!data[y * width + x]) {
|
|
2923
|
+
continue;
|
|
2924
|
+
}
|
|
2925
|
+
minX = Math.min(minX, x);
|
|
2926
|
+
minY = Math.min(minY, y);
|
|
2927
|
+
maxX = Math.max(maxX, x);
|
|
2928
|
+
maxY = Math.max(maxY, y);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
if (maxX < 0) {
|
|
2932
|
+
return null;
|
|
2933
|
+
}
|
|
2934
|
+
const boundsWidth = maxX - minX + 1;
|
|
2935
|
+
const boundsHeight = maxY - minY + 1;
|
|
2936
|
+
return {
|
|
2937
|
+
height: boundsHeight,
|
|
2938
|
+
width: boundsWidth,
|
|
2939
|
+
x: minX + boundsWidth / 2,
|
|
2940
|
+
y: minY + boundsHeight / 2,
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
function computeDetectionMaskRect(mask) {
|
|
2944
|
+
const decoded = decodeCompressedRleMask(mask);
|
|
2945
|
+
const bounds = computeMaskBounds(decoded.data, decoded.width, decoded.height);
|
|
2946
|
+
return bounds ?? undefined;
|
|
2947
|
+
}
|
|
2948
|
+
function detectMaskBorders(data, width, height) {
|
|
2949
|
+
assertMaskDimensions(data, width, height);
|
|
2950
|
+
const borders = new Uint8Array(data.length);
|
|
2951
|
+
for (let y = 0; y < height; y += 1) {
|
|
2952
|
+
for (let x = 0; x < width; x += 1) {
|
|
2953
|
+
const offset = y * width + x;
|
|
2954
|
+
if (!data[offset]) {
|
|
2955
|
+
continue;
|
|
2956
|
+
}
|
|
2957
|
+
if (x === 0 ||
|
|
2958
|
+
y === 0 ||
|
|
2959
|
+
x === width - 1 ||
|
|
2960
|
+
y === height - 1 ||
|
|
2961
|
+
!data[offset - 1] ||
|
|
2962
|
+
!data[offset + 1] ||
|
|
2963
|
+
!data[offset - width] ||
|
|
2964
|
+
!data[offset + width]) {
|
|
2965
|
+
borders[offset] = 1;
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
return borders;
|
|
2970
|
+
}
|
|
2971
|
+
function extractMaskContour(data, width, height) {
|
|
2972
|
+
assertMaskDimensions(data, width, height);
|
|
2973
|
+
const stride = Math.max(1, Math.floor(height / 100));
|
|
2974
|
+
const leftEdge = [];
|
|
2975
|
+
const rightEdge = [];
|
|
2976
|
+
for (let y = 0; y < height; y += stride) {
|
|
2977
|
+
let left = -1;
|
|
2978
|
+
let right = -1;
|
|
2979
|
+
for (let x = 0; x < width; x += 1) {
|
|
2980
|
+
if (data[y * width + x]) {
|
|
2981
|
+
left = left === -1 ? x : left;
|
|
2982
|
+
right = x;
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
if (left !== -1) {
|
|
2986
|
+
leftEdge.push({ x: left, y });
|
|
2987
|
+
rightEdge.push({ x: right, y });
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
return leftEdge.length < 2
|
|
2991
|
+
? undefined
|
|
2992
|
+
: [...leftEdge, ...rightEdge.reverse()];
|
|
2993
|
+
}
|
|
2994
|
+
function extractMaskRectRuns(data, width, height) {
|
|
2995
|
+
assertMaskDimensions(data, width, height);
|
|
2996
|
+
const rects = [];
|
|
2997
|
+
const openRects = new Map();
|
|
2998
|
+
for (let y = 0; y < height; y += 1) {
|
|
2999
|
+
const activeSpans = new Set();
|
|
3000
|
+
let x = 0;
|
|
3001
|
+
while (x < width) {
|
|
3002
|
+
while (x < width && !data[y * width + x]) {
|
|
3003
|
+
x += 1;
|
|
3004
|
+
}
|
|
3005
|
+
if (x >= width) {
|
|
3006
|
+
break;
|
|
3007
|
+
}
|
|
3008
|
+
const startX = x;
|
|
3009
|
+
while (x < width && data[y * width + x]) {
|
|
3010
|
+
x += 1;
|
|
3011
|
+
}
|
|
3012
|
+
const runWidth = x - startX;
|
|
3013
|
+
const key = `${startX}:${runWidth}`;
|
|
3014
|
+
const openRect = openRects.get(key);
|
|
3015
|
+
activeSpans.add(key);
|
|
3016
|
+
if (openRect && openRect.y + openRect.height === y) {
|
|
3017
|
+
openRects.set(key, { ...openRect, height: openRect.height + 1 });
|
|
3018
|
+
}
|
|
3019
|
+
else {
|
|
3020
|
+
if (openRect) {
|
|
3021
|
+
rects.push(openRect);
|
|
3022
|
+
}
|
|
3023
|
+
openRects.set(key, { height: 1, width: runWidth, x: startX, y });
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
for (const [key, openRect] of openRects) {
|
|
3027
|
+
if (!activeSpans.has(key)) {
|
|
3028
|
+
rects.push(openRect);
|
|
3029
|
+
openRects.delete(key);
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
rects.push(...openRects.values());
|
|
3034
|
+
return rects.length > 0 ? rects : undefined;
|
|
3035
|
+
}
|
|
3036
|
+
function assertMaskDimensions(data, width, height) {
|
|
3037
|
+
if (!Number.isInteger(width) || width <= 0) {
|
|
3038
|
+
throw new Error("Mask width must be a positive integer.");
|
|
3039
|
+
}
|
|
3040
|
+
if (!Number.isInteger(height) || height <= 0) {
|
|
3041
|
+
throw new Error("Mask height must be a positive integer.");
|
|
3042
|
+
}
|
|
3043
|
+
if (data.length !== width * height) {
|
|
3044
|
+
throw new Error("Mask data length must equal width * height.");
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
function centerRectToTopLeftRect(rect) {
|
|
3049
|
+
return {
|
|
3050
|
+
height: rect.height,
|
|
3051
|
+
width: rect.width,
|
|
3052
|
+
x: rect.x - rect.width / 2,
|
|
3053
|
+
y: rect.y - rect.height / 2,
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
function topLeftRectToCenterRect(rect) {
|
|
3057
|
+
return {
|
|
3058
|
+
height: rect.height,
|
|
3059
|
+
width: rect.width,
|
|
3060
|
+
x: rect.x + rect.width / 2,
|
|
3061
|
+
y: rect.y + rect.height / 2,
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
function getPointsRect(points) {
|
|
3065
|
+
if (points.length === 0) {
|
|
3066
|
+
return undefined;
|
|
3067
|
+
}
|
|
3068
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
3069
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
3070
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
3071
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
3072
|
+
for (const point of points) {
|
|
3073
|
+
minX = Math.min(minX, point.x);
|
|
3074
|
+
minY = Math.min(minY, point.y);
|
|
3075
|
+
maxX = Math.max(maxX, point.x);
|
|
3076
|
+
maxY = Math.max(maxY, point.y);
|
|
3077
|
+
}
|
|
3078
|
+
return {
|
|
3079
|
+
height: maxY - minY,
|
|
3080
|
+
width: maxX - minX,
|
|
3081
|
+
x: (minX + maxX) / 2,
|
|
3082
|
+
y: (minY + maxY) / 2,
|
|
3083
|
+
};
|
|
3084
|
+
}
|
|
3085
|
+
function getDetectionRect(detection) {
|
|
3086
|
+
return (detection.rect ??
|
|
3087
|
+
getPointsRect(detection.polygon?.points ?? []) ??
|
|
3088
|
+
getPointsRect(detection.polyline?.points ?? []) ??
|
|
3089
|
+
getPointsRect(detection.keypoints?.points ?? []));
|
|
3090
|
+
}
|
|
3091
|
+
function containsPoint(rect, point, padding = 0) {
|
|
3092
|
+
const halfWidth = rect.width / 2;
|
|
3093
|
+
const halfHeight = rect.height / 2;
|
|
3094
|
+
return (point.x >= rect.x - halfWidth - padding &&
|
|
3095
|
+
point.x <= rect.x + halfWidth + padding &&
|
|
3096
|
+
point.y >= rect.y - halfHeight - padding &&
|
|
3097
|
+
point.y <= rect.y + halfHeight + padding);
|
|
3098
|
+
}
|
|
3099
|
+
function pointInPolygon(point, polygon) {
|
|
3100
|
+
if (polygon.length < 3) {
|
|
3101
|
+
return false;
|
|
3102
|
+
}
|
|
3103
|
+
let inside = false;
|
|
3104
|
+
for (let currentIndex = 0, previousIndex = polygon.length - 1; currentIndex < polygon.length; previousIndex = currentIndex, currentIndex += 1) {
|
|
3105
|
+
const current = polygon[currentIndex];
|
|
3106
|
+
const previous = polygon[previousIndex];
|
|
3107
|
+
const crossesRay = current.y > point.y !== previous.y > point.y &&
|
|
3108
|
+
point.x <
|
|
3109
|
+
((previous.x - current.x) * (point.y - current.y)) /
|
|
3110
|
+
(previous.y - current.y) +
|
|
3111
|
+
current.x;
|
|
3112
|
+
if (crossesRay) {
|
|
3113
|
+
inside = !inside;
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
return inside;
|
|
3117
|
+
}
|
|
3118
|
+
function distanceToSegment(point, start, end) {
|
|
3119
|
+
const deltaX = end.x - start.x;
|
|
3120
|
+
const deltaY = end.y - start.y;
|
|
3121
|
+
const lengthSquared = deltaX * deltaX + deltaY * deltaY;
|
|
3122
|
+
if (lengthSquared === 0) {
|
|
3123
|
+
return Math.hypot(point.x - start.x, point.y - start.y);
|
|
3124
|
+
}
|
|
3125
|
+
const projection = Math.max(0, Math.min(1, ((point.x - start.x) * deltaX + (point.y - start.y) * deltaY) /
|
|
3126
|
+
lengthSquared));
|
|
3127
|
+
const closestX = start.x + projection * deltaX;
|
|
3128
|
+
const closestY = start.y + projection * deltaY;
|
|
3129
|
+
return Math.hypot(point.x - closestX, point.y - closestY);
|
|
3130
|
+
}
|
|
3131
|
+
function polygonArea(points) {
|
|
3132
|
+
if (points.length < 3) {
|
|
3133
|
+
return 0;
|
|
3134
|
+
}
|
|
3135
|
+
let twiceArea = 0;
|
|
3136
|
+
for (let index = 0; index < points.length; index += 1) {
|
|
3137
|
+
const current = points[index];
|
|
3138
|
+
const next = points[(index + 1) % points.length];
|
|
3139
|
+
twiceArea += current.x * next.y - next.x * current.y;
|
|
3140
|
+
}
|
|
3141
|
+
return Math.abs(twiceArea) / 2;
|
|
3142
|
+
}
|
|
3143
|
+
function rectArea(rect) {
|
|
3144
|
+
return rect ? Math.max(0, rect.width) * Math.max(0, rect.height) : 0;
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
const DEFAULT_LOST_TRACK_BUFFER = 30;
|
|
3148
|
+
const DEFAULT_FRAME_RATE = 30;
|
|
3149
|
+
const DEFAULT_TRACK_ACTIVATION_THRESHOLD = 0.25;
|
|
3150
|
+
const DEFAULT_MINIMUM_CONSECUTIVE_FRAMES = 3;
|
|
3151
|
+
const DEFAULT_MINIMUM_IOU_THRESHOLD = 0.3;
|
|
3152
|
+
const DEFAULT_BYTE_TRACK_ACTIVATION_THRESHOLD = 0.7;
|
|
3153
|
+
const DEFAULT_BYTE_MINIMUM_CONSECUTIVE_FRAMES = 2;
|
|
3154
|
+
const DEFAULT_BYTE_MINIMUM_IOU_THRESHOLD = 0.1;
|
|
3155
|
+
const DEFAULT_HIGH_CONFIDENCE_DETECTION_THRESHOLD = 0.6;
|
|
3156
|
+
const DEFAULT_CBIOU_FIRST_IOU_THRESHOLD = 0.2;
|
|
3157
|
+
const DEFAULT_CBIOU_SECOND_IOU_THRESHOLD = 0.5;
|
|
3158
|
+
const DEFAULT_CBIOU_UNCONFIRMED_IOU_THRESHOLD = 0.3;
|
|
3159
|
+
const DEFAULT_CBIOU_FIRST_BUFFER_RATIO = 0.3;
|
|
3160
|
+
const DEFAULT_CBIOU_SECOND_BUFFER_RATIO = 0.5;
|
|
3161
|
+
const DEFAULT_OCSORT_DIRECTION_CONSISTENCY_WEIGHT = 0.2;
|
|
3162
|
+
const DEFAULT_OCSORT_DELTA_T = 3;
|
|
3163
|
+
const detectionPostProcessors = {
|
|
3164
|
+
tracking: ((options = {}) => {
|
|
3165
|
+
const isByteTrack = options.algorithm === "bytetrack";
|
|
3166
|
+
const isCBIoU = options.algorithm === "cbiou";
|
|
3167
|
+
const isOCSort = options.algorithm === "ocsort";
|
|
3168
|
+
const lostTrackBuffer = normalizeNonNegativeInteger(options.lostTrackBuffer, DEFAULT_LOST_TRACK_BUFFER, "lostTrackBuffer");
|
|
3169
|
+
const frameRate = options.frameRate ?? DEFAULT_FRAME_RATE;
|
|
3170
|
+
const trackActivationThreshold = options.trackActivationThreshold ??
|
|
3171
|
+
(isByteTrack || isCBIoU
|
|
3172
|
+
? DEFAULT_BYTE_TRACK_ACTIVATION_THRESHOLD
|
|
3173
|
+
: DEFAULT_TRACK_ACTIVATION_THRESHOLD);
|
|
3174
|
+
const minimumConsecutiveFrames = normalizePositiveInteger(options.minimumConsecutiveFrames, isByteTrack || isCBIoU
|
|
3175
|
+
? DEFAULT_BYTE_MINIMUM_CONSECUTIVE_FRAMES
|
|
3176
|
+
: DEFAULT_MINIMUM_CONSECUTIVE_FRAMES, "minimumConsecutiveFrames");
|
|
3177
|
+
const minimumIouThreshold = options.minimumIouThreshold ??
|
|
3178
|
+
(isByteTrack
|
|
3179
|
+
? DEFAULT_BYTE_MINIMUM_IOU_THRESHOLD
|
|
3180
|
+
: DEFAULT_MINIMUM_IOU_THRESHOLD);
|
|
3181
|
+
if (!Number.isFinite(frameRate) || frameRate <= 0) {
|
|
3182
|
+
throw new Error("frameRate must be a finite positive value.");
|
|
3183
|
+
}
|
|
3184
|
+
normalizeUnitInterval(trackActivationThreshold, "trackActivationThreshold");
|
|
3185
|
+
normalizeUnitInterval(minimumIouThreshold, "minimumIouThreshold");
|
|
3186
|
+
const base = {
|
|
3187
|
+
geometry: options.geometry ?? TrackingGeometry.Box,
|
|
3188
|
+
kind: "tracking",
|
|
3189
|
+
};
|
|
3190
|
+
if (isCBIoU) {
|
|
3191
|
+
const highConfidenceDetectionThreshold = options.highConfidenceDetectionThreshold ??
|
|
3192
|
+
DEFAULT_HIGH_CONFIDENCE_DETECTION_THRESHOLD;
|
|
3193
|
+
const minimumIouThresholdFirstAssociation = options.minimumIouThresholdFirstAssociation ??
|
|
3194
|
+
DEFAULT_CBIOU_FIRST_IOU_THRESHOLD;
|
|
3195
|
+
const minimumIouThresholdSecondAssociation = options.minimumIouThresholdSecondAssociation ??
|
|
3196
|
+
DEFAULT_CBIOU_SECOND_IOU_THRESHOLD;
|
|
3197
|
+
const minimumIouThresholdUnconfirmedAssociation = options.minimumIouThresholdUnconfirmedAssociation ??
|
|
3198
|
+
DEFAULT_CBIOU_UNCONFIRMED_IOU_THRESHOLD;
|
|
3199
|
+
const bufferRatioFirst = options.bufferRatioFirst ?? DEFAULT_CBIOU_FIRST_BUFFER_RATIO;
|
|
3200
|
+
const bufferRatioSecond = options.bufferRatioSecond ?? DEFAULT_CBIOU_SECOND_BUFFER_RATIO;
|
|
3201
|
+
normalizeUnitInterval(highConfidenceDetectionThreshold, "highConfidenceDetectionThreshold");
|
|
3202
|
+
normalizeUnitInterval(minimumIouThresholdFirstAssociation, "minimumIouThresholdFirstAssociation");
|
|
3203
|
+
normalizeUnitInterval(minimumIouThresholdSecondAssociation, "minimumIouThresholdSecondAssociation");
|
|
3204
|
+
normalizeUnitInterval(minimumIouThresholdUnconfirmedAssociation, "minimumIouThresholdUnconfirmedAssociation");
|
|
3205
|
+
normalizeNonNegativeFinite(bufferRatioFirst, "bufferRatioFirst");
|
|
3206
|
+
normalizeNonNegativeFinite(bufferRatioSecond, "bufferRatioSecond");
|
|
3207
|
+
return {
|
|
3208
|
+
...base,
|
|
3209
|
+
algorithm: "cbiou",
|
|
3210
|
+
options: {
|
|
3211
|
+
bufferRatioFirst,
|
|
3212
|
+
bufferRatioSecond,
|
|
3213
|
+
frameRate,
|
|
3214
|
+
highConfidenceDetectionThreshold,
|
|
3215
|
+
instantFirstFrameActivation: options.instantFirstFrameActivation ?? true,
|
|
3216
|
+
lostTrackBuffer,
|
|
3217
|
+
minimumConsecutiveFrames,
|
|
3218
|
+
minimumIouThresholdFirstAssociation,
|
|
3219
|
+
minimumIouThresholdSecondAssociation,
|
|
3220
|
+
minimumIouThresholdUnconfirmedAssociation,
|
|
3221
|
+
trackActivationThreshold,
|
|
3222
|
+
},
|
|
3223
|
+
};
|
|
3224
|
+
}
|
|
3225
|
+
if (isOCSort) {
|
|
3226
|
+
const directionConsistencyWeight = options.directionConsistencyWeight ??
|
|
3227
|
+
DEFAULT_OCSORT_DIRECTION_CONSISTENCY_WEIGHT;
|
|
3228
|
+
const highConfidenceDetectionThreshold = options.highConfidenceDetectionThreshold ??
|
|
3229
|
+
DEFAULT_HIGH_CONFIDENCE_DETECTION_THRESHOLD;
|
|
3230
|
+
const deltaT = normalizePositiveInteger(options.deltaT, DEFAULT_OCSORT_DELTA_T, "deltaT");
|
|
3231
|
+
normalizeUnitInterval(directionConsistencyWeight, "directionConsistencyWeight");
|
|
3232
|
+
normalizeUnitInterval(highConfidenceDetectionThreshold, "highConfidenceDetectionThreshold");
|
|
3233
|
+
return {
|
|
3234
|
+
...base,
|
|
3235
|
+
algorithm: "ocsort",
|
|
3236
|
+
options: {
|
|
3237
|
+
deltaT,
|
|
3238
|
+
directionConsistencyWeight,
|
|
3239
|
+
frameRate,
|
|
3240
|
+
highConfidenceDetectionThreshold,
|
|
3241
|
+
lostTrackBuffer,
|
|
3242
|
+
minimumConsecutiveFrames,
|
|
3243
|
+
minimumIouThreshold,
|
|
3244
|
+
},
|
|
3245
|
+
};
|
|
3246
|
+
}
|
|
3247
|
+
if (isByteTrack) {
|
|
3248
|
+
const highConfidenceDetectionThreshold = options.highConfidenceDetectionThreshold ??
|
|
3249
|
+
DEFAULT_HIGH_CONFIDENCE_DETECTION_THRESHOLD;
|
|
3250
|
+
normalizeUnitInterval(highConfidenceDetectionThreshold, "highConfidenceDetectionThreshold");
|
|
3251
|
+
return {
|
|
3252
|
+
...base,
|
|
3253
|
+
algorithm: "bytetrack",
|
|
3254
|
+
options: {
|
|
3255
|
+
frameRate,
|
|
3256
|
+
highConfidenceDetectionThreshold,
|
|
3257
|
+
lostTrackBuffer,
|
|
3258
|
+
minimumConsecutiveFrames,
|
|
3259
|
+
minimumIouThreshold,
|
|
3260
|
+
trackActivationThreshold,
|
|
3261
|
+
},
|
|
3262
|
+
};
|
|
3263
|
+
}
|
|
3264
|
+
return {
|
|
3265
|
+
...base,
|
|
3266
|
+
algorithm: "sort",
|
|
3267
|
+
options: {
|
|
3268
|
+
frameRate,
|
|
3269
|
+
lostTrackBuffer,
|
|
3270
|
+
minimumConsecutiveFrames,
|
|
3271
|
+
minimumIouThreshold,
|
|
3272
|
+
trackActivationThreshold,
|
|
3273
|
+
},
|
|
3274
|
+
};
|
|
3275
|
+
}),
|
|
3276
|
+
};
|
|
3277
|
+
/**
|
|
3278
|
+
* Creates the lightweight projection sent to a tracker or tracking worker.
|
|
3279
|
+
* Masks and keypoint arrays never cross the worker boundary.
|
|
3280
|
+
*/
|
|
3281
|
+
function projectDetectionFrameForTracking(frame, geometry) {
|
|
3282
|
+
return frame.detections.flatMap((detection, detectionIndex) => {
|
|
3283
|
+
const rect = resolveTrackingRect(detection, geometry);
|
|
3284
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
3285
|
+
return [];
|
|
3286
|
+
}
|
|
3287
|
+
return [
|
|
3288
|
+
{
|
|
3289
|
+
...(detection.confidence === undefined
|
|
3290
|
+
? {}
|
|
3291
|
+
: { confidence: detection.confidence }),
|
|
3292
|
+
detectionIndex,
|
|
3293
|
+
rect,
|
|
3294
|
+
},
|
|
3295
|
+
];
|
|
3296
|
+
});
|
|
3297
|
+
}
|
|
3298
|
+
function resolveTrackingRect(detection, geometry) {
|
|
3299
|
+
switch (geometry) {
|
|
3300
|
+
case TrackingGeometry.Box:
|
|
3301
|
+
return detection.rect;
|
|
3302
|
+
case TrackingGeometry.Mask:
|
|
3303
|
+
if (!detection.mask) {
|
|
3304
|
+
return undefined;
|
|
3305
|
+
}
|
|
3306
|
+
// Most segmentation producers already supply the exact mask bounds as a
|
|
3307
|
+
// rect. Decode RLE only when that inexpensive projection is absent.
|
|
3308
|
+
return detection.rect ?? computeDetectionMaskRect(detection.mask);
|
|
3309
|
+
case TrackingGeometry.Keypoints:
|
|
3310
|
+
return detection.keypoints
|
|
3311
|
+
? getPointsRect(detection.keypoints.points)
|
|
3312
|
+
: undefined;
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
function normalizePositiveInteger(value, fallback, label) {
|
|
3316
|
+
const resolved = value ?? fallback;
|
|
3317
|
+
if (!Number.isInteger(resolved) || resolved < 1) {
|
|
3318
|
+
throw new Error(`${label} must be a positive integer.`);
|
|
3319
|
+
}
|
|
3320
|
+
return resolved;
|
|
3321
|
+
}
|
|
3322
|
+
function normalizeNonNegativeInteger(value, fallback, label) {
|
|
3323
|
+
const resolved = value ?? fallback;
|
|
3324
|
+
if (!Number.isInteger(resolved) || resolved < 0) {
|
|
3325
|
+
throw new Error(`${label} must be a non-negative integer.`);
|
|
3326
|
+
}
|
|
3327
|
+
return resolved;
|
|
3328
|
+
}
|
|
3329
|
+
function normalizeUnitInterval(value, label) {
|
|
3330
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
3331
|
+
throw new Error(`${label} must be between 0 and 1.`);
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
function normalizeNonNegativeFinite(value, label) {
|
|
3335
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
3336
|
+
throw new Error(`${label} must be a finite non-negative value.`);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
var DetectionPickTarget;
|
|
3341
|
+
(function (DetectionPickTarget) {
|
|
3342
|
+
DetectionPickTarget["Box"] = "box";
|
|
3343
|
+
DetectionPickTarget["Edge"] = "edge";
|
|
3344
|
+
DetectionPickTarget["Keypoint"] = "keypoint";
|
|
3345
|
+
DetectionPickTarget["Label"] = "label";
|
|
3346
|
+
DetectionPickTarget["Mask"] = "mask";
|
|
3347
|
+
DetectionPickTarget["Polygon"] = "polygon";
|
|
3348
|
+
DetectionPickTarget["Polyline"] = "polyline";
|
|
3349
|
+
})(DetectionPickTarget || (DetectionPickTarget = {}));
|
|
3350
|
+
var MediaInteractionMode;
|
|
3351
|
+
(function (MediaInteractionMode) {
|
|
3352
|
+
MediaInteractionMode["Always"] = "always";
|
|
3353
|
+
MediaInteractionMode["Disabled"] = "disabled";
|
|
3354
|
+
MediaInteractionMode["PausedOnly"] = "pausedOnly";
|
|
3355
|
+
})(MediaInteractionMode || (MediaInteractionMode = {}));
|
|
3356
|
+
|
|
3357
|
+
const decodedMaskCache = new WeakMap();
|
|
3358
|
+
function pickDetectionAtPoint(frame, point, options = {}) {
|
|
3359
|
+
if (!frame) {
|
|
3360
|
+
return null;
|
|
3361
|
+
}
|
|
3362
|
+
const padding = Math.max(0, options.padding ?? 0);
|
|
3363
|
+
const polylinePadding = Math.max(0, options.polylinePadding ?? 6);
|
|
3364
|
+
const keypointPadding = Math.max(0, options.keypointPadding ?? 10);
|
|
3365
|
+
const edgePadding = Math.max(0, options.edgePadding ?? 8);
|
|
3366
|
+
const candidates = [];
|
|
3367
|
+
for (let detectionIndex = 0; detectionIndex < frame.detections.length; detectionIndex += 1) {
|
|
3368
|
+
const detection = frame.detections[detectionIndex];
|
|
3369
|
+
if (!detection ||
|
|
3370
|
+
(detection.locked && options.includeLocked === false) ||
|
|
3371
|
+
options.filter?.(detection, detectionIndex) === false) {
|
|
3372
|
+
continue;
|
|
3373
|
+
}
|
|
3374
|
+
const detectionArea = Math.max(1, rectArea(getDetectionRect(detection)));
|
|
3375
|
+
const pushCandidate = (target, area = detectionArea, geometryIndex) => {
|
|
3376
|
+
candidates.push({
|
|
3377
|
+
area,
|
|
3378
|
+
priority: getPickTargetPriority(target),
|
|
3379
|
+
result: {
|
|
3380
|
+
detection,
|
|
3381
|
+
detectionIndex,
|
|
3382
|
+
frame,
|
|
3383
|
+
geometryIndex,
|
|
3384
|
+
mediaTime: frame.mediaTime,
|
|
3385
|
+
point,
|
|
3386
|
+
target,
|
|
3387
|
+
},
|
|
3388
|
+
zIndex: detection.zIndex ?? detectionIndex,
|
|
3389
|
+
});
|
|
3390
|
+
};
|
|
3391
|
+
if (detection.rect && containsPoint(detection.rect, point, padding)) {
|
|
3392
|
+
pushCandidate(DetectionPickTarget.Box, rectArea(detection.rect));
|
|
3393
|
+
}
|
|
3394
|
+
if (detection.polygon && pointInPolygon(point, detection.polygon.points)) {
|
|
3395
|
+
pushCandidate(DetectionPickTarget.Polygon, Math.max(1, polygonArea(detection.polygon.points)));
|
|
3396
|
+
}
|
|
3397
|
+
if (detection.polyline) {
|
|
3398
|
+
const segmentIndex = findHitSegment(point, detection.polyline.points, polylinePadding);
|
|
3399
|
+
if (segmentIndex !== -1) {
|
|
3400
|
+
pushCandidate(DetectionPickTarget.Polyline, detectionArea, segmentIndex);
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
if (detection.keypoints) {
|
|
3404
|
+
const keypointIndex = detection.keypoints.points.findIndex((keypoint, index) => detection.keypoints?.visibility?.[index] !==
|
|
3405
|
+
KeypointVisibility.NotLabeled &&
|
|
3406
|
+
Math.hypot(point.x - keypoint.x, point.y - keypoint.y) <=
|
|
3407
|
+
keypointPadding);
|
|
3408
|
+
if (keypointIndex !== -1) {
|
|
3409
|
+
pushCandidate(DetectionPickTarget.Keypoint, Math.PI * keypointPadding * keypointPadding, keypointIndex);
|
|
3410
|
+
}
|
|
3411
|
+
else {
|
|
3412
|
+
const edgeIndex = detection.keypoints.edges.findIndex(([fromIndex, toIndex]) => {
|
|
3413
|
+
const from = detection.keypoints?.points[fromIndex];
|
|
3414
|
+
const to = detection.keypoints?.points[toIndex];
|
|
3415
|
+
return Boolean(from &&
|
|
3416
|
+
to &&
|
|
3417
|
+
detection.keypoints?.visibility?.[fromIndex] !==
|
|
3418
|
+
KeypointVisibility.NotLabeled &&
|
|
3419
|
+
detection.keypoints?.visibility?.[toIndex] !==
|
|
3420
|
+
KeypointVisibility.NotLabeled &&
|
|
3421
|
+
distanceToSegment(point, from, to) <= edgePadding);
|
|
3422
|
+
});
|
|
3423
|
+
if (edgeIndex !== -1) {
|
|
3424
|
+
const [fromIndex, toIndex] = detection.keypoints.edges[edgeIndex];
|
|
3425
|
+
const from = detection.keypoints.points[fromIndex];
|
|
3426
|
+
const to = detection.keypoints.points[toIndex];
|
|
3427
|
+
const edgeArea = Math.max(1, Math.hypot(to.x - from.x, to.y - from.y) * edgePadding * 2);
|
|
3428
|
+
pushCandidate(DetectionPickTarget.Edge, edgeArea, edgeIndex);
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
if (detection.mask && options.includeMasks !== false) {
|
|
3433
|
+
const decoded = getDecodedMask(detection.mask);
|
|
3434
|
+
const mediaDimensions = options.maskMediaDimensions;
|
|
3435
|
+
const x = Math.floor(mediaDimensions && mediaDimensions.width > 0
|
|
3436
|
+
? (point.x / mediaDimensions.width) * decoded.width
|
|
3437
|
+
: point.x);
|
|
3438
|
+
const y = Math.floor(mediaDimensions && mediaDimensions.height > 0
|
|
3439
|
+
? (point.y / mediaDimensions.height) * decoded.height
|
|
3440
|
+
: point.y);
|
|
3441
|
+
if (x >= 0 &&
|
|
3442
|
+
y >= 0 &&
|
|
3443
|
+
x < decoded.width &&
|
|
3444
|
+
y < decoded.height &&
|
|
3445
|
+
decoded.data[y * decoded.width + x]) {
|
|
3446
|
+
pushCandidate(DetectionPickTarget.Mask, getMaskMediaArea(decoded, mediaDimensions));
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
candidates.sort((left, right) => {
|
|
3451
|
+
const priorityDifference = left.priority - right.priority;
|
|
3452
|
+
if (priorityDifference !== 0) {
|
|
3453
|
+
return priorityDifference;
|
|
3454
|
+
}
|
|
3455
|
+
const areaDifference = left.area - right.area;
|
|
1875
3456
|
if (areaDifference !== 0) {
|
|
1876
3457
|
return areaDifference;
|
|
1877
3458
|
}
|
|
@@ -1928,6 +3509,29 @@ function createDetectionPickKey(pick) {
|
|
|
1928
3509
|
pick.geometryIndex ?? "geometry",
|
|
1929
3510
|
].join(":");
|
|
1930
3511
|
}
|
|
3512
|
+
/**
|
|
3513
|
+
* Compares the frame-independent membership of two ordered selections.
|
|
3514
|
+
*
|
|
3515
|
+
* Detection ids retain their runtime type, so numeric `1` and string `"1"`
|
|
3516
|
+
* remain distinct identities. Anonymous detections keep their frame-local
|
|
3517
|
+
* index identity.
|
|
3518
|
+
*/
|
|
3519
|
+
function haveSameDetectionPickIdentities(current, next) {
|
|
3520
|
+
if (current.length !== next.length) {
|
|
3521
|
+
return false;
|
|
3522
|
+
}
|
|
3523
|
+
return current.every((pick, index) => {
|
|
3524
|
+
const nextPick = next[index];
|
|
3525
|
+
if (!nextPick) {
|
|
3526
|
+
return false;
|
|
3527
|
+
}
|
|
3528
|
+
const id = pick.detection.id;
|
|
3529
|
+
const nextId = nextPick.detection.id;
|
|
3530
|
+
return id === undefined || nextId === undefined
|
|
3531
|
+
? id === nextId && pick.detectionIndex === nextPick.detectionIndex
|
|
3532
|
+
: typeof id === typeof nextId && id === nextId;
|
|
3533
|
+
});
|
|
3534
|
+
}
|
|
1931
3535
|
function rebaseDetectionPickToFrame(pick, frame) {
|
|
1932
3536
|
if (!pick || !frame) {
|
|
1933
3537
|
return null;
|
|
@@ -1953,13 +3557,55 @@ function rebaseDetectionPickToFrame(pick, frame) {
|
|
|
1953
3557
|
? rebasedPick
|
|
1954
3558
|
: null;
|
|
1955
3559
|
}
|
|
3560
|
+
/**
|
|
3561
|
+
* Carries a pick forward onto a different frame of the same timeline.
|
|
3562
|
+
*
|
|
3563
|
+
* Same-frame rebases behave exactly like `rebaseDetectionPickToFrame`. Across
|
|
3564
|
+
* frames, the pick follows its detection only when the detection id is defined
|
|
3565
|
+
* and unique in both frames, so anonymous and duplicate-id detections keep the
|
|
3566
|
+
* per-frame lifetime they have today. Returns `null` when the detection has no
|
|
3567
|
+
* followable identity or has left the frame.
|
|
3568
|
+
*/
|
|
3569
|
+
function followDetectionPickAcrossFrames(pick, frame) {
|
|
3570
|
+
const rebasedPick = rebaseDetectionPickToFrame(pick, frame);
|
|
3571
|
+
if (rebasedPick || !pick || !frame) {
|
|
3572
|
+
return rebasedPick;
|
|
3573
|
+
}
|
|
3574
|
+
const id = pick.detection.id;
|
|
3575
|
+
if (id === undefined ||
|
|
3576
|
+
!hasUniqueDetectionId(pick.frame, id) ||
|
|
3577
|
+
!hasUniqueDetectionId(frame, id)) {
|
|
3578
|
+
return null;
|
|
3579
|
+
}
|
|
3580
|
+
const detectionIndex = frame.detections.findIndex((detection) => detection.id === id);
|
|
3581
|
+
const detection = frame.detections[detectionIndex];
|
|
3582
|
+
if (!detection) {
|
|
3583
|
+
return null;
|
|
3584
|
+
}
|
|
3585
|
+
return {
|
|
3586
|
+
detection,
|
|
3587
|
+
detectionIndex,
|
|
3588
|
+
frame,
|
|
3589
|
+
geometryIndex: pick.geometryIndex,
|
|
3590
|
+
mediaTime: frame.mediaTime,
|
|
3591
|
+
point: pick.point,
|
|
3592
|
+
target: pick.target,
|
|
3593
|
+
};
|
|
3594
|
+
}
|
|
1956
3595
|
function detectionPickKey(pick) {
|
|
1957
3596
|
if (pick.detection.id === undefined) {
|
|
1958
3597
|
return ["anonymous", pick.detectionIndex];
|
|
1959
3598
|
}
|
|
3599
|
+
const idType = typeof pick.detection.id;
|
|
1960
3600
|
return hasUniqueDetectionId(pick.frame, pick.detection.id)
|
|
1961
|
-
? ["id", String(pick.detection.id)]
|
|
1962
|
-
: [
|
|
3601
|
+
? ["id", idType, String(pick.detection.id)]
|
|
3602
|
+
: [
|
|
3603
|
+
"duplicate-id",
|
|
3604
|
+
idType,
|
|
3605
|
+
String(pick.detection.id),
|
|
3606
|
+
"index",
|
|
3607
|
+
pick.detectionIndex,
|
|
3608
|
+
];
|
|
1963
3609
|
}
|
|
1964
3610
|
function hasUniqueDetectionId(frame, id) {
|
|
1965
3611
|
let count = 0;
|
|
@@ -2652,17 +4298,46 @@ function resolveStyleValue(value, detection, context) {
|
|
|
2652
4298
|
: value;
|
|
2653
4299
|
}
|
|
2654
4300
|
|
|
4301
|
+
/**
|
|
4302
|
+
* Resolves the renderer-neutral stroke contract without dropping optional
|
|
4303
|
+
* geometry controls as styles move from public configuration to draw
|
|
4304
|
+
* instructions.
|
|
4305
|
+
*
|
|
4306
|
+
* Keep this as the single normalization point for every built-in style. That
|
|
4307
|
+
* makes additions to `StrokeStyle` deliberate instead of relying on each style
|
|
4308
|
+
* implementation to copy a growing property list correctly.
|
|
4309
|
+
*/
|
|
4310
|
+
function resolveStrokeStyle(stroke, defaults) {
|
|
4311
|
+
const alignment = stroke?.alignment ?? defaults.alignment;
|
|
4312
|
+
const cap = stroke?.cap ?? defaults.cap;
|
|
4313
|
+
const dash = stroke?.dash ?? defaults.dash;
|
|
4314
|
+
const join = stroke?.join ?? defaults.join;
|
|
4315
|
+
const miterLimit = stroke?.miterLimit ?? defaults.miterLimit;
|
|
4316
|
+
return {
|
|
4317
|
+
alpha: stroke?.alpha ?? defaults.alpha,
|
|
4318
|
+
color: stroke?.color ?? defaults.color,
|
|
4319
|
+
width: stroke?.width ?? defaults.width,
|
|
4320
|
+
...(alignment === undefined ? {} : { alignment }),
|
|
4321
|
+
...(cap === undefined ? {} : { cap }),
|
|
4322
|
+
...(dash === undefined ? {} : { dash }),
|
|
4323
|
+
...(join === undefined ? {} : { join }),
|
|
4324
|
+
...(miterLimit === undefined ? {} : { miterLimit }),
|
|
4325
|
+
};
|
|
4326
|
+
}
|
|
4327
|
+
|
|
4328
|
+
/** Alignment for strokes applied to closed rendered geometry. */
|
|
4329
|
+
var StrokeAlignment;
|
|
4330
|
+
(function (StrokeAlignment) {
|
|
4331
|
+
StrokeAlignment["Inside"] = "inside";
|
|
4332
|
+
StrokeAlignment["Center"] = "center";
|
|
4333
|
+
StrokeAlignment["Outside"] = "outside";
|
|
4334
|
+
})(StrokeAlignment || (StrokeAlignment = {}));
|
|
4335
|
+
|
|
2655
4336
|
var BoxShape;
|
|
2656
4337
|
(function (BoxShape) {
|
|
2657
4338
|
BoxShape["Rect"] = "rect";
|
|
2658
4339
|
BoxShape["RoundedRect"] = "roundedRect";
|
|
2659
4340
|
})(BoxShape || (BoxShape = {}));
|
|
2660
|
-
var BoxStrokeAlignment;
|
|
2661
|
-
(function (BoxStrokeAlignment) {
|
|
2662
|
-
BoxStrokeAlignment["Inside"] = "inside";
|
|
2663
|
-
BoxStrokeAlignment["Center"] = "center";
|
|
2664
|
-
BoxStrokeAlignment["Outside"] = "outside";
|
|
2665
|
-
})(BoxStrokeAlignment || (BoxStrokeAlignment = {}));
|
|
2666
4341
|
|
|
2667
4342
|
const DEFAULT_BOX_STROKE_ALPHA = 1;
|
|
2668
4343
|
const DEFAULT_BOX_STROKE_COLOR = 0x00ff66;
|
|
@@ -2716,22 +4391,12 @@ class BaseBoxStyle {
|
|
|
2716
4391
|
if (stroke === null) {
|
|
2717
4392
|
return undefined;
|
|
2718
4393
|
}
|
|
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
|
-
};
|
|
4394
|
+
return resolveStrokeStyle(stroke, {
|
|
4395
|
+
alpha: DEFAULT_BOX_STROKE_ALPHA,
|
|
4396
|
+
color: DEFAULT_BOX_STROKE_COLOR,
|
|
4397
|
+
...(context.ephemeral ? { dash: [6, 4] } : {}),
|
|
4398
|
+
width: DEFAULT_BOX_STROKE_WIDTH,
|
|
4399
|
+
});
|
|
2735
4400
|
}
|
|
2736
4401
|
resolveFill(detection, context) {
|
|
2737
4402
|
const fill = resolveStyleValue(this.options.fill, detection, context);
|
|
@@ -2745,6 +4410,83 @@ class BaseBoxStyle {
|
|
|
2745
4410
|
}
|
|
2746
4411
|
}
|
|
2747
4412
|
|
|
4413
|
+
const DEFAULT_CORNER_LENGTH = 20;
|
|
4414
|
+
const DEFAULT_CORNER_STROKE = {
|
|
4415
|
+
alpha: 1,
|
|
4416
|
+
cap: "round",
|
|
4417
|
+
color: 0x00ff66,
|
|
4418
|
+
join: "round",
|
|
4419
|
+
width: 2,
|
|
4420
|
+
};
|
|
4421
|
+
/**
|
|
4422
|
+
* Configurable four-corner decoration derived from a detection rectangle.
|
|
4423
|
+
*
|
|
4424
|
+
* This is the BoxCornerAnnotator-style facade: it keeps the rectangle semantic
|
|
4425
|
+
* and draws only its four open corner segments.
|
|
4426
|
+
*/
|
|
4427
|
+
class BaseBoxCornerStyle {
|
|
4428
|
+
options;
|
|
4429
|
+
constructor(options = {}) {
|
|
4430
|
+
this.options = options;
|
|
4431
|
+
}
|
|
4432
|
+
resolve(detection, context) {
|
|
4433
|
+
if (!detection.rect ||
|
|
4434
|
+
context.hidden ||
|
|
4435
|
+
this.options.shouldRender?.(detection, context) === false) {
|
|
4436
|
+
return undefined;
|
|
4437
|
+
}
|
|
4438
|
+
const resolvedLength = resolveStyleValue(this.options.length, detection, context) ??
|
|
4439
|
+
DEFAULT_CORNER_LENGTH;
|
|
4440
|
+
const viewportScale = context.viewportScale && context.viewportScale > 0
|
|
4441
|
+
? context.viewportScale
|
|
4442
|
+
: 1;
|
|
4443
|
+
const length = Math.min(Math.max(0, resolvedLength / viewportScale), detection.rect.width / 2, detection.rect.height / 2);
|
|
4444
|
+
const stroke = resolveStyleValue(this.options.stroke, detection, context);
|
|
4445
|
+
if (length <= 0 || stroke === null)
|
|
4446
|
+
return undefined;
|
|
4447
|
+
const { height, width, x, y } = detection.rect;
|
|
4448
|
+
const left = x - width / 2;
|
|
4449
|
+
const right = x + width / 2;
|
|
4450
|
+
const top = y - height / 2;
|
|
4451
|
+
const bottom = y + height / 2;
|
|
4452
|
+
const resolvedStroke = resolveStrokeStyle(stroke, DEFAULT_CORNER_STROKE);
|
|
4453
|
+
const openStroke = {
|
|
4454
|
+
alpha: resolvedStroke.alpha,
|
|
4455
|
+
cap: resolvedStroke.cap,
|
|
4456
|
+
color: resolvedStroke.color,
|
|
4457
|
+
dash: resolvedStroke.dash,
|
|
4458
|
+
join: resolvedStroke.join,
|
|
4459
|
+
miterLimit: resolvedStroke.miterLimit,
|
|
4460
|
+
width: resolvedStroke.width,
|
|
4461
|
+
};
|
|
4462
|
+
return {
|
|
4463
|
+
segments: [
|
|
4464
|
+
[
|
|
4465
|
+
{ x: left + length, y: top },
|
|
4466
|
+
{ x: left, y: top },
|
|
4467
|
+
{ x: left, y: top + length },
|
|
4468
|
+
],
|
|
4469
|
+
[
|
|
4470
|
+
{ x: right - length, y: top },
|
|
4471
|
+
{ x: right, y: top },
|
|
4472
|
+
{ x: right, y: top + length },
|
|
4473
|
+
],
|
|
4474
|
+
[
|
|
4475
|
+
{ x: left + length, y: bottom },
|
|
4476
|
+
{ x: left, y: bottom },
|
|
4477
|
+
{ x: left, y: bottom - length },
|
|
4478
|
+
],
|
|
4479
|
+
[
|
|
4480
|
+
{ x: right - length, y: bottom },
|
|
4481
|
+
{ x: right, y: bottom },
|
|
4482
|
+
{ x: right, y: bottom - length },
|
|
4483
|
+
],
|
|
4484
|
+
],
|
|
4485
|
+
stroke: openStroke,
|
|
4486
|
+
};
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
|
|
2748
4490
|
var FocusTargetMode;
|
|
2749
4491
|
(function (FocusTargetMode) {
|
|
2750
4492
|
FocusTargetMode["Hovered"] = "hovered";
|
|
@@ -2943,18 +4685,11 @@ class BaseInteractionStyle {
|
|
|
2943
4685
|
return undefined;
|
|
2944
4686
|
}
|
|
2945
4687
|
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;
|
|
4688
|
+
return resolveStrokeStyle(stroke, {
|
|
4689
|
+
alpha: defaults.strokeAlpha,
|
|
4690
|
+
color: defaults.strokeColor,
|
|
4691
|
+
width: defaults.strokeWidth,
|
|
4692
|
+
});
|
|
2958
4693
|
}
|
|
2959
4694
|
resolveFill(detection, context) {
|
|
2960
4695
|
const fill = resolveStyleValue(this.options.fill, detection, context);
|
|
@@ -3206,6 +4941,115 @@ function createArtifactKey(options) {
|
|
|
3206
4941
|
return `base:${color}:${fillAlpha}:${mode}:${serializeStroke(resolveStroke(options.stroke, color, mode))}`;
|
|
3207
4942
|
}
|
|
3208
4943
|
|
|
4944
|
+
/**
|
|
4945
|
+
* Discriminator for renderer-neutral shape draw instructions.
|
|
4946
|
+
*/
|
|
4947
|
+
var ShapeInstructionKind;
|
|
4948
|
+
(function (ShapeInstructionKind) {
|
|
4949
|
+
ShapeInstructionKind["Ellipse"] = "ellipse";
|
|
4950
|
+
ShapeInstructionKind["Marker"] = "marker";
|
|
4951
|
+
ShapeInstructionKind["Path"] = "path";
|
|
4952
|
+
})(ShapeInstructionKind || (ShapeInstructionKind = {}));
|
|
4953
|
+
var MarkerShape;
|
|
4954
|
+
(function (MarkerShape) {
|
|
4955
|
+
MarkerShape["Circle"] = "circle";
|
|
4956
|
+
MarkerShape["Cross"] = "cross";
|
|
4957
|
+
MarkerShape["Square"] = "square";
|
|
4958
|
+
MarkerShape["Triangle"] = "triangle";
|
|
4959
|
+
})(MarkerShape || (MarkerShape = {}));
|
|
4960
|
+
/**
|
|
4961
|
+
* Coordinate space for marker sizing. Media sizes scale with the viewport;
|
|
4962
|
+
* screen sizes stay constant on screen, matching stroke-width semantics.
|
|
4963
|
+
*/
|
|
4964
|
+
var MarkerSizeSpace;
|
|
4965
|
+
(function (MarkerSizeSpace) {
|
|
4966
|
+
MarkerSizeSpace["Media"] = "media";
|
|
4967
|
+
MarkerSizeSpace["Screen"] = "screen";
|
|
4968
|
+
})(MarkerSizeSpace || (MarkerSizeSpace = {}));
|
|
4969
|
+
|
|
4970
|
+
const DEFAULT_FILL = { alpha: 1, color: 0x00ff66 };
|
|
4971
|
+
const DEFAULT_STROKE = { alpha: 1, color: 0xffffff, width: 1 };
|
|
4972
|
+
const DEFAULT_SIZE = 12;
|
|
4973
|
+
/**
|
|
4974
|
+
* Configurable marker renderer style anchored to semantic detection geometry.
|
|
4975
|
+
*
|
|
4976
|
+
* Use `center` for any domain-specific anchor (keypoint, centroid, or custom
|
|
4977
|
+
* metadata). Without it, the marker lands at the detection rectangle center.
|
|
4978
|
+
*/
|
|
4979
|
+
class BaseMarkerStyle {
|
|
4980
|
+
options;
|
|
4981
|
+
constructor(options = {}) {
|
|
4982
|
+
this.options = options;
|
|
4983
|
+
}
|
|
4984
|
+
resolve(detection, context) {
|
|
4985
|
+
if (context.hidden ||
|
|
4986
|
+
this.options.shouldRender?.(detection, context) === false) {
|
|
4987
|
+
return undefined;
|
|
4988
|
+
}
|
|
4989
|
+
const center = resolveStyleValue(this.options.center, detection, context) ??
|
|
4990
|
+
(detection.rect
|
|
4991
|
+
? { x: detection.rect.x, y: detection.rect.y }
|
|
4992
|
+
: undefined);
|
|
4993
|
+
const shape = resolveStyleValue(this.options.shape, detection, context) ??
|
|
4994
|
+
MarkerShape.Circle;
|
|
4995
|
+
const size = resolveStyleValue(this.options.size, detection, context) ?? DEFAULT_SIZE;
|
|
4996
|
+
const sizeSpace = resolveStyleValue(this.options.sizeSpace, detection, context) ??
|
|
4997
|
+
MarkerSizeSpace.Screen;
|
|
4998
|
+
const rotation = resolveStyleValue(this.options.rotation, detection, context);
|
|
4999
|
+
const stroke = resolveStyleValue(this.options.stroke, detection, context);
|
|
5000
|
+
if (!center || !Number.isFinite(size) || size <= 0) {
|
|
5001
|
+
return undefined;
|
|
5002
|
+
}
|
|
5003
|
+
if (shape === MarkerShape.Cross) {
|
|
5004
|
+
if (stroke === null) {
|
|
5005
|
+
return undefined;
|
|
5006
|
+
}
|
|
5007
|
+
const resolvedStroke = resolveStrokeStyle(stroke, DEFAULT_STROKE);
|
|
5008
|
+
const openStroke = {
|
|
5009
|
+
alpha: resolvedStroke.alpha,
|
|
5010
|
+
cap: resolvedStroke.cap,
|
|
5011
|
+
color: resolvedStroke.color,
|
|
5012
|
+
dash: resolvedStroke.dash,
|
|
5013
|
+
join: resolvedStroke.join,
|
|
5014
|
+
miterLimit: resolvedStroke.miterLimit,
|
|
5015
|
+
width: resolvedStroke.width,
|
|
5016
|
+
};
|
|
5017
|
+
return {
|
|
5018
|
+
center,
|
|
5019
|
+
shape,
|
|
5020
|
+
size,
|
|
5021
|
+
sizeSpace,
|
|
5022
|
+
...(rotation === undefined ? {} : { rotation }),
|
|
5023
|
+
stroke: openStroke,
|
|
5024
|
+
};
|
|
5025
|
+
}
|
|
5026
|
+
const fill = resolveStyleValue(this.options.fill, detection, context);
|
|
5027
|
+
if (fill === null && stroke === null) {
|
|
5028
|
+
return undefined;
|
|
5029
|
+
}
|
|
5030
|
+
return {
|
|
5031
|
+
center,
|
|
5032
|
+
shape,
|
|
5033
|
+
size,
|
|
5034
|
+
sizeSpace,
|
|
5035
|
+
...(rotation === undefined ? {} : { rotation }),
|
|
5036
|
+
...(fill === null
|
|
5037
|
+
? {}
|
|
5038
|
+
: {
|
|
5039
|
+
fill: {
|
|
5040
|
+
alpha: fill?.alpha ?? DEFAULT_FILL.alpha,
|
|
5041
|
+
color: fill?.color ?? DEFAULT_FILL.color,
|
|
5042
|
+
},
|
|
5043
|
+
}),
|
|
5044
|
+
...(stroke === null
|
|
5045
|
+
? {}
|
|
5046
|
+
: stroke === undefined
|
|
5047
|
+
? { stroke: DEFAULT_STROKE }
|
|
5048
|
+
: { stroke: resolveStrokeStyle(stroke, DEFAULT_STROKE) }),
|
|
5049
|
+
};
|
|
5050
|
+
}
|
|
5051
|
+
}
|
|
5052
|
+
|
|
3209
5053
|
class BasePolygonStyle {
|
|
3210
5054
|
options;
|
|
3211
5055
|
constructor(options = {}) {
|
|
@@ -3233,15 +5077,11 @@ class BasePolygonStyle {
|
|
|
3233
5077
|
...(stroke === null
|
|
3234
5078
|
? {}
|
|
3235
5079
|
: {
|
|
3236
|
-
stroke: {
|
|
3237
|
-
alpha:
|
|
3238
|
-
color:
|
|
3239
|
-
width:
|
|
3240
|
-
|
|
3241
|
-
? {}
|
|
3242
|
-
: { alignment: stroke.alignment }),
|
|
3243
|
-
...(stroke?.dash === undefined ? {} : { dash: stroke.dash }),
|
|
3244
|
-
},
|
|
5080
|
+
stroke: resolveStrokeStyle(stroke, {
|
|
5081
|
+
alpha: 1,
|
|
5082
|
+
color: 0x00ff66,
|
|
5083
|
+
width: 2,
|
|
5084
|
+
}),
|
|
3245
5085
|
}),
|
|
3246
5086
|
};
|
|
3247
5087
|
}
|
|
@@ -3265,15 +5105,11 @@ class BasePolylineStyle {
|
|
|
3265
5105
|
}
|
|
3266
5106
|
return {
|
|
3267
5107
|
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
|
-
},
|
|
5108
|
+
stroke: resolveStrokeStyle(stroke, {
|
|
5109
|
+
alpha: 1,
|
|
5110
|
+
color: 0x00ff66,
|
|
5111
|
+
width: 2,
|
|
5112
|
+
}),
|
|
3277
5113
|
};
|
|
3278
5114
|
}
|
|
3279
5115
|
}
|
|
@@ -3307,19 +5143,21 @@ class BaseKeypointStyle {
|
|
|
3307
5143
|
const edges = geometry.edges.map(([fromIndex, toIndex], edgeIndex) => ({
|
|
3308
5144
|
from: geometry.points[fromIndex],
|
|
3309
5145
|
to: geometry.points[toIndex],
|
|
3310
|
-
stroke:
|
|
3311
|
-
|
|
3312
|
-
color: definition
|
|
3313
|
-
|
|
3314
|
-
|
|
5146
|
+
stroke: resolveStrokeStyle(definition?.edges[edgeIndex]?.color === undefined
|
|
5147
|
+
? edgeStroke
|
|
5148
|
+
: { ...edgeStroke, color: definition.edges[edgeIndex].color }, {
|
|
5149
|
+
alpha: 1,
|
|
5150
|
+
color: 0x00ff66,
|
|
5151
|
+
width: 2,
|
|
5152
|
+
}),
|
|
3315
5153
|
...(shadowStroke === null
|
|
3316
5154
|
? {}
|
|
3317
5155
|
: {
|
|
3318
|
-
shadowStroke: {
|
|
3319
|
-
alpha:
|
|
3320
|
-
color:
|
|
3321
|
-
width:
|
|
3322
|
-
},
|
|
5156
|
+
shadowStroke: resolveStrokeStyle(shadowStroke, {
|
|
5157
|
+
alpha: 0.65,
|
|
5158
|
+
color: 0x000000,
|
|
5159
|
+
width: 4,
|
|
5160
|
+
}),
|
|
3323
5161
|
}),
|
|
3324
5162
|
}));
|
|
3325
5163
|
const markers = geometry.points.flatMap((point, index) => {
|
|
@@ -3341,11 +5179,11 @@ class BaseKeypointStyle {
|
|
|
3341
5179
|
shape: visibility === KeypointVisibility.Occluded
|
|
3342
5180
|
? KeypointMarkerShape.Cross
|
|
3343
5181
|
: KeypointMarkerShape.Circle,
|
|
3344
|
-
stroke: {
|
|
3345
|
-
alpha:
|
|
3346
|
-
color:
|
|
3347
|
-
width:
|
|
3348
|
-
},
|
|
5182
|
+
stroke: resolveStrokeStyle(markerStroke, {
|
|
5183
|
+
alpha: 1,
|
|
5184
|
+
color: 0xffffff,
|
|
5185
|
+
width: 2,
|
|
5186
|
+
}),
|
|
3349
5187
|
},
|
|
3350
5188
|
];
|
|
3351
5189
|
});
|
|
@@ -3426,6 +5264,9 @@ function createClassColorStyle(fill, labelBackground, labelText, stroke) {
|
|
|
3426
5264
|
}
|
|
3427
5265
|
|
|
3428
5266
|
const DEFAULT_BOX_CORNER_RADIUS = 1;
|
|
5267
|
+
const DEFAULT_ELLIPSE_AXIS_RATIO = 0.35;
|
|
5268
|
+
const DEFAULT_ELLIPSE_START_ANGLE = (-45 * Math.PI) / 180;
|
|
5269
|
+
const DEFAULT_ELLIPSE_END_ANGLE = (235 * Math.PI) / 180;
|
|
3429
5270
|
const DEFAULT_OUTLINE_WIDTH = 2;
|
|
3430
5271
|
const DEFAULT_FILL_ALPHA = 0.08;
|
|
3431
5272
|
const DEFAULT_MASK_FILL_ALPHA = 0.45;
|
|
@@ -3480,6 +5321,47 @@ function createDefaultBoxStyle(options = {}) {
|
|
|
3480
5321
|
}),
|
|
3481
5322
|
});
|
|
3482
5323
|
}
|
|
5324
|
+
/** Canonical opt-in BoxCornerAnnotator-style presentation. */
|
|
5325
|
+
function createDefaultBoxCornerStyle(options = {}) {
|
|
5326
|
+
const getClassColor = createClassColorResolver(options);
|
|
5327
|
+
return new BaseBoxCornerStyle({
|
|
5328
|
+
stroke: (detection) => ({ color: getClassColor(detection) }),
|
|
5329
|
+
});
|
|
5330
|
+
}
|
|
5331
|
+
/**
|
|
5332
|
+
* Matches the Python Supervision EllipseAnnotator: an elliptical footprint
|
|
5333
|
+
* arc under the detection box, swept from -45deg to 235deg, in the class
|
|
5334
|
+
* color.
|
|
5335
|
+
*/
|
|
5336
|
+
function createDefaultEllipseStyle(options = {}) {
|
|
5337
|
+
const getClassColor = createClassColorResolver(options);
|
|
5338
|
+
return {
|
|
5339
|
+
resolve(detection, context) {
|
|
5340
|
+
if (!detection.rect || context.hidden) {
|
|
5341
|
+
return undefined;
|
|
5342
|
+
}
|
|
5343
|
+
const radiusX = detection.rect.width / 2;
|
|
5344
|
+
const radiusY = radiusX * DEFAULT_ELLIPSE_AXIS_RATIO;
|
|
5345
|
+
return {
|
|
5346
|
+
center: {
|
|
5347
|
+
x: detection.rect.x,
|
|
5348
|
+
// Bottom-tangent so the arc hugs the detection instead of dipping
|
|
5349
|
+
// below its feet.
|
|
5350
|
+
y: detection.rect.y + detection.rect.height / 2 - radiusY,
|
|
5351
|
+
},
|
|
5352
|
+
endAngle: DEFAULT_ELLIPSE_END_ANGLE,
|
|
5353
|
+
radiusX,
|
|
5354
|
+
radiusY,
|
|
5355
|
+
startAngle: DEFAULT_ELLIPSE_START_ANGLE,
|
|
5356
|
+
stroke: {
|
|
5357
|
+
alpha: 1,
|
|
5358
|
+
color: getClassColor(detection),
|
|
5359
|
+
width: DEFAULT_OUTLINE_WIDTH,
|
|
5360
|
+
},
|
|
5361
|
+
};
|
|
5362
|
+
},
|
|
5363
|
+
};
|
|
5364
|
+
}
|
|
3483
5365
|
function createDefaultKeypointStyle(options = {}) {
|
|
3484
5366
|
const getClassColor = createClassColorResolver(options);
|
|
3485
5367
|
return new BaseKeypointStyle({
|
|
@@ -3538,6 +5420,31 @@ function createDefaultMaskStyle(options = {}) {
|
|
|
3538
5420
|
}),
|
|
3539
5421
|
});
|
|
3540
5422
|
}
|
|
5423
|
+
/** Canonical opt-in marker presentation. */
|
|
5424
|
+
function createDefaultMarkerStyle() {
|
|
5425
|
+
return new BaseMarkerStyle();
|
|
5426
|
+
}
|
|
5427
|
+
const DEFAULT_MASK_HALO_ALPHA = 0.6;
|
|
5428
|
+
const DEFAULT_MASK_HALO_SPREAD = 12;
|
|
5429
|
+
/**
|
|
5430
|
+
* Canonical mask halo: a class-colored glow that follows the mask silhouette.
|
|
5431
|
+
* Used when the `maskHalo` renderer is listed without an explicit style.
|
|
5432
|
+
*/
|
|
5433
|
+
function createDefaultMaskHaloStyle(options = {}) {
|
|
5434
|
+
const getClassColor = createClassColorResolver(options);
|
|
5435
|
+
return {
|
|
5436
|
+
resolve(detection, context) {
|
|
5437
|
+
if (!detection.mask || context.hidden) {
|
|
5438
|
+
return undefined;
|
|
5439
|
+
}
|
|
5440
|
+
return {
|
|
5441
|
+
alpha: DEFAULT_MASK_HALO_ALPHA,
|
|
5442
|
+
color: getClassColor(detection),
|
|
5443
|
+
spread: DEFAULT_MASK_HALO_SPREAD,
|
|
5444
|
+
};
|
|
5445
|
+
},
|
|
5446
|
+
};
|
|
5447
|
+
}
|
|
3541
5448
|
function createDefaultPolygonStyle(options = {}) {
|
|
3542
5449
|
const getClassColor = createClassColorResolver(options);
|
|
3543
5450
|
return new BasePolygonStyle({
|
|
@@ -3580,12 +5487,30 @@ function resolveDefaultClassColor(detection, getClassColor) {
|
|
|
3580
5487
|
*/
|
|
3581
5488
|
const annotationRendererKinds = [
|
|
3582
5489
|
"box",
|
|
5490
|
+
"box-corners",
|
|
5491
|
+
"ellipse",
|
|
3583
5492
|
"keypoints",
|
|
3584
5493
|
"label",
|
|
3585
5494
|
"mask",
|
|
5495
|
+
"maskHalo",
|
|
5496
|
+
"marker",
|
|
3586
5497
|
"polygon",
|
|
3587
5498
|
"polyline",
|
|
5499
|
+
"region",
|
|
3588
5500
|
];
|
|
5501
|
+
/** Sources currently supported by a region renderer. */
|
|
5502
|
+
const RegionRendererSourceKind = {
|
|
5503
|
+
Asset: "asset",
|
|
5504
|
+
};
|
|
5505
|
+
/** Media-space regions currently supported by a region renderer. */
|
|
5506
|
+
const RegionRendererRegionKind = {
|
|
5507
|
+
Bounds: "bounds",
|
|
5508
|
+
KeypointAnchor: "keypoint-anchor",
|
|
5509
|
+
};
|
|
5510
|
+
/** Composition modes currently supported by a region renderer. */
|
|
5511
|
+
const RegionRendererComposeMode = {
|
|
5512
|
+
Over: "over",
|
|
5513
|
+
};
|
|
3589
5514
|
/**
|
|
3590
5515
|
* Creates built-in annotation renderer descriptors for
|
|
3591
5516
|
* `MediaRendererPresentation.renderers`.
|
|
@@ -3596,11 +5521,16 @@ const annotationRendererKinds = [
|
|
|
3596
5521
|
*/
|
|
3597
5522
|
const annotationRenderers = {
|
|
3598
5523
|
box: (options) => createAnnotationRenderer("box", options),
|
|
5524
|
+
boxCorners: (options) => createAnnotationRenderer("box-corners", options),
|
|
5525
|
+
ellipse: (options) => createAnnotationRenderer("ellipse", options),
|
|
3599
5526
|
keypoints: (options) => createAnnotationRenderer("keypoints", options),
|
|
3600
5527
|
label: (options) => createAnnotationRenderer("label", options),
|
|
3601
5528
|
mask: (options) => createAnnotationRenderer("mask", options),
|
|
5529
|
+
maskHalo: (options) => createAnnotationRenderer("maskHalo", options),
|
|
5530
|
+
marker: (options) => createAnnotationRenderer("marker", options),
|
|
3602
5531
|
polygon: (options) => createAnnotationRenderer("polygon", options),
|
|
3603
5532
|
polyline: (options) => createAnnotationRenderer("polyline", options),
|
|
5533
|
+
region: (options) => ({ kind: "region", ...options }),
|
|
3604
5534
|
};
|
|
3605
5535
|
function createAnnotationRenderer(kind, options) {
|
|
3606
5536
|
// Every built-in renderer keeps its kind as its stable id. TypeScript cannot
|
|
@@ -3610,31 +5540,67 @@ function createAnnotationRenderer(kind, options) {
|
|
|
3610
5540
|
}
|
|
3611
5541
|
|
|
3612
5542
|
const annotationRendererRegistry = {
|
|
3613
|
-
box: {
|
|
5543
|
+
box: {
|
|
5544
|
+
cardinality: "singleton",
|
|
5545
|
+
createCanonicalStyle: createDefaultBoxStyle,
|
|
5546
|
+
styleField: "boxStyle",
|
|
5547
|
+
},
|
|
5548
|
+
"box-corners": {
|
|
5549
|
+
cardinality: "singleton",
|
|
5550
|
+
createCanonicalStyle: createDefaultBoxCornerStyle,
|
|
5551
|
+
styleField: "boxCornerStyle",
|
|
5552
|
+
},
|
|
5553
|
+
ellipse: {
|
|
5554
|
+
cardinality: "singleton",
|
|
5555
|
+
createCanonicalStyle: createDefaultEllipseStyle,
|
|
5556
|
+
styleField: "ellipseStyle",
|
|
5557
|
+
},
|
|
3614
5558
|
keypoints: {
|
|
5559
|
+
cardinality: "singleton",
|
|
3615
5560
|
createCanonicalStyle: createDefaultKeypointStyle,
|
|
3616
5561
|
styleField: "keypointStyle",
|
|
3617
5562
|
},
|
|
3618
5563
|
label: {
|
|
5564
|
+
cardinality: "singleton",
|
|
3619
5565
|
createCanonicalStyle: createDefaultLabelStyle,
|
|
3620
5566
|
styleField: "labelStyle",
|
|
3621
5567
|
},
|
|
3622
5568
|
mask: {
|
|
5569
|
+
cardinality: "singleton",
|
|
3623
5570
|
createCanonicalStyle: createDefaultMaskStyle,
|
|
3624
5571
|
styleField: "maskStyle",
|
|
3625
5572
|
},
|
|
5573
|
+
maskHalo: {
|
|
5574
|
+
cardinality: "singleton",
|
|
5575
|
+
createCanonicalStyle: () => createDefaultMaskHaloStyle(),
|
|
5576
|
+
styleField: "maskHaloStyle",
|
|
5577
|
+
},
|
|
5578
|
+
marker: {
|
|
5579
|
+
cardinality: "singleton",
|
|
5580
|
+
createCanonicalStyle: createDefaultMarkerStyle,
|
|
5581
|
+
styleField: "markerStyle",
|
|
5582
|
+
},
|
|
3626
5583
|
polygon: {
|
|
5584
|
+
cardinality: "singleton",
|
|
3627
5585
|
createCanonicalStyle: createDefaultPolygonStyle,
|
|
3628
5586
|
styleField: "polygonStyle",
|
|
3629
5587
|
},
|
|
3630
5588
|
polyline: {
|
|
5589
|
+
cardinality: "singleton",
|
|
3631
5590
|
createCanonicalStyle: createDefaultPolylineStyle,
|
|
3632
5591
|
styleField: "polylineStyle",
|
|
3633
5592
|
},
|
|
5593
|
+
region: { cardinality: "multiple" },
|
|
3634
5594
|
};
|
|
5595
|
+
function isStyleBackedAnnotationRendererKind(kind) {
|
|
5596
|
+
return annotationRendererRegistry[kind].cardinality === "singleton";
|
|
5597
|
+
}
|
|
3635
5598
|
/** Presentation fields owned by the given renderer kinds, in the same order. */
|
|
3636
5599
|
function resolveAnnotationRendererStyleFields(kinds) {
|
|
3637
|
-
return kinds.
|
|
5600
|
+
return kinds.flatMap((kind) => {
|
|
5601
|
+
const metadata = annotationRendererRegistry[kind];
|
|
5602
|
+
return "styleField" in metadata ? [metadata.styleField] : [];
|
|
5603
|
+
});
|
|
3638
5604
|
}
|
|
3639
5605
|
|
|
3640
5606
|
/**
|
|
@@ -3650,9 +5616,13 @@ function resolveAnnotationRendererPresentation(presentation) {
|
|
|
3650
5616
|
}
|
|
3651
5617
|
const resolved = {
|
|
3652
5618
|
boxStyle: null,
|
|
5619
|
+
boxCornerStyle: null,
|
|
5620
|
+
ellipseStyle: null,
|
|
3653
5621
|
keypointStyle: null,
|
|
3654
5622
|
labelStyle: null,
|
|
5623
|
+
maskHaloStyle: null,
|
|
3655
5624
|
maskStyle: null,
|
|
5625
|
+
markerStyle: null,
|
|
3656
5626
|
polygonStyle: null,
|
|
3657
5627
|
polylineStyle: null,
|
|
3658
5628
|
};
|
|
@@ -3663,17 +5633,23 @@ function resolveAnnotationRendererPresentation(presentation) {
|
|
|
3663
5633
|
throw new RangeError(`MediaRendererPresentation.renderers contains duplicate renderer id "${renderer.id}".`);
|
|
3664
5634
|
}
|
|
3665
5635
|
rendererIds.add(renderer.id);
|
|
3666
|
-
if (
|
|
5636
|
+
if (annotationRendererRegistry[renderer.kind].cardinality === "singleton" &&
|
|
5637
|
+
rendererKinds.has(renderer.kind)) {
|
|
3667
5638
|
throw new RangeError(`MediaRendererPresentation.renderers contains duplicate renderer kind "${renderer.kind}".`);
|
|
3668
5639
|
}
|
|
3669
5640
|
rendererKinds.add(renderer.kind);
|
|
3670
|
-
|
|
5641
|
+
if (isStyleBackedAnnotationRenderer(renderer)) {
|
|
5642
|
+
applyRendererStyle(resolved, presentation, renderer);
|
|
5643
|
+
}
|
|
3671
5644
|
}
|
|
3672
5645
|
return {
|
|
3673
5646
|
...presentation,
|
|
3674
5647
|
...resolved,
|
|
3675
5648
|
};
|
|
3676
5649
|
}
|
|
5650
|
+
function isStyleBackedAnnotationRenderer(renderer) {
|
|
5651
|
+
return isStyleBackedAnnotationRendererKind(renderer.kind);
|
|
5652
|
+
}
|
|
3677
5653
|
function applyRendererStyle(resolved, configured, renderer) {
|
|
3678
5654
|
const { createCanonicalStyle, styleField } = annotationRendererRegistry[renderer.kind];
|
|
3679
5655
|
const configuredStyle = configured[styleField];
|
|
@@ -3703,12 +5679,24 @@ function createSourceAwarePresentation(globalPresentation = {}, sources, options
|
|
|
3703
5679
|
boxStyle: shouldApplySourceStyle("boxStyle")
|
|
3704
5680
|
? new SourceAwareBoxStyle(normalizeGlobalBoxStyle(globalPresentation.boxStyle), sourcePresentations)
|
|
3705
5681
|
: globalPresentation.boxStyle,
|
|
5682
|
+
boxCornerStyle: shouldApplySourceStyle("boxCornerStyle")
|
|
5683
|
+
? new SourceAwareBoxCornerStyle(globalPresentation.boxCornerStyle ?? null, sourcePresentations)
|
|
5684
|
+
: globalPresentation.boxCornerStyle,
|
|
5685
|
+
ellipseStyle: shouldApplySourceStyle("ellipseStyle")
|
|
5686
|
+
? new SourceAwareEllipseStyle(globalPresentation.ellipseStyle ?? null, sourcePresentations)
|
|
5687
|
+
: globalPresentation.ellipseStyle,
|
|
3706
5688
|
labelStyle: shouldApplySourceStyle("labelStyle")
|
|
3707
5689
|
? new SourceAwareLabelStyle(normalizeGlobalLabelStyle(globalPresentation.labelStyle), sourcePresentations)
|
|
3708
5690
|
: globalPresentation.labelStyle,
|
|
3709
5691
|
maskStyle: shouldApplySourceStyle("maskStyle")
|
|
3710
5692
|
? new SourceAwareMaskStyle(normalizeGlobalMaskStyle(globalPresentation.maskStyle), sourcePresentations)
|
|
3711
5693
|
: globalPresentation.maskStyle,
|
|
5694
|
+
markerStyle: shouldApplySourceStyle("markerStyle")
|
|
5695
|
+
? new SourceAwareMarkerStyle(normalizeGlobalMarkerStyle(globalPresentation.markerStyle), sourcePresentations)
|
|
5696
|
+
: globalPresentation.markerStyle,
|
|
5697
|
+
maskHaloStyle: shouldApplySourceStyle("maskHaloStyle")
|
|
5698
|
+
? new SourceAwareMaskHaloStyle(normalizeGlobalMaskHaloStyle(globalPresentation.maskHaloStyle), sourcePresentations)
|
|
5699
|
+
: globalPresentation.maskHaloStyle,
|
|
3712
5700
|
polygonStyle: shouldApplySourceStyle("polygonStyle")
|
|
3713
5701
|
? new SourceAwarePolygonStyle(normalizeGlobalPolygonStyle(globalPresentation.polygonStyle), sourcePresentations)
|
|
3714
5702
|
: globalPresentation.polygonStyle,
|
|
@@ -3720,58 +5708,7 @@ function createSourceAwarePresentation(globalPresentation = {}, sources, options
|
|
|
3720
5708
|
: globalPresentation.keypointStyle,
|
|
3721
5709
|
};
|
|
3722
5710
|
}
|
|
3723
|
-
class
|
|
3724
|
-
globalStyle;
|
|
3725
|
-
sourcePresentations;
|
|
3726
|
-
constructor(globalStyle, sourcePresentations) {
|
|
3727
|
-
this.globalStyle = globalStyle;
|
|
3728
|
-
this.sourcePresentations = sourcePresentations;
|
|
3729
|
-
}
|
|
3730
|
-
resolve(detection, context) {
|
|
3731
|
-
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "boxStyle");
|
|
3732
|
-
return style?.resolve(detection, context);
|
|
3733
|
-
}
|
|
3734
|
-
}
|
|
3735
|
-
class SourceAwareLabelStyle {
|
|
3736
|
-
globalStyle;
|
|
3737
|
-
sourcePresentations;
|
|
3738
|
-
constructor(globalStyle, sourcePresentations) {
|
|
3739
|
-
this.globalStyle = globalStyle;
|
|
3740
|
-
this.sourcePresentations = sourcePresentations;
|
|
3741
|
-
}
|
|
3742
|
-
resolve(detection, context) {
|
|
3743
|
-
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "labelStyle");
|
|
3744
|
-
return style?.resolve(detection, context);
|
|
3745
|
-
}
|
|
3746
|
-
}
|
|
3747
|
-
class SourceAwareMaskStyle {
|
|
3748
|
-
globalStyle;
|
|
3749
|
-
sourcePresentations;
|
|
3750
|
-
artifactKey = undefined;
|
|
3751
|
-
opacity;
|
|
3752
|
-
constructor(globalStyle, sourcePresentations) {
|
|
3753
|
-
this.globalStyle = globalStyle;
|
|
3754
|
-
this.sourcePresentations = sourcePresentations;
|
|
3755
|
-
this.opacity = globalStyle?.opacity;
|
|
3756
|
-
}
|
|
3757
|
-
resolve(detection, context) {
|
|
3758
|
-
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "maskStyle");
|
|
3759
|
-
return style?.resolve(detection, context);
|
|
3760
|
-
}
|
|
3761
|
-
}
|
|
3762
|
-
class SourceAwarePolygonStyle {
|
|
3763
|
-
globalStyle;
|
|
3764
|
-
sourcePresentations;
|
|
3765
|
-
constructor(globalStyle, sourcePresentations) {
|
|
3766
|
-
this.globalStyle = globalStyle;
|
|
3767
|
-
this.sourcePresentations = sourcePresentations;
|
|
3768
|
-
}
|
|
3769
|
-
resolve(detection, context) {
|
|
3770
|
-
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "polygonStyle");
|
|
3771
|
-
return style?.resolve(detection, context);
|
|
3772
|
-
}
|
|
3773
|
-
}
|
|
3774
|
-
class SourceAwarePolylineStyle {
|
|
5711
|
+
class SourceAwareBoxCornerStyle {
|
|
3775
5712
|
globalStyle;
|
|
3776
5713
|
sourcePresentations;
|
|
3777
5714
|
constructor(globalStyle, sourcePresentations) {
|
|
@@ -3779,11 +5716,10 @@ class SourceAwarePolylineStyle {
|
|
|
3779
5716
|
this.sourcePresentations = sourcePresentations;
|
|
3780
5717
|
}
|
|
3781
5718
|
resolve(detection, context) {
|
|
3782
|
-
|
|
3783
|
-
return style?.resolve(detection, context);
|
|
5719
|
+
return resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "boxCornerStyle")?.resolve(detection, context);
|
|
3784
5720
|
}
|
|
3785
5721
|
}
|
|
3786
|
-
class
|
|
5722
|
+
class SourceAwareBoxStyle {
|
|
3787
5723
|
globalStyle;
|
|
3788
5724
|
sourcePresentations;
|
|
3789
5725
|
constructor(globalStyle, sourcePresentations) {
|
|
@@ -3791,274 +5727,143 @@ class SourceAwareKeypointStyle {
|
|
|
3791
5727
|
this.sourcePresentations = sourcePresentations;
|
|
3792
5728
|
}
|
|
3793
5729
|
resolve(detection, context) {
|
|
3794
|
-
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "
|
|
5730
|
+
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "boxStyle");
|
|
3795
5731
|
return style?.resolve(detection, context);
|
|
3796
5732
|
}
|
|
3797
|
-
}
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
function normalizeGlobalBoxStyle(style) {
|
|
3809
|
-
return style === undefined ? new BaseBoxStyle() : style;
|
|
3810
|
-
}
|
|
3811
|
-
function normalizeGlobalLabelStyle(style) {
|
|
3812
|
-
return style === undefined ? new BaseLabelStyle() : style;
|
|
3813
|
-
}
|
|
3814
|
-
function normalizeGlobalMaskStyle(style) {
|
|
3815
|
-
return style === undefined ? new BaseMaskStyle() : style;
|
|
3816
|
-
}
|
|
3817
|
-
function normalizeGlobalPolygonStyle(style) {
|
|
3818
|
-
return style === undefined ? new BasePolygonStyle() : style;
|
|
3819
|
-
}
|
|
3820
|
-
function normalizeGlobalPolylineStyle(style) {
|
|
3821
|
-
return style === undefined ? new BasePolylineStyle() : style;
|
|
3822
|
-
}
|
|
3823
|
-
function normalizeGlobalKeypointStyle(style) {
|
|
3824
|
-
return style === undefined ? new BaseKeypointStyle() : style;
|
|
3825
|
-
}
|
|
3826
|
-
|
|
3827
|
-
var DetectionMaskPayloadFormat;
|
|
3828
|
-
(function (DetectionMaskPayloadFormat) {
|
|
3829
|
-
DetectionMaskPayloadFormat["RawCocoRle"] = "rawCocoRle";
|
|
3830
|
-
DetectionMaskPayloadFormat["DeflatedBase64"] = "deflatedBase64";
|
|
3831
|
-
})(DetectionMaskPayloadFormat || (DetectionMaskPayloadFormat = {}));
|
|
3832
|
-
function encodeBinaryMask(data, width, height) {
|
|
3833
|
-
assertMaskDimensions(data, width, height);
|
|
3834
|
-
const runs = [];
|
|
3835
|
-
let currentValue = 0;
|
|
3836
|
-
let runLength = 0;
|
|
3837
|
-
for (let x = 0; x < width; x += 1) {
|
|
3838
|
-
for (let y = 0; y < height; y += 1) {
|
|
3839
|
-
const value = data[y * width + x] ? 1 : 0;
|
|
3840
|
-
if (value === currentValue) {
|
|
3841
|
-
runLength += 1;
|
|
3842
|
-
}
|
|
3843
|
-
else {
|
|
3844
|
-
runs.push(runLength);
|
|
3845
|
-
currentValue = value;
|
|
3846
|
-
runLength = 1;
|
|
3847
|
-
}
|
|
3848
|
-
}
|
|
3849
|
-
}
|
|
3850
|
-
runs.push(runLength);
|
|
3851
|
-
return {
|
|
3852
|
-
counts: encodeCompressedRleCounts(runs),
|
|
3853
|
-
encoding: DetectionMaskEncoding.CompressedRle,
|
|
3854
|
-
height,
|
|
3855
|
-
width,
|
|
3856
|
-
};
|
|
3857
|
-
}
|
|
3858
|
-
/** Encodes a binary mask and derives its bounds in the same raster traversal. */
|
|
3859
|
-
function encodeBinaryMaskWithBounds(data, width, height) {
|
|
3860
|
-
assertMaskDimensions(data, width, height);
|
|
3861
|
-
const runs = [];
|
|
3862
|
-
let currentValue = 0;
|
|
3863
|
-
let runLength = 0;
|
|
3864
|
-
let minX = width;
|
|
3865
|
-
let minY = height;
|
|
3866
|
-
let maxX = -1;
|
|
3867
|
-
let maxY = -1;
|
|
3868
|
-
for (let x = 0; x < width; x += 1) {
|
|
3869
|
-
for (let y = 0; y < height; y += 1) {
|
|
3870
|
-
const value = data[y * width + x] ? 1 : 0;
|
|
3871
|
-
if (value) {
|
|
3872
|
-
minX = Math.min(minX, x);
|
|
3873
|
-
minY = Math.min(minY, y);
|
|
3874
|
-
maxX = Math.max(maxX, x);
|
|
3875
|
-
maxY = Math.max(maxY, y);
|
|
3876
|
-
}
|
|
3877
|
-
if (value === currentValue) {
|
|
3878
|
-
runLength += 1;
|
|
3879
|
-
}
|
|
3880
|
-
else {
|
|
3881
|
-
runs.push(runLength);
|
|
3882
|
-
currentValue = value;
|
|
3883
|
-
runLength = 1;
|
|
3884
|
-
}
|
|
3885
|
-
}
|
|
3886
|
-
}
|
|
3887
|
-
runs.push(runLength);
|
|
3888
|
-
return {
|
|
3889
|
-
bounds: maxX < 0
|
|
3890
|
-
? null
|
|
3891
|
-
: {
|
|
3892
|
-
height: maxY - minY + 1,
|
|
3893
|
-
width: maxX - minX + 1,
|
|
3894
|
-
x: minX + (maxX - minX + 1) / 2,
|
|
3895
|
-
y: minY + (maxY - minY + 1) / 2,
|
|
3896
|
-
},
|
|
3897
|
-
mask: {
|
|
3898
|
-
counts: encodeCompressedRleCounts(runs),
|
|
3899
|
-
encoding: DetectionMaskEncoding.CompressedRle,
|
|
3900
|
-
height,
|
|
3901
|
-
width,
|
|
3902
|
-
},
|
|
3903
|
-
};
|
|
3904
|
-
}
|
|
3905
|
-
function encodeDetectionMaskPayload(mask, codec) {
|
|
3906
|
-
return codec ? codec.deflate(mask.counts) : mask.counts;
|
|
3907
|
-
}
|
|
3908
|
-
function decodeDetectionMaskPayload(payload, width, height, options = {}) {
|
|
3909
|
-
const format = options.format ??
|
|
3910
|
-
(isDeflatedBase64DetectionMaskPayload(payload)
|
|
3911
|
-
? DetectionMaskPayloadFormat.DeflatedBase64
|
|
3912
|
-
: DetectionMaskPayloadFormat.RawCocoRle);
|
|
3913
|
-
if (format === DetectionMaskPayloadFormat.DeflatedBase64 && !options.codec) {
|
|
3914
|
-
throw new Error("A detection mask compression codec is required for deflated payloads.");
|
|
3915
|
-
}
|
|
3916
|
-
return {
|
|
3917
|
-
counts: format === DetectionMaskPayloadFormat.DeflatedBase64
|
|
3918
|
-
? options.codec.inflate(payload)
|
|
3919
|
-
: payload,
|
|
3920
|
-
encoding: DetectionMaskEncoding.CompressedRle,
|
|
3921
|
-
height,
|
|
3922
|
-
width,
|
|
3923
|
-
};
|
|
3924
|
-
}
|
|
3925
|
-
/** Matches the annotation editor's legacy transport-format heuristic. */
|
|
3926
|
-
function isDeflatedBase64DetectionMaskPayload(value) {
|
|
3927
|
-
return value.length > 100 && /^[A-Za-z0-9+/]+={0,2}$/.test(value);
|
|
3928
|
-
}
|
|
3929
|
-
function computeMaskBounds(data, width, height) {
|
|
3930
|
-
assertMaskDimensions(data, width, height);
|
|
3931
|
-
let minX = width;
|
|
3932
|
-
let minY = height;
|
|
3933
|
-
let maxX = -1;
|
|
3934
|
-
let maxY = -1;
|
|
3935
|
-
for (let y = 0; y < height; y += 1) {
|
|
3936
|
-
for (let x = 0; x < width; x += 1) {
|
|
3937
|
-
if (!data[y * width + x]) {
|
|
3938
|
-
continue;
|
|
3939
|
-
}
|
|
3940
|
-
minX = Math.min(minX, x);
|
|
3941
|
-
minY = Math.min(minY, y);
|
|
3942
|
-
maxX = Math.max(maxX, x);
|
|
3943
|
-
maxY = Math.max(maxY, y);
|
|
3944
|
-
}
|
|
3945
|
-
}
|
|
3946
|
-
if (maxX < 0) {
|
|
3947
|
-
return null;
|
|
5733
|
+
}
|
|
5734
|
+
class SourceAwareEllipseStyle {
|
|
5735
|
+
globalStyle;
|
|
5736
|
+
sourcePresentations;
|
|
5737
|
+
constructor(globalStyle, sourcePresentations) {
|
|
5738
|
+
this.globalStyle = globalStyle;
|
|
5739
|
+
this.sourcePresentations = sourcePresentations;
|
|
5740
|
+
}
|
|
5741
|
+
resolve(detection, context) {
|
|
5742
|
+
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "ellipseStyle");
|
|
5743
|
+
return style?.resolve(detection, context);
|
|
3948
5744
|
}
|
|
3949
|
-
const boundsWidth = maxX - minX + 1;
|
|
3950
|
-
const boundsHeight = maxY - minY + 1;
|
|
3951
|
-
return {
|
|
3952
|
-
height: boundsHeight,
|
|
3953
|
-
width: boundsWidth,
|
|
3954
|
-
x: minX + boundsWidth / 2,
|
|
3955
|
-
y: minY + boundsHeight / 2,
|
|
3956
|
-
};
|
|
3957
5745
|
}
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
5746
|
+
class SourceAwareLabelStyle {
|
|
5747
|
+
globalStyle;
|
|
5748
|
+
sourcePresentations;
|
|
5749
|
+
constructor(globalStyle, sourcePresentations) {
|
|
5750
|
+
this.globalStyle = globalStyle;
|
|
5751
|
+
this.sourcePresentations = sourcePresentations;
|
|
5752
|
+
}
|
|
5753
|
+
resolve(detection, context) {
|
|
5754
|
+
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "labelStyle");
|
|
5755
|
+
return style?.resolve(detection, context);
|
|
5756
|
+
}
|
|
3962
5757
|
}
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
!data[offset - 1] ||
|
|
3977
|
-
!data[offset + 1] ||
|
|
3978
|
-
!data[offset - width] ||
|
|
3979
|
-
!data[offset + width]) {
|
|
3980
|
-
borders[offset] = 1;
|
|
3981
|
-
}
|
|
3982
|
-
}
|
|
5758
|
+
class SourceAwareMaskStyle {
|
|
5759
|
+
globalStyle;
|
|
5760
|
+
sourcePresentations;
|
|
5761
|
+
artifactKey = undefined;
|
|
5762
|
+
opacity;
|
|
5763
|
+
constructor(globalStyle, sourcePresentations) {
|
|
5764
|
+
this.globalStyle = globalStyle;
|
|
5765
|
+
this.sourcePresentations = sourcePresentations;
|
|
5766
|
+
this.opacity = globalStyle?.opacity;
|
|
5767
|
+
}
|
|
5768
|
+
resolve(detection, context) {
|
|
5769
|
+
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "maskStyle");
|
|
5770
|
+
return style?.resolve(detection, context);
|
|
3983
5771
|
}
|
|
3984
|
-
return borders;
|
|
3985
5772
|
}
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
left = left === -1 ? x : left;
|
|
3997
|
-
right = x;
|
|
3998
|
-
}
|
|
3999
|
-
}
|
|
4000
|
-
if (left !== -1) {
|
|
4001
|
-
leftEdge.push({ x: left, y });
|
|
4002
|
-
rightEdge.push({ x: right, y });
|
|
4003
|
-
}
|
|
5773
|
+
class SourceAwareMaskHaloStyle {
|
|
5774
|
+
globalStyle;
|
|
5775
|
+
sourcePresentations;
|
|
5776
|
+
constructor(globalStyle, sourcePresentations) {
|
|
5777
|
+
this.globalStyle = globalStyle;
|
|
5778
|
+
this.sourcePresentations = sourcePresentations;
|
|
5779
|
+
}
|
|
5780
|
+
resolve(detection, context) {
|
|
5781
|
+
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "maskHaloStyle");
|
|
5782
|
+
return style?.resolve(detection, context);
|
|
4004
5783
|
}
|
|
4005
|
-
return leftEdge.length < 2
|
|
4006
|
-
? undefined
|
|
4007
|
-
: [...leftEdge, ...rightEdge.reverse()];
|
|
4008
5784
|
}
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
}
|
|
4020
|
-
if (x >= width) {
|
|
4021
|
-
break;
|
|
4022
|
-
}
|
|
4023
|
-
const startX = x;
|
|
4024
|
-
while (x < width && data[y * width + x]) {
|
|
4025
|
-
x += 1;
|
|
4026
|
-
}
|
|
4027
|
-
const runWidth = x - startX;
|
|
4028
|
-
const key = `${startX}:${runWidth}`;
|
|
4029
|
-
const openRect = openRects.get(key);
|
|
4030
|
-
activeSpans.add(key);
|
|
4031
|
-
if (openRect && openRect.y + openRect.height === y) {
|
|
4032
|
-
openRects.set(key, { ...openRect, height: openRect.height + 1 });
|
|
4033
|
-
}
|
|
4034
|
-
else {
|
|
4035
|
-
if (openRect) {
|
|
4036
|
-
rects.push(openRect);
|
|
4037
|
-
}
|
|
4038
|
-
openRects.set(key, { height: 1, width: runWidth, x: startX, y });
|
|
4039
|
-
}
|
|
4040
|
-
}
|
|
4041
|
-
for (const [key, openRect] of openRects) {
|
|
4042
|
-
if (!activeSpans.has(key)) {
|
|
4043
|
-
rects.push(openRect);
|
|
4044
|
-
openRects.delete(key);
|
|
4045
|
-
}
|
|
4046
|
-
}
|
|
5785
|
+
class SourceAwareMarkerStyle {
|
|
5786
|
+
globalStyle;
|
|
5787
|
+
sourcePresentations;
|
|
5788
|
+
constructor(globalStyle, sourcePresentations) {
|
|
5789
|
+
this.globalStyle = globalStyle;
|
|
5790
|
+
this.sourcePresentations = sourcePresentations;
|
|
5791
|
+
}
|
|
5792
|
+
resolve(detection, context) {
|
|
5793
|
+
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "markerStyle");
|
|
5794
|
+
return style?.resolve(detection, context);
|
|
4047
5795
|
}
|
|
4048
|
-
rects.push(...openRects.values());
|
|
4049
|
-
return rects.length > 0 ? rects : undefined;
|
|
4050
5796
|
}
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
5797
|
+
class SourceAwarePolygonStyle {
|
|
5798
|
+
globalStyle;
|
|
5799
|
+
sourcePresentations;
|
|
5800
|
+
constructor(globalStyle, sourcePresentations) {
|
|
5801
|
+
this.globalStyle = globalStyle;
|
|
5802
|
+
this.sourcePresentations = sourcePresentations;
|
|
4054
5803
|
}
|
|
4055
|
-
|
|
4056
|
-
|
|
5804
|
+
resolve(detection, context) {
|
|
5805
|
+
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "polygonStyle");
|
|
5806
|
+
return style?.resolve(detection, context);
|
|
4057
5807
|
}
|
|
4058
|
-
|
|
4059
|
-
|
|
5808
|
+
}
|
|
5809
|
+
class SourceAwarePolylineStyle {
|
|
5810
|
+
globalStyle;
|
|
5811
|
+
sourcePresentations;
|
|
5812
|
+
constructor(globalStyle, sourcePresentations) {
|
|
5813
|
+
this.globalStyle = globalStyle;
|
|
5814
|
+
this.sourcePresentations = sourcePresentations;
|
|
5815
|
+
}
|
|
5816
|
+
resolve(detection, context) {
|
|
5817
|
+
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "polylineStyle");
|
|
5818
|
+
return style?.resolve(detection, context);
|
|
5819
|
+
}
|
|
5820
|
+
}
|
|
5821
|
+
class SourceAwareKeypointStyle {
|
|
5822
|
+
globalStyle;
|
|
5823
|
+
sourcePresentations;
|
|
5824
|
+
constructor(globalStyle, sourcePresentations) {
|
|
5825
|
+
this.globalStyle = globalStyle;
|
|
5826
|
+
this.sourcePresentations = sourcePresentations;
|
|
5827
|
+
}
|
|
5828
|
+
resolve(detection, context) {
|
|
5829
|
+
const style = resolveSourceStyle(detection, this.globalStyle, this.sourcePresentations, "keypointStyle");
|
|
5830
|
+
return style?.resolve(detection, context);
|
|
4060
5831
|
}
|
|
4061
5832
|
}
|
|
5833
|
+
function hasSourceStyle(sources, key) {
|
|
5834
|
+
return sources.some((source) => source.presentation?.[key] !== undefined);
|
|
5835
|
+
}
|
|
5836
|
+
function resolveSourceStyle(detection, globalStyle, sourcePresentations, key) {
|
|
5837
|
+
const sourcePresentation = detection.sourceId
|
|
5838
|
+
? sourcePresentations.get(detection.sourceId)
|
|
5839
|
+
: undefined;
|
|
5840
|
+
const sourceStyle = sourcePresentation?.[key];
|
|
5841
|
+
return sourceStyle === undefined ? globalStyle : sourceStyle;
|
|
5842
|
+
}
|
|
5843
|
+
function normalizeGlobalBoxStyle(style) {
|
|
5844
|
+
return style === undefined ? new BaseBoxStyle() : style;
|
|
5845
|
+
}
|
|
5846
|
+
function normalizeGlobalLabelStyle(style) {
|
|
5847
|
+
return style === undefined ? new BaseLabelStyle() : style;
|
|
5848
|
+
}
|
|
5849
|
+
function normalizeGlobalMaskStyle(style) {
|
|
5850
|
+
return style === undefined ? new BaseMaskStyle() : style;
|
|
5851
|
+
}
|
|
5852
|
+
function normalizeGlobalMaskHaloStyle(style) {
|
|
5853
|
+
return style === undefined ? createDefaultMaskHaloStyle() : style;
|
|
5854
|
+
}
|
|
5855
|
+
function normalizeGlobalMarkerStyle(style) {
|
|
5856
|
+
return style === undefined ? new BaseMarkerStyle() : style;
|
|
5857
|
+
}
|
|
5858
|
+
function normalizeGlobalPolygonStyle(style) {
|
|
5859
|
+
return style === undefined ? new BasePolygonStyle() : style;
|
|
5860
|
+
}
|
|
5861
|
+
function normalizeGlobalPolylineStyle(style) {
|
|
5862
|
+
return style === undefined ? new BasePolylineStyle() : style;
|
|
5863
|
+
}
|
|
5864
|
+
function normalizeGlobalKeypointStyle(style) {
|
|
5865
|
+
return style === undefined ? new BaseKeypointStyle() : style;
|
|
5866
|
+
}
|
|
4062
5867
|
|
|
4063
5868
|
function rectToPolygon(rect) {
|
|
4064
5869
|
const halfWidth = rect.width / 2;
|
|
@@ -4310,6 +6115,180 @@ function writePaletteEntry(palette, id, color, alpha) {
|
|
|
4310
6115
|
palette[offset + 3] = Math.max(0, Math.min(alpha, 1));
|
|
4311
6116
|
}
|
|
4312
6117
|
|
|
6118
|
+
const DEFAULT_ELLIPSE_SEGMENT_COUNT = 48;
|
|
6119
|
+
const FULL_TURN = Math.PI * 2;
|
|
6120
|
+
const GEOMETRY_EPSILON = 1e-9;
|
|
6121
|
+
const MIN_ELLIPSE_SEGMENT_COUNT = 4;
|
|
6122
|
+
const MAX_ELLIPSE_SEGMENT_COUNT = 512;
|
|
6123
|
+
/**
|
|
6124
|
+
* Samples an ellipse or elliptical arc into a deterministic polyline so every
|
|
6125
|
+
* backend rasterizes the same structural geometry, including dashed strokes.
|
|
6126
|
+
*
|
|
6127
|
+
* A closed ellipse returns `segmentCount` points without repeating the first
|
|
6128
|
+
* point; an open arc returns `segmentCount + 1` points including both
|
|
6129
|
+
* endpoints.
|
|
6130
|
+
*/
|
|
6131
|
+
function sampleEllipseArc(ellipse, segmentCount = DEFAULT_ELLIPSE_SEGMENT_COUNT) {
|
|
6132
|
+
validateEllipseGeometry(ellipse);
|
|
6133
|
+
assertIntegerAtLeast(segmentCount, MIN_ELLIPSE_SEGMENT_COUNT, "segmentCount");
|
|
6134
|
+
const rotation = ellipse.rotation ?? 0;
|
|
6135
|
+
const startAngle = ellipse.startAngle ?? 0;
|
|
6136
|
+
const endAngle = ellipse.endAngle ?? startAngle + FULL_TURN;
|
|
6137
|
+
const range = endAngle - startAngle;
|
|
6138
|
+
const closed = ellipse.startAngle === undefined;
|
|
6139
|
+
const pointCount = closed ? segmentCount : segmentCount + 1;
|
|
6140
|
+
const cosRotation = Math.cos(rotation);
|
|
6141
|
+
const sinRotation = Math.sin(rotation);
|
|
6142
|
+
const points = [];
|
|
6143
|
+
for (let index = 0; index < pointCount; index += 1) {
|
|
6144
|
+
const angle = startAngle + (range * index) / segmentCount;
|
|
6145
|
+
const localX = Math.cos(angle) * ellipse.radiusX;
|
|
6146
|
+
const localY = Math.sin(angle) * ellipse.radiusY;
|
|
6147
|
+
points.push({
|
|
6148
|
+
x: ellipse.center.x + localX * cosRotation - localY * sinRotation,
|
|
6149
|
+
y: ellipse.center.y + localX * sinRotation + localY * cosRotation,
|
|
6150
|
+
});
|
|
6151
|
+
}
|
|
6152
|
+
return { closed, points };
|
|
6153
|
+
}
|
|
6154
|
+
/**
|
|
6155
|
+
* Chooses a bounded segment count from the ellipse's on-screen radius. The
|
|
6156
|
+
* result targets sub-pixel chord error without making tiny markers expensive.
|
|
6157
|
+
*/
|
|
6158
|
+
function resolveEllipseSegmentCount(ellipse, viewportScale = 1, maxErrorPixels = 0.5) {
|
|
6159
|
+
validateEllipseGeometry(ellipse);
|
|
6160
|
+
assertPositiveFinite(viewportScale, "viewportScale");
|
|
6161
|
+
assertPositiveFinite(maxErrorPixels, "maxErrorPixels");
|
|
6162
|
+
const screenRadius = Math.max(ellipse.radiusX, ellipse.radiusY) * viewportScale;
|
|
6163
|
+
assertPositiveFinite(screenRadius, "screenRadius");
|
|
6164
|
+
const clampedError = Math.min(maxErrorPixels, screenRadius);
|
|
6165
|
+
const maxStep = clampedError === screenRadius
|
|
6166
|
+
? Math.PI / 2
|
|
6167
|
+
: 2 * Math.acos(1 - clampedError / screenRadius);
|
|
6168
|
+
const startAngle = ellipse.startAngle ?? 0;
|
|
6169
|
+
const endAngle = ellipse.endAngle ?? startAngle + FULL_TURN;
|
|
6170
|
+
const arcLength = Math.abs(endAngle - startAngle);
|
|
6171
|
+
const segmentCount = Math.ceil(arcLength / maxStep);
|
|
6172
|
+
return Math.min(MAX_ELLIPSE_SEGMENT_COUNT, Math.max(MIN_ELLIPSE_SEGMENT_COUNT, segmentCount));
|
|
6173
|
+
}
|
|
6174
|
+
/**
|
|
6175
|
+
* Resolves a marker instruction into media-space geometry. Screen-space sizes
|
|
6176
|
+
* are divided by the viewport scale, matching stroke-width semantics.
|
|
6177
|
+
*/
|
|
6178
|
+
function resolveMarkerGeometry(marker, viewportScale = 1) {
|
|
6179
|
+
assertFinitePoint(marker.center, "center");
|
|
6180
|
+
assertPositiveFinite(marker.size, "size");
|
|
6181
|
+
assertPositiveFinite(viewportScale, "viewportScale");
|
|
6182
|
+
if (marker.rotation !== undefined) {
|
|
6183
|
+
assertFinite(marker.rotation, "rotation");
|
|
6184
|
+
}
|
|
6185
|
+
const size = marker.sizeSpace === MarkerSizeSpace.Screen
|
|
6186
|
+
? marker.size / viewportScale
|
|
6187
|
+
: marker.size;
|
|
6188
|
+
const radius = size / 2;
|
|
6189
|
+
const rotation = marker.rotation ?? 0;
|
|
6190
|
+
if (marker.shape === MarkerShape.Circle) {
|
|
6191
|
+
return { center: marker.center, kind: "circle", radius };
|
|
6192
|
+
}
|
|
6193
|
+
if (marker.shape === MarkerShape.Cross) {
|
|
6194
|
+
return {
|
|
6195
|
+
closed: false,
|
|
6196
|
+
kind: "subpaths",
|
|
6197
|
+
subpaths: [
|
|
6198
|
+
[
|
|
6199
|
+
rotatePoint(marker.center, -radius, -radius, rotation),
|
|
6200
|
+
rotatePoint(marker.center, radius, radius, rotation),
|
|
6201
|
+
],
|
|
6202
|
+
[
|
|
6203
|
+
rotatePoint(marker.center, radius, -radius, rotation),
|
|
6204
|
+
rotatePoint(marker.center, -radius, radius, rotation),
|
|
6205
|
+
],
|
|
6206
|
+
],
|
|
6207
|
+
};
|
|
6208
|
+
}
|
|
6209
|
+
if (marker.shape === MarkerShape.Square) {
|
|
6210
|
+
return {
|
|
6211
|
+
closed: true,
|
|
6212
|
+
kind: "subpaths",
|
|
6213
|
+
subpaths: [
|
|
6214
|
+
[
|
|
6215
|
+
rotatePoint(marker.center, -radius, -radius, rotation),
|
|
6216
|
+
rotatePoint(marker.center, radius, -radius, rotation),
|
|
6217
|
+
rotatePoint(marker.center, radius, radius, rotation),
|
|
6218
|
+
rotatePoint(marker.center, -radius, radius, rotation),
|
|
6219
|
+
],
|
|
6220
|
+
],
|
|
6221
|
+
};
|
|
6222
|
+
}
|
|
6223
|
+
// Triangle: equilateral, inscribed in the marker circle, pointing toward
|
|
6224
|
+
// positive y at rotation 0.
|
|
6225
|
+
const baseOffsetY = -radius / 2;
|
|
6226
|
+
const baseOffsetX = radius * (Math.sqrt(3) / 2);
|
|
6227
|
+
return {
|
|
6228
|
+
closed: true,
|
|
6229
|
+
kind: "subpaths",
|
|
6230
|
+
subpaths: [
|
|
6231
|
+
[
|
|
6232
|
+
rotatePoint(marker.center, 0, radius, rotation),
|
|
6233
|
+
rotatePoint(marker.center, baseOffsetX, baseOffsetY, rotation),
|
|
6234
|
+
rotatePoint(marker.center, -baseOffsetX, baseOffsetY, rotation),
|
|
6235
|
+
],
|
|
6236
|
+
],
|
|
6237
|
+
};
|
|
6238
|
+
}
|
|
6239
|
+
function validateEllipseGeometry(ellipse) {
|
|
6240
|
+
assertFinitePoint(ellipse.center, "center");
|
|
6241
|
+
assertPositiveFinite(ellipse.radiusX, "radiusX");
|
|
6242
|
+
assertPositiveFinite(ellipse.radiusY, "radiusY");
|
|
6243
|
+
if (ellipse.rotation !== undefined) {
|
|
6244
|
+
assertFinite(ellipse.rotation, "rotation");
|
|
6245
|
+
}
|
|
6246
|
+
const hasStart = ellipse.startAngle !== undefined;
|
|
6247
|
+
const hasEnd = ellipse.endAngle !== undefined;
|
|
6248
|
+
if (hasStart !== hasEnd) {
|
|
6249
|
+
throw new RangeError("startAngle and endAngle must be provided together");
|
|
6250
|
+
}
|
|
6251
|
+
if (hasStart && hasEnd) {
|
|
6252
|
+
assertFinite(ellipse.startAngle, "startAngle");
|
|
6253
|
+
assertFinite(ellipse.endAngle, "endAngle");
|
|
6254
|
+
const range = Math.abs(ellipse.endAngle - ellipse.startAngle);
|
|
6255
|
+
if (range <= GEOMETRY_EPSILON || range >= FULL_TURN - GEOMETRY_EPSILON) {
|
|
6256
|
+
throw new RangeError("ellipse arc range must be greater than 0 and less than one full turn");
|
|
6257
|
+
}
|
|
6258
|
+
}
|
|
6259
|
+
}
|
|
6260
|
+
function assertFinitePoint(point, name) {
|
|
6261
|
+
assertFinite(point.x, `${name}.x`);
|
|
6262
|
+
assertFinite(point.y, `${name}.y`);
|
|
6263
|
+
}
|
|
6264
|
+
function assertFinite(value, name) {
|
|
6265
|
+
if (!Number.isFinite(value)) {
|
|
6266
|
+
throw new RangeError(`${name} must be finite`);
|
|
6267
|
+
}
|
|
6268
|
+
}
|
|
6269
|
+
function assertPositiveFinite(value, name) {
|
|
6270
|
+
assertFinite(value, name);
|
|
6271
|
+
if (value <= 0) {
|
|
6272
|
+
throw new RangeError(`${name} must be greater than 0`);
|
|
6273
|
+
}
|
|
6274
|
+
}
|
|
6275
|
+
function assertIntegerAtLeast(value, minimum, name) {
|
|
6276
|
+
if (!Number.isInteger(value) || value < minimum) {
|
|
6277
|
+
throw new RangeError(`${name} must be an integer of at least ${minimum}`);
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
function rotatePoint(center, offsetX, offsetY, rotation) {
|
|
6281
|
+
if (rotation === 0) {
|
|
6282
|
+
return { x: center.x + offsetX, y: center.y + offsetY };
|
|
6283
|
+
}
|
|
6284
|
+
const cosRotation = Math.cos(rotation);
|
|
6285
|
+
const sinRotation = Math.sin(rotation);
|
|
6286
|
+
return {
|
|
6287
|
+
x: center.x + offsetX * cosRotation - offsetY * sinRotation,
|
|
6288
|
+
y: center.y + offsetX * sinRotation + offsetY * cosRotation,
|
|
6289
|
+
};
|
|
6290
|
+
}
|
|
6291
|
+
|
|
4313
6292
|
function includeDefined(values) {
|
|
4314
6293
|
return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
|
|
4315
6294
|
}
|
|
@@ -4426,5 +6405,5 @@ var MediaSessionActivityStatus;
|
|
|
4426
6405
|
MediaSessionActivityStatus["Waiting"] = "waiting";
|
|
4427
6406
|
})(MediaSessionActivityStatus || (MediaSessionActivityStatus = {}));
|
|
4428
6407
|
|
|
4429
|
-
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, 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, getAnnotationHandles, getBufferedDetectionTimelineFrameSnapshot, getDetectionRect, getPointsRect, includeDefined, isDeflatedBase64DetectionMaskPayload, lightenColor, mediaToScreen, mergeDetectionMasks, mergeDetectionPolygonsByClass, normalizeDetectionClassName, offsetDetection, pickAnnotationHandle, pickDetectionAtPoint, pickDetectionByMaskId, pointInPolygon, polygonArea, polygonToRect, rasterizePolygonToMask, rasterizeRectToMask, rebaseDetectionPickToFrame, rectArea, rectToPolygon, resolveAnnotationRendererPresentation, resolveAnnotationStyleState, resolveContrastTextColor, resolveDetectionClassColorStyle, resolveMaskStyleOpacity, resolveStyleValue, screenToMedia, selectDetectionFrame, topLeftRectToCenterRect, validateDetectionFrames };
|
|
6408
|
+
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, TrackingGeometry, annotationRendererKinds, annotationRenderers, applyAnnotationHandleDrag, canReuseMaskStyleArtifacts, centerRectToTopLeftRect, computeDetectionMaskRect, computeMaskBounds, containsPoint, convertDetectionBoxToMask, convertDetectionBoxToPolygon, convertDetectionMaskToBox, convertDetectionMaskToPolygon, convertDetectionPolygonToBox, convertDetectionPolygonToMask, copySortedDetectionFrames, createAnnotationEditingEngine, createArrayDetectionFrameSource, createBufferedDetectionTimeline, createByteTrackTracker, createCBIoUTracker, createColdDetectionFrameSource, createCompositeDetectionFrameSource, createDefaultAnnotationPresentation, createDetectionPickKey, createEditableAnnotationFrameSession, createIdMaskFrame, createIdleDetectionBufferState, createMemoryColdDetectionFrameStore, createOCSortTracker, createSortTracker, createSourceAwarePresentation, createViewportController, createWritableDetectionFrameSource, decodeCompressedRleCounts, decodeCompressedRleMask, decodeDetectionMaskPayload, deleteAnnotationVertex, detectMaskBorders, detectionFrameOverlapsRange, detectionPostProcessors, 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, projectDetectionFrameForTracking, rasterizePolygonToMask, rasterizeRectToMask, rebaseDetectionPickToFrame, rectArea, rectToPolygon, resolveAnnotationRendererPresentation, resolveAnnotationStyleState, resolveContrastTextColor, resolveDetectionClassColorStyle, resolveEllipseSegmentCount, resolveMarkerGeometry, resolveMaskStyleOpacity, resolveStyleValue, sampleEllipseArc, screenToMedia, selectDetectionFrame, topLeftRectToCenterRect, validateDetectionFrames };
|
|
4430
6409
|
//# sourceMappingURL=index.js.map
|