core: add object detection ui

This commit is contained in:
Koushik Dutta
2023-03-14 14:50:04 -07:00
parent 4e25aedbe7
commit 00e523e268
4 changed files with 107 additions and 7 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/core",
"version": "0.1.101",
"version": "0.1.102",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/core",
"version": "0.1.101",
"version": "0.1.102",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/core",
"version": "0.1.101",
"version": "0.1.102",
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -5,7 +5,7 @@
color="primary" icon="mdi-vuetify" border="left">
<template v-slot:prepend>
<v-icon class="white--text mr-3" size="sm" color="#a9afbb">{{
getAlertIcon(alert)
getAlertIcon(alert)
}}</v-icon>
</template>
<div class="caption">{{ alert.title }}</div>
@@ -185,7 +185,8 @@
<LogCard :rows="15" :logRoute="`/device/${id}/`"></LogCard>
</v-flex>
<v-flex xs12 v-if="!device.interfaces.includes(ScryptedInterface.Settings) && (availableMixins.length || deviceIsEditable(device))">
<v-flex xs12
v-if="!device.interfaces.includes(ScryptedInterface.Settings) && (availableMixins.length || deviceIsEditable(device))">
<Settings :device="device"></Settings>
</v-flex>
</v-layout>
@@ -240,6 +241,7 @@ import Scene from "../interfaces/Scene.vue";
import TemperatureSetting from "../interfaces/TemperatureSetting.vue";
import PositionSensor from "../interfaces/sensors/PositionSensor.vue";
import DeviceProvider from "../interfaces/DeviceProvider.vue";
import ObjectDetection from "../interfaces/ObjectDetection.vue";
import MixinProvider from "../interfaces/MixinProvider.vue";
import Readme from "../interfaces/Readme.vue";
import Scriptable from "../interfaces/automation/Scriptable.vue";
@@ -286,7 +288,9 @@ const leftInterfaces = [
ScryptedInterface.DeviceProvider,
ScryptedInterface.Readme,
];
const leftAboveInterfaces = [ScryptedInterface.Camera];
const leftAboveInterfaces = [
ScryptedInterface.Camera,
];
const noCardInterfaces = [
ScryptedInterface.Camera,
@@ -294,7 +298,10 @@ const noCardInterfaces = [
ScryptedInterface.Scriptable,
];
const aboveInterfaces = [ScryptedInterface.Scriptable];
const aboveInterfaces = [
ScryptedInterface.ObjectDetection,
ScryptedInterface.Scriptable
];
const cardActionInterfaces = [
ScryptedInterface.OauthClient,
@@ -379,6 +386,8 @@ export default {
Automation,
Program,
Scriptable,
ObjectDetection,
},
mixins: [Mixin],
data() {

View File

@@ -0,0 +1,91 @@
<template>
<v-sheet :height="600" width="100%" class="d-flex align-center justify-center flex-wrap text-center mx-auto"
@drop="onDrop" @dragover="allowDrop">
<div v-if="!img">Drag and Drop a JPG or PNG to analyze.</div>
<div v-else style="position: relative; height: 100%;">
<img :src="img" style="height: 100%">
<svg v-if="lastDetection" :viewBox="`0 0 ${svgWidth} ${svgHeight}`" ref="svg" style="
top: 0;
left: 0;
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
" v-html="svgContents"></svg>
</div>
</v-sheet>
</template>
<script>
import RPCInterface from "./RPCInterface.vue";
export default {
mixins: [RPCInterface],
data() {
return {
img: null,
lastDetection: null,
}
},
mounted() {
},
computed: {
svgWidth() {
return this.lastDetection?.inputDimensions?.[0] || 1920;
},
svgHeight() {
return this.lastDetection?.inputDimensions?.[1] || 1080;
},
svgContents() {
if (!this.lastDetection) return "";
let contents = "";
for (const detection of this.lastDetection.detections || []) {
if (!detection.boundingBox) continue;
const svgScale = this.svgWidth / 1080;
const sw = 6 * svgScale;
const s = "red";
const x = detection.boundingBox[0];
const y = detection.boundingBox[1];
const w = detection.boundingBox[2];
const h = detection.boundingBox[3];
let t = ``;
let toffset = 0;
if (detection.score && detection.className !== 'motion') {
t += `<tspan x='${x}' dy='${toffset}em'>${Math.round(detection.score * 100) / 100}</tspan>`
toffset -= 1.2;
}
const tname = detection.className + (detection.id ? `: ${detection.id}` : '')
t += `<tspan x='${x}' dy='${toffset}em'>${tname}</tspan>`
const fs = 30 * svgScale;
const box = `<rect x="${x}" y="${y}" width="${w}" height="${h}" stroke="${s}" stroke-width="${sw}" fill="none" />
<text x="${x}" y="${y - 5}" font-size="${fs}" dx="0.05em" dy="0.05em" fill="black">${t}</text>
<text x="${x}" y="${y - 5}" font-size="${fs}" fill="white">${t}</text>
`;
contents += box;
}
return contents;
},
},
methods: {
async onDrop(ev) {
ev.preventDefault()
const file = ev.dataTransfer.files[0];
this.img = URL.createObjectURL(file);
const buffer = Buffer.from(await file.arrayBuffer());
const mediaManager = this.$scrypted.mediaManager;
const mo = await mediaManager.createMediaObject(buffer, 'image/*');
const detected = await this.rpc().detectObjects(mo);
this.lastDetection = detected;
},
allowDrop(ev) {
ev.preventDefault();
}
}
}
</script>