supervision 0.1.4 → 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 +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +432 -83
- package/dist/index.js.map +1 -1
- package/dist/mask-preparation.worker.js +67 -59
- 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/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/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 +6 -0
- package/node_modules/supervision-js-core/dist/index.d.ts.map +1 -1
- package/node_modules/supervision-js-core/dist/index.js +1600 -255
- package/node_modules/supervision-js-core/dist/index.js.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/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/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/utils/detection-frames.d.ts.map +1 -1
- 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,22 +1663,1387 @@ 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
|
-
|
|
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;
|
|
1691
|
+
return {
|
|
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
|
+
},
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
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);
|
|
1808
|
+
return {
|
|
1809
|
+
matches,
|
|
1810
|
+
unmatchedDetections: detections.flatMap((_, index) => matchedDetections.has(index) ? [] : [index]),
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
/** Hungarian assignment for a rectangular score matrix. */
|
|
1814
|
+
function maximizeAssignment(scores) {
|
|
1815
|
+
if (scores.length === 0 || scores[0]?.length === 0) {
|
|
1816
|
+
return [];
|
|
1817
|
+
}
|
|
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]);
|
|
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]);
|
|
1895
|
+
return {
|
|
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,
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
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;
|
|
1918
|
+
}
|
|
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);
|
|
1936
|
+
}
|
|
1937
|
+
function normalizePositiveInteger$1(value, label) {
|
|
1938
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
1939
|
+
throw new Error(`${label} must be a positive integer.`);
|
|
1940
|
+
}
|
|
1941
|
+
return value;
|
|
1942
|
+
}
|
|
1943
|
+
function normalizeNonNegativeInteger$1(value, label) {
|
|
1944
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
1945
|
+
throw new Error(`${label} must be a non-negative integer.`);
|
|
1946
|
+
}
|
|
1947
|
+
return value;
|
|
1948
|
+
}
|
|
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.`);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
function identity(size) {
|
|
1955
|
+
return Array.from({ length: size }, (_, row) => Array.from({ length: size }, (_, column) => (row === column ? 1 : 0)));
|
|
1956
|
+
}
|
|
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
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
if (Math.abs(augmented[pivot][column]) < 1e-12) {
|
|
1987
|
+
throw new Error("SORT Kalman covariance is singular.");
|
|
1988
|
+
}
|
|
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]);
|
|
2000
|
+
}
|
|
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);
|
|
2057
|
+
}
|
|
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);
|
|
2062
|
+
}
|
|
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));
|
|
2068
|
+
}
|
|
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;
|
|
2094
|
+
}
|
|
2095
|
+
if (track.trackerId !== undefined) {
|
|
2096
|
+
assignments.set(detection.detectionIndex, track.trackerId);
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
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;
|
|
2118
|
+
}
|
|
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
|
+
}
|
|
1676
3047
|
|
|
1677
3048
|
function centerRectToTopLeftRect(rect) {
|
|
1678
3049
|
return {
|
|
@@ -1769,10 +3140,220 @@ function polygonArea(points) {
|
|
|
1769
3140
|
}
|
|
1770
3141
|
return Math.abs(twiceArea) / 2;
|
|
1771
3142
|
}
|
|
1772
|
-
function rectArea(rect) {
|
|
1773
|
-
return rect ? Math.max(0, rect.width) * Math.max(0, rect.height) : 0;
|
|
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
|
+
}
|
|
1774
3338
|
}
|
|
1775
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
|
+
|
|
1776
3357
|
const decodedMaskCache = new WeakMap();
|
|
1777
3358
|
function pickDetectionAtPoint(frame, point, options = {}) {
|
|
1778
3359
|
if (!frame) {
|
|
@@ -4284,242 +5865,6 @@ function normalizeGlobalKeypointStyle(style) {
|
|
|
4284
5865
|
return style === undefined ? new BaseKeypointStyle() : style;
|
|
4285
5866
|
}
|
|
4286
5867
|
|
|
4287
|
-
var DetectionMaskPayloadFormat;
|
|
4288
|
-
(function (DetectionMaskPayloadFormat) {
|
|
4289
|
-
DetectionMaskPayloadFormat["RawCocoRle"] = "rawCocoRle";
|
|
4290
|
-
DetectionMaskPayloadFormat["DeflatedBase64"] = "deflatedBase64";
|
|
4291
|
-
})(DetectionMaskPayloadFormat || (DetectionMaskPayloadFormat = {}));
|
|
4292
|
-
function encodeBinaryMask(data, width, height) {
|
|
4293
|
-
assertMaskDimensions(data, width, height);
|
|
4294
|
-
const runs = [];
|
|
4295
|
-
let currentValue = 0;
|
|
4296
|
-
let runLength = 0;
|
|
4297
|
-
for (let x = 0; x < width; x += 1) {
|
|
4298
|
-
for (let y = 0; y < height; y += 1) {
|
|
4299
|
-
const value = data[y * width + x] ? 1 : 0;
|
|
4300
|
-
if (value === currentValue) {
|
|
4301
|
-
runLength += 1;
|
|
4302
|
-
}
|
|
4303
|
-
else {
|
|
4304
|
-
runs.push(runLength);
|
|
4305
|
-
currentValue = value;
|
|
4306
|
-
runLength = 1;
|
|
4307
|
-
}
|
|
4308
|
-
}
|
|
4309
|
-
}
|
|
4310
|
-
runs.push(runLength);
|
|
4311
|
-
return {
|
|
4312
|
-
counts: encodeCompressedRleCounts(runs),
|
|
4313
|
-
encoding: DetectionMaskEncoding.CompressedRle,
|
|
4314
|
-
height,
|
|
4315
|
-
width,
|
|
4316
|
-
};
|
|
4317
|
-
}
|
|
4318
|
-
/** Encodes a binary mask and derives its bounds in the same raster traversal. */
|
|
4319
|
-
function encodeBinaryMaskWithBounds(data, width, height) {
|
|
4320
|
-
assertMaskDimensions(data, width, height);
|
|
4321
|
-
const runs = [];
|
|
4322
|
-
let currentValue = 0;
|
|
4323
|
-
let runLength = 0;
|
|
4324
|
-
let minX = width;
|
|
4325
|
-
let minY = height;
|
|
4326
|
-
let maxX = -1;
|
|
4327
|
-
let maxY = -1;
|
|
4328
|
-
for (let x = 0; x < width; x += 1) {
|
|
4329
|
-
for (let y = 0; y < height; y += 1) {
|
|
4330
|
-
const value = data[y * width + x] ? 1 : 0;
|
|
4331
|
-
if (value) {
|
|
4332
|
-
minX = Math.min(minX, x);
|
|
4333
|
-
minY = Math.min(minY, y);
|
|
4334
|
-
maxX = Math.max(maxX, x);
|
|
4335
|
-
maxY = Math.max(maxY, y);
|
|
4336
|
-
}
|
|
4337
|
-
if (value === currentValue) {
|
|
4338
|
-
runLength += 1;
|
|
4339
|
-
}
|
|
4340
|
-
else {
|
|
4341
|
-
runs.push(runLength);
|
|
4342
|
-
currentValue = value;
|
|
4343
|
-
runLength = 1;
|
|
4344
|
-
}
|
|
4345
|
-
}
|
|
4346
|
-
}
|
|
4347
|
-
runs.push(runLength);
|
|
4348
|
-
return {
|
|
4349
|
-
bounds: maxX < 0
|
|
4350
|
-
? null
|
|
4351
|
-
: {
|
|
4352
|
-
height: maxY - minY + 1,
|
|
4353
|
-
width: maxX - minX + 1,
|
|
4354
|
-
x: minX + (maxX - minX + 1) / 2,
|
|
4355
|
-
y: minY + (maxY - minY + 1) / 2,
|
|
4356
|
-
},
|
|
4357
|
-
mask: {
|
|
4358
|
-
counts: encodeCompressedRleCounts(runs),
|
|
4359
|
-
encoding: DetectionMaskEncoding.CompressedRle,
|
|
4360
|
-
height,
|
|
4361
|
-
width,
|
|
4362
|
-
},
|
|
4363
|
-
};
|
|
4364
|
-
}
|
|
4365
|
-
function encodeDetectionMaskPayload(mask, codec) {
|
|
4366
|
-
return codec ? codec.deflate(mask.counts) : mask.counts;
|
|
4367
|
-
}
|
|
4368
|
-
function decodeDetectionMaskPayload(payload, width, height, options = {}) {
|
|
4369
|
-
const format = options.format ??
|
|
4370
|
-
(isDeflatedBase64DetectionMaskPayload(payload)
|
|
4371
|
-
? DetectionMaskPayloadFormat.DeflatedBase64
|
|
4372
|
-
: DetectionMaskPayloadFormat.RawCocoRle);
|
|
4373
|
-
if (format === DetectionMaskPayloadFormat.DeflatedBase64 && !options.codec) {
|
|
4374
|
-
throw new Error("A detection mask compression codec is required for deflated payloads.");
|
|
4375
|
-
}
|
|
4376
|
-
return {
|
|
4377
|
-
counts: format === DetectionMaskPayloadFormat.DeflatedBase64
|
|
4378
|
-
? options.codec.inflate(payload)
|
|
4379
|
-
: payload,
|
|
4380
|
-
encoding: DetectionMaskEncoding.CompressedRle,
|
|
4381
|
-
height,
|
|
4382
|
-
width,
|
|
4383
|
-
};
|
|
4384
|
-
}
|
|
4385
|
-
/** Matches the annotation editor's legacy transport-format heuristic. */
|
|
4386
|
-
function isDeflatedBase64DetectionMaskPayload(value) {
|
|
4387
|
-
return value.length > 100 && /^[A-Za-z0-9+/]+={0,2}$/.test(value);
|
|
4388
|
-
}
|
|
4389
|
-
function computeMaskBounds(data, width, height) {
|
|
4390
|
-
assertMaskDimensions(data, width, height);
|
|
4391
|
-
let minX = width;
|
|
4392
|
-
let minY = height;
|
|
4393
|
-
let maxX = -1;
|
|
4394
|
-
let maxY = -1;
|
|
4395
|
-
for (let y = 0; y < height; y += 1) {
|
|
4396
|
-
for (let x = 0; x < width; x += 1) {
|
|
4397
|
-
if (!data[y * width + x]) {
|
|
4398
|
-
continue;
|
|
4399
|
-
}
|
|
4400
|
-
minX = Math.min(minX, x);
|
|
4401
|
-
minY = Math.min(minY, y);
|
|
4402
|
-
maxX = Math.max(maxX, x);
|
|
4403
|
-
maxY = Math.max(maxY, y);
|
|
4404
|
-
}
|
|
4405
|
-
}
|
|
4406
|
-
if (maxX < 0) {
|
|
4407
|
-
return null;
|
|
4408
|
-
}
|
|
4409
|
-
const boundsWidth = maxX - minX + 1;
|
|
4410
|
-
const boundsHeight = maxY - minY + 1;
|
|
4411
|
-
return {
|
|
4412
|
-
height: boundsHeight,
|
|
4413
|
-
width: boundsWidth,
|
|
4414
|
-
x: minX + boundsWidth / 2,
|
|
4415
|
-
y: minY + boundsHeight / 2,
|
|
4416
|
-
};
|
|
4417
|
-
}
|
|
4418
|
-
function computeDetectionMaskRect(mask) {
|
|
4419
|
-
const decoded = decodeCompressedRleMask(mask);
|
|
4420
|
-
const bounds = computeMaskBounds(decoded.data, decoded.width, decoded.height);
|
|
4421
|
-
return bounds ?? undefined;
|
|
4422
|
-
}
|
|
4423
|
-
function detectMaskBorders(data, width, height) {
|
|
4424
|
-
assertMaskDimensions(data, width, height);
|
|
4425
|
-
const borders = new Uint8Array(data.length);
|
|
4426
|
-
for (let y = 0; y < height; y += 1) {
|
|
4427
|
-
for (let x = 0; x < width; x += 1) {
|
|
4428
|
-
const offset = y * width + x;
|
|
4429
|
-
if (!data[offset]) {
|
|
4430
|
-
continue;
|
|
4431
|
-
}
|
|
4432
|
-
if (x === 0 ||
|
|
4433
|
-
y === 0 ||
|
|
4434
|
-
x === width - 1 ||
|
|
4435
|
-
y === height - 1 ||
|
|
4436
|
-
!data[offset - 1] ||
|
|
4437
|
-
!data[offset + 1] ||
|
|
4438
|
-
!data[offset - width] ||
|
|
4439
|
-
!data[offset + width]) {
|
|
4440
|
-
borders[offset] = 1;
|
|
4441
|
-
}
|
|
4442
|
-
}
|
|
4443
|
-
}
|
|
4444
|
-
return borders;
|
|
4445
|
-
}
|
|
4446
|
-
function extractMaskContour(data, width, height) {
|
|
4447
|
-
assertMaskDimensions(data, width, height);
|
|
4448
|
-
const stride = Math.max(1, Math.floor(height / 100));
|
|
4449
|
-
const leftEdge = [];
|
|
4450
|
-
const rightEdge = [];
|
|
4451
|
-
for (let y = 0; y < height; y += stride) {
|
|
4452
|
-
let left = -1;
|
|
4453
|
-
let right = -1;
|
|
4454
|
-
for (let x = 0; x < width; x += 1) {
|
|
4455
|
-
if (data[y * width + x]) {
|
|
4456
|
-
left = left === -1 ? x : left;
|
|
4457
|
-
right = x;
|
|
4458
|
-
}
|
|
4459
|
-
}
|
|
4460
|
-
if (left !== -1) {
|
|
4461
|
-
leftEdge.push({ x: left, y });
|
|
4462
|
-
rightEdge.push({ x: right, y });
|
|
4463
|
-
}
|
|
4464
|
-
}
|
|
4465
|
-
return leftEdge.length < 2
|
|
4466
|
-
? undefined
|
|
4467
|
-
: [...leftEdge, ...rightEdge.reverse()];
|
|
4468
|
-
}
|
|
4469
|
-
function extractMaskRectRuns(data, width, height) {
|
|
4470
|
-
assertMaskDimensions(data, width, height);
|
|
4471
|
-
const rects = [];
|
|
4472
|
-
const openRects = new Map();
|
|
4473
|
-
for (let y = 0; y < height; y += 1) {
|
|
4474
|
-
const activeSpans = new Set();
|
|
4475
|
-
let x = 0;
|
|
4476
|
-
while (x < width) {
|
|
4477
|
-
while (x < width && !data[y * width + x]) {
|
|
4478
|
-
x += 1;
|
|
4479
|
-
}
|
|
4480
|
-
if (x >= width) {
|
|
4481
|
-
break;
|
|
4482
|
-
}
|
|
4483
|
-
const startX = x;
|
|
4484
|
-
while (x < width && data[y * width + x]) {
|
|
4485
|
-
x += 1;
|
|
4486
|
-
}
|
|
4487
|
-
const runWidth = x - startX;
|
|
4488
|
-
const key = `${startX}:${runWidth}`;
|
|
4489
|
-
const openRect = openRects.get(key);
|
|
4490
|
-
activeSpans.add(key);
|
|
4491
|
-
if (openRect && openRect.y + openRect.height === y) {
|
|
4492
|
-
openRects.set(key, { ...openRect, height: openRect.height + 1 });
|
|
4493
|
-
}
|
|
4494
|
-
else {
|
|
4495
|
-
if (openRect) {
|
|
4496
|
-
rects.push(openRect);
|
|
4497
|
-
}
|
|
4498
|
-
openRects.set(key, { height: 1, width: runWidth, x: startX, y });
|
|
4499
|
-
}
|
|
4500
|
-
}
|
|
4501
|
-
for (const [key, openRect] of openRects) {
|
|
4502
|
-
if (!activeSpans.has(key)) {
|
|
4503
|
-
rects.push(openRect);
|
|
4504
|
-
openRects.delete(key);
|
|
4505
|
-
}
|
|
4506
|
-
}
|
|
4507
|
-
}
|
|
4508
|
-
rects.push(...openRects.values());
|
|
4509
|
-
return rects.length > 0 ? rects : undefined;
|
|
4510
|
-
}
|
|
4511
|
-
function assertMaskDimensions(data, width, height) {
|
|
4512
|
-
if (!Number.isInteger(width) || width <= 0) {
|
|
4513
|
-
throw new Error("Mask width must be a positive integer.");
|
|
4514
|
-
}
|
|
4515
|
-
if (!Number.isInteger(height) || height <= 0) {
|
|
4516
|
-
throw new Error("Mask height must be a positive integer.");
|
|
4517
|
-
}
|
|
4518
|
-
if (data.length !== width * height) {
|
|
4519
|
-
throw new Error("Mask data length must equal width * height.");
|
|
4520
|
-
}
|
|
4521
|
-
}
|
|
4522
|
-
|
|
4523
5868
|
function rectToPolygon(rect) {
|
|
4524
5869
|
const halfWidth = rect.width / 2;
|
|
4525
5870
|
const halfHeight = rect.height / 2;
|
|
@@ -5060,5 +6405,5 @@ var MediaSessionActivityStatus;
|
|
|
5060
6405
|
MediaSessionActivityStatus["Waiting"] = "waiting";
|
|
5061
6406
|
})(MediaSessionActivityStatus || (MediaSessionActivityStatus = {}));
|
|
5062
6407
|
|
|
5063
|
-
export { AnnotationFrameMutationKind, AnnotationGeometryKind, AnnotationGestureStateKind, AnnotationHandleKind, BaseBoxCornerStyle, BaseBoxStyle, BaseFocusStyle, BaseInteractionStyle, BaseKeypointStyle, BaseLabelStyle, BaseMarkerStyle, BaseMaskStyle, BasePolygonStyle, BasePolylineStyle, BoxShape, StrokeAlignment as BoxStrokeAlignment, DEFAULT_DETECTION_CLASS_STYLES, DEFAULT_DETECTION_COLOR_SEQUENCE, DetectionBufferStatus, DetectionFrameRetentionMode, DetectionFrameSelectionMode, DetectionInteractionState, DetectionMaskEncoding, DetectionMaskPayloadFormat, DetectionPickTarget, FocusTargetMode, KeypointMarkerShape, KeypointVisibility, LabelPlacement, LabelVisibilityMode, MAX_ID_MASK_PALETTE_ENTRIES, MAX_ID_MASK_STROKE_WIDTH, MarkerShape, MarkerSizeSpace, MaskRenderMode, MediaInteractionMode, MediaRendererFit, MediaRendererPlaybackState, MediaSessionActivityKind, MediaSessionActivityStatus, MediaSessionMode, MediaSessionStatus, MediaSourceStatus, RegionRendererComposeMode, RegionRendererRegionKind, RegionRendererSourceKind, SUPERVISION_ROBOFLOW_COLOR, ShapeInstructionKind, StrokeAlignment, annotationRendererKinds, annotationRenderers, applyAnnotationHandleDrag, canReuseMaskStyleArtifacts, centerRectToTopLeftRect, computeDetectionMaskRect, computeMaskBounds, containsPoint, convertDetectionBoxToMask, convertDetectionBoxToPolygon, convertDetectionMaskToBox, convertDetectionMaskToPolygon, convertDetectionPolygonToBox, convertDetectionPolygonToMask, copySortedDetectionFrames, createAnnotationEditingEngine, createArrayDetectionFrameSource, createBufferedDetectionTimeline, createColdDetectionFrameSource, createCompositeDetectionFrameSource, createDefaultAnnotationPresentation, createDetectionPickKey, createEditableAnnotationFrameSession, createIdMaskFrame, createIdleDetectionBufferState, createMemoryColdDetectionFrameStore, createSourceAwarePresentation, createViewportController, createWritableDetectionFrameSource, decodeCompressedRleCounts, decodeCompressedRleMask, decodeDetectionMaskPayload, deleteAnnotationVertex, detectMaskBorders, detectionFrameOverlapsRange, distanceToSegment, encodeBinaryMask, encodeBinaryMaskWithBounds, encodeCompressedRleCounts, encodeDetectionMaskPayload, extractMaskContour, extractMaskRectRuns, filterDetectionFramesForRange, findClosestAnnotationSegment, followDetectionPickAcrossFrames, getAnnotationHandles, getBufferedDetectionTimelineFrameSnapshot, getDetectionRect, getPointsRect, haveSameDetectionPickIdentities, includeDefined, isDeflatedBase64DetectionMaskPayload, lightenColor, mediaToScreen, mergeDetectionMasks, mergeDetectionPolygonsByClass, normalizeDetectionClassName, offsetDetection, pickAnnotationHandle, pickDetectionAtPoint, pickDetectionByMaskId, pointInPolygon, polygonArea, polygonToRect, rasterizePolygonToMask, rasterizeRectToMask, rebaseDetectionPickToFrame, rectArea, rectToPolygon, resolveAnnotationRendererPresentation, resolveAnnotationStyleState, resolveContrastTextColor, resolveDetectionClassColorStyle, resolveEllipseSegmentCount, resolveMarkerGeometry, resolveMaskStyleOpacity, resolveStyleValue, sampleEllipseArc, screenToMedia, selectDetectionFrame, topLeftRectToCenterRect, validateDetectionFrames };
|
|
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 };
|
|
5064
6409
|
//# sourceMappingURL=index.js.map
|