scrypted-detection-trainer 0.1.13 → 0.1.15
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 +65 -54
- package/out/main.nodejs.js.map +1 -1
- package/out/plugin.zip +0 -0
- package/package.json +1 -1
- package/src/main.ts +72 -58
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
|
|
1797
|
-
if (path === '/api/browse-img') {
|
|
1798
|
-
const
|
|
1799
|
-
const
|
|
1800
|
-
const
|
|
1801
|
-
if (!cameraId || !
|
|
1796
|
+
// Serve browse event image via getDetectionInput
|
|
1797
|
+
if (path === '/api/browse-img' && request.body) {
|
|
1798
|
+
const rawBody = request.body;
|
|
1799
|
+
const body = JSON.parse(typeof rawBody === 'string' ? rawBody : Buffer.isBuffer(rawBody) ? rawBody.toString() : String(rawBody));
|
|
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,22 +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
|
-
|
|
1865
|
-
.
|
|
1866
|
-
.
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
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)
|
|
1870
|
+
.slice(0, limit)
|
|
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' } });
|
|
1878
1887
|
}
|
|
1879
1888
|
catch (e) {
|
|
1880
1889
|
return response.send(JSON.stringify({ error: e.message }), { headers: { 'Content-Type': 'application/json' }, code: 500 });
|
|
@@ -1884,29 +1893,28 @@ class DetectionTrainer extends sdk_1.ScryptedDeviceBase {
|
|
|
1884
1893
|
if (path === '/api/add-event' && request.body) {
|
|
1885
1894
|
const rawBody = request.body;
|
|
1886
1895
|
const body = JSON.parse(typeof rawBody === 'string' ? rawBody : Buffer.isBuffer(rawBody) ? rawBody.toString() : String(rawBody));
|
|
1887
|
-
const { cameraId, cameraName,
|
|
1896
|
+
const { cameraId, cameraName, detectionId, timestamp, detectedClass, score, boundingBox, inputDimensions, label } = body;
|
|
1888
1897
|
if (!label || label === 'discard')
|
|
1889
1898
|
return response.send(JSON.stringify({ ok: true }), { headers: { 'Content-Type': 'application/json' } });
|
|
1890
|
-
// Get image via thumbnail
|
|
1891
1899
|
let jpeg;
|
|
1892
1900
|
try {
|
|
1893
1901
|
const cam = systemManager.getDeviceById(cameraId);
|
|
1894
|
-
const mo = await cam.
|
|
1902
|
+
const mo = await cam.getDetectionInput(detectionId);
|
|
1895
1903
|
jpeg = await mediaManager.convertMediaObjectToBuffer(mo, 'image/jpeg');
|
|
1896
1904
|
}
|
|
1897
1905
|
catch (e) {
|
|
1898
|
-
this.console.warn(`Could not get
|
|
1906
|
+
this.console.warn(`Could not get image for browse event:`, e);
|
|
1899
1907
|
}
|
|
1900
1908
|
if (!jpeg)
|
|
1901
1909
|
return response.send(JSON.stringify({ error: 'Could not retrieve image' }), { headers: { 'Content-Type': 'application/json' }, code: 500 });
|
|
1902
1910
|
const id = `browse-${timestamp}-${Math.random().toString(36).slice(2, 6)}`;
|
|
1903
|
-
// For clips we don't have per-detection bounding boxes — store full frame dimensions
|
|
1904
1911
|
const record = {
|
|
1905
1912
|
id, cameraId, cameraName, timestamp,
|
|
1906
1913
|
detectedClass: detectedClass || 'unknown',
|
|
1907
|
-
score: 1,
|
|
1914
|
+
score: score || 1,
|
|
1908
1915
|
boundingBox: boundingBox || [0, 0, inputDimensions?.[0] || 1920, inputDimensions?.[1] || 1080],
|
|
1909
1916
|
inputDimensions: inputDimensions || [1920, 1080],
|
|
1917
|
+
detectionId,
|
|
1910
1918
|
reviewed: true, label,
|
|
1911
1919
|
};
|
|
1912
1920
|
this.captures.set(id, record);
|
|
@@ -2127,6 +2135,12 @@ class DetectionTrainer extends sdk_1.ScryptedDeviceBase {
|
|
|
2127
2135
|
<option value="24" selected>Last 24 hours</option>
|
|
2128
2136
|
<option value="72">Last 3 days</option>
|
|
2129
2137
|
</select>
|
|
2138
|
+
<select id="browse-limit" style="padding:8px 12px;background:#222;border:1px solid #444;color:#fff;border-radius:6px;font-size:13px;">
|
|
2139
|
+
<option value="50">50 events</option>
|
|
2140
|
+
<option value="100" selected>100 events</option>
|
|
2141
|
+
<option value="250">250 events</option>
|
|
2142
|
+
<option value="500">500 events</option>
|
|
2143
|
+
</select>
|
|
2130
2144
|
<button class="export-btn" onclick="loadBrowse()" style="padding:8px 16px;">Load Events</button>
|
|
2131
2145
|
<span id="browse-status" style="font-size:13px;color:#888;"></span>
|
|
2132
2146
|
</div>
|
|
@@ -2346,6 +2360,7 @@ let browseEvents = [];
|
|
|
2346
2360
|
async function loadBrowse() {
|
|
2347
2361
|
const cameraId = document.getElementById('browse-camera').value;
|
|
2348
2362
|
const hours = document.getElementById('browse-hours').value;
|
|
2363
|
+
const limit = document.getElementById('browse-limit').value;
|
|
2349
2364
|
const status = document.getElementById('browse-status');
|
|
2350
2365
|
const list = document.getElementById('browse-list');
|
|
2351
2366
|
|
|
@@ -2356,7 +2371,7 @@ async function loadBrowse() {
|
|
|
2356
2371
|
browseEvents = [];
|
|
2357
2372
|
|
|
2358
2373
|
try {
|
|
2359
|
-
const res = await fetch(BASE + '/api/browse?cameraId=' + cameraId + '&hours=' + hours);
|
|
2374
|
+
const res = await fetch(BASE + '/api/browse?cameraId=' + cameraId + '&hours=' + hours + '&limit=' + limit);
|
|
2360
2375
|
const events = await res.json();
|
|
2361
2376
|
|
|
2362
2377
|
if (events.error) { status.textContent = 'Error: ' + events.error; return; }
|
|
@@ -2416,35 +2431,29 @@ async function loadBrowse() {
|
|
|
2416
2431
|
}
|
|
2417
2432
|
|
|
2418
2433
|
function loadBrowseImage(i, ev) {
|
|
2419
|
-
fetch(BASE + '/api/browse-img
|
|
2434
|
+
fetch(BASE + '/api/browse-img', {
|
|
2435
|
+
method: 'POST',
|
|
2436
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2437
|
+
body: JSON.stringify({ cameraId: ev.cameraId, detectionId: ev.detectionId }),
|
|
2438
|
+
})
|
|
2420
2439
|
.then(r => r.ok ? r.blob() : null)
|
|
2421
2440
|
.then(blob => {
|
|
2422
2441
|
if (!blob) return;
|
|
2423
2442
|
const url = URL.createObjectURL(blob);
|
|
2424
2443
|
const img = new Image();
|
|
2425
2444
|
img.onload = () => {
|
|
2445
|
+
const primary = (ev.detections || [])[0];
|
|
2446
|
+
if (!primary?.boundingBox) return;
|
|
2426
2447
|
imgCache.set('browse-' + i, img);
|
|
2448
|
+
// Use drawDetection by temporarily remapping canvas IDs
|
|
2427
2449
|
const fullCanvas = document.getElementById('bcanvas-full-' + i);
|
|
2428
2450
|
const cropCanvas = document.getElementById('bcanvas-crop-' + i);
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
const dw = iw * scale, dh = ih * scale;
|
|
2436
|
-
ctx.fillStyle = '#111'; ctx.fillRect(0, 0, cw, ch);
|
|
2437
|
-
ctx.drawImage(img, (cw-dw)/2, (ch-dh)/2, dw, dh);
|
|
2438
|
-
// Label classes
|
|
2439
|
-
const labels = (ev.detectionClasses || []).join(', ');
|
|
2440
|
-
ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(0, ch-20, cw, 20);
|
|
2441
|
-
ctx.fillStyle = '#f90'; ctx.font = 'bold 11px sans-serif';
|
|
2442
|
-
ctx.fillText(labels, 4, ch-5);
|
|
2443
|
-
fullCanvas.onclick = () => openLightboxImg(img, ev.cameraName, ev.timestamp);
|
|
2444
|
-
}
|
|
2445
|
-
// Hide crop panel — no bounding box available
|
|
2446
|
-
const cropPanel = document.getElementById('bcanvas-crop-' + i)?.closest('.img-panel');
|
|
2447
|
-
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); }
|
|
2448
2457
|
URL.revokeObjectURL(url);
|
|
2449
2458
|
};
|
|
2450
2459
|
img.src = url;
|
|
@@ -2459,17 +2468,19 @@ async function addBrowseEvent(i, label) {
|
|
|
2459
2468
|
|
|
2460
2469
|
if (label !== 'discard') {
|
|
2461
2470
|
try {
|
|
2471
|
+
const primary = (ev.detections || [])[0] || {};
|
|
2462
2472
|
const res = await fetch(BASE + '/api/add-event', {
|
|
2463
2473
|
method: 'POST',
|
|
2464
2474
|
headers: { 'Content-Type': 'application/json' },
|
|
2465
2475
|
body: JSON.stringify({
|
|
2466
2476
|
cameraId: ev.cameraId,
|
|
2467
2477
|
cameraName: ev.cameraName,
|
|
2468
|
-
|
|
2478
|
+
detectionId: ev.detectionId,
|
|
2469
2479
|
timestamp: ev.timestamp,
|
|
2470
|
-
detectedClass:
|
|
2471
|
-
|
|
2472
|
-
|
|
2480
|
+
detectedClass: primary.className || 'unknown',
|
|
2481
|
+
score: primary.score || 1,
|
|
2482
|
+
boundingBox: primary.boundingBox || null,
|
|
2483
|
+
inputDimensions: ev.inputDimensions || null,
|
|
2473
2484
|
label,
|
|
2474
2485
|
}),
|
|
2475
2486
|
});
|