scrypted-detection-trainer 0.1.14 → 0.1.16
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/main.nodejs.js +1 -1
- package/dist/main.nodejs.js.map +1 -1
- package/dist/plugin.zip +0 -0
- package/out/main.nodejs.js +49 -50
- package/out/main.nodejs.js.map +1 -1
- package/out/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/main.ts +55 -53
package/dist/plugin.zip
CHANGED
|
Binary file
|
package/out/main.nodejs.js
CHANGED
|
@@ -1793,16 +1793,16 @@ class DetectionTrainer extends sdk_1.ScryptedDeviceBase {
|
|
|
1793
1793
|
async onRequest(request, response) {
|
|
1794
1794
|
const url = new URL(request.url, 'http://localhost');
|
|
1795
1795
|
const path = url.pathname.replace(request.rootPath, '');
|
|
1796
|
-
// Serve browse event image via
|
|
1796
|
+
// Serve browse event image via getDetectionInput
|
|
1797
1797
|
if (path === '/api/browse-img' && request.body) {
|
|
1798
1798
|
const rawBody = request.body;
|
|
1799
1799
|
const body = JSON.parse(typeof rawBody === 'string' ? rawBody : Buffer.isBuffer(rawBody) ? rawBody.toString() : String(rawBody));
|
|
1800
|
-
const { cameraId,
|
|
1801
|
-
if (!cameraId ||
|
|
1800
|
+
const { cameraId, detectionId } = body;
|
|
1801
|
+
if (!cameraId || !detectionId)
|
|
1802
1802
|
return response.send('Missing params', { code: 400 });
|
|
1803
1803
|
try {
|
|
1804
1804
|
const cam = systemManager.getDeviceById(cameraId);
|
|
1805
|
-
const mo = await cam.
|
|
1805
|
+
const mo = await cam.getDetectionInput(detectionId);
|
|
1806
1806
|
const jpeg = await mediaManager.convertMediaObjectToBuffer(mo, 'image/jpeg');
|
|
1807
1807
|
return response.send(jpeg, { headers: { 'Content-Type': 'image/jpeg', 'Cache-Control': 'max-age=3600' } });
|
|
1808
1808
|
}
|
|
@@ -1851,6 +1851,7 @@ class DetectionTrainer extends sdk_1.ScryptedDeviceBase {
|
|
|
1851
1851
|
const params = new URL(request.url, 'http://localhost').searchParams;
|
|
1852
1852
|
const cameraId = params.get('cameraId');
|
|
1853
1853
|
const hours = parseInt(params.get('hours') || '24');
|
|
1854
|
+
const limit = parseInt(params.get('limit') || '100');
|
|
1854
1855
|
if (!cameraId)
|
|
1855
1856
|
return response.send('Missing cameraId', { code: 400 });
|
|
1856
1857
|
try {
|
|
@@ -1859,23 +1860,30 @@ class DetectionTrainer extends sdk_1.ScryptedDeviceBase {
|
|
|
1859
1860
|
return response.send('Camera not found', { code: 404 });
|
|
1860
1861
|
const endTime = Date.now();
|
|
1861
1862
|
const startTime = endTime - hours * 3600 * 1000;
|
|
1862
|
-
|
|
1863
|
-
const
|
|
1864
|
-
const
|
|
1865
|
-
.filter((
|
|
1863
|
+
// Use getRecordedEvents to get ObjectDetector events with bounding boxes
|
|
1864
|
+
const recorded = await cam.getRecordedEvents({ startTime, endTime });
|
|
1865
|
+
const detectionEvents = (recorded || [])
|
|
1866
|
+
.filter((e) => e.details?.eventInterface === 'ObjectDetector' &&
|
|
1867
|
+
e.data?.detections?.length &&
|
|
1868
|
+
e.data?.detectionId &&
|
|
1869
|
+
e.data?.inputDimensions)
|
|
1866
1870
|
.slice(0, limit)
|
|
1867
|
-
.map((
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1871
|
+
.map((e) => {
|
|
1872
|
+
const ts = e.details?.eventTime || e.data?.timestamp;
|
|
1873
|
+
return {
|
|
1874
|
+
detectionId: e.data.detectionId,
|
|
1875
|
+
timestamp: ts,
|
|
1876
|
+
detections: (e.data.detections || []).map((d) => ({
|
|
1877
|
+
className: d.className,
|
|
1878
|
+
score: d.score,
|
|
1879
|
+
boundingBox: d.boundingBox,
|
|
1880
|
+
})),
|
|
1881
|
+
inputDimensions: e.data.inputDimensions,
|
|
1882
|
+
cameraId,
|
|
1883
|
+
cameraName: cam.name,
|
|
1884
|
+
};
|
|
1885
|
+
});
|
|
1886
|
+
return response.send(JSON.stringify(detectionEvents), { headers: { 'Content-Type': 'application/json' } });
|
|
1879
1887
|
}
|
|
1880
1888
|
catch (e) {
|
|
1881
1889
|
return response.send(JSON.stringify({ error: e.message }), { headers: { 'Content-Type': 'application/json' }, code: 500 });
|
|
@@ -1885,29 +1893,28 @@ class DetectionTrainer extends sdk_1.ScryptedDeviceBase {
|
|
|
1885
1893
|
if (path === '/api/add-event' && request.body) {
|
|
1886
1894
|
const rawBody = request.body;
|
|
1887
1895
|
const body = JSON.parse(typeof rawBody === 'string' ? rawBody : Buffer.isBuffer(rawBody) ? rawBody.toString() : String(rawBody));
|
|
1888
|
-
const { cameraId, cameraName,
|
|
1896
|
+
const { cameraId, cameraName, detectionId, timestamp, detectedClass, score, boundingBox, inputDimensions, label } = body;
|
|
1889
1897
|
if (!label || label === 'discard')
|
|
1890
1898
|
return response.send(JSON.stringify({ ok: true }), { headers: { 'Content-Type': 'application/json' } });
|
|
1891
|
-
// Get image via thumbnail
|
|
1892
1899
|
let jpeg;
|
|
1893
1900
|
try {
|
|
1894
1901
|
const cam = systemManager.getDeviceById(cameraId);
|
|
1895
|
-
const mo = await cam.
|
|
1902
|
+
const mo = await cam.getDetectionInput(detectionId);
|
|
1896
1903
|
jpeg = await mediaManager.convertMediaObjectToBuffer(mo, 'image/jpeg');
|
|
1897
1904
|
}
|
|
1898
1905
|
catch (e) {
|
|
1899
|
-
this.console.warn(`Could not get
|
|
1906
|
+
this.console.warn(`Could not get image for browse event:`, e);
|
|
1900
1907
|
}
|
|
1901
1908
|
if (!jpeg)
|
|
1902
1909
|
return response.send(JSON.stringify({ error: 'Could not retrieve image' }), { headers: { 'Content-Type': 'application/json' }, code: 500 });
|
|
1903
1910
|
const id = `browse-${timestamp}-${Math.random().toString(36).slice(2, 6)}`;
|
|
1904
|
-
// For clips we don't have per-detection bounding boxes — store full frame dimensions
|
|
1905
1911
|
const record = {
|
|
1906
1912
|
id, cameraId, cameraName, timestamp,
|
|
1907
1913
|
detectedClass: detectedClass || 'unknown',
|
|
1908
|
-
score: 1,
|
|
1914
|
+
score: score || 1,
|
|
1909
1915
|
boundingBox: boundingBox || [0, 0, inputDimensions?.[0] || 1920, inputDimensions?.[1] || 1080],
|
|
1910
1916
|
inputDimensions: inputDimensions || [1920, 1080],
|
|
1917
|
+
detectionId,
|
|
1911
1918
|
reviewed: true, label,
|
|
1912
1919
|
};
|
|
1913
1920
|
this.captures.set(id, record);
|
|
@@ -2427,7 +2434,7 @@ function loadBrowseImage(i, ev) {
|
|
|
2427
2434
|
fetch(BASE + '/api/browse-img', {
|
|
2428
2435
|
method: 'POST',
|
|
2429
2436
|
headers: { 'Content-Type': 'application/json' },
|
|
2430
|
-
body: JSON.stringify({ cameraId: ev.cameraId,
|
|
2437
|
+
body: JSON.stringify({ cameraId: ev.cameraId, detectionId: ev.detectionId }),
|
|
2431
2438
|
})
|
|
2432
2439
|
.then(r => r.ok ? r.blob() : null)
|
|
2433
2440
|
.then(blob => {
|
|
@@ -2435,28 +2442,18 @@ function loadBrowseImage(i, ev) {
|
|
|
2435
2442
|
const url = URL.createObjectURL(blob);
|
|
2436
2443
|
const img = new Image();
|
|
2437
2444
|
img.onload = () => {
|
|
2445
|
+
const primary = (ev.detections || [])[0];
|
|
2446
|
+
if (!primary?.boundingBox) return;
|
|
2438
2447
|
imgCache.set('browse-' + i, img);
|
|
2448
|
+
// Use drawDetection by temporarily remapping canvas IDs
|
|
2439
2449
|
const fullCanvas = document.getElementById('bcanvas-full-' + i);
|
|
2440
2450
|
const cropCanvas = document.getElementById('bcanvas-crop-' + i);
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
const dw = iw * scale, dh = ih * scale;
|
|
2448
|
-
ctx.fillStyle = '#111'; ctx.fillRect(0, 0, cw, ch);
|
|
2449
|
-
ctx.drawImage(img, (cw-dw)/2, (ch-dh)/2, dw, dh);
|
|
2450
|
-
// Label classes
|
|
2451
|
-
const labels = (ev.detectionClasses || []).join(', ');
|
|
2452
|
-
ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(0, ch-20, cw, 20);
|
|
2453
|
-
ctx.fillStyle = '#f90'; ctx.font = 'bold 11px sans-serif';
|
|
2454
|
-
ctx.fillText(labels, 4, ch-5);
|
|
2455
|
-
fullCanvas.onclick = () => openLightboxImg(img, ev.cameraName, ev.timestamp);
|
|
2456
|
-
}
|
|
2457
|
-
// Hide crop panel — no bounding box available
|
|
2458
|
-
const cropPanel = document.getElementById('bcanvas-crop-' + i)?.closest('.img-panel');
|
|
2459
|
-
if (cropPanel) cropPanel.style.display = 'none';
|
|
2451
|
+
if (fullCanvas) fullCanvas.id = 'canvas-full-' + i;
|
|
2452
|
+
if (cropCanvas) cropCanvas.id = 'canvas-crop-' + i;
|
|
2453
|
+
const fakeR = { id: String(i), boundingBox: primary.boundingBox, detectedClass: primary.className, score: primary.score, inputDimensions: ev.inputDimensions };
|
|
2454
|
+
drawDetection(img, fakeR);
|
|
2455
|
+
if (fullCanvas) { fullCanvas.id = 'bcanvas-full-' + i; fullCanvas.onclick = () => openLightbox(fakeR); }
|
|
2456
|
+
if (cropCanvas) { cropCanvas.id = 'bcanvas-crop-' + i; cropCanvas.onclick = () => openLightbox(fakeR); }
|
|
2460
2457
|
URL.revokeObjectURL(url);
|
|
2461
2458
|
};
|
|
2462
2459
|
img.src = url;
|
|
@@ -2471,17 +2468,19 @@ async function addBrowseEvent(i, label) {
|
|
|
2471
2468
|
|
|
2472
2469
|
if (label !== 'discard') {
|
|
2473
2470
|
try {
|
|
2471
|
+
const primary = (ev.detections || [])[0] || {};
|
|
2474
2472
|
const res = await fetch(BASE + '/api/add-event', {
|
|
2475
2473
|
method: 'POST',
|
|
2476
2474
|
headers: { 'Content-Type': 'application/json' },
|
|
2477
2475
|
body: JSON.stringify({
|
|
2478
2476
|
cameraId: ev.cameraId,
|
|
2479
2477
|
cameraName: ev.cameraName,
|
|
2480
|
-
|
|
2478
|
+
detectionId: ev.detectionId,
|
|
2481
2479
|
timestamp: ev.timestamp,
|
|
2482
|
-
detectedClass:
|
|
2483
|
-
|
|
2484
|
-
|
|
2480
|
+
detectedClass: primary.className || 'unknown',
|
|
2481
|
+
score: primary.score || 1,
|
|
2482
|
+
boundingBox: primary.boundingBox || null,
|
|
2483
|
+
inputDimensions: ev.inputDimensions || null,
|
|
2485
2484
|
label,
|
|
2486
2485
|
}),
|
|
2487
2486
|
});
|