mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-19 00:41:41 +00:00
UI patches (#905)
- Show 0 clients when NT server props are undefined - Add Prettier --------- Co-authored-by: Matthew Morley <matthew.morley.ca@gmail.com>
This commit is contained in:
5
.github/workflows/main.yml
vendored
5
.github/workflows/main.yml
vendored
@@ -67,8 +67,11 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Check Formatting
|
||||
- name: Check Linting
|
||||
run: npm run lint
|
||||
|
||||
- name: Check Formatting
|
||||
run: npm run check-format
|
||||
photon-build-examples:
|
||||
runs-on: ubuntu-22.04
|
||||
name: "Build Examples"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": [
|
||||
"plugin:vue/vue3-recommended",
|
||||
"eslint:recommended",
|
||||
"plugin:vue/recommended",
|
||||
"@vue/eslint-config-typescript/recommended"
|
||||
"@vue/eslint-config-typescript",
|
||||
"@vue/eslint-config-prettier/skip-formatting"
|
||||
],
|
||||
"rules": {
|
||||
"quotes": ["error", "double"],
|
||||
|
||||
1
photon-client/.prettierignore
Normal file
1
photon-client/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
src/assets/fonts/PromptRegular.ts
|
||||
8
photon-client/.prettierrc.json
Normal file
8
photon-client/.prettierrc.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": false,
|
||||
"printWidth": 120,
|
||||
"trailingComma": "none"
|
||||
}
|
||||
927
photon-client/package-lock.json
generated
927
photon-client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,9 @@
|
||||
"build": "run-p build-only",
|
||||
"preview": "vite preview --port 4173",
|
||||
"build-only": "vite build",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||
"format": "prettier --write src/",
|
||||
"check-format": "prettier --check src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/prompt": "^5.0.5",
|
||||
@@ -22,10 +24,13 @@
|
||||
"vuetify": "^2.6.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rushstack/eslint-patch": "^1.3.2",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"prettier": "^3.0.0",
|
||||
"@types/node": "^16.11.45",
|
||||
"@types/three": "^0.154.0",
|
||||
"@vitejs/plugin-vue2": "^2.2.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"deepmerge": "^4.3.1",
|
||||
"eslint": "^8.45.0",
|
||||
|
||||
@@ -9,39 +9,39 @@ import PhotonLogView from "@/components/app/photon-log-view.vue";
|
||||
import PhotonErrorSnackbar from "@/components/app/photon-error-snackbar.vue";
|
||||
|
||||
const websocket = new AutoReconnectingWebsocket(
|
||||
`ws://${inject("backendHost")}/websocket_data`,
|
||||
() => {
|
||||
useStateStore().$patch({ backendConnected: true });
|
||||
},
|
||||
(data) => {
|
||||
if(data.log !== undefined) {
|
||||
useStateStore().addLogFromWebsocket(data.log);
|
||||
}
|
||||
if(data.settings !== undefined) {
|
||||
useSettingsStore().updateGeneralSettingsFromWebsocket(data.settings);
|
||||
}
|
||||
if(data.cameraSettings !== undefined) {
|
||||
useCameraSettingsStore().updateCameraSettingsFromWebsocket(data.cameraSettings);
|
||||
}
|
||||
if(data.ntConnectionInfo !== undefined) {
|
||||
useStateStore().updateNTConnectionStatusFromWebsocket(data.ntConnectionInfo);
|
||||
}
|
||||
if(data.metrics !== undefined) {
|
||||
useSettingsStore().updateMetricsFromWebsocket(data.metrics);
|
||||
}
|
||||
if(data.updatePipelineResult !== undefined) {
|
||||
useStateStore().updatePipelineResultsFromWebsocket(data.updatePipelineResult);
|
||||
}
|
||||
if(data.mutatePipelineSettings !== undefined && data.cameraIndex !== undefined) {
|
||||
useCameraSettingsStore().changePipelineSettingsInStore(data.mutatePipelineSettings, data.cameraIndex);
|
||||
}
|
||||
if(data.calibrationData !== undefined) {
|
||||
useStateStore().updateCalibrationStateValuesFromWebsocket(data.calibrationData);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
useStateStore().$patch({ backendConnected: false });
|
||||
`ws://${inject("backendHost")}/websocket_data`,
|
||||
() => {
|
||||
useStateStore().$patch({ backendConnected: true });
|
||||
},
|
||||
(data) => {
|
||||
if (data.log !== undefined) {
|
||||
useStateStore().addLogFromWebsocket(data.log);
|
||||
}
|
||||
if (data.settings !== undefined) {
|
||||
useSettingsStore().updateGeneralSettingsFromWebsocket(data.settings);
|
||||
}
|
||||
if (data.cameraSettings !== undefined) {
|
||||
useCameraSettingsStore().updateCameraSettingsFromWebsocket(data.cameraSettings);
|
||||
}
|
||||
if (data.ntConnectionInfo !== undefined) {
|
||||
useStateStore().updateNTConnectionStatusFromWebsocket(data.ntConnectionInfo);
|
||||
}
|
||||
if (data.metrics !== undefined) {
|
||||
useSettingsStore().updateMetricsFromWebsocket(data.metrics);
|
||||
}
|
||||
if (data.updatePipelineResult !== undefined) {
|
||||
useStateStore().updatePipelineResultsFromWebsocket(data.updatePipelineResult);
|
||||
}
|
||||
if (data.mutatePipelineSettings !== undefined && data.cameraIndex !== undefined) {
|
||||
useCameraSettingsStore().changePipelineSettingsInStore(data.mutatePipelineSettings, data.cameraIndex);
|
||||
}
|
||||
if (data.calibrationData !== undefined) {
|
||||
useStateStore().updateCalibrationStateValuesFromWebsocket(data.calibrationData);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
useStateStore().$patch({ backendConnected: false });
|
||||
}
|
||||
);
|
||||
|
||||
useStateStore().$patch({ websocket: websocket });
|
||||
@@ -51,11 +51,7 @@ useStateStore().$patch({ websocket: websocket });
|
||||
<v-app>
|
||||
<photon-sidebar />
|
||||
<v-main>
|
||||
<v-container
|
||||
class="main-container"
|
||||
fluid
|
||||
fill-height
|
||||
>
|
||||
<v-container class="main-container" fluid fill-height>
|
||||
<v-layout>
|
||||
<v-flex>
|
||||
<router-view />
|
||||
@@ -70,7 +66,7 @@ useStateStore().$patch({ websocket: websocket });
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import 'vuetify/src/styles/settings/_variables';
|
||||
@import "vuetify/src/styles/settings/_variables";
|
||||
|
||||
@media #{map-get($display-breakpoints, 'md-and-down')} {
|
||||
html {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { TrackballControls } from "three/examples/jsm/controls/TrackballControls
|
||||
import { type Object3D } from "three";
|
||||
|
||||
const props = defineProps<{
|
||||
targets: PhotonTarget[]
|
||||
targets: PhotonTarget[];
|
||||
}>();
|
||||
|
||||
let scene: Scene | undefined;
|
||||
@@ -29,25 +29,20 @@ let controls: TrackballControls | undefined;
|
||||
let previousTargets: Object3D[] = [];
|
||||
const drawTargets = (targets: PhotonTarget[]) => {
|
||||
// Check here, since if we check in watchEffect this never gets called
|
||||
if(scene === undefined || camera === undefined || renderer === undefined || controls === undefined) {
|
||||
if (scene === undefined || camera === undefined || renderer === undefined || controls === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
scene.remove(...previousTargets);
|
||||
previousTargets = [];
|
||||
|
||||
targets.forEach(target => {
|
||||
if(target.pose === undefined) return;
|
||||
targets.forEach((target) => {
|
||||
if (target.pose === undefined) return;
|
||||
|
||||
const geometry = new BoxGeometry(0.3 / 5, 0.2, 0.2);
|
||||
const material = new MeshNormalMaterial();
|
||||
|
||||
const quaternion = new Quaternion(
|
||||
target.pose.qx,
|
||||
target.pose.qy,
|
||||
target.pose.qz,
|
||||
target.pose.qw
|
||||
);
|
||||
const quaternion = new Quaternion(target.pose.qx, target.pose.qy, target.pose.qz, target.pose.qw);
|
||||
|
||||
const cube = new Mesh(geometry, material);
|
||||
cube.position.set(target.pose.x, target.pose.y, target.pose.z);
|
||||
@@ -72,7 +67,7 @@ const drawTargets = (targets: PhotonTarget[]) => {
|
||||
previousTargets.push(arrow);
|
||||
});
|
||||
|
||||
if(previousTargets.length > 0) {
|
||||
if (previousTargets.length > 0) {
|
||||
scene.add(...previousTargets);
|
||||
}
|
||||
};
|
||||
@@ -80,7 +75,7 @@ const onWindowResize = () => {
|
||||
const container = document.getElementById("container");
|
||||
const canvas = document.getElementById("view");
|
||||
|
||||
if(container === null || canvas === null || camera === undefined || renderer === undefined) {
|
||||
if (container === null || canvas === null || camera === undefined || renderer === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -91,7 +86,7 @@ const onWindowResize = () => {
|
||||
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
|
||||
};
|
||||
const resetCamFirstPerson = () => {
|
||||
if(scene === undefined || camera === undefined || controls === undefined) {
|
||||
if (scene === undefined || camera === undefined || controls === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -100,12 +95,12 @@ const resetCamFirstPerson = () => {
|
||||
camera.up.set(0, 0, 1);
|
||||
controls.target.set(4.0, 0.0, 0.0);
|
||||
controls.update();
|
||||
if(previousTargets.length > 0) {
|
||||
if (previousTargets.length > 0) {
|
||||
scene.add(...previousTargets);
|
||||
}
|
||||
};
|
||||
const resetCamThirdPerson = () => {
|
||||
if(scene === undefined || camera === undefined || controls === undefined) {
|
||||
if (scene === undefined || camera === undefined || controls === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -114,7 +109,7 @@ const resetCamThirdPerson = () => {
|
||||
camera.up.set(0, 0, 1);
|
||||
controls.target.set(4.0, 0.0, 0.0);
|
||||
controls.update();
|
||||
if(previousTargets.length > 0) {
|
||||
if (previousTargets.length > 0) {
|
||||
scene.add(...previousTargets);
|
||||
}
|
||||
};
|
||||
@@ -124,7 +119,7 @@ onMounted(() => {
|
||||
camera = new PerspectiveCamera(75, 800 / 800, 0.1, 1000);
|
||||
|
||||
const canvas = document.getElementById("view");
|
||||
if(canvas === null) return;
|
||||
if (canvas === null) return;
|
||||
renderer = new WebGLRenderer({ canvas: canvas });
|
||||
|
||||
scene.background = new Color(0xa9a9a9);
|
||||
@@ -133,14 +128,20 @@ onMounted(() => {
|
||||
window.addEventListener("resize", onWindowResize);
|
||||
|
||||
const referenceFrameCues: Object3D[] = [];
|
||||
referenceFrameCues.push(new ArrowHelper(new Vector3(1, 0, 0).normalize(), new Vector3(0, 0, 0), 1, 0xff0000, 0.1, 0.1));
|
||||
referenceFrameCues.push(new ArrowHelper(new Vector3(0, 1, 0).normalize(), new Vector3(0, 0, 0), 1, 0x00ff00, 0.1, 0.1));
|
||||
referenceFrameCues.push(new ArrowHelper(new Vector3(0, 0, 1).normalize(), new Vector3(0, 0, 0), 1, 0x0000ff, 0.1, 0.1));
|
||||
referenceFrameCues.push(
|
||||
new ArrowHelper(new Vector3(1, 0, 0).normalize(), new Vector3(0, 0, 0), 1, 0xff0000, 0.1, 0.1)
|
||||
);
|
||||
referenceFrameCues.push(
|
||||
new ArrowHelper(new Vector3(0, 1, 0).normalize(), new Vector3(0, 0, 0), 1, 0x00ff00, 0.1, 0.1)
|
||||
);
|
||||
referenceFrameCues.push(
|
||||
new ArrowHelper(new Vector3(0, 0, 1).normalize(), new Vector3(0, 0, 0), 1, 0x0000ff, 0.1, 0.1)
|
||||
);
|
||||
|
||||
// Draw the Camera Body
|
||||
const camSize = 0.2;
|
||||
const camBodyGeometry = new BoxGeometry(camSize, camSize, camSize);
|
||||
const camLensGeometry = new ConeGeometry(camSize*0.4, camSize*0.8, 30);
|
||||
const camLensGeometry = new ConeGeometry(camSize * 0.4, camSize * 0.8, 30);
|
||||
const camMaterial = new MeshNormalMaterial();
|
||||
const camBody = new Mesh(camBodyGeometry, camMaterial);
|
||||
const camLens = new Mesh(camLensGeometry, camMaterial);
|
||||
@@ -150,10 +151,7 @@ onMounted(() => {
|
||||
referenceFrameCues.push(camBody);
|
||||
referenceFrameCues.push(camLens);
|
||||
|
||||
controls = new TrackballControls(
|
||||
camera,
|
||||
renderer.domElement
|
||||
);
|
||||
controls = new TrackballControls(camera, renderer.domElement);
|
||||
controls.rotateSpeed = 1.0;
|
||||
controls.zoomSpeed = 1.2;
|
||||
controls.panSpeed = 0.8;
|
||||
@@ -168,7 +166,7 @@ onMounted(() => {
|
||||
controls.update();
|
||||
|
||||
const animate = () => {
|
||||
if(scene === undefined || camera === undefined || renderer === undefined || controls === undefined) {
|
||||
if (scene === undefined || camera === undefined || renderer === undefined || controls === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -185,41 +183,23 @@ onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", onWindowResize);
|
||||
});
|
||||
watchEffect(() => {
|
||||
drawTargets(props.targets);
|
||||
drawTargets(props.targets);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
id="container"
|
||||
style="width: 100%"
|
||||
>
|
||||
<div id="container" style="width: 100%">
|
||||
<v-row>
|
||||
<v-col
|
||||
align-self="stretch"
|
||||
style="display: flex; justify-content: center"
|
||||
>
|
||||
<canvas
|
||||
id="view"
|
||||
/>
|
||||
<v-col align-self="stretch" style="display: flex; justify-content: center">
|
||||
<canvas id="view" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row style="margin-bottom: 24px">
|
||||
<v-col style="display: flex; justify-content: center">
|
||||
<v-btn
|
||||
color="secondary"
|
||||
@click="resetCamFirstPerson"
|
||||
>
|
||||
First Person
|
||||
</v-btn>
|
||||
<v-btn color="secondary" @click="resetCamFirstPerson"> First Person </v-btn>
|
||||
</v-col>
|
||||
<v-col style="display: flex; justify-content: center">
|
||||
<v-btn
|
||||
color="secondary"
|
||||
@click="resetCamThirdPerson"
|
||||
>
|
||||
Third Person
|
||||
</v-btn>
|
||||
<v-btn color="secondary" @click="resetCamThirdPerson"> Third Person </v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
@@ -6,14 +6,15 @@ import loadingImage from "@/assets/images/loading.svg";
|
||||
import type { StyleValue } from "vue/types/jsx";
|
||||
|
||||
const props = defineProps<{
|
||||
streamType: "Raw" | "Processed",
|
||||
id?: string
|
||||
streamType: "Raw" | "Processed";
|
||||
id?: string;
|
||||
}>();
|
||||
|
||||
const src = computed<string>(() => {
|
||||
const port = useCameraSettingsStore().currentCameraSettings.stream[props.streamType === "Raw" ? "inputPort" : "outputPort"];
|
||||
const port =
|
||||
useCameraSettingsStore().currentCameraSettings.stream[props.streamType === "Raw" ? "inputPort" : "outputPort"];
|
||||
|
||||
if(!useStateStore().backendConnected || port === 0) {
|
||||
if (!useStateStore().backendConnected || port === 0) {
|
||||
return loadingImage;
|
||||
}
|
||||
|
||||
@@ -22,9 +23,9 @@ const src = computed<string>(() => {
|
||||
const alt = computed<string>(() => `${props.streamType} Stream View`);
|
||||
|
||||
const style = computed<StyleValue>(() => {
|
||||
if(useStateStore().colorPickingMode) {
|
||||
if (useStateStore().colorPickingMode) {
|
||||
return { cursor: "crosshair" };
|
||||
} else if(src.value !== loadingImage) {
|
||||
} else if (src.value !== loadingImage) {
|
||||
return { cursor: "pointer" };
|
||||
}
|
||||
|
||||
@@ -32,19 +33,12 @@ const style = computed<StyleValue>(() => {
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
if(!useStateStore().colorPickingMode && src.value !== loadingImage) {
|
||||
if (!useStateStore().colorPickingMode && src.value !== loadingImage) {
|
||||
window.open(src.value);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<img
|
||||
:id="id"
|
||||
crossorigin="anonymous"
|
||||
:src="src"
|
||||
:alt="alt"
|
||||
:style="style"
|
||||
@click="handleClick"
|
||||
>
|
||||
<img :id="id" crossorigin="anonymous" :src="src" :alt="alt" :style="style" @click="handleClick" />
|
||||
</template>
|
||||
|
||||
@@ -5,7 +5,9 @@ import { useStateStore } from "@/stores/StateStore";
|
||||
|
||||
const selectedLogLevels = ref<LogLevel[]>([LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO]);
|
||||
|
||||
const logs = computed<LogMessage[]>(() => useStateStore().logMessages.filter(message => selectedLogLevels.value.includes(message.level)));
|
||||
const logs = computed<LogMessage[]>(() =>
|
||||
useStateStore().logMessages.filter((message) => selectedLogLevels.value.includes(message.level))
|
||||
);
|
||||
|
||||
const backendHost = inject<string>("backendHost");
|
||||
|
||||
@@ -33,38 +35,22 @@ const handleLogExport = () => {
|
||||
exportLogFile.value.click();
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", e => {
|
||||
document.addEventListener("keydown", (e) => {
|
||||
switch (e.key) {
|
||||
case "`":
|
||||
useStateStore().$patch(state => state.showLogModal = !state.showLogModal);
|
||||
useStateStore().$patch((state) => (state.showLogModal = !state.showLogModal));
|
||||
break;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-dialog
|
||||
v-model="useStateStore().showLogModal"
|
||||
width="1500"
|
||||
dark
|
||||
>
|
||||
<v-card
|
||||
dark
|
||||
class="pt-3"
|
||||
color="primary"
|
||||
flat
|
||||
>
|
||||
<v-dialog v-model="useStateStore().showLogModal" width="1500" dark>
|
||||
<v-card dark class="pt-3" color="primary" flat>
|
||||
<v-card-title>
|
||||
View Program Logs
|
||||
<v-btn
|
||||
color="secondary"
|
||||
style="margin-left: auto;"
|
||||
depressed
|
||||
@click="handleLogExport"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-download
|
||||
</v-icon>
|
||||
<v-btn color="secondary" style="margin-left: auto" depressed @click="handleLogExport">
|
||||
<v-icon left> mdi-download </v-icon>
|
||||
Download Current Log
|
||||
|
||||
<!-- Special hidden link that gets 'clicked' when the user exports journalctl logs -->
|
||||
@@ -79,34 +65,16 @@ document.addEventListener("keydown", e => {
|
||||
</v-card-title>
|
||||
|
||||
<div class="pr-6 pl-6">
|
||||
<v-btn-toggle
|
||||
v-model="selectedLogLevels"
|
||||
dark
|
||||
multiple
|
||||
class="fill mb-4"
|
||||
>
|
||||
<v-btn
|
||||
v-for="(level) in [0, 1, 2, 3]"
|
||||
:key="level"
|
||||
color="secondary"
|
||||
class="fill"
|
||||
>
|
||||
<v-btn-toggle v-model="selectedLogLevels" dark multiple class="fill mb-4">
|
||||
<v-btn v-for="level in [0, 1, 2, 3]" :key="level" color="secondary" class="fill">
|
||||
{{ getLogLevelFromIndex(level) }}
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
<v-card-text
|
||||
v-if="logs.length === 0"
|
||||
style="font-size: 18px; font-weight: 600"
|
||||
>
|
||||
<v-card-text v-if="logs.length === 0" style="font-size: 18px; font-weight: 600">
|
||||
There are no Logs to show
|
||||
</v-card-text>
|
||||
<v-virtual-scroll
|
||||
v-else
|
||||
:items="logs"
|
||||
item-height="50"
|
||||
height="600"
|
||||
>
|
||||
<template #default="{item}">
|
||||
<v-virtual-scroll v-else :items="logs" item-height="50" height="600">
|
||||
<template #default="{ item }">
|
||||
<div :class="[getLogColor(item.level) + '--text', 'log-item']">
|
||||
{{ item.message }}
|
||||
</div>
|
||||
@@ -118,13 +86,7 @@ document.addEventListener("keydown", e => {
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
color="white"
|
||||
text
|
||||
@click="() => useStateStore().showLogModal = false"
|
||||
>
|
||||
Close
|
||||
</v-btn>
|
||||
<v-btn color="white" text @click="() => (useStateStore().showLogModal = false)"> Close </v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
@@ -4,8 +4,12 @@ import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
|
||||
const compact = computed<boolean>({
|
||||
get: () => { return useStateStore().sidebarFolded; },
|
||||
set: (val) => { useStateStore().setSidebarFolded(val); }
|
||||
get: () => {
|
||||
return useStateStore().sidebarFolded;
|
||||
},
|
||||
set: (val) => {
|
||||
useStateStore().setSidebarFolded(val);
|
||||
}
|
||||
});
|
||||
|
||||
// Vuetify2 doesn't yet support the useDisplay API so this is required to access the prop when using the Composition API
|
||||
@@ -13,39 +17,17 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-navigation-drawer
|
||||
dark
|
||||
app
|
||||
permanent
|
||||
:mini-variant="compact || !mdAndUp"
|
||||
color="primary"
|
||||
>
|
||||
<v-navigation-drawer dark app permanent :mini-variant="compact || !mdAndUp" color="primary">
|
||||
<v-list>
|
||||
<!-- List item for the heading; note that there are some tricks in setting padding and image width make things look right -->
|
||||
<v-list-item
|
||||
:class="(compact || !mdAndUp) ? 'pr-0 pl-0' : ''"
|
||||
style="display: flex; justify-content: center"
|
||||
>
|
||||
<v-list-item :class="compact || !mdAndUp ? 'pr-0 pl-0' : ''" style="display: flex; justify-content: center">
|
||||
<v-list-item-icon class="mr-0">
|
||||
<img
|
||||
v-if="!(compact || !mdAndUp)"
|
||||
class="logo"
|
||||
src="@/assets/images/logoLarge.svg"
|
||||
alt="large logo"
|
||||
>
|
||||
<img
|
||||
v-else
|
||||
class="logo"
|
||||
src="@/assets/images/logoSmall.svg"
|
||||
alt="small logo"
|
||||
>
|
||||
<img v-if="!(compact || !mdAndUp)" class="logo" src="@/assets/images/logoLarge.svg" alt="large logo" />
|
||||
<img v-else class="logo" src="@/assets/images/logoSmall.svg" alt="small logo" />
|
||||
</v-list-item-icon>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item
|
||||
link
|
||||
to="/dashboard"
|
||||
>
|
||||
<v-list-item link to="/dashboard">
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-view-dashboard</v-icon>
|
||||
</v-list-item-icon>
|
||||
@@ -53,11 +35,7 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
|
||||
<v-list-item-title>Dashboard</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
ref="camerasTabOpener"
|
||||
link
|
||||
to="/cameras"
|
||||
>
|
||||
<v-list-item ref="camerasTabOpener" link to="/cameras">
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-camera</v-icon>
|
||||
</v-list-item-icon>
|
||||
@@ -65,10 +43,7 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
|
||||
<v-list-item-title>Cameras</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
link
|
||||
to="/settings"
|
||||
>
|
||||
<v-list-item link to="/settings">
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-cog</v-icon>
|
||||
</v-list-item-icon>
|
||||
@@ -76,10 +51,7 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
|
||||
<v-list-item-title>Settings</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
link
|
||||
to="/docs"
|
||||
>
|
||||
<v-list-item link to="/docs">
|
||||
<v-list-item-icon>
|
||||
<v-icon>mdi-bookshelf</v-icon>
|
||||
</v-list-item-icon>
|
||||
@@ -87,46 +59,27 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
|
||||
<v-list-item-title>Documentation</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item
|
||||
v-if="mdAndUp"
|
||||
link
|
||||
@click="() => compact = !compact"
|
||||
>
|
||||
<v-list-item v-if="mdAndUp" link @click="() => (compact = !compact)">
|
||||
<v-list-item-icon>
|
||||
<v-icon v-if="compact || !mdAndUp">
|
||||
mdi-chevron-right
|
||||
</v-icon>
|
||||
<v-icon v-else>
|
||||
mdi-chevron-left
|
||||
</v-icon>
|
||||
<v-icon v-if="compact || !mdAndUp"> mdi-chevron-right </v-icon>
|
||||
<v-icon v-else> mdi-chevron-left </v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>Compact Mode</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<div style="position: absolute; bottom: 0; left: 0;">
|
||||
<div style="position: absolute; bottom: 0; left: 0">
|
||||
<v-list-item>
|
||||
<v-list-item-icon>
|
||||
<v-icon v-if="useSettingsStore().network.runNTServer">
|
||||
mdi-server
|
||||
</v-icon>
|
||||
<v-icon v-else-if="useStateStore().ntConnectionStatus.connected">
|
||||
mdi-robot
|
||||
</v-icon>
|
||||
<v-icon
|
||||
v-else
|
||||
style="border-radius: 100%"
|
||||
>
|
||||
mdi-robot-off
|
||||
</v-icon>
|
||||
<v-icon v-if="useSettingsStore().network.runNTServer"> mdi-server </v-icon>
|
||||
<v-icon v-else-if="useStateStore().ntConnectionStatus.connected"> mdi-robot </v-icon>
|
||||
<v-icon v-else style="border-radius: 100%"> mdi-robot-off </v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title
|
||||
v-if="useSettingsStore().network.runNTServer"
|
||||
class="text-wrap"
|
||||
>
|
||||
NetworkTables server running for <span class="accent--text">{{ useStateStore().ntConnectionStatus.clients }}</span> clients
|
||||
<v-list-item-title v-if="useSettingsStore().network.runNTServer" class="text-wrap">
|
||||
NetworkTables server running for
|
||||
<span class="accent--text">{{ useStateStore().ntConnectionStatus.clients || 0 }}</span> clients
|
||||
</v-list-item-title>
|
||||
<v-list-item-title
|
||||
v-else-if="useStateStore().ntConnectionStatus.connected && useStateStore().backendConnected"
|
||||
@@ -134,17 +87,11 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
|
||||
style="flex-direction: column; display: flex"
|
||||
>
|
||||
NetworkTables Server Connected!
|
||||
<span
|
||||
class="accent--text"
|
||||
>
|
||||
<span class="accent--text">
|
||||
{{ useStateStore().ntConnectionStatus.address }}
|
||||
</span>
|
||||
</v-list-item-title>
|
||||
<v-list-item-title
|
||||
v-else
|
||||
class="text-wrap"
|
||||
style="flex-direction: column; display: flex"
|
||||
>
|
||||
<v-list-item-title v-else class="text-wrap" style="flex-direction: column; display: flex">
|
||||
Not connected to NetworkTables Server!
|
||||
</v-list-item-title>
|
||||
</v-list-item-content>
|
||||
@@ -152,15 +99,8 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
|
||||
|
||||
<v-list-item>
|
||||
<v-list-item-icon>
|
||||
<v-icon v-if="useStateStore().backendConnected">
|
||||
mdi-server-network
|
||||
</v-icon>
|
||||
<v-icon
|
||||
v-else
|
||||
style="border-radius: 100%;"
|
||||
>
|
||||
mdi-server-network-off
|
||||
</v-icon>
|
||||
<v-icon v-if="useStateStore().backendConnected"> mdi-server-network </v-icon>
|
||||
<v-icon v-else style="border-radius: 100%"> mdi-server-network-off </v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="text-wrap">
|
||||
|
||||
@@ -12,41 +12,60 @@ import CvSelect from "@/components/common/cv-select.vue";
|
||||
import CvNumberInput from "@/components/common/cv-number-input.vue";
|
||||
import { WebsocketPipelineType } from "@/types/WebsocketDataTypes";
|
||||
|
||||
|
||||
const settingsValid = ref(true);
|
||||
|
||||
const getCalibrationCoeffs = (resolution: Resolution) => {
|
||||
return useCameraSettingsStore().currentCameraSettings.completeCalibrations.find(cal => cal.resolution.width === resolution.width && cal.resolution.height === resolution.height);
|
||||
return useCameraSettingsStore().currentCameraSettings.completeCalibrations.find(
|
||||
(cal) => cal.resolution.width === resolution.width && cal.resolution.height === resolution.height
|
||||
);
|
||||
};
|
||||
const getUniqueVideoResolutions = (): VideoFormat[] => {
|
||||
const uniqueResolutions: VideoFormat[] = [];
|
||||
useCameraSettingsStore().currentCameraSettings.validVideoFormats.forEach((format, index) => {
|
||||
if(!uniqueResolutions.some(v => v.resolution.width === format.resolution.width && v.resolution.height === format.resolution.height)) {
|
||||
if (
|
||||
!uniqueResolutions.some(
|
||||
(v) => v.resolution.width === format.resolution.width && v.resolution.height === format.resolution.height
|
||||
)
|
||||
) {
|
||||
format.index = index;
|
||||
|
||||
const calib = getCalibrationCoeffs(format.resolution);
|
||||
if(calib !== undefined) {
|
||||
if (calib !== undefined) {
|
||||
format.standardDeviation = calib.standardDeviation;
|
||||
format.mean = calib.perViewErrors.reduce((a, b) => a + b) / calib.perViewErrors.length;
|
||||
format.horizontalFOV = 2 * Math.atan2(format.resolution.width/2, calib.intrinsics[0]) * (180/Math.PI);
|
||||
format.verticalFOV = 2 * Math.atan2(format.resolution.height/2, calib.intrinsics[4]) * (180/Math.PI);
|
||||
format.diagonalFOV = 2 * Math.atan2(Math.sqrt(format.resolution.width**2 + (format.resolution.height/(calib.intrinsics[4]/calib.intrinsics[0]))**2)/2, calib.intrinsics[0]) * (180/Math.PI);
|
||||
format.horizontalFOV = 2 * Math.atan2(format.resolution.width / 2, calib.intrinsics[0]) * (180 / Math.PI);
|
||||
format.verticalFOV = 2 * Math.atan2(format.resolution.height / 2, calib.intrinsics[4]) * (180 / Math.PI);
|
||||
format.diagonalFOV =
|
||||
2 *
|
||||
Math.atan2(
|
||||
Math.sqrt(
|
||||
format.resolution.width ** 2 +
|
||||
(format.resolution.height / (calib.intrinsics[4] / calib.intrinsics[0])) ** 2
|
||||
) / 2,
|
||||
calib.intrinsics[0]
|
||||
) *
|
||||
(180 / Math.PI);
|
||||
}
|
||||
uniqueResolutions.push(format);
|
||||
}
|
||||
});
|
||||
uniqueResolutions.sort((a, b) => (b.resolution.width + b.resolution.height) - (a.resolution.width + a.resolution.height));
|
||||
uniqueResolutions.sort(
|
||||
(a, b) => b.resolution.width + b.resolution.height - (a.resolution.width + a.resolution.height)
|
||||
);
|
||||
return uniqueResolutions;
|
||||
};
|
||||
const getUniqueVideoResolutionStrings = () => getUniqueVideoResolutions().map<{name: string, value: number}>(f => ({
|
||||
name: `${f.resolution.width} X ${f.resolution.height}`,
|
||||
// Index won't ever be undefined
|
||||
value: f.index || 0
|
||||
}));
|
||||
const calibrationDivisors = computed(() => [1, 2, 4].filter(v => {
|
||||
const currentRes = useCameraSettingsStore().currentVideoFormat.resolution;
|
||||
return ((currentRes.width / v) >= 300 && (currentRes.height / v) >= 220) || (v === 1);
|
||||
}));
|
||||
const getUniqueVideoResolutionStrings = () =>
|
||||
getUniqueVideoResolutions().map<{ name: string; value: number }>((f) => ({
|
||||
name: `${f.resolution.width} X ${f.resolution.height}`,
|
||||
// Index won't ever be undefined
|
||||
value: f.index || 0
|
||||
}));
|
||||
const calibrationDivisors = computed(() =>
|
||||
[1, 2, 4].filter((v) => {
|
||||
const currentRes = useCameraSettingsStore().currentVideoFormat.resolution;
|
||||
return (currentRes.width / v >= 300 && currentRes.height / v >= 220) || v === 1;
|
||||
})
|
||||
);
|
||||
|
||||
const squareSizeIn = ref(1);
|
||||
const patternWidth = ref(8);
|
||||
@@ -85,7 +104,8 @@ const downloadCalibBoard = () => {
|
||||
break;
|
||||
case CalibrationBoardTypes.DotBoard:
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const dotgridStartX = (paperWidth - (2 * (patternWidth.value - 1) + ((patternHeight.value - 1) % 2)) * squareSizeIn.value) / 2.0;
|
||||
const dotgridStartX =
|
||||
(paperWidth - (2 * (patternWidth.value - 1) + ((patternHeight.value - 1) % 2)) * squareSizeIn.value) / 2.0;
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const dotgridStartY = (paperHeight - (patternHeight.value - squareSizeIn.value)) / 2;
|
||||
|
||||
@@ -118,12 +138,10 @@ const downloadCalibBoard = () => {
|
||||
logoImage.src = MonoLogo;
|
||||
doc.addImage(logoImage, "PNG", 1.0, 0.75, 1.4, 0.5);
|
||||
|
||||
doc.text(`${patternWidth.value} x ${patternHeight.value} | ${squareSizeIn.value}in`, paperWidth - 1, 1.0,
|
||||
{
|
||||
maxWidth: (paperWidth - 2.0) / 2,
|
||||
align: "right"
|
||||
}
|
||||
);
|
||||
doc.text(`${patternWidth.value} x ${patternHeight.value} | ${squareSizeIn.value}in`, paperWidth - 1, 1.0, {
|
||||
maxWidth: (paperWidth - 2.0) / 2,
|
||||
align: "right"
|
||||
});
|
||||
|
||||
doc.save(`calibrationTarget-${CalibrationBoardTypes[boardType.value]}.pdf`);
|
||||
};
|
||||
@@ -132,28 +150,29 @@ const importCalibrationFromCalibDB = ref();
|
||||
const openCalibUploadPrompt = () => {
|
||||
importCalibrationFromCalibDB.value.click();
|
||||
};
|
||||
const readImportedCalibration = ({ files } : { files: FileList}) => {
|
||||
files[0].text().then(text => {
|
||||
useCameraSettingsStore().importCalibDB({ payload: text, filename: files[0].name })
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: response.status === 200 ? "success" : "error"
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while uploading calibration file! The backend didn't respond to the upload attempt.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while uploading calibration file!",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
const readImportedCalibration = ({ files }: { files: FileList }) => {
|
||||
files[0].text().then((text) => {
|
||||
useCameraSettingsStore()
|
||||
.importCalibDB({ payload: text, filename: files[0].name })
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: response.status === 200 ? "success" : "error"
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while uploading calibration file! The backend didn't respond to the upload attempt.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while uploading calibration file!",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -174,13 +193,14 @@ const showCalibEndDialog = ref(false);
|
||||
const calibCanceled = ref(false);
|
||||
const calibSuccess = ref<boolean | undefined>(undefined);
|
||||
const endCalibration = () => {
|
||||
if(!useStateStore().calibrationData.hasEnoughImages) {
|
||||
if (!useStateStore().calibrationData.hasEnoughImages) {
|
||||
calibCanceled.value = true;
|
||||
}
|
||||
|
||||
showCalibEndDialog.value = true;
|
||||
// Check if calibration finished cleanly or was canceled
|
||||
useCameraSettingsStore().endPnPCalibration()
|
||||
useCameraSettingsStore()
|
||||
.endPnPCalibration()
|
||||
.then(() => {
|
||||
calibSuccess.value = true;
|
||||
})
|
||||
@@ -195,22 +215,12 @@ const endCalibration = () => {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-card
|
||||
class="pr-6 pb-3"
|
||||
color="primary"
|
||||
dark
|
||||
>
|
||||
<v-card class="pr-6 pb-3" color="primary" dark>
|
||||
<v-card-title>Camera Calibration</v-card-title>
|
||||
<div class="ml-5">
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<v-form
|
||||
ref="form"
|
||||
v-model="settingsValid"
|
||||
>
|
||||
<v-col cols="12" md="6">
|
||||
<v-form ref="form" v-model="settingsValid">
|
||||
<cv-select
|
||||
v-model="useStateStore().calibrationData.videoFormatIndex"
|
||||
label="Resolution"
|
||||
@@ -225,7 +235,9 @@ const endCalibration = () => {
|
||||
tooltip="Resolution to which camera frames are downscaled for detection. Calibration still uses full-res"
|
||||
:items="calibrationDivisors"
|
||||
:select-cols="7"
|
||||
@input="v => useCameraSettingsStore().changeCurrentPipelineSetting({streamingFrameDivisor: v}, false)"
|
||||
@input="
|
||||
(v) => useCameraSettingsStore().changeCurrentPipelineSetting({ streamingFrameDivisor: v }, false)
|
||||
"
|
||||
/>
|
||||
<cv-select
|
||||
v-model="boardType"
|
||||
@@ -240,7 +252,7 @@ const endCalibration = () => {
|
||||
label="Pattern Spacing (in)"
|
||||
tooltip="Spacing between pattern features in inches"
|
||||
:disabled="isCalibrating"
|
||||
:rules="[v => (v > 0) || 'Size must be positive']"
|
||||
:rules="[(v) => v > 0 || 'Size must be positive']"
|
||||
:label-cols="5"
|
||||
/>
|
||||
<cv-number-input
|
||||
@@ -248,7 +260,7 @@ const endCalibration = () => {
|
||||
label="Board Width (in)"
|
||||
tooltip="Width of the board in dots or chessboard squares"
|
||||
:disabled="isCalibrating"
|
||||
:rules="[v => (v >= 4) || 'Width must be at least 4']"
|
||||
:rules="[(v) => v >= 4 || 'Width must be at least 4']"
|
||||
:label-cols="5"
|
||||
/>
|
||||
<cv-number-input
|
||||
@@ -256,54 +268,31 @@ const endCalibration = () => {
|
||||
label="Board Height (in)"
|
||||
tooltip="Height of the board in dots or chessboard squares"
|
||||
:disabled="isCalibrating"
|
||||
:rules="[v => (v >= 4) || 'Height must be at least 4']"
|
||||
:rules="[(v) => v >= 4 || 'Height must be at least 4']"
|
||||
:label-cols="5"
|
||||
/>
|
||||
</v-form>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<v-row
|
||||
align="start"
|
||||
class="pb-4 pt-2"
|
||||
>
|
||||
<v-simple-table
|
||||
fixed-header
|
||||
height="100%"
|
||||
dense
|
||||
>
|
||||
<v-col cols="12" md="6">
|
||||
<v-row align="start" class="pb-4 pt-2">
|
||||
<v-simple-table fixed-header height="100%" dense>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Resolution
|
||||
</th>
|
||||
<th>
|
||||
Mean Error
|
||||
</th>
|
||||
<th>
|
||||
Standard Deviation
|
||||
</th>
|
||||
<th>
|
||||
Horizontal FOV
|
||||
</th>
|
||||
<th>
|
||||
Vertical FOV
|
||||
</th>
|
||||
<th>
|
||||
Diagonal FOV
|
||||
</th>
|
||||
<th>Resolution</th>
|
||||
<th>Mean Error</th>
|
||||
<th>Standard Deviation</th>
|
||||
<th>Horizontal FOV</th>
|
||||
<th>Vertical FOV</th>
|
||||
<th>Diagonal FOV</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(value, index) in getUniqueVideoResolutions()"
|
||||
:key="index"
|
||||
>
|
||||
<tr v-for="(value, index) in getUniqueVideoResolutions()" :key="index">
|
||||
<td>{{ value.resolution.width }} X {{ value.resolution.height }}</td>
|
||||
<td>{{ value.mean !== undefined ? value.mean.toFixed(2) + "px" : "-" }}</td>
|
||||
<td>{{ value.standardDeviation !== undefined ? value.standardDeviation.toFixed(2) + "px" : "-" }}</td>
|
||||
<td>
|
||||
{{ value.standardDeviation !== undefined ? value.standardDeviation.toFixed(2) + "px" : "-" }}
|
||||
</td>
|
||||
<td>{{ value.horizontalFOV !== undefined ? value.horizontalFOV.toFixed(2) + "°" : "-" }}</td>
|
||||
<td>{{ value.verticalFOV !== undefined ? value.verticalFOV.toFixed(2) + "°" : "-" }}</td>
|
||||
<td>{{ value.diagonalFOV !== undefined ? value.diagonalFOV.toFixed(2) + "°" : "-" }}</td>
|
||||
@@ -317,16 +306,14 @@ const endCalibration = () => {
|
||||
label
|
||||
:color="useStateStore().calibrationData.hasEnoughImages ? 'secondary' : 'gray'"
|
||||
>
|
||||
Snapshots: {{ useStateStore().calibrationData.imageCount }} of at least {{ useStateStore().calibrationData.minimumImageCount }}
|
||||
Snapshots: {{ useStateStore().calibrationData.imageCount }} of at least
|
||||
{{ useStateStore().calibrationData.minimumImageCount }}
|
||||
</v-chip>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="isCalibrating">
|
||||
<v-col
|
||||
cols="12"
|
||||
class="pt-0"
|
||||
>
|
||||
<v-col cols="12" class="pt-0">
|
||||
<cv-slider
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.cameraExposure"
|
||||
:disabled="useCameraSettingsStore().currentCameraSettings.pipelineSettings.cameraAutoExposure"
|
||||
@@ -336,7 +323,7 @@ const endCalibration = () => {
|
||||
:max="100"
|
||||
:slider-cols="8"
|
||||
:step="0.1"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineSetting({cameraExposure: args}, false)"
|
||||
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposure: args }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.cameraBrightness"
|
||||
@@ -344,7 +331,9 @@ const endCalibration = () => {
|
||||
:min="0"
|
||||
:max="100"
|
||||
:slider-cols="8"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineSetting({cameraBrightness: args}, false)"
|
||||
@input="
|
||||
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBrightness: args }, false)
|
||||
"
|
||||
/>
|
||||
<cv-switch
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.cameraAutoExposure"
|
||||
@@ -352,7 +341,9 @@ const endCalibration = () => {
|
||||
label="Auto Exposure"
|
||||
:label-cols="4"
|
||||
tooltip="Enables or Disables camera automatic adjustment for current lighting conditions"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineSetting({cameraAutoExposure: args}, false)"
|
||||
@input="
|
||||
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraAutoExposure: args }, false)
|
||||
"
|
||||
/>
|
||||
<cv-slider
|
||||
v-if="useCameraSettingsStore().currentPipelineSettings.cameraGain >= 0"
|
||||
@@ -361,7 +352,7 @@ const endCalibration = () => {
|
||||
tooltip="Controls camera gain, similar to brightness"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineSetting({cameraGain: args}, false)"
|
||||
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraGain: args }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-if="useCameraSettingsStore().currentPipelineSettings.cameraRedGain !== -1"
|
||||
@@ -370,7 +361,7 @@ const endCalibration = () => {
|
||||
:min="0"
|
||||
:max="100"
|
||||
tooltip="Controls red automatic white balance gain, which affects how the camera captures colors in different conditions"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineSetting({cameraRedGain: args}, false)"
|
||||
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraRedGain: args }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-if="useCameraSettingsStore().currentPipelineSettings.cameraBlueGain !== -1"
|
||||
@@ -379,7 +370,7 @@ const endCalibration = () => {
|
||||
:min="0"
|
||||
:max="100"
|
||||
tooltip="Controls blue automatic white balance gain, which affects how the camera captures colors in different conditions"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineSetting({cameraBlueGain: args}, false)"
|
||||
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBlueGain: args }, false)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
@@ -388,7 +379,7 @@ const endCalibration = () => {
|
||||
<v-btn
|
||||
small
|
||||
color="secondary"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
:disabled="!settingsValid"
|
||||
@click="isCalibrating ? useCameraSettingsStore().takeCalibrationSnapshot(true) : startCalibration()"
|
||||
>
|
||||
@@ -400,7 +391,7 @@ const endCalibration = () => {
|
||||
small
|
||||
:color="useStateStore().calibrationData.hasEnoughImages ? 'accent' : 'red'"
|
||||
:class="useStateStore().calibrationData.hasEnoughImages ? 'black--text' : 'white---text'"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
:disabled="!isCalibrating || !settingsValid"
|
||||
@click="endCalibration"
|
||||
>
|
||||
@@ -414,104 +405,70 @@ const endCalibration = () => {
|
||||
color="accent"
|
||||
small
|
||||
outlined
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
:disabled="!settingsValid"
|
||||
@click="downloadCalibBoard"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-download
|
||||
</v-icon>
|
||||
<v-icon left> mdi-download </v-icon>
|
||||
Generate Board
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col :cols="6">
|
||||
<v-btn
|
||||
color="secondary"
|
||||
:disabled="isCalibrating"
|
||||
small
|
||||
style="width: 100%;"
|
||||
@click="openCalibUploadPrompt"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-upload
|
||||
</v-icon>
|
||||
<v-btn color="secondary" :disabled="isCalibrating" small style="width: 100%" @click="openCalibUploadPrompt">
|
||||
<v-icon left> mdi-upload </v-icon>
|
||||
Import From CalibDB
|
||||
</v-btn>
|
||||
<input
|
||||
ref="importCalibrationFromCalibDB"
|
||||
type="file"
|
||||
accept=".json"
|
||||
style="display: none;"
|
||||
style="display: none"
|
||||
@change="readImportedCalibration"
|
||||
>
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</v-card>
|
||||
<v-dialog
|
||||
v-model="showCalibEndDialog"
|
||||
width="500px"
|
||||
:persistent="true"
|
||||
>
|
||||
<v-card
|
||||
color="primary"
|
||||
dark
|
||||
>
|
||||
<v-card-title class="pb-8">
|
||||
Camera Calibration
|
||||
</v-card-title>
|
||||
<v-dialog v-model="showCalibEndDialog" width="500px" :persistent="true">
|
||||
<v-card color="primary" dark>
|
||||
<v-card-title class="pb-8"> Camera Calibration </v-card-title>
|
||||
<div class="ml-3">
|
||||
<v-col style="text-align: center">
|
||||
<template v-if="calibCanceled">
|
||||
<v-icon
|
||||
color="blue"
|
||||
size="70"
|
||||
<v-icon color="blue" size="70"> mdi-cancel </v-icon>
|
||||
<v-card-text
|
||||
>Camera Calibration has been Canceled, the backend is attempting to cleanly cancel the calibration
|
||||
process.</v-card-text
|
||||
>
|
||||
mdi-cancel
|
||||
</v-icon>
|
||||
<v-card-text>Camera Calibration has been Canceled, the backend is attempting to cleanly cancel the calibration process.</v-card-text>
|
||||
</template>
|
||||
<template v-else-if="isCalibrating">
|
||||
<v-progress-circular
|
||||
indeterminate
|
||||
:size="70"
|
||||
:width="8"
|
||||
color="accent"
|
||||
/>
|
||||
<v-progress-circular indeterminate :size="70" :width="8" color="accent" />
|
||||
<v-card-text>Camera is being calibrated. This process may take several minutes...</v-card-text>
|
||||
</template>
|
||||
<template v-else-if="calibSuccess">
|
||||
<v-icon
|
||||
color="green"
|
||||
size="70"
|
||||
>
|
||||
mdi-check-bold
|
||||
</v-icon>
|
||||
<v-icon color="green" size="70"> mdi-check-bold </v-icon>
|
||||
<v-card-text>
|
||||
Camera has been successfully calibrated for {{ getUniqueVideoResolutionStrings().find(v => v.value === useStateStore().calibrationData.videoFormatIndex).name }}!
|
||||
Camera has been successfully calibrated for
|
||||
{{
|
||||
getUniqueVideoResolutionStrings().find(
|
||||
(v) => v.value === useStateStore().calibrationData.videoFormatIndex
|
||||
).name
|
||||
}}!
|
||||
</v-card-text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-icon
|
||||
color="red"
|
||||
size="70"
|
||||
<v-icon color="red" size="70"> mdi-close </v-icon>
|
||||
<v-card-text
|
||||
>Camera calibration failed! Make sure that the photos are taken such that the rainbow grid circles align
|
||||
with the corners of the chessboard, and try again. More information is available in the program
|
||||
logs.</v-card-text
|
||||
>
|
||||
mdi-close
|
||||
</v-icon>
|
||||
<v-card-text>Camera calibration failed! Make sure that the photos are taken such that the rainbow grid circles align with the corners of the chessboard, and try again. More information is available in the program logs.</v-card-text>
|
||||
</template>
|
||||
</v-col>
|
||||
</div>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="!isCalibrating"
|
||||
color="white"
|
||||
text
|
||||
@click="showCalibEndDialog = false"
|
||||
>
|
||||
OK
|
||||
</v-btn>
|
||||
<v-btn v-if="!isCalibrating" color="white" text @click="showCalibEndDialog = false"> OK </v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
@@ -522,7 +479,8 @@ const endCalibration = () => {
|
||||
.v-data-table {
|
||||
text-align: center;
|
||||
|
||||
th, td {
|
||||
th,
|
||||
td {
|
||||
background-color: #006492 !important;
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
@@ -8,43 +8,40 @@ import { ref } from "vue";
|
||||
const currentFov = ref(useCameraSettingsStore().currentCameraSettings.fov.value);
|
||||
|
||||
const saveCameraSettings = () => {
|
||||
useCameraSettingsStore().updateCameraSettings({ fov: currentFov.value }, true)
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "success",
|
||||
message: response.data.text || response.data
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
if(error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
} else if(error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
useCameraSettingsStore().currentCameraSettings.fov.value = currentFov.value;
|
||||
useCameraSettingsStore()
|
||||
.updateCameraSettings({ fov: currentFov.value }, true)
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "success",
|
||||
message: response.data.text || response.data
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
useCameraSettingsStore().currentCameraSettings.fov.value = currentFov.value;
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
class="mb-3 pr-6 pb-3"
|
||||
color="primary"
|
||||
dark
|
||||
>
|
||||
<v-card class="mb-3 pr-6 pb-3" color="primary" dark>
|
||||
<v-card-title>Camera Settings</v-card-title>
|
||||
<div class="ml-5">
|
||||
<cv-select
|
||||
@@ -52,29 +49,33 @@ const saveCameraSettings = () => {
|
||||
label="Camera"
|
||||
:items="useCameraSettingsStore().cameraNames"
|
||||
:select-cols="8"
|
||||
@input="args => {
|
||||
currentFov = useCameraSettingsStore().cameras[args].fov.value;
|
||||
useCameraSettingsStore().setCurrentCameraIndex(args);
|
||||
}"
|
||||
@input="
|
||||
(args) => {
|
||||
currentFov = useCameraSettingsStore().cameras[args].fov.value;
|
||||
useCameraSettingsStore().setCurrentCameraIndex(args);
|
||||
}
|
||||
"
|
||||
/>
|
||||
<cv-number-input
|
||||
v-model="currentFov"
|
||||
:tooltip="!useCameraSettingsStore().currentCameraSettings.fov.managedByVendor ? 'Field of view (in degrees) of the camera measured across the diagonal of the frame, in a video mode which covers the whole sensor area.' : 'This setting is managed by a vendor'"
|
||||
:tooltip="
|
||||
!useCameraSettingsStore().currentCameraSettings.fov.managedByVendor
|
||||
? 'Field of view (in degrees) of the camera measured across the diagonal of the frame, in a video mode which covers the whole sensor area.'
|
||||
: 'This setting is managed by a vendor'
|
||||
"
|
||||
label="Maximum Diagonal FOV"
|
||||
:disabled="useCameraSettingsStore().currentCameraSettings.fov.managedByVendor"
|
||||
:label-cols="4"
|
||||
/>
|
||||
<br>
|
||||
<br />
|
||||
<v-btn
|
||||
style="margin-top:10px"
|
||||
style="margin-top: 10px"
|
||||
small
|
||||
color="secondary"
|
||||
:disabled="currentFov === useCameraSettingsStore().currentCameraSettings.fov.value"
|
||||
@click="saveCameraSettings"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-content-save
|
||||
</v-icon>
|
||||
<v-icon left> mdi-content-save </v-icon>
|
||||
Save Changes
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
@@ -8,22 +8,25 @@ import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||
|
||||
const props = defineProps<{
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: number[]
|
||||
value: number[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "input", value: number[]): void
|
||||
(e: "input", value: number[]): void;
|
||||
}>();
|
||||
|
||||
|
||||
const localValue = computed({
|
||||
get: () => props.value,
|
||||
set: v => emit("input", v)
|
||||
set: (v) => emit("input", v)
|
||||
});
|
||||
|
||||
const driverMode = computed<boolean>({
|
||||
get: () => useCameraSettingsStore().isDriverMode,
|
||||
set: v => useCameraSettingsStore().changeCurrentPipelineIndex(v ? -1 : useCameraSettingsStore().currentCameraSettings.lastPipelineIndex || 0, true)
|
||||
set: (v) =>
|
||||
useCameraSettingsStore().changeCurrentPipelineIndex(
|
||||
v ? -1 : useCameraSettingsStore().currentCameraSettings.lastPipelineIndex || 0,
|
||||
true
|
||||
)
|
||||
});
|
||||
|
||||
const fpsTooLow = computed<boolean>(() => {
|
||||
@@ -33,38 +36,30 @@ const fpsTooLow = computed<boolean>(() => {
|
||||
const gpuAccel = useSettingsStore().general.gpuAcceleration !== undefined;
|
||||
const isReflective = useCameraSettingsStore().currentPipelineSettings.pipelineType === PipelineType.Reflective;
|
||||
|
||||
return (currFPS - targetFPS) < -5 && currFPS !== 0 && !driverMode && gpuAccel && isReflective;
|
||||
return currFPS - targetFPS < -5 && currFPS !== 0 && !driverMode && gpuAccel && isReflective;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
class="mb-3 pr-6 pb-3 pa-4"
|
||||
color="primary"
|
||||
dark
|
||||
>
|
||||
<v-card class="mb-3 pr-6 pb-3 pa-4" color="primary" dark>
|
||||
<v-card-title
|
||||
class="pb-0 mb-2 pl-4 pt-1"
|
||||
style="min-height: 50px; justify-content: space-between; align-content: center"
|
||||
>
|
||||
<div style="display: flex; flex-wrap: wrap">
|
||||
<div>
|
||||
<span
|
||||
class="mr-4"
|
||||
style="white-space: nowrap"
|
||||
>
|
||||
Cameras
|
||||
</span>
|
||||
<span class="mr-4" style="white-space: nowrap"> Cameras </span>
|
||||
</div>
|
||||
<div>
|
||||
<v-chip
|
||||
label
|
||||
:color="fpsTooLow ? 'error' : 'transparent'"
|
||||
:text-color="fpsTooLow ? '#C7EA46' : '#ff4d00'"
|
||||
style="font-size: 1rem; padding: 0; margin: 0;"
|
||||
style="font-size: 1rem; padding: 0; margin: 0"
|
||||
>
|
||||
<span class="pr-1">
|
||||
{{ Math.round(useStateStore().pipelineResults?.fps || 0) }} FPS – {{ Math.min(Math.round(useStateStore().pipelineResults?.latency || 0), 9999) }} ms latency
|
||||
{{ Math.round(useStateStore().pipelineResults?.fps || 0) }} FPS –
|
||||
{{ Math.min(Math.round(useStateStore().pipelineResults?.latency || 0), 9999) }} ms latency
|
||||
</span>
|
||||
</v-chip>
|
||||
</div>
|
||||
@@ -75,43 +70,24 @@ const fpsTooLow = computed<boolean>(() => {
|
||||
v-model="driverMode"
|
||||
:disabled="useCameraSettingsStore().isCalibrationMode"
|
||||
label="Driver Mode"
|
||||
style="margin-left: auto;"
|
||||
style="margin-left: auto"
|
||||
color="accent"
|
||||
class="pt-2"
|
||||
/>
|
||||
</div>
|
||||
</v-card-title>
|
||||
<div
|
||||
class="stream-container pb-4"
|
||||
>
|
||||
<div class="stream-container pb-4">
|
||||
<div class="stream">
|
||||
<photon-camera-stream
|
||||
v-show="value.includes(0)"
|
||||
stream-type="Raw"
|
||||
style="max-width: 100%"
|
||||
/>
|
||||
<photon-camera-stream v-show="value.includes(0)" stream-type="Raw" style="max-width: 100%" />
|
||||
</div>
|
||||
<div class="stream">
|
||||
<photon-camera-stream
|
||||
v-show="value.includes(1)"
|
||||
stream-type="Processed"
|
||||
style="max-width: 100%"
|
||||
/>
|
||||
<photon-camera-stream v-show="value.includes(1)" stream-type="Processed" style="max-width: 100%" />
|
||||
</div>
|
||||
</div>
|
||||
<v-divider />
|
||||
<div class="pt-4">
|
||||
<p style="color: white;">
|
||||
Stream Display
|
||||
</p>
|
||||
<v-btn-toggle
|
||||
v-model="localValue"
|
||||
:multiple="true"
|
||||
mandatory
|
||||
dark
|
||||
class="fill"
|
||||
style="width: 100%"
|
||||
>
|
||||
<p style="color: white">Stream Display</p>
|
||||
<v-btn-toggle v-model="localValue" :multiple="true" mandatory dark class="fill" style="width: 100%">
|
||||
<v-btn
|
||||
color="secondary"
|
||||
class="fill"
|
||||
|
||||
@@ -1,34 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(defineProps<{
|
||||
iconName: string,
|
||||
color?: string,
|
||||
tooltip?: string,
|
||||
right?: boolean,
|
||||
hover?: boolean
|
||||
}>(), {
|
||||
right: false,
|
||||
hover: false
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
iconName: string;
|
||||
color?: string;
|
||||
tooltip?: string;
|
||||
right?: boolean;
|
||||
hover?: boolean;
|
||||
}>(),
|
||||
{
|
||||
right: false,
|
||||
hover: false
|
||||
}
|
||||
);
|
||||
|
||||
defineEmits<{
|
||||
(e: "click"): void;
|
||||
}>();
|
||||
|
||||
const hoverClass = props.hover ? "hover" : "";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-tooltip
|
||||
:right="right"
|
||||
:bottom="!right"
|
||||
nudge-right="10"
|
||||
:disabled="tooltip === undefined"
|
||||
>
|
||||
<v-tooltip :right="right" :bottom="!right" nudge-right="10" :disabled="tooltip === undefined">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-icon
|
||||
:class="hoverClass"
|
||||
:color="color"
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<v-icon :class="hoverClass" :color="color" v-bind="attrs" v-on="on" @click="$emit('click')">
|
||||
{{ iconName }}
|
||||
</v-icon>
|
||||
</template>
|
||||
@@ -38,7 +34,7 @@ const hoverClass = props.hover ? "hover" : "";
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.hover:hover {
|
||||
color: white !important;
|
||||
}
|
||||
.hover:hover {
|
||||
color: white !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,38 +2,40 @@
|
||||
import { computed } from "vue";
|
||||
import TooltippedLabel from "@/components/common/cv-tooltipped-label.vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
label?: string,
|
||||
tooltip?: string,
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: string,
|
||||
disabled?: boolean,
|
||||
errorMessage?: string,
|
||||
placeholder?: string,
|
||||
labelCols?: number,
|
||||
inputCols?: number,
|
||||
rules?: ((v: string) => boolean | string)[]
|
||||
}>(), {
|
||||
disabled: false,
|
||||
inputCols: 8
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
errorMessage?: string;
|
||||
placeholder?: string;
|
||||
labelCols?: number;
|
||||
inputCols?: number;
|
||||
rules?: ((v: string) => boolean | string)[];
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
inputCols: 8
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "input", value: string): void
|
||||
(e: "onEnter", value: string): void
|
||||
(e: "onEscape"): void
|
||||
(e: "input", value: string): void;
|
||||
(e: "onEnter", value: string): void;
|
||||
(e: "onEscape"): void;
|
||||
}>();
|
||||
|
||||
const localValue = computed({
|
||||
get: () => props.value,
|
||||
set: v => emit("input", v)
|
||||
set: (v) => emit("input", v)
|
||||
});
|
||||
|
||||
|
||||
const handleKeydown = ({ key }) => {
|
||||
switch (key) {
|
||||
case "Enter":
|
||||
if(!(props.rules || []).some(v => v(localValue.value) === false || typeof v(localValue.value) === "string")) {
|
||||
if (!(props.rules || []).some((v) => v(localValue.value) === false || typeof v(localValue.value) === "string")) {
|
||||
emit("onEnter", localValue.value);
|
||||
}
|
||||
break;
|
||||
@@ -42,20 +44,13 @@ const handleKeydown = ({ key }) => {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-row
|
||||
dense
|
||||
align="center"
|
||||
>
|
||||
<v-col :cols="labelCols || (12 - inputCols)">
|
||||
<tooltipped-label
|
||||
:tooltip="tooltip"
|
||||
:label="label"
|
||||
/>
|
||||
<v-row dense align="center">
|
||||
<v-col :cols="labelCols || 12 - inputCols">
|
||||
<tooltipped-label :tooltip="tooltip" :label="label" />
|
||||
</v-col>
|
||||
|
||||
<v-col :cols="inputCols">
|
||||
|
||||
@@ -2,42 +2,39 @@
|
||||
import TooltippedLabel from "@/components/common/cv-tooltipped-label.vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
label?: string,
|
||||
tooltip?: string,
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: number,
|
||||
disabled?: boolean,
|
||||
labelCols?: number,
|
||||
rules?: ((v: number) => boolean | string)[],
|
||||
step?: number
|
||||
}>(), {
|
||||
disabled: false,
|
||||
labelCols: 2,
|
||||
step: 1
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: number;
|
||||
disabled?: boolean;
|
||||
labelCols?: number;
|
||||
rules?: ((v: number) => boolean | string)[];
|
||||
step?: number;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
labelCols: 2,
|
||||
step: 1
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "input", value: number): void
|
||||
(e: "input", value: number): void;
|
||||
}>();
|
||||
|
||||
const localValue = computed({
|
||||
get: () => props.value,
|
||||
set: v => emit("input", parseFloat(v as unknown as string))
|
||||
set: (v) => emit("input", parseFloat(v as unknown as string))
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-row
|
||||
dense
|
||||
align="center"
|
||||
>
|
||||
<v-row dense align="center">
|
||||
<v-col :cols="labelCols">
|
||||
<tooltipped-label
|
||||
:tooltip="tooltip"
|
||||
:label="label"
|
||||
/>
|
||||
<tooltipped-label :tooltip="tooltip" :label="label" />
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
|
||||
@@ -2,48 +2,40 @@
|
||||
import { computed } from "vue";
|
||||
import TooltippedLabel from "@/components/common/cv-tooltipped-label.vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
label?: string,
|
||||
tooltip?: string,
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: number,
|
||||
disabled?: boolean,
|
||||
inputCols?: number,
|
||||
list: string[]
|
||||
}>(), {
|
||||
disabled: false,
|
||||
inputCols: 8
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: number;
|
||||
disabled?: boolean;
|
||||
inputCols?: number;
|
||||
list: string[];
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
inputCols: 8
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "input", value: number): void
|
||||
(e: "input", value: number): void;
|
||||
}>();
|
||||
|
||||
const localValue = computed({
|
||||
get: () => props.value,
|
||||
set: v => emit("input", v)
|
||||
set: (v) => emit("input", v)
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-row
|
||||
dense
|
||||
align="center"
|
||||
>
|
||||
<v-row dense align="center">
|
||||
<v-col :cols="12 - inputCols">
|
||||
<tooltipped-label
|
||||
:tooltip="tooltip"
|
||||
:label="label"
|
||||
/>
|
||||
<tooltipped-label :tooltip="tooltip" :label="label" />
|
||||
</v-col>
|
||||
<v-col :cols="inputCols">
|
||||
<v-radio-group
|
||||
v-model="localValue"
|
||||
row
|
||||
dark
|
||||
:mandatory="true"
|
||||
>
|
||||
<v-radio-group v-model="localValue" row dark :mandatory="true">
|
||||
<v-radio
|
||||
v-for="(radioName, index) in list"
|
||||
:key="index"
|
||||
|
||||
@@ -2,34 +2,37 @@
|
||||
import { computed } from "vue";
|
||||
import TooltippedLabel from "@/components/common/cv-tooltipped-label.vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
label?: string,
|
||||
tooltip?: string,
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
// value: [number, number] | WebsocketNumberPair, // Vue doesnt like Union types for the value prop for some reason.
|
||||
value: [number, number],
|
||||
min: number,
|
||||
max: number,
|
||||
step?: number,
|
||||
sliderCols?: number,
|
||||
disabled?: boolean,
|
||||
inverted?: boolean,
|
||||
}>(), {
|
||||
step: 1,
|
||||
disabled: false,
|
||||
inverted: false,
|
||||
sliderCols: 10
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
// value: [number, number] | WebsocketNumberPair, // Vue doesnt like Union types for the value prop for some reason.
|
||||
value: [number, number];
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
sliderCols?: number;
|
||||
disabled?: boolean;
|
||||
inverted?: boolean;
|
||||
}>(),
|
||||
{
|
||||
step: 1,
|
||||
disabled: false,
|
||||
inverted: false,
|
||||
sliderCols: 10
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "input", value: [number, number]): void
|
||||
(e: "input", value: [number, number]): void;
|
||||
}>();
|
||||
|
||||
const localValue = computed<[number, number]>({
|
||||
get: ():[number, number] => {
|
||||
get: (): [number, number] => {
|
||||
return Object.values(props.value) as [number, number];
|
||||
},
|
||||
set: v => emit("input", v)
|
||||
set: (v) => emit("input", v)
|
||||
});
|
||||
|
||||
const changeFromSlot = (v: number, i: number) => {
|
||||
@@ -42,15 +45,9 @@ const changeFromSlot = (v: number, i: number) => {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-row
|
||||
dense
|
||||
align="center"
|
||||
>
|
||||
<v-row dense align="center">
|
||||
<v-col :cols="12 - sliderCols">
|
||||
<tooltipped-label
|
||||
:tooltip="tooltip"
|
||||
:label="label"
|
||||
/>
|
||||
<tooltipped-label :tooltip="tooltip" :label="label" />
|
||||
</v-col>
|
||||
<v-col :cols="sliderCols">
|
||||
<v-range-slider
|
||||
@@ -79,7 +76,7 @@ const changeFromSlot = (v: number, i: number) => {
|
||||
:step="step"
|
||||
type="number"
|
||||
style="width: 60px"
|
||||
@input="v => changeFromSlot(v, 0)"
|
||||
@input="(v) => changeFromSlot(v, 0)"
|
||||
/>
|
||||
</template>
|
||||
<template #append>
|
||||
@@ -95,7 +92,7 @@ const changeFromSlot = (v: number, i: number) => {
|
||||
:step="step"
|
||||
type="number"
|
||||
style="width: 60px"
|
||||
@input="v => changeFromSlot(v, 1)"
|
||||
@input="(v) => changeFromSlot(v, 1)"
|
||||
/>
|
||||
</template>
|
||||
</v-range-slider>
|
||||
|
||||
@@ -3,37 +3,40 @@ import { computed } from "vue";
|
||||
import TooltippedLabel from "@/components/common/cv-tooltipped-label.vue";
|
||||
|
||||
interface SelectItem {
|
||||
name: string | number,
|
||||
value: string | number,
|
||||
disabled?: boolean
|
||||
name: string | number;
|
||||
value: string | number;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
label?: string,
|
||||
tooltip?: string,
|
||||
selectCols?: number,
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: number,
|
||||
disabled?: boolean,
|
||||
items: string[] | number[] | SelectItem[]
|
||||
}>(), {
|
||||
selectCols: 9,
|
||||
disabled: false
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
selectCols?: number;
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: number;
|
||||
disabled?: boolean;
|
||||
items: string[] | number[] | SelectItem[];
|
||||
}>(),
|
||||
{
|
||||
selectCols: 9,
|
||||
disabled: false
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "input", value: number): void
|
||||
(e: "input", value: number): void;
|
||||
}>();
|
||||
|
||||
const localValue = computed({
|
||||
get: () => props.value,
|
||||
set: v => emit("input", v)
|
||||
set: (v) => emit("input", v)
|
||||
});
|
||||
|
||||
// Computed in case items changes
|
||||
const items = computed<SelectItem[]>(() => {
|
||||
// Check if the prop exists on the object to infer object type
|
||||
if((props.items[0] as SelectItem).name) {
|
||||
if ((props.items[0] as SelectItem).name) {
|
||||
return props.items as SelectItem[];
|
||||
}
|
||||
return props.items.map((v, i) => ({ name: v, value: i }));
|
||||
@@ -42,15 +45,9 @@ const items = computed<SelectItem[]>(() => {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-row
|
||||
dense
|
||||
align="center"
|
||||
>
|
||||
<v-row dense align="center">
|
||||
<v-col :cols="12 - selectCols">
|
||||
<tooltipped-label
|
||||
:tooltip="tooltip"
|
||||
:label="label"
|
||||
/>
|
||||
<tooltipped-label :tooltip="tooltip" :label="label" />
|
||||
</v-col>
|
||||
<v-col :cols="selectCols">
|
||||
<v-select
|
||||
|
||||
@@ -2,43 +2,40 @@
|
||||
import { computed } from "vue";
|
||||
import TooltippedLabel from "@/components/common/cv-tooltipped-label.vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
label?: string,
|
||||
tooltip?: string,
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: number,
|
||||
min: number,
|
||||
max: number,
|
||||
step?: number
|
||||
disabled?: boolean,
|
||||
sliderCols?: number,
|
||||
}>(), {
|
||||
step: 1,
|
||||
disabled: false,
|
||||
sliderCols: 8
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
disabled?: boolean;
|
||||
sliderCols?: number;
|
||||
}>(),
|
||||
{
|
||||
step: 1,
|
||||
disabled: false,
|
||||
sliderCols: 8
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "input", value: number): void
|
||||
(e: "input", value: number): void;
|
||||
}>();
|
||||
|
||||
const localValue = computed({
|
||||
get: () => props.value,
|
||||
set: v => emit("input", v)
|
||||
set: (v) => emit("input", v)
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-row
|
||||
dense
|
||||
align="center"
|
||||
>
|
||||
<v-row dense align="center">
|
||||
<v-col :cols="12 - sliderCols">
|
||||
<tooltipped-label
|
||||
:tooltip="tooltip"
|
||||
:label="label"
|
||||
/>
|
||||
<tooltipped-label :tooltip="tooltip" :label="label" />
|
||||
</v-col>
|
||||
<v-col :cols="sliderCols">
|
||||
<v-slider
|
||||
|
||||
@@ -2,48 +2,40 @@
|
||||
import TooltippedLabel from "@/components/common/cv-tooltipped-label.vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
label?: string,
|
||||
tooltip?: string,
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: boolean,
|
||||
disabled?: boolean,
|
||||
labelCols?: number,
|
||||
switchCols?: number
|
||||
}>(), {
|
||||
disabled: false,
|
||||
labelCols: 2
|
||||
});
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: boolean;
|
||||
disabled?: boolean;
|
||||
labelCols?: number;
|
||||
switchCols?: number;
|
||||
}>(),
|
||||
{
|
||||
disabled: false,
|
||||
labelCols: 2
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "input", value: boolean): void
|
||||
(e: "input", value: boolean): void;
|
||||
}>();
|
||||
|
||||
const localValue = computed({
|
||||
get: () => props.value,
|
||||
set: v => emit("input", v)
|
||||
set: (v) => emit("input", v)
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-row
|
||||
dense
|
||||
align="center"
|
||||
>
|
||||
<v-col :cols="(12 - switchCols) || labelCols">
|
||||
<tooltipped-label
|
||||
:tooltip="tooltip"
|
||||
:label="label"
|
||||
/>
|
||||
<v-row dense align="center">
|
||||
<v-col :cols="12 - switchCols || labelCols">
|
||||
<tooltipped-label :tooltip="tooltip" :label="label" />
|
||||
</v-col>
|
||||
<v-col :cols="switchCols || (12 - labelCols)">
|
||||
<v-switch
|
||||
v-model="localValue"
|
||||
dark
|
||||
:disabled="disabled"
|
||||
color="#ffd843"
|
||||
/>
|
||||
<v-col :cols="switchCols || 12 - labelCols">
|
||||
<v-switch v-model="localValue" dark :disabled="disabled" color="#ffd843" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
label?: string,
|
||||
tooltip?: string
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-tooltip
|
||||
:disabled="tooltip === undefined"
|
||||
right
|
||||
open-delay="300"
|
||||
>
|
||||
<v-tooltip :disabled="tooltip === undefined" right open-delay="300">
|
||||
<template #activator="{ on, attrs }">
|
||||
<span
|
||||
style="cursor: text !important;"
|
||||
class="white--text"
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>{{ label }}</span>
|
||||
<span style="cursor: text !important" class="white--text" v-bind="attrs" v-on="on">{{ label }}</span>
|
||||
</template>
|
||||
<span>{{ tooltip }}</span>
|
||||
</v-tooltip>
|
||||
|
||||
@@ -38,40 +38,43 @@ const startCameraNameEdit = () => {
|
||||
isCameraNameEdit.value = true;
|
||||
};
|
||||
const checkCameraName = (name: string): string | boolean => {
|
||||
if(!nameChangeRegex.test(name)) return "A camera name can only contain letters, numbers, spaces, underscores, hyphens, parenthesis, and periods";
|
||||
if(useCameraSettingsStore().cameraNames.some(cameraName => cameraName === name)) return "This camera name has already been used";
|
||||
if (!nameChangeRegex.test(name))
|
||||
return "A camera name can only contain letters, numbers, spaces, underscores, hyphens, parenthesis, and periods";
|
||||
if (useCameraSettingsStore().cameraNames.some((cameraName) => cameraName === name))
|
||||
return "This camera name has already been used";
|
||||
|
||||
return true;
|
||||
};
|
||||
const saveCameraNameEdit = (newName: string) => {
|
||||
useCameraSettingsStore().changeCameraNickname(newName, false)
|
||||
.then(response => {
|
||||
useCameraSettingsStore()
|
||||
.changeCameraNickname(newName, false)
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "success",
|
||||
message: response.data.text || response.data
|
||||
});
|
||||
useCameraSettingsStore().currentCameraSettings.nickname = newName;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "success",
|
||||
message: response.data.text || response.data
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
useCameraSettingsStore().currentCameraSettings.nickname = newName;
|
||||
})
|
||||
.catch(error => {
|
||||
if(error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
} else if(error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
});
|
||||
}
|
||||
currentCameraName.value = useCameraSettingsStore().currentCameraSettings.nickname;
|
||||
})
|
||||
.finally(() => isCameraNameEdit.value = false);
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
});
|
||||
}
|
||||
currentCameraName.value = useCameraSettingsStore().currentCameraSettings.nickname;
|
||||
})
|
||||
.finally(() => (isCameraNameEdit.value = false));
|
||||
};
|
||||
const cancelCameraNameEdit = () => {
|
||||
isCameraNameEdit.value = false;
|
||||
@@ -79,13 +82,13 @@ const cancelCameraNameEdit = () => {
|
||||
};
|
||||
|
||||
// Pipeline Name Edit
|
||||
const pipelineNamesWrapper = computed<{name: string, value: number}[]>(() => {
|
||||
const pipelineNamesWrapper = computed<{ name: string; value: number }[]>(() => {
|
||||
const pipelineNames = useCameraSettingsStore().pipelineNames.map((name, index) => ({ name: name, value: index }));
|
||||
|
||||
if(useCameraSettingsStore().isDriverMode) {
|
||||
if (useCameraSettingsStore().isDriverMode) {
|
||||
pipelineNames.push({ name: "Driver Mode", value: WebsocketPipelineType.DriverMode });
|
||||
}
|
||||
if(useCameraSettingsStore().isCalibrationMode) {
|
||||
if (useCameraSettingsStore().isCalibrationMode) {
|
||||
pipelineNames.push({ name: "3D Calibration Mode", value: WebsocketPipelineType.Calib3d });
|
||||
}
|
||||
|
||||
@@ -98,8 +101,10 @@ const startPipelineNameEdit = () => {
|
||||
isPipelineNameEdit.value = true;
|
||||
};
|
||||
const checkPipelineName = (name: string): string | boolean => {
|
||||
if(!nameChangeRegex.test(name)) return "A pipeline name can only contain letters, numbers, spaces, underscores, hyphens, parenthesis, and periods";
|
||||
if(useCameraSettingsStore().pipelineNames.some(pipelineName => pipelineName === name)) return "This pipeline name has already been used";
|
||||
if (!nameChangeRegex.test(name))
|
||||
return "A pipeline name can only contain letters, numbers, spaces, underscores, hyphens, parenthesis, and periods";
|
||||
if (useCameraSettingsStore().pipelineNames.some((pipelineName) => pipelineName === name))
|
||||
return "This pipeline name has already been used";
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -123,7 +128,7 @@ const showCreatePipelineDialog = () => {
|
||||
};
|
||||
const createNewPipeline = () => {
|
||||
const type = newPipelineType.value;
|
||||
if(type === WebsocketPipelineType.DriverMode || type === WebsocketPipelineType.Calib3d) return;
|
||||
if (type === WebsocketPipelineType.DriverMode || type === WebsocketPipelineType.Calib3d) return;
|
||||
useCameraSettingsStore().createNewPipeline(newPipelineName.value, type);
|
||||
showPipelineCreationDialog.value = false;
|
||||
};
|
||||
@@ -142,18 +147,18 @@ const confirmDeleteCurrentPipeline = () => {
|
||||
|
||||
// Pipeline Type Change
|
||||
const showPipelineTypeChangeDialog = ref(false);
|
||||
const pipelineTypesWrapper = computed<{name: string, value: number}[]>(() => {
|
||||
const pipelineTypes =[
|
||||
const pipelineTypesWrapper = computed<{ name: string; value: number }[]>(() => {
|
||||
const pipelineTypes = [
|
||||
{ name: "Reflective", value: WebsocketPipelineType.Reflective },
|
||||
{ name: "Colored Shape", value: WebsocketPipelineType.ColoredShape },
|
||||
{ name: "AprilTag", value: WebsocketPipelineType.AprilTag }
|
||||
// { name: "Aruco", value: WebsocketPipelineType.Aruco }
|
||||
];
|
||||
|
||||
if(useCameraSettingsStore().isDriverMode) {
|
||||
if (useCameraSettingsStore().isDriverMode) {
|
||||
pipelineTypes.push({ name: "Driver Mode", value: WebsocketPipelineType.DriverMode });
|
||||
}
|
||||
if(useCameraSettingsStore().isCalibrationMode) {
|
||||
if (useCameraSettingsStore().isCalibrationMode) {
|
||||
pipelineTypes.push({ name: "3D Calibration Mode", value: WebsocketPipelineType.Calib3d });
|
||||
}
|
||||
|
||||
@@ -162,17 +167,17 @@ const pipelineTypesWrapper = computed<{name: string, value: number}[]>(() => {
|
||||
const pipelineType = ref<WebsocketPipelineType>(useCameraSettingsStore().currentWebsocketPipelineType);
|
||||
const currentPipelineType = computed<WebsocketPipelineType>({
|
||||
get: () => {
|
||||
if(useCameraSettingsStore().isDriverMode) return WebsocketPipelineType.DriverMode;
|
||||
if(useCameraSettingsStore().isCalibrationMode) return WebsocketPipelineType.Calib3d;
|
||||
if (useCameraSettingsStore().isDriverMode) return WebsocketPipelineType.DriverMode;
|
||||
if (useCameraSettingsStore().isCalibrationMode) return WebsocketPipelineType.Calib3d;
|
||||
return pipelineType.value;
|
||||
},
|
||||
set: v => {
|
||||
set: (v) => {
|
||||
pipelineType.value = v;
|
||||
}
|
||||
});
|
||||
const confirmChangePipelineType = () => {
|
||||
const type = currentPipelineType.value;
|
||||
if(type === WebsocketPipelineType.DriverMode || type === WebsocketPipelineType.Calib3d) return;
|
||||
if (type === WebsocketPipelineType.DriverMode || type === WebsocketPipelineType.Calib3d) return;
|
||||
useCameraSettingsStore().changeCurrentPipelineType(type);
|
||||
showPipelineTypeChangeDialog.value = false;
|
||||
};
|
||||
@@ -203,14 +208,9 @@ useCameraSettingsStore().$subscribe((mutation, state) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
color="primary"
|
||||
>
|
||||
<v-card color="primary">
|
||||
<v-row style="padding: 12px 12px 0 24px">
|
||||
<v-col
|
||||
cols="10"
|
||||
class="pa-0"
|
||||
>
|
||||
<v-col cols="10" class="pa-0">
|
||||
<cv-select
|
||||
v-if="!isCameraNameEdit"
|
||||
v-model="useStateStore().currentCameraIndex"
|
||||
@@ -222,164 +222,101 @@ useCameraSettingsStore().$subscribe((mutation, state) => {
|
||||
v-else
|
||||
v-model="currentCameraName"
|
||||
class="pt-2"
|
||||
:input-cols="12-3"
|
||||
:rules="[v => checkCameraName(v)]"
|
||||
:input-cols="12 - 3"
|
||||
:rules="[(v) => checkCameraName(v)]"
|
||||
label="Camera"
|
||||
@onEnter="saveCameraNameEdit"
|
||||
@onEscape="cancelCameraNameEdit"
|
||||
@on-enter="saveCameraNameEdit"
|
||||
@on-escape="cancelCameraNameEdit"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="2"
|
||||
style="display: flex; align-items: center; justify-content: center"
|
||||
>
|
||||
<cv-icon
|
||||
color="#c5c5c5"
|
||||
icon-name="mdi-pencil"
|
||||
tooltip="Edit Camera Name"
|
||||
@click="startCameraNameEdit"
|
||||
/>
|
||||
<v-col cols="2" style="display: flex; align-items: center; justify-content: center">
|
||||
<cv-icon color="#c5c5c5" icon-name="mdi-pencil" tooltip="Edit Camera Name" @click="startCameraNameEdit" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row style="padding: 0 12px 0 24px;">
|
||||
<v-col
|
||||
cols="10"
|
||||
class="pa-0"
|
||||
>
|
||||
<v-row style="padding: 0 12px 0 24px">
|
||||
<v-col cols="10" class="pa-0">
|
||||
<cv-select
|
||||
v-if="!isPipelineNameEdit"
|
||||
:value="useCameraSettingsStore().currentCameraSettings.currentPipelineIndex"
|
||||
label="Pipeline"
|
||||
tooltip="Each pipeline runs on a camera output and stores a unique set of processing settings"
|
||||
:disabled="useCameraSettingsStore().isDriverMode
|
||||
|| useCameraSettingsStore().isCalibrationMode"
|
||||
:disabled="useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode"
|
||||
:items="pipelineNamesWrapper"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineIndex(args, true)"
|
||||
@input="(args) => useCameraSettingsStore().changeCurrentPipelineIndex(args, true)"
|
||||
/>
|
||||
<cv-input
|
||||
v-else
|
||||
v-model="currentPipelineName"
|
||||
:input-cols="12-3"
|
||||
:rules="[v => checkPipelineName(v)]"
|
||||
:input-cols="12 - 3"
|
||||
:rules="[(v) => checkPipelineName(v)]"
|
||||
label="Pipeline"
|
||||
@onEnter="v => savePipelineNameEdit(v)"
|
||||
@onEscape="cancelPipelineNameEdit"
|
||||
@on-enter="(v) => savePipelineNameEdit(v)"
|
||||
@on-escape="cancelPipelineNameEdit"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="2"
|
||||
class="pa-0"
|
||||
style="display: flex; align-items: center; justify-content: center"
|
||||
>
|
||||
<v-menu
|
||||
v-if="!useCameraSettingsStore().isDriverMode"
|
||||
offset-y
|
||||
nudge-bottom="7"
|
||||
auto
|
||||
>
|
||||
<v-col cols="2" class="pa-0" style="display: flex; align-items: center; justify-content: center">
|
||||
<v-menu v-if="!useCameraSettingsStore().isDriverMode" offset-y nudge-bottom="7" auto>
|
||||
<template #activator="{ on }">
|
||||
<v-icon
|
||||
color="#c5c5c5"
|
||||
v-on="on"
|
||||
@click="cancelPipelineNameEdit"
|
||||
>
|
||||
mdi-menu
|
||||
</v-icon>
|
||||
<v-icon color="#c5c5c5" v-on="on" @click="cancelPipelineNameEdit"> mdi-menu </v-icon>
|
||||
</template>
|
||||
<v-list
|
||||
dark
|
||||
dense
|
||||
color="primary"
|
||||
>
|
||||
<v-list dark dense color="primary">
|
||||
<v-list-item @click="startPipelineNameEdit">
|
||||
<v-list-item-title>
|
||||
<cv-icon
|
||||
color="#c5c5c5"
|
||||
:right="true"
|
||||
icon-name="mdi-pencil"
|
||||
tooltip="Edit pipeline name"
|
||||
/>
|
||||
<cv-icon color="#c5c5c5" :right="true" icon-name="mdi-pencil" tooltip="Edit pipeline name" />
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="showCreatePipelineDialog">
|
||||
<v-list-item-title>
|
||||
<cv-icon
|
||||
color="#c5c5c5"
|
||||
:right="true"
|
||||
icon-name="mdi-plus"
|
||||
tooltip="Add new pipeline"
|
||||
/>
|
||||
<cv-icon color="#c5c5c5" :right="true" icon-name="mdi-plus" tooltip="Add new pipeline" />
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="showPipelineDeletionConfirmationDialog = true">
|
||||
<v-list-item-title>
|
||||
<cv-icon
|
||||
color="red darken-2"
|
||||
:right="true"
|
||||
icon-name="mdi-delete"
|
||||
tooltip="Delete pipeline"
|
||||
/>
|
||||
<cv-icon color="red darken-2" :right="true" icon-name="mdi-delete" tooltip="Delete pipeline" />
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="useCameraSettingsStore().duplicatePipeline(useCameraSettingsStore().currentCameraSettings.currentPipelineIndex)">
|
||||
<v-list-item
|
||||
@click="
|
||||
useCameraSettingsStore().duplicatePipeline(
|
||||
useCameraSettingsStore().currentCameraSettings.currentPipelineIndex
|
||||
)
|
||||
"
|
||||
>
|
||||
<v-list-item-title>
|
||||
<cv-icon
|
||||
color="#c5c5c5"
|
||||
:right="true"
|
||||
icon-name="mdi-content-copy"
|
||||
tooltip="Duplicate pipeline"
|
||||
/>
|
||||
<cv-icon color="#c5c5c5" :right="true" icon-name="mdi-content-copy" tooltip="Duplicate pipeline" />
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row style="padding: 0 12px 12px 24px;">
|
||||
<v-col
|
||||
cols="10"
|
||||
class="pa-0"
|
||||
>
|
||||
<v-row style="padding: 0 12px 12px 24px">
|
||||
<v-col cols="10" class="pa-0">
|
||||
<cv-select
|
||||
v-model="currentPipelineType"
|
||||
label="Type"
|
||||
tooltip="Changes the pipeline type, which changes the type of processing that will happen on input frames"
|
||||
:disabled="useCameraSettingsStore().isDriverMode
|
||||
|| useCameraSettingsStore().isCalibrationMode"
|
||||
:disabled="useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode"
|
||||
:items="pipelineTypesWrapper"
|
||||
@input="showPipelineTypeChangeDialog = true"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-dialog
|
||||
v-model="showPipelineCreationDialog"
|
||||
dark
|
||||
persistent
|
||||
width="500"
|
||||
>
|
||||
<v-card
|
||||
dark
|
||||
color="primary"
|
||||
>
|
||||
<v-card-title
|
||||
class="headline"
|
||||
style="font-family: 'Prompt', sans-serif !important;"
|
||||
primary-title
|
||||
>
|
||||
Create New Pipeline
|
||||
</v-card-title>
|
||||
<v-dialog v-model="showPipelineCreationDialog" dark persistent width="500">
|
||||
<v-card dark color="primary">
|
||||
<v-card-title> Create New Pipeline </v-card-title>
|
||||
<v-card-text>
|
||||
<cv-input
|
||||
v-model="newPipelineName"
|
||||
placeholder="Pipeline Name"
|
||||
:label-cols="3"
|
||||
:input-cols="12-3"
|
||||
:input-cols="12 - 3"
|
||||
label="Pipeline Name"
|
||||
:rules="[v => checkPipelineName(v)]"
|
||||
:rules="[(v) => checkPipelineName(v)]"
|
||||
/>
|
||||
<cv-select
|
||||
v-model="newPipelineType"
|
||||
:select-cols="12-3"
|
||||
:select-cols="12 - 3"
|
||||
label="Tracking Type"
|
||||
tooltip="Pipeline type, which changes the type of processing that will happen on input frames"
|
||||
:items="[
|
||||
@@ -393,87 +330,38 @@ useCameraSettingsStore().$subscribe((mutation, state) => {
|
||||
<v-divider />
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
color="#ffd843"
|
||||
:disabled="checkPipelineName(newPipelineName) !== true"
|
||||
@click="createNewPipeline"
|
||||
>
|
||||
<v-btn color="#ffd843" :disabled="checkPipelineName(newPipelineName) !== true" @click="createNewPipeline">
|
||||
Save
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
@click="cancelPipelineCreation"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn color="error" @click="cancelPipelineCreation"> Cancel </v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog
|
||||
v-model="showPipelineDeletionConfirmationDialog"
|
||||
dark
|
||||
width="500"
|
||||
>
|
||||
<v-card
|
||||
dark
|
||||
color="primary"
|
||||
>
|
||||
<v-card-title
|
||||
class="headline"
|
||||
style="font-family: 'Prompt', sans-serif !important;"
|
||||
primary-title
|
||||
>
|
||||
Pipeline Deletion Confirmation
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
Are you sure you want to delete this pipeline? This cannot be undone.
|
||||
</v-card-text>
|
||||
<v-dialog v-model="showPipelineDeletionConfirmationDialog" dark width="500">
|
||||
<v-card dark color="primary">
|
||||
<v-card-title> Pipeline Deletion Confirmation </v-card-title>
|
||||
<v-card-text> Are you sure you want to delete this pipeline? This cannot be undone. </v-card-text>
|
||||
<v-divider />
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
color="error"
|
||||
@click="confirmDeleteCurrentPipeline"
|
||||
>
|
||||
Yes, I'm sure
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="#ffd843"
|
||||
@click="showPipelineDeletionConfirmationDialog = false"
|
||||
>
|
||||
No, take me back
|
||||
</v-btn>
|
||||
<v-btn color="error" @click="confirmDeleteCurrentPipeline"> Yes, I'm sure </v-btn>
|
||||
<v-btn color="#ffd843" @click="showPipelineDeletionConfirmationDialog = false"> No, take me back </v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog
|
||||
v-model="showPipelineTypeChangeDialog"
|
||||
persistent
|
||||
width="600"
|
||||
>
|
||||
<v-card
|
||||
color="primary"
|
||||
dark
|
||||
>
|
||||
<v-dialog v-model="showPipelineTypeChangeDialog" persistent width="600">
|
||||
<v-card color="primary" dark>
|
||||
<v-card-title>Change Pipeline Type</v-card-title>
|
||||
<v-card-text>
|
||||
Are you sure you want to change the current pipeline type? This will cause all the pipeline settings to be overwritten and they will be lost. If this isn't what you want, duplicate this pipeline first or export settings.
|
||||
Are you sure you want to change the current pipeline type? This will cause all the pipeline settings to be
|
||||
overwritten and they will be lost. If this isn't what you want, duplicate this pipeline first or export
|
||||
settings.
|
||||
</v-card-text>
|
||||
<v-divider />
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
color="error"
|
||||
@click="confirmChangePipelineType"
|
||||
>
|
||||
Yes, I'm sure
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="#ffd843"
|
||||
@click="cancelChangePipelineType"
|
||||
>
|
||||
No, take me back
|
||||
</v-btn>
|
||||
<v-btn color="error" @click="confirmChangePipelineType"> Yes, I'm sure </v-btn>
|
||||
<v-btn color="#ffd843" @click="cancelChangePipelineType"> No, take me back </v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
@@ -8,12 +8,16 @@ import PhotonCameraStream from "@/components/app/photon-camera-stream.vue";
|
||||
|
||||
defineProps<{
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: number[]
|
||||
value: number[];
|
||||
}>();
|
||||
|
||||
const driverMode = computed<boolean>({
|
||||
get: () => useCameraSettingsStore().isDriverMode,
|
||||
set: v => useCameraSettingsStore().changeCurrentPipelineIndex(v ? -1 : useCameraSettingsStore().currentCameraSettings.lastPipelineIndex || 0, true)
|
||||
set: (v) =>
|
||||
useCameraSettingsStore().changeCurrentPipelineIndex(
|
||||
v ? -1 : useCameraSettingsStore().currentCameraSettings.lastPipelineIndex || 0,
|
||||
true
|
||||
)
|
||||
});
|
||||
|
||||
const fpsTooLow = computed<boolean>(() => {
|
||||
@@ -23,17 +27,12 @@ const fpsTooLow = computed<boolean>(() => {
|
||||
const gpuAccel = useSettingsStore().general.gpuAcceleration !== undefined;
|
||||
const isReflective = useCameraSettingsStore().currentPipelineSettings.pipelineType === PipelineType.Reflective;
|
||||
|
||||
return (currFPS - targetFPS) < -5 && currFPS !== 0 && !driverMode && gpuAccel && isReflective;
|
||||
return currFPS - targetFPS < -5 && currFPS !== 0 && !driverMode && gpuAccel && isReflective;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
color="primary"
|
||||
height="100%"
|
||||
style="display: flex; flex-direction: column"
|
||||
dark
|
||||
>
|
||||
<v-card color="primary" height="100%" style="display: flex; flex-direction: column" dark>
|
||||
<v-card-title
|
||||
class="pb-0 mb-0 pl-4 pt-1"
|
||||
style="min-height: 50px; justify-content: space-between; align-content: center"
|
||||
@@ -49,7 +48,13 @@ const fpsTooLow = computed<boolean>(() => {
|
||||
<span class="pr-1">
|
||||
Processing @ {{ Math.round(useStateStore().pipelineResults?.fps || 0) }} FPS –
|
||||
</span>
|
||||
<span v-if="fpsTooLow && !useCameraSettingsStore().currentPipelineSettings.inputShouldShow && useCameraSettingsStore().currentPipelineSettings.pipelineType === PipelineType.Reflective">
|
||||
<span
|
||||
v-if="
|
||||
fpsTooLow &&
|
||||
!useCameraSettingsStore().currentPipelineSettings.inputShouldShow &&
|
||||
useCameraSettingsStore().currentPipelineSettings.pipelineType === PipelineType.Reflective
|
||||
"
|
||||
>
|
||||
HSV thresholds are too broad; narrow them for better performance
|
||||
</span>
|
||||
<span v-else-if="fpsTooLow && useCameraSettingsStore().currentPipelineSettings.inputShouldShow">
|
||||
@@ -61,39 +66,16 @@ const fpsTooLow = computed<boolean>(() => {
|
||||
</v-chip>
|
||||
</div>
|
||||
<div>
|
||||
<v-switch
|
||||
v-model="driverMode"
|
||||
label="Driver Mode"
|
||||
style="margin-left: auto;"
|
||||
color="accent"
|
||||
class="pt-2"
|
||||
/>
|
||||
<v-switch v-model="driverMode" label="Driver Mode" style="margin-left: auto" color="accent" class="pt-2" />
|
||||
</div>
|
||||
</v-card-title>
|
||||
<v-divider style="border-color: white" />
|
||||
<v-row
|
||||
class="pl-3 pr-3 pt-3 pb-3"
|
||||
style="flex-wrap: nowrap; justify-content: center"
|
||||
>
|
||||
<v-col
|
||||
v-show="value.includes(0)"
|
||||
style="max-width: 500px; display: flex; align-items: center"
|
||||
>
|
||||
<photon-camera-stream
|
||||
id="input-camera-stream"
|
||||
stream-type="Raw"
|
||||
style="width: 100%; height: auto"
|
||||
/>
|
||||
<v-row class="pl-3 pr-3 pt-3 pb-3" style="flex-wrap: nowrap; justify-content: center">
|
||||
<v-col v-show="value.includes(0)" style="max-width: 500px; display: flex; align-items: center">
|
||||
<photon-camera-stream id="input-camera-stream" stream-type="Raw" style="width: 100%; height: auto" />
|
||||
</v-col>
|
||||
<v-col
|
||||
v-show="value.includes(1)"
|
||||
style="max-width: 500px; display: flex; align-items: center"
|
||||
>
|
||||
<photon-camera-stream
|
||||
id="output-camera-stream"
|
||||
stream-type="Processed"
|
||||
style="width: 100%; height: auto"
|
||||
/>
|
||||
<v-col v-show="value.includes(1)" style="max-width: 500px; display: flex; align-items: center">
|
||||
<photon-camera-stream id="output-camera-stream" stream-type="Processed" style="width: 100%; height: auto" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
|
||||
@@ -15,8 +15,8 @@ import Map3DTab from "@/components/dashboard/tabs/Map3DTab.vue";
|
||||
import { WebsocketPipelineType } from "@/types/WebsocketDataTypes";
|
||||
|
||||
interface ConfigOption {
|
||||
tabName: string,
|
||||
component: Component
|
||||
tabName: string;
|
||||
component: Component;
|
||||
}
|
||||
|
||||
const allTabs = Object.freeze({
|
||||
@@ -65,20 +65,27 @@ const getTabGroups = (): ConfigOption[][] => {
|
||||
const lgAndDown = getCurrentInstance()?.proxy.$vuetify.breakpoint.lgAndDown || false;
|
||||
const xl = getCurrentInstance()?.proxy.$vuetify.breakpoint.xl || false;
|
||||
|
||||
if(smAndDown || useCameraSettingsStore().isDriverMode || (mdAndDown && !useStateStore().sidebarFolded)) {
|
||||
if (smAndDown || useCameraSettingsStore().isDriverMode || (mdAndDown && !useStateStore().sidebarFolded)) {
|
||||
return [Object.values(allTabs)];
|
||||
} else if(mdAndDown || !useStateStore().sidebarFolded) {
|
||||
} else if (mdAndDown || !useStateStore().sidebarFolded) {
|
||||
return [
|
||||
[allTabs.inputTab, allTabs.thresholdTab, allTabs.contoursTab, allTabs.apriltagTab, allTabs.arucoTab, allTabs.outputTab],
|
||||
[allTabs.targetsTab, allTabs.pnpTab, allTabs.map3dTab]
|
||||
[
|
||||
allTabs.inputTab,
|
||||
allTabs.thresholdTab,
|
||||
allTabs.contoursTab,
|
||||
allTabs.apriltagTab,
|
||||
allTabs.arucoTab,
|
||||
allTabs.outputTab
|
||||
],
|
||||
[allTabs.targetsTab, allTabs.pnpTab, allTabs.map3dTab]
|
||||
];
|
||||
} else if(lgAndDown) {
|
||||
} else if (lgAndDown) {
|
||||
return [
|
||||
[allTabs.inputTab],
|
||||
[allTabs.thresholdTab, allTabs.contoursTab, allTabs.apriltagTab, allTabs.arucoTab, allTabs.outputTab],
|
||||
[allTabs.targetsTab, allTabs.pnpTab, allTabs.map3dTab]
|
||||
[allTabs.inputTab],
|
||||
[allTabs.thresholdTab, allTabs.contoursTab, allTabs.apriltagTab, allTabs.arucoTab, allTabs.outputTab],
|
||||
[allTabs.targetsTab, allTabs.pnpTab, allTabs.map3dTab]
|
||||
];
|
||||
} else if(xl) {
|
||||
} else if (xl) {
|
||||
return [
|
||||
[allTabs.inputTab],
|
||||
[allTabs.thresholdTab],
|
||||
@@ -91,45 +98,41 @@ const getTabGroups = (): ConfigOption[][] => {
|
||||
};
|
||||
const tabGroups = computed<ConfigOption[][]>(() => {
|
||||
// Just return the input tab because we know that is always the case in driver mode
|
||||
if(useCameraSettingsStore().isDriverMode) return [[allTabs.inputTab]];
|
||||
if (useCameraSettingsStore().isDriverMode) return [[allTabs.inputTab]];
|
||||
|
||||
const allow3d = useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled;
|
||||
const isAprilTag = useCameraSettingsStore().currentWebsocketPipelineType === WebsocketPipelineType.AprilTag;
|
||||
const isAruco = useCameraSettingsStore().currentWebsocketPipelineType === WebsocketPipelineType.Aruco;
|
||||
|
||||
return getTabGroups().map(tabGroup => tabGroup.filter(tabConfig =>
|
||||
!(!allow3d && tabConfig.tabName === "3D") //Filter out 3D tab any time 3D isn't calibrated
|
||||
&& !((!allow3d || isAprilTag || isAruco) && tabConfig.tabName === "PnP") //Filter out the PnP config tab if 3D isn't available, or we're doing AprilTags
|
||||
&& !((isAprilTag || isAruco) && (tabConfig.tabName === "Threshold")) //Filter out threshold tab if we're doing AprilTags
|
||||
&& !((isAprilTag || isAruco) && (tabConfig.tabName === "Contours")) //Filter out contours if we're doing AprilTags
|
||||
&& !(!isAprilTag && tabConfig.tabName === "AprilTag") //Filter out apriltag unless we actually are doing AprilTags
|
||||
&& !(!isAruco && tabConfig.tabName === "Aruco") //Filter out aruco unless we actually are doing Aruco
|
||||
));
|
||||
return getTabGroups().map((tabGroup) =>
|
||||
tabGroup.filter(
|
||||
(tabConfig) =>
|
||||
!(!allow3d && tabConfig.tabName === "3D") && //Filter out 3D tab any time 3D isn't calibrated
|
||||
!((!allow3d || isAprilTag || isAruco) && tabConfig.tabName === "PnP") && //Filter out the PnP config tab if 3D isn't available, or we're doing AprilTags
|
||||
!((isAprilTag || isAruco) && tabConfig.tabName === "Threshold") && //Filter out threshold tab if we're doing AprilTags
|
||||
!((isAprilTag || isAruco) && tabConfig.tabName === "Contours") && //Filter out contours if we're doing AprilTags
|
||||
!(!isAprilTag && tabConfig.tabName === "AprilTag") && //Filter out apriltag unless we actually are doing AprilTags
|
||||
!(!isAruco && tabConfig.tabName === "Aruco") //Filter out aruco unless we actually are doing Aruco
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
onBeforeUpdate(() => {
|
||||
// Force the current tab to the input tab on driver mode change
|
||||
if(useCameraSettingsStore().isDriverMode) {
|
||||
if (useCameraSettingsStore().isDriverMode) {
|
||||
selectedTabs.value[0] = 0;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-row
|
||||
no-gutters
|
||||
class="tabGroups"
|
||||
>
|
||||
<v-row no-gutters class="tabGroups">
|
||||
<v-col
|
||||
v-for="(tabGroupData, tabGroupIndex) in tabGroups"
|
||||
:key="tabGroupIndex"
|
||||
:class="tabGroupIndex !== tabGroups.length - 1 && 'pr-3'"
|
||||
>
|
||||
<v-card
|
||||
color="primary"
|
||||
height="100%"
|
||||
class="pr-4 pl-4"
|
||||
>
|
||||
<v-card color="primary" height="100%" class="pr-4 pl-4">
|
||||
<v-tabs
|
||||
v-model="selectedTabs[tabGroupIndex]"
|
||||
grow
|
||||
@@ -138,10 +141,7 @@ onBeforeUpdate(() => {
|
||||
height="48"
|
||||
slider-color="accent"
|
||||
>
|
||||
<v-tab
|
||||
v-for="(tabConfig, index) in tabGroupData"
|
||||
:key="index"
|
||||
>
|
||||
<v-tab v-for="(tabConfig, index) in tabGroupData" :key="index">
|
||||
{{ tabConfig.tabName }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
@@ -156,7 +156,8 @@ onBeforeUpdate(() => {
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.v-slide-group__next--disabled, .v-slide-group__prev--disabled {
|
||||
.v-slide-group__next--disabled,
|
||||
.v-slide-group__prev--disabled {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,23 +5,22 @@ import { useStateStore } from "@/stores/StateStore";
|
||||
|
||||
const props = defineProps<{
|
||||
// TODO fully update v-model usage in custom components on Vue3 update
|
||||
value: number[]
|
||||
value: number[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "input", value: number[]): void
|
||||
(e: "input", value: number[]): void;
|
||||
}>();
|
||||
|
||||
|
||||
const localValue = computed({
|
||||
get: () => props.value,
|
||||
set: v => emit("input", v)
|
||||
set: (v) => emit("input", v)
|
||||
});
|
||||
|
||||
const processingMode = computed<number>({
|
||||
get: () => useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled ? 1: 0,
|
||||
set: v => {
|
||||
if(useCameraSettingsStore().isCurrentVideoFormatCalibrated) {
|
||||
get: () => (useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled ? 1 : 0),
|
||||
set: (v) => {
|
||||
if (useCameraSettingsStore().isCurrentVideoFormatCalibrated) {
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting({ solvePNPEnabled: v === 1 }, true);
|
||||
}
|
||||
}
|
||||
@@ -35,62 +34,30 @@ const processingMode = computed<number>({
|
||||
color="primary"
|
||||
style="height: 100%; display: flex; flex-direction: column"
|
||||
>
|
||||
<v-row
|
||||
align="center"
|
||||
class="pa-3 pb-0"
|
||||
>
|
||||
<v-row align="center" class="pa-3 pb-0">
|
||||
<v-col>
|
||||
<p style="color: white;">
|
||||
Processing Mode
|
||||
</p>
|
||||
<v-btn-toggle
|
||||
v-model="processingMode"
|
||||
mandatory
|
||||
dark
|
||||
class="fill"
|
||||
>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
>
|
||||
<p style="color: white">Processing Mode</p>
|
||||
<v-btn-toggle v-model="processingMode" mandatory dark class="fill">
|
||||
<v-btn color="secondary">
|
||||
<v-icon>mdi-square-outline</v-icon>
|
||||
<span>2D</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
:disabled="!useCameraSettingsStore().isCurrentVideoFormatCalibrated"
|
||||
>
|
||||
<v-btn color="secondary" :disabled="!useCameraSettingsStore().isCurrentVideoFormatCalibrated">
|
||||
<v-icon>mdi-cube-outline</v-icon>
|
||||
<span>3D</span>
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row
|
||||
align="center"
|
||||
class="pa-3 pt-0"
|
||||
>
|
||||
<v-row align="center" class="pa-3 pt-0">
|
||||
<v-col>
|
||||
<p style="color: white;">
|
||||
Stream Display
|
||||
</p>
|
||||
<v-btn-toggle
|
||||
v-model="localValue"
|
||||
:multiple="true"
|
||||
mandatory
|
||||
dark
|
||||
class="fill"
|
||||
>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
class="fill"
|
||||
>
|
||||
<p style="color: white">Stream Display</p>
|
||||
<v-btn-toggle v-model="localValue" :multiple="true" mandatory dark class="fill">
|
||||
<v-btn color="secondary" class="fill">
|
||||
<v-icon>mdi-import</v-icon>
|
||||
<span>Raw</span>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
class="fill"
|
||||
>
|
||||
<v-btn color="secondary" class="fill">
|
||||
<v-icon>mdi-export</v-icon>
|
||||
<span>Processed</span>
|
||||
</v-btn>
|
||||
|
||||
@@ -11,7 +11,13 @@ import { useStateStore } from "@/stores/StateStore";
|
||||
// Defer reference to store access method
|
||||
const currentPipelineSettings = useCameraSettingsStore().currentPipelineSettings;
|
||||
|
||||
const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)) ? 9 : 8;
|
||||
const interactiveCols = computed(
|
||||
() =>
|
||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||
)
|
||||
? 9
|
||||
: 8;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -21,7 +27,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
label="Target family"
|
||||
:items="['AprilTag Family 36h11', 'AprilTag Family 25h9', 'AprilTag Family 16h5']"
|
||||
:select-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({tagFamily: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ tagFamily: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="currentPipelineSettings.decimate"
|
||||
@@ -31,7 +37,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
tooltip="Increases FPS at the expense of range by reducing image resolution initially"
|
||||
:min="1"
|
||||
:max="8"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({decimate: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ decimate: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="currentPipelineSettings.blur"
|
||||
@@ -42,7 +48,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="0"
|
||||
:max="5"
|
||||
:step="0.1"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({blur: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ blur: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="currentPipelineSettings.threads"
|
||||
@@ -52,14 +58,14 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
tooltip="Number of threads spawned by the AprilTag detector"
|
||||
:min="1"
|
||||
:max="8"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({threads: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ threads: value }, false)"
|
||||
/>
|
||||
<cv-switch
|
||||
v-model="currentPipelineSettings.refineEdges"
|
||||
class="pt-2"
|
||||
label="Refine Edges"
|
||||
tooltip="Further refines the AprilTag corner position initial estimate, suggested left on"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({refineEdges: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ refineEdges: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="currentPipelineSettings.decisionMargin"
|
||||
@@ -69,7 +75,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
tooltip="Tags with a 'margin' (decoding quality score) less than this wil be rejected. Increase this to reduce the number of false positive detections"
|
||||
:min="0"
|
||||
:max="250"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({decisionMargin: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ decisionMargin: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="currentPipelineSettings.numIterations"
|
||||
@@ -79,7 +85,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
tooltip="Number of iterations the pose estimation algorithm will run, 50-100 is a good starting point"
|
||||
:min="0"
|
||||
:max="500"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({numIterations: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ numIterations: value }, false)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -9,7 +9,13 @@ import { useStateStore } from "@/stores/StateStore";
|
||||
// Defer reference to store access method
|
||||
const currentPipelineSettings = useCameraSettingsStore().currentPipelineSettings;
|
||||
|
||||
const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)) ? 9 : 8;
|
||||
const interactiveCols = computed(
|
||||
() =>
|
||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||
)
|
||||
? 9
|
||||
: 8;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -22,7 +28,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
tooltip="Increases FPS at the expense of range by reducing image resolution initially"
|
||||
:min="1"
|
||||
:max="8"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({decimate: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ decimate: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="currentPipelineSettings.numIterations"
|
||||
@@ -33,7 +39,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="30"
|
||||
:max="1000"
|
||||
:step="5"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({numIterations: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ numIterations: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="currentPipelineSettings.cornerAccuracy"
|
||||
@@ -44,7 +50,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="0.01"
|
||||
:max="100"
|
||||
:step="0.01"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({cornerAccuracy: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ cornerAccuracy: value }, false)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -14,34 +14,46 @@ const currentPipelineSettings = useCameraSettingsStore().currentPipelineSettings
|
||||
// TODO fix cv-range-slider so that store access doesn't need to be deferred
|
||||
const contourArea = computed<[number, number]>({
|
||||
get: () => Object.values(useCameraSettingsStore().currentPipelineSettings.contourArea) as [number, number],
|
||||
set: v => useCameraSettingsStore().currentPipelineSettings.contourArea = v
|
||||
set: (v) => (useCameraSettingsStore().currentPipelineSettings.contourArea = v)
|
||||
});
|
||||
const contourRatio = computed<[number, number]>({
|
||||
get: () => Object.values(useCameraSettingsStore().currentPipelineSettings.contourRatio) as [number, number],
|
||||
set: v => useCameraSettingsStore().currentPipelineSettings.contourRatio = v
|
||||
set: (v) => (useCameraSettingsStore().currentPipelineSettings.contourRatio = v)
|
||||
});
|
||||
const contourFullness = computed<[number, number]>({
|
||||
get: () => Object.values(useCameraSettingsStore().currentPipelineSettings.contourFullness) as [number, number],
|
||||
set: v => useCameraSettingsStore().currentPipelineSettings.contourFullness = v
|
||||
set: (v) => (useCameraSettingsStore().currentPipelineSettings.contourFullness = v)
|
||||
});
|
||||
const contourPerimeter = computed<[number, number]>({
|
||||
get: () => currentPipelineSettings.pipelineType === PipelineType.ColoredShape ? Object.values(currentPipelineSettings.contourPerimeter) as [number, number] : [0, 0] as [number, number],
|
||||
set: v => {
|
||||
if(currentPipelineSettings.pipelineType === PipelineType.ColoredShape) {
|
||||
get: () =>
|
||||
currentPipelineSettings.pipelineType === PipelineType.ColoredShape
|
||||
? (Object.values(currentPipelineSettings.contourPerimeter) as [number, number])
|
||||
: ([0, 0] as [number, number]),
|
||||
set: (v) => {
|
||||
if (currentPipelineSettings.pipelineType === PipelineType.ColoredShape) {
|
||||
currentPipelineSettings.contourPerimeter = v;
|
||||
}
|
||||
}
|
||||
});
|
||||
const contourRadius = computed<[number, number]>({
|
||||
get: () => currentPipelineSettings.pipelineType === PipelineType.ColoredShape ? Object.values(currentPipelineSettings.contourRadius) as [number, number] : [0, 0] as [number, number],
|
||||
set: v => {
|
||||
if(currentPipelineSettings.pipelineType === PipelineType.ColoredShape) {
|
||||
get: () =>
|
||||
currentPipelineSettings.pipelineType === PipelineType.ColoredShape
|
||||
? (Object.values(currentPipelineSettings.contourRadius) as [number, number])
|
||||
: ([0, 0] as [number, number]),
|
||||
set: (v) => {
|
||||
if (currentPipelineSettings.pipelineType === PipelineType.ColoredShape) {
|
||||
currentPipelineSettings.contourRadius = v;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)) ? 9 : 8;
|
||||
const interactiveCols = computed(
|
||||
() =>
|
||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||
)
|
||||
? 9
|
||||
: 8;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -53,7 +65,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
:step="0.01"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourArea: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourArea: value }, false)"
|
||||
/>
|
||||
<cv-range-slider
|
||||
v-if="useCameraSettingsStore().currentPipelineType !== PipelineType.ColoredShape"
|
||||
@@ -64,7 +76,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
:step="0.1"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourRatio: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourRatio: value }, false)"
|
||||
/>
|
||||
<cv-select
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.contourTargetOrientation"
|
||||
@@ -72,7 +84,9 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
tooltip="Used to determine how to calculate target landmarks, as well as aspect ratio"
|
||||
:items="['Portrait', 'Landscape']"
|
||||
:select-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourTargetOrientation: value}, false)"
|
||||
@input="
|
||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOrientation: value }, false)
|
||||
"
|
||||
/>
|
||||
<cv-range-slider
|
||||
v-if="useCameraSettingsStore().currentPipelineType === PipelineType.ColoredShape"
|
||||
@@ -82,7 +96,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="0"
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourFullness: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourFullness: value }, false)"
|
||||
/>
|
||||
<cv-range-slider
|
||||
v-if="currentPipelineSettings.pipelineType === PipelineType.ColoredShape"
|
||||
@@ -92,7 +106,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
min="0"
|
||||
max="4000"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourPerimeter: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourPerimeter: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.contourSpecklePercentage"
|
||||
@@ -101,7 +115,9 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="0"
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourSpecklePercentage: value}, false)"
|
||||
@input="
|
||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourSpecklePercentage: value }, false)
|
||||
"
|
||||
/>
|
||||
<template v-if="currentPipelineSettings.pipelineType === PipelineType.Reflective">
|
||||
<cv-slider
|
||||
@@ -112,7 +128,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:max="4"
|
||||
:step="0.1"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourFilterRangeX: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourFilterRangeX: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="currentPipelineSettings.contourFilterRangeY"
|
||||
@@ -122,24 +138,24 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:max="4"
|
||||
:step="0.1"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourFilterRangeY: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourFilterRangeY: value }, false)"
|
||||
/>
|
||||
<cv-select
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.contourGroupingMode"
|
||||
label="Target Grouping"
|
||||
tooltip="Whether or not every two targets are paired with each other (good for e.g. 2019 targets)"
|
||||
:select-cols="interactiveCols"
|
||||
:items="['Single','Dual','Two or More']"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourGroupingMode: value}, false)"
|
||||
:items="['Single', 'Dual', 'Two or More']"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourGroupingMode: value }, false)"
|
||||
/>
|
||||
<cv-select
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.contourIntersection"
|
||||
label="Target Intersection"
|
||||
tooltip="If target grouping is in dual mode it will use this dropdown to decide how targets are grouped with adjacent targets"
|
||||
:select-cols="interactiveCols"
|
||||
:items="['None','Up','Down','Left','Right']"
|
||||
:items="['None', 'Up', 'Down', 'Left', 'Right']"
|
||||
:disabled="useCameraSettingsStore().currentPipelineSettings.contourGroupingMode === 0"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourIntersection: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourIntersection: value }, false)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="currentPipelineSettings.pipelineType === PipelineType.ColoredShape">
|
||||
@@ -150,7 +166,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
tooltip="The shape of targets to look for"
|
||||
:select-cols="interactiveCols"
|
||||
:items="['Circle', 'Polygon', 'Triangle', 'Quadrilateral']"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourShape: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourShape: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="currentPipelineSettings.accuracyPercentage"
|
||||
@@ -160,7 +176,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="0"
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({accuracyPercentage: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ accuracyPercentage: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="currentPipelineSettings.circleDetectThreshold"
|
||||
@@ -170,7 +186,9 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="1"
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({circleDetectThreshold: value}, false)"
|
||||
@input="
|
||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ circleDetectThreshold: value }, false)
|
||||
"
|
||||
/>
|
||||
<cv-range-slider
|
||||
v-model="contourRadius"
|
||||
@@ -179,7 +197,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="0"
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourRadius: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourRadius: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="currentPipelineSettings.maxCannyThresh"
|
||||
@@ -188,7 +206,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="1"
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({maxCannyThresh: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ maxCannyThresh: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="currentPipelineSettings.circleAccuracy"
|
||||
@@ -197,7 +215,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="1"
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({circleAccuracy: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ circleAccuracy: value }, false)"
|
||||
/>
|
||||
<v-divider class="mt-3" />
|
||||
</template>
|
||||
@@ -206,8 +224,8 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
label="Target Sort"
|
||||
tooltip="Chooses the sorting mode used to determine the 'best' targets to provide to user code"
|
||||
:select-cols="interactiveCols"
|
||||
:items="['Largest','Smallest','Highest','Lowest','Rightmost','Leftmost','Centermost']"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourSortMode: value}, false)"
|
||||
:items="['Largest', 'Smallest', 'Highest', 'Lowest', 'Rightmost', 'Leftmost', 'Centermost']"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourSortMode: value }, false)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -8,26 +8,38 @@ import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
|
||||
// Due to something with libcamera or something else IDK much about, the 90° rotations need to be disabled if the libcamera drivers are being used.
|
||||
const cameraRotations = computed(() => ["Normal", "90° CW", "180°", "90° CCW"].map((v, i) => ({ name: v, value: i, disabled: useSettingsStore().gpuAccelerationEnabled ? [1, 3].includes(i) : false })));
|
||||
const cameraRotations = computed(() =>
|
||||
["Normal", "90° CW", "180°", "90° CCW"].map((v, i) => ({
|
||||
name: v,
|
||||
value: i,
|
||||
disabled: useSettingsStore().gpuAccelerationEnabled ? [1, 3].includes(i) : false
|
||||
}))
|
||||
);
|
||||
|
||||
const streamDivisors = [1, 2, 4, 6];
|
||||
const getFilteredStreamDivisors = (): number[] => {
|
||||
const currentResolutionWidth = useCameraSettingsStore().currentVideoFormat.resolution.width;
|
||||
return streamDivisors.filter(x =>
|
||||
useCameraSettingsStore().isDriverMode
|
||||
|| !useSettingsStore().gpuAccelerationEnabled
|
||||
|| currentResolutionWidth / x < 400);
|
||||
return streamDivisors.filter(
|
||||
(x) =>
|
||||
useCameraSettingsStore().isDriverMode ||
|
||||
!useSettingsStore().gpuAccelerationEnabled ||
|
||||
currentResolutionWidth / x < 400
|
||||
);
|
||||
};
|
||||
const getNumberOfSkippedDivisors = () => streamDivisors.length - getFilteredStreamDivisors().length;
|
||||
|
||||
const cameraResolutions = computed(() => useCameraSettingsStore().currentCameraSettings.validVideoFormats.map(f => `${f.resolution.width} X ${f.resolution.height} at ${f.fps} FPS, ${f.pixelFormat}`));
|
||||
const cameraResolutions = computed(() =>
|
||||
useCameraSettingsStore().currentCameraSettings.validVideoFormats.map(
|
||||
(f) => `${f.resolution.width} X ${f.resolution.height} at ${f.fps} FPS, ${f.pixelFormat}`
|
||||
)
|
||||
);
|
||||
const handleResolutionChange = (value: number) => {
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting({ cameraVideoModeIndex: value }, false);
|
||||
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting({ streamingFrameDivisor: getNumberOfSkippedDivisors() }, false);
|
||||
useCameraSettingsStore().currentPipelineSettings.streamingFrameDivisor = 0;
|
||||
|
||||
if(!useCameraSettingsStore().isCurrentVideoFormatCalibrated) {
|
||||
if (!useCameraSettingsStore().isCurrentVideoFormatCalibrated) {
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting({ solvePNPEnabled: false }, true);
|
||||
}
|
||||
};
|
||||
@@ -35,14 +47,24 @@ const handleResolutionChange = (value: number) => {
|
||||
const streamResolutions = computed(() => {
|
||||
const streamDivisors = getFilteredStreamDivisors();
|
||||
const currentResolution = useCameraSettingsStore().currentVideoFormat.resolution;
|
||||
return streamDivisors
|
||||
.map(x => `${Math.floor(currentResolution.width / x)} X ${Math.floor(currentResolution.height / x)}`);
|
||||
return streamDivisors.map(
|
||||
(x) => `${Math.floor(currentResolution.width / x)} X ${Math.floor(currentResolution.height / x)}`
|
||||
);
|
||||
});
|
||||
const handleStreamResolutionChange = (value: number) => {
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting({ streamingFrameDivisor: value + getNumberOfSkippedDivisors() }, false);
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting(
|
||||
{ streamingFrameDivisor: value + getNumberOfSkippedDivisors() },
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)) ? 9 : 8;
|
||||
const interactiveCols = computed(
|
||||
() =>
|
||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||
)
|
||||
? 9
|
||||
: 8;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -56,7 +78,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
:step="0.1"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineSetting({cameraExposure: args}, false)"
|
||||
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposure: args }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.cameraBrightness"
|
||||
@@ -64,7 +86,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="0"
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineSetting({cameraBrightness: args}, false)"
|
||||
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBrightness: args }, false)"
|
||||
/>
|
||||
<cv-switch
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.cameraAutoExposure"
|
||||
@@ -72,7 +94,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
label="Auto Exposure"
|
||||
:switch-cols="interactiveCols"
|
||||
tooltip="Enables or Disables camera automatic adjustment for current lighting conditions"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineSetting({cameraAutoExposure: args}, false)"
|
||||
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraAutoExposure: args }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-if="useCameraSettingsStore().currentPipelineSettings.cameraGain >= 0"
|
||||
@@ -82,7 +104,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="0"
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineSetting({cameraGain: args}, false)"
|
||||
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraGain: args }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-if="useCameraSettingsStore().currentPipelineSettings.cameraRedGain !== -1"
|
||||
@@ -92,7 +114,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
tooltip="Controls red automatic white balance gain, which affects how the camera captures colors in different conditions"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineSetting({cameraRedGain: args}, false)"
|
||||
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraRedGain: args }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-if="useCameraSettingsStore().currentPipelineSettings.cameraBlueGain !== -1"
|
||||
@@ -102,7 +124,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:max="100"
|
||||
:slider-cols="interactiveCols"
|
||||
tooltip="Controls blue automatic white balance gain, which affects how the camera captures colors in different conditions"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineSetting({cameraBlueGain: args}, false)"
|
||||
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBlueGain: args }, false)"
|
||||
/>
|
||||
<cv-select
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.inputImageRotationMode"
|
||||
@@ -110,7 +132,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
tooltip="Rotates the camera stream"
|
||||
:items="cameraRotations"
|
||||
:select-cols="interactiveCols"
|
||||
@input="args => useCameraSettingsStore().changeCurrentPipelineSetting({inputImageRotationMode: args}, false)"
|
||||
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ inputImageRotationMode: args }, false)"
|
||||
/>
|
||||
<cv-select
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.cameraVideoModeIndex"
|
||||
@@ -118,7 +140,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
tooltip="Resolution and FPS the camera should directly capture at"
|
||||
:items="cameraResolutions"
|
||||
:select-cols="interactiveCols"
|
||||
@input="args => handleResolutionChange(args)"
|
||||
@input="(args) => handleResolutionChange(args)"
|
||||
/>
|
||||
<cv-select
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.streamingFrameDivisor"
|
||||
@@ -126,7 +148,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
tooltip="Resolution to which camera frames are downscaled for streaming to the dashboard"
|
||||
:items="streamResolutions"
|
||||
:select-cols="interactiveCols"
|
||||
@input="args => handleStreamResolutionChange(args)"
|
||||
@input="(args) => handleStreamResolutionChange(args)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -16,9 +16,7 @@ const trackedTargets = computed<PhotonTarget[]>(() => useStateStore().pipelineRe
|
||||
</v-row>
|
||||
<v-row style="width: 100%">
|
||||
<v-col style="display: flex; align-items: center; justify-content: center">
|
||||
<photon3d-visualizer
|
||||
:targets="trackedTargets"
|
||||
/>
|
||||
<photon3d-visualizer :targets="trackedTargets" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
@@ -7,11 +7,15 @@ import { computed, getCurrentInstance } from "vue";
|
||||
import { RobotOffsetType } from "@/types/SettingTypes";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
|
||||
const isTagPipeline = computed(() => useCameraSettingsStore().currentPipelineType === PipelineType.AprilTag || useCameraSettingsStore().currentPipelineType === PipelineType.Aruco);
|
||||
const isTagPipeline = computed(
|
||||
() =>
|
||||
useCameraSettingsStore().currentPipelineType === PipelineType.AprilTag ||
|
||||
useCameraSettingsStore().currentPipelineType === PipelineType.Aruco
|
||||
);
|
||||
|
||||
interface MetricItem {
|
||||
header: string,
|
||||
value?: string
|
||||
header: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
const offsetPoints = computed<MetricItem[]>(() => {
|
||||
@@ -24,10 +28,11 @@ const offsetPoints = computed<MetricItem[]>(() => {
|
||||
const firstPointArea = useCameraSettingsStore().currentPipelineSettings.offsetDualPointAArea;
|
||||
const secondPoint = Object.values(useCameraSettingsStore().currentPipelineSettings.offsetDualPointB);
|
||||
const secondPointArea = useCameraSettingsStore().currentPipelineSettings.offsetDualPointBArea;
|
||||
return [{ header: "First Offset Point", value: `(${firstPoint[0].toFixed(2)}°, ${firstPoint[1].toFixed(2)}°)` },
|
||||
{ header: "First Offset Point Area", value: `${firstPointArea.toFixed(2)}%` },
|
||||
{ header: "Second Offset Point", value: `(${secondPoint[0].toFixed(2)}°, ${secondPoint[1].toFixed(2)}°)` },
|
||||
{ header: "Second Offset Point Area", value: `${secondPointArea.toFixed(2)}%` }
|
||||
return [
|
||||
{ header: "First Offset Point", value: `(${firstPoint[0].toFixed(2)}°, ${firstPoint[1].toFixed(2)}°)` },
|
||||
{ header: "First Offset Point Area", value: `${firstPointArea.toFixed(2)}%` },
|
||||
{ header: "Second Offset Point", value: `(${secondPoint[0].toFixed(2)}°, ${secondPoint[1].toFixed(2)}°)` },
|
||||
{ header: "Second Offset Point Area", value: `${secondPointArea.toFixed(2)}%` }
|
||||
];
|
||||
default:
|
||||
case RobotOffsetPointMode.None:
|
||||
@@ -35,7 +40,13 @@ const offsetPoints = computed<MetricItem[]>(() => {
|
||||
}
|
||||
});
|
||||
|
||||
const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)) ? 9 : 8;
|
||||
const interactiveCols = computed(
|
||||
() =>
|
||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||
)
|
||||
? 9
|
||||
: 8;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -44,9 +55,11 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.contourTargetOffsetPointEdge"
|
||||
label="Target Offset Point"
|
||||
tooltip="Changes where the 'center' of the target is (used for calculating e.g. pitch and yaw)"
|
||||
:items="['Center','Top','Bottom','Left','Right']"
|
||||
:items="['Center', 'Top', 'Bottom', 'Left', 'Right']"
|
||||
:select-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourTargetOffsetPointEdge: value}, false)"
|
||||
@input="
|
||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOffsetPointEdge: value }, false)
|
||||
"
|
||||
/>
|
||||
<cv-select
|
||||
v-if="!isTagPipeline"
|
||||
@@ -55,7 +68,9 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
tooltip="Used to determine how to calculate target landmarks (e.g. the top, left, or bottom of the target)"
|
||||
:items="['Portrait', 'Landscape']"
|
||||
:select-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({contourTargetOrientation: value}, false)"
|
||||
@input="
|
||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOrientation: value }, false)
|
||||
"
|
||||
/>
|
||||
<cv-switch
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.outputShowMultipleTargets"
|
||||
@@ -63,7 +78,9 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
tooltip="If enabled, up to five targets will be displayed and sent to user code, instead of just one"
|
||||
:disabled="isTagPipeline"
|
||||
:switch-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({outputShowMultipleTargets: value}, false)"
|
||||
@input="
|
||||
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ outputShowMultipleTargets: value }, false)
|
||||
"
|
||||
/>
|
||||
<v-divider />
|
||||
<table
|
||||
@@ -71,20 +88,12 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
class="metrics-table mt-3 mb-3"
|
||||
>
|
||||
<tr>
|
||||
<th
|
||||
v-for="(item, itemIndex) in offsetPoints"
|
||||
:key="itemIndex"
|
||||
class="metric-item metric-item-title"
|
||||
>
|
||||
<th v-for="(item, itemIndex) in offsetPoints" :key="itemIndex" class="metric-item metric-item-title">
|
||||
{{ item.header }}
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
v-for="(item, itemIndex) in offsetPoints"
|
||||
:key="itemIndex"
|
||||
class="metric-item"
|
||||
>
|
||||
<td v-for="(item, itemIndex) in offsetPoints" :key="itemIndex" class="metric-item">
|
||||
{{ item.value }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -93,21 +102,23 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.offsetRobotOffsetMode"
|
||||
label="Robot Offset Mode"
|
||||
tooltip="Used to add an arbitrary offset to the location of the targeting crosshair"
|
||||
:items="['None','Single Point','Dual Point']"
|
||||
:items="['None', 'Single Point', 'Dual Point']"
|
||||
:select-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({offsetRobotOffsetMode: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ offsetRobotOffsetMode: value }, false)"
|
||||
/>
|
||||
<v-row
|
||||
v-if="useCameraSettingsStore().currentPipelineSettings.offsetRobotOffsetMode !== RobotOffsetPointMode.None"
|
||||
align="center"
|
||||
justify="start"
|
||||
>
|
||||
<v-row v-if="useCameraSettingsStore().currentPipelineSettings.offsetRobotOffsetMode === RobotOffsetPointMode.Single">
|
||||
<v-row
|
||||
v-if="useCameraSettingsStore().currentPipelineSettings.offsetRobotOffsetMode === RobotOffsetPointMode.Single"
|
||||
>
|
||||
<v-col>
|
||||
<v-btn
|
||||
small
|
||||
color="accent"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
class="black--text"
|
||||
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.Single)"
|
||||
>
|
||||
@@ -115,12 +126,14 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-else-if="useCameraSettingsStore().currentPipelineSettings.offsetRobotOffsetMode === RobotOffsetPointMode.Dual">
|
||||
<v-row
|
||||
v-else-if="useCameraSettingsStore().currentPipelineSettings.offsetRobotOffsetMode === RobotOffsetPointMode.Dual"
|
||||
>
|
||||
<v-col>
|
||||
<v-btn
|
||||
small
|
||||
color="accent"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
class="black--text"
|
||||
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.DualFirst)"
|
||||
>
|
||||
@@ -131,7 +144,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
<v-btn
|
||||
small
|
||||
color="accent"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
class="black--text"
|
||||
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.DualSecond)"
|
||||
>
|
||||
@@ -143,7 +156,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
<v-btn
|
||||
small
|
||||
color="yellow darken-3"
|
||||
style="width: 100%;"
|
||||
style="width: 100%"
|
||||
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.Clear)"
|
||||
>
|
||||
Clear All Points
|
||||
@@ -154,7 +167,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.metrics-table{
|
||||
.metrics-table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
border-radius: 5px;
|
||||
|
||||
@@ -6,7 +6,13 @@ import CvSlider from "@/components/common/cv-slider.vue";
|
||||
import { computed, getCurrentInstance } from "vue";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
|
||||
const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)) ? 9 : 8;
|
||||
const interactiveCols = computed(
|
||||
() =>
|
||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||
)
|
||||
? 9
|
||||
: 8;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -15,18 +21,18 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.targetModel"
|
||||
label="Target Model"
|
||||
:items="[
|
||||
{name: '2020 High Goal Outer', value: TargetModel.InfiniteRechargeHighGoalOuter},
|
||||
{name: '2020 High Goal Inner', value: TargetModel.InfiniteRechargeHighGoalInner},
|
||||
{name: '2019 Dual Target', value: TargetModel.DeepSpaceDualTarget},
|
||||
{name: '2020 Power Cell (7in)', value: TargetModel.CircularPowerCell7in},
|
||||
{name: '2022 Cargo Ball (9.5in)', value: TargetModel.RapidReactCircularCargoBall},
|
||||
{name: '2016 High Goal', value: TargetModel.StrongholdHighGoal},
|
||||
{name: '200mm AprilTag', value: TargetModel.Apriltag_200mm},
|
||||
{name: '6in (16h5) Aruco', value: TargetModel.Aruco6in_16h5},
|
||||
{name: '6in (16h5) AprilTag', value: TargetModel.Apriltag6in_16h5}
|
||||
{ name: '2020 High Goal Outer', value: TargetModel.InfiniteRechargeHighGoalOuter },
|
||||
{ name: '2020 High Goal Inner', value: TargetModel.InfiniteRechargeHighGoalInner },
|
||||
{ name: '2019 Dual Target', value: TargetModel.DeepSpaceDualTarget },
|
||||
{ name: '2020 Power Cell (7in)', value: TargetModel.CircularPowerCell7in },
|
||||
{ name: '2022 Cargo Ball (9.5in)', value: TargetModel.RapidReactCircularCargoBall },
|
||||
{ name: '2016 High Goal', value: TargetModel.StrongholdHighGoal },
|
||||
{ name: '200mm AprilTag', value: TargetModel.Apriltag_200mm },
|
||||
{ name: '6in (16h5) Aruco', value: TargetModel.Aruco6in_16h5 },
|
||||
{ name: '6in (16h5) AprilTag', value: TargetModel.Apriltag6in_16h5 }
|
||||
]"
|
||||
:select-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({targetModel: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ targetModel: value }, false)"
|
||||
/>
|
||||
<cv-slider
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.cornerDetectionAccuracyPercentage"
|
||||
@@ -35,7 +41,10 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
label="Contour simplification Percentage"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({cornerDetectionAccuracyPercentage: value}, false)"
|
||||
@input="
|
||||
(value) =>
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting({ cornerDetectionAccuracyPercentage: value }, false)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -6,69 +6,52 @@ import { useStateStore } from "@/stores/StateStore";
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-row
|
||||
align="start"
|
||||
class="pb-4"
|
||||
style="height: 300px;"
|
||||
>
|
||||
<v-row align="start" class="pb-4" style="height: 300px">
|
||||
<!-- Simple table height must be set here and in the CSS for the fixed-header to work -->
|
||||
<v-simple-table
|
||||
fixed-header
|
||||
height="100%"
|
||||
dense
|
||||
dark
|
||||
>
|
||||
<v-simple-table fixed-header height="100%" dense dark>
|
||||
<template #default>
|
||||
<thead style="font-size: 1.25rem;">
|
||||
<thead style="font-size: 1.25rem">
|
||||
<tr>
|
||||
<th class="text-center">
|
||||
Target Count
|
||||
</th>
|
||||
<th class="text-center">Target Count</th>
|
||||
<th
|
||||
v-if="useCameraSettingsStore().currentPipelineType === PipelineType.AprilTag || useCameraSettingsStore().currentPipelineType === PipelineType.Aruco"
|
||||
v-if="
|
||||
useCameraSettingsStore().currentPipelineType === PipelineType.AprilTag ||
|
||||
useCameraSettingsStore().currentPipelineType === PipelineType.Aruco
|
||||
"
|
||||
class="text-center"
|
||||
>
|
||||
Fiducial ID
|
||||
</th>
|
||||
<template v-if="!useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled">
|
||||
<th class="text-center">
|
||||
Pitch θ°
|
||||
</th>
|
||||
<th class="text-center">
|
||||
Yaw θ°
|
||||
</th>
|
||||
<th class="text-center">
|
||||
Skew θ°
|
||||
</th>
|
||||
<th class="text-center">
|
||||
Area %
|
||||
</th>
|
||||
<th class="text-center">Pitch θ°</th>
|
||||
<th class="text-center">Yaw θ°</th>
|
||||
<th class="text-center">Skew θ°</th>
|
||||
<th class="text-center">Area %</th>
|
||||
</template>
|
||||
<template v-else>
|
||||
<th class="text-center">
|
||||
X meters
|
||||
</th>
|
||||
<th class="text-center">
|
||||
Y meters
|
||||
</th>
|
||||
<th class="text-center">
|
||||
Z Angle θ°
|
||||
</th>
|
||||
<th class="text-center">X meters</th>
|
||||
<th class="text-center">Y meters</th>
|
||||
<th class="text-center">Z Angle θ°</th>
|
||||
</template>
|
||||
<template v-if="useCameraSettingsStore().currentPipelineType === PipelineType.AprilTag && useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled">
|
||||
<th class="text-center">
|
||||
Ambiguity %
|
||||
</th>
|
||||
<template
|
||||
v-if="
|
||||
useCameraSettingsStore().currentPipelineType === PipelineType.AprilTag &&
|
||||
useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled
|
||||
"
|
||||
>
|
||||
<th class="text-center">Ambiguity %</th>
|
||||
</template>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(target, index) in useStateStore().pipelineResults?.targets"
|
||||
:key="index"
|
||||
>
|
||||
<tr v-for="(target, index) in useStateStore().pipelineResults?.targets" :key="index">
|
||||
<td>{{ index }}</td>
|
||||
<td v-if="useCameraSettingsStore().currentPipelineType === PipelineType.AprilTag || useCameraSettingsStore().currentPipelineType === PipelineType.Aruco">
|
||||
<td
|
||||
v-if="
|
||||
useCameraSettingsStore().currentPipelineType === PipelineType.AprilTag ||
|
||||
useCameraSettingsStore().currentPipelineType === PipelineType.Aruco
|
||||
"
|
||||
>
|
||||
{{ target.fiducialId }}
|
||||
</td>
|
||||
<template v-if="!useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled">
|
||||
@@ -80,9 +63,14 @@ import { useStateStore } from "@/stores/StateStore";
|
||||
<template v-else-if="useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled">
|
||||
<td>{{ target.pose?.x.toFixed(2) }} m</td>
|
||||
<td>{{ target.pose?.y.toFixed(2) }} m</td>
|
||||
<td>{{ (target.pose?.angle_z * 180.0 / Math.PI).toFixed(2) }}°</td>
|
||||
<td>{{ ((target.pose?.angle_z * 180.0) / Math.PI).toFixed(2) }}°</td>
|
||||
</template>
|
||||
<template v-if="useCameraSettingsStore().currentPipelineType === PipelineType.AprilTag && useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled">
|
||||
<template
|
||||
v-if="
|
||||
useCameraSettingsStore().currentPipelineType === PipelineType.AprilTag &&
|
||||
useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled
|
||||
"
|
||||
>
|
||||
<td>{{ target.ambiguity?.toFixed(2) }}%</td>
|
||||
</template>
|
||||
</tr>
|
||||
@@ -100,7 +88,8 @@ import { useStateStore } from "@/stores/StateStore";
|
||||
text-align: center;
|
||||
background-color: #006492 !important;
|
||||
|
||||
th, td {
|
||||
th,
|
||||
td {
|
||||
background-color: #006492 !important;
|
||||
font-size: 1rem !important;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ const averageHue = computed<number>(() => {
|
||||
const isHueInverted = useCameraSettingsStore().currentPipelineSettings.hueInverted;
|
||||
let val = Object.values(useCameraSettingsStore().currentPipelineSettings.hsvHue).reduce((a, b) => a + b, 0);
|
||||
|
||||
if(isHueInverted) val += 180;
|
||||
if (isHueInverted) val += 180;
|
||||
if (val > 360) val -= 360;
|
||||
|
||||
return val;
|
||||
@@ -19,23 +19,23 @@ const averageHue = computed<number>(() => {
|
||||
// TODO fix cv-range-slider so that store access doesn't need to be deferred
|
||||
const hsvHue = computed<[number, number]>({
|
||||
get: () => Object.values(useCameraSettingsStore().currentPipelineSettings.hsvHue) as [number, number],
|
||||
set: v => useCameraSettingsStore().currentPipelineSettings.hsvHue = v
|
||||
set: (v) => (useCameraSettingsStore().currentPipelineSettings.hsvHue = v)
|
||||
});
|
||||
const hsvSaturation = computed<[number, number]>({
|
||||
get: () => Object.values(useCameraSettingsStore().currentPipelineSettings.hsvSaturation) as [number, number],
|
||||
set: v => useCameraSettingsStore().currentPipelineSettings.hsvSaturation = v
|
||||
set: (v) => (useCameraSettingsStore().currentPipelineSettings.hsvSaturation = v)
|
||||
});
|
||||
const hsvValue = computed<[number, number]>({
|
||||
get: () => Object.values(useCameraSettingsStore().currentPipelineSettings.hsvValue) as [number, number],
|
||||
set: v => useCameraSettingsStore().currentPipelineSettings.hsvValue = v
|
||||
set: (v) => (useCameraSettingsStore().currentPipelineSettings.hsvValue = v)
|
||||
});
|
||||
|
||||
let selectedEventMode: 0 | 1 | 2 | 3 = 0;
|
||||
const handleStreamClick = (event: MouseEvent) => {
|
||||
if(!useStateStore().colorPickingMode || selectedEventMode === 0) return;
|
||||
if (!useStateStore().colorPickingMode || selectedEventMode === 0) return;
|
||||
|
||||
const cameraStream = document.getElementById("input-camera-stream");
|
||||
if(cameraStream === null) return;
|
||||
if (cameraStream === null) return;
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = cameraStream.clientWidth;
|
||||
@@ -43,18 +43,21 @@ const handleStreamClick = (event: MouseEvent) => {
|
||||
|
||||
// Get the (x, y) position of the click with (0, 0) in the top left corner
|
||||
const rect = cameraStream.getBoundingClientRect();
|
||||
const x = Math.round((event.clientX - rect.left) / rect.width * cameraStream.clientWidth);
|
||||
const y = Math.round((event.clientY - rect.top) / rect.height * cameraStream.clientHeight);
|
||||
const x = Math.round(((event.clientX - rect.left) / rect.width) * cameraStream.clientWidth);
|
||||
const y = Math.round(((event.clientY - rect.top) / rect.height) * cameraStream.clientHeight);
|
||||
|
||||
const context = canvas.getContext("2d");
|
||||
if(context === null) return;
|
||||
if (context === null) return;
|
||||
|
||||
context.drawImage(cameraStream as CanvasImageSource, 0, 0, cameraStream.clientWidth, cameraStream.clientHeight);
|
||||
const colorPicker = new ColorPicker(context.getImageData(x, y, 1, 1).data);
|
||||
|
||||
// Calculate HSV values based on the mode
|
||||
let selectedHSVData: [HSV, HSV] = [[0, 0, 0], [0, 0, 0]];
|
||||
if(selectedEventMode === 1) {
|
||||
let selectedHSVData: [HSV, HSV] = [
|
||||
[0, 0, 0],
|
||||
[0, 0, 0]
|
||||
];
|
||||
if (selectedEventMode === 1) {
|
||||
selectedHSVData = colorPicker.selectedColorRange();
|
||||
} else {
|
||||
const currentHue = Object.values(useCameraSettingsStore().currentPipelineSettings.hsvHue);
|
||||
@@ -66,19 +69,22 @@ const handleStreamClick = (event: MouseEvent) => {
|
||||
[currentHue[1], currentSaturation[1], currentValue[1]]
|
||||
];
|
||||
|
||||
if(selectedEventMode === 2) {
|
||||
if (selectedEventMode === 2) {
|
||||
selectedHSVData = colorPicker.expandColorRange(currentData);
|
||||
} else if(selectedEventMode === 3) {
|
||||
} else if (selectedEventMode === 3) {
|
||||
selectedHSVData = colorPicker.shrinkColorRange(currentData);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the store and backend with the new HSV values
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting({
|
||||
hsvHue: [selectedHSVData[0][0], selectedHSVData[1][0]],
|
||||
hsvSaturation: [selectedHSVData[0][1], selectedHSVData[1][1]],
|
||||
hsvValue: [selectedHSVData[0][2], selectedHSVData[1][2]]
|
||||
}, true);
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting(
|
||||
{
|
||||
hsvHue: [selectedHSVData[0][0], selectedHSVData[1][0]],
|
||||
hsvSaturation: [selectedHSVData[0][1], selectedHSVData[1][1]],
|
||||
hsvValue: [selectedHSVData[0][2], selectedHSVData[1][2]]
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
disableColorPicking();
|
||||
};
|
||||
@@ -90,36 +96,45 @@ const enableColorPicking = (mode: 1 | 2 | 3) => {
|
||||
useStateStore().colorPickingMode = true;
|
||||
inputShowing = useCameraSettingsStore().currentPipelineSettings.inputShouldShow;
|
||||
outputShowing = useCameraSettingsStore().currentPipelineSettings.outputShouldShow;
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting({ outputShouldDraw: false, inputShouldShow: true, outputShouldShow: false }, true);
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting(
|
||||
{ outputShouldDraw: false, inputShouldShow: true, outputShouldShow: false },
|
||||
true
|
||||
);
|
||||
selectedEventMode = mode;
|
||||
};
|
||||
const disableColorPicking = () => {
|
||||
useStateStore().colorPickingMode = false;
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting({ outputShouldDraw: true, inputShouldShow: inputShowing, outputShouldShow: outputShowing }, true);
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting(
|
||||
{ outputShouldDraw: true, inputShouldShow: inputShowing, outputShouldShow: outputShowing },
|
||||
true
|
||||
);
|
||||
selectedEventMode = 0;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const cameraStream = document.getElementById("input-camera-stream");
|
||||
if(cameraStream === null) return;
|
||||
if (cameraStream === null) return;
|
||||
|
||||
cameraStream.addEventListener("click", handleStreamClick);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
const cameraStream = document.getElementById("input-camera-stream");
|
||||
if(cameraStream === null) return;
|
||||
if (cameraStream === null) return;
|
||||
|
||||
cameraStream.removeEventListener("click", handleStreamClick);
|
||||
});
|
||||
|
||||
const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)) ? 9 : 8;
|
||||
const interactiveCols = computed(
|
||||
() =>
|
||||
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
|
||||
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
|
||||
)
|
||||
? 9
|
||||
: 8;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="threshold-modifiers"
|
||||
:style="{'--averageHue': averageHue}"
|
||||
>
|
||||
<div class="threshold-modifiers" :style="{ '--averageHue': averageHue }">
|
||||
<cv-range-slider
|
||||
id="hue-slider"
|
||||
v-model="hsvHue"
|
||||
@@ -130,7 +145,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:max="180"
|
||||
:slider-cols="interactiveCols"
|
||||
:inverted="useCameraSettingsStore().currentPipelineSettings.hueInverted"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({hsvHue: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ hsvHue: value }, false)"
|
||||
/>
|
||||
<cv-range-slider
|
||||
id="sat-slider"
|
||||
@@ -141,7 +156,7 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="0"
|
||||
:max="255"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({hsvSaturation: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ hsvSaturation: value }, false)"
|
||||
/>
|
||||
<cv-range-slider
|
||||
id="value-slider"
|
||||
@@ -152,26 +167,19 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
:min="0"
|
||||
:max="255"
|
||||
:slider-cols="interactiveCols"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({hsvValue: value}, false)"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ hsvValue: value }, false)"
|
||||
/>
|
||||
<cv-switch
|
||||
v-model="useCameraSettingsStore().currentPipelineSettings.hueInverted"
|
||||
label="Invert Hue"
|
||||
:switch-cols="interactiveCols"
|
||||
tooltip="Selects the hue range outside of the hue slider bounds instead of inside"
|
||||
@input="value => useCameraSettingsStore().changeCurrentPipelineSetting({hueInverted: value}, false)"
|
||||
/>
|
||||
<v-divider
|
||||
class="mt-3"
|
||||
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ hueInverted: value }, false)"
|
||||
/>
|
||||
<v-divider class="mt-3" />
|
||||
<div>
|
||||
<div class="pt-3 white--text">
|
||||
Color Picker
|
||||
</div>
|
||||
<v-row
|
||||
justify="center"
|
||||
class="mt-3 mb-3"
|
||||
>
|
||||
<div class="pt-3 white--text">Color Picker</div>
|
||||
<v-row justify="center" class="mt-3 mb-3">
|
||||
<template v-if="!useStateStore().colorPickingMode">
|
||||
<v-btn
|
||||
color="accent"
|
||||
@@ -179,42 +187,25 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
small
|
||||
@click="enableColorPicking(useCameraSettingsStore().currentPipelineSettings.hueInverted ? 2 : 3)"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-minus
|
||||
</v-icon>
|
||||
<v-icon left> mdi-minus </v-icon>
|
||||
Shrink Range
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="accent"
|
||||
class="ma-2 black--text"
|
||||
small
|
||||
@click="enableColorPicking(1)"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-plus-minus
|
||||
</v-icon>
|
||||
<v-btn color="accent" class="ma-2 black--text" small @click="enableColorPicking(1)">
|
||||
<v-icon left> mdi-plus-minus </v-icon>
|
||||
{{ useCameraSettingsStore().currentPipelineSettings.hueInverted ? "Exclude" : "Set to" }} Average
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="accent"
|
||||
class="ma-2 black--text"
|
||||
small
|
||||
@click="enableColorPicking(useCameraSettingsStore().currentPipelineSettings.hueInverted ? 3: 2)"
|
||||
@click="enableColorPicking(useCameraSettingsStore().currentPipelineSettings.hueInverted ? 3 : 2)"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-plus
|
||||
</v-icon>
|
||||
<v-icon left> mdi-plus </v-icon>
|
||||
Expand Range
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-btn
|
||||
color="accent"
|
||||
class="ma-2 black--text"
|
||||
style="width: 30%;"
|
||||
small
|
||||
@click="disableColorPicking"
|
||||
>
|
||||
<v-btn color="accent" class="ma-2 black--text" style="width: 30%" small @click="disableColorPicking">
|
||||
Cancel
|
||||
</v-btn>
|
||||
</template>
|
||||
@@ -228,18 +219,21 @@ const interactiveCols = computed(() => (getCurrentInstance()?.proxy.$vuetify.bre
|
||||
--averageHue: 0;
|
||||
}
|
||||
#hue-slider >>> .v-slider {
|
||||
background: linear-gradient( to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100% );
|
||||
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
|
||||
border-radius: 10px;
|
||||
/* prettier-ignore */
|
||||
box-shadow: 0 0 5px #333, inset 0 0 3px #333;
|
||||
}
|
||||
#sat-slider >>> .v-slider {
|
||||
background: linear-gradient( to right, #fff 0%, hsl(var(--averageHue), 100%, 50%) 100% );
|
||||
background: linear-gradient(to right, #fff 0%, hsl(var(--averageHue), 100%, 50%) 100%);
|
||||
border-radius: 10px;
|
||||
/* prettier-ignore */
|
||||
box-shadow: 0 0 5px #333, inset 0 0 3px #333;
|
||||
}
|
||||
#value-slider >>> .v-slider {
|
||||
background: linear-gradient( to right, #000 0%, hsl(var(--averageHue), 100%, 50%) 100% );
|
||||
background: linear-gradient(to right, #000 0%, hsl(var(--averageHue), 100%, 50%) 100%);
|
||||
border-radius: 10px;
|
||||
/* prettier-ignore */
|
||||
box-shadow: 0 0 5px #333, inset 0 0 3px #333;
|
||||
}
|
||||
>>> .v-slider__thumb {
|
||||
|
||||
@@ -5,54 +5,56 @@ import CvSelect from "@/components/common/cv-select.vue";
|
||||
import axios from "axios";
|
||||
|
||||
const restartProgram = () => {
|
||||
axios.post("/utils/restartProgram")
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Successfully sent program restart request",
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
// This endpoint always return 204 regardless of outcome
|
||||
if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request.",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
axios
|
||||
.post("/utils/restartProgram")
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Successfully sent program restart request",
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
// This endpoint always return 204 regardless of outcome
|
||||
if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request.",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
const restartDevice = () => {
|
||||
axios.post("/utils/restartDevice")
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Successfully dispatched the restart command. It isn't confirmed if a device restart will occur.",
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "The backend is unable to fulfil the request to restart the device.",
|
||||
color: "error"
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request.",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
axios
|
||||
.post("/utils/restartDevice")
|
||||
.then(() => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Successfully dispatched the restart command. It isn't confirmed if a device restart will occur.",
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "The backend is unable to fulfil the request to restart the device.",
|
||||
color: "error"
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Error while trying to process the request! The backend didn't respond.",
|
||||
color: "error"
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "An error occurred while trying to process the request.",
|
||||
color: "error"
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const address = inject<string>("backendHost");
|
||||
@@ -61,55 +63,60 @@ const offlineUpdate = ref();
|
||||
const openOfflineUpdatePrompt = () => {
|
||||
offlineUpdate.value.click();
|
||||
};
|
||||
const handleOfflineUpdate = ({ files } : { files: FileList}) => {
|
||||
useStateStore().showSnackbarMessage({ message: "New Software Upload in Progress...", color: "secondary", timeout: -1 });
|
||||
const handleOfflineUpdate = ({ files }: { files: FileList }) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "New Software Upload in Progress...",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
});
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("jarData", files[0]);
|
||||
|
||||
axios.post("/utils/offlineUpdate", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
onUploadProgress: ({ progress }) => {
|
||||
const uploadPercentage = ((progress || 0) * 100.0);
|
||||
if (uploadPercentage < 99.5) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "New Software Upload in Process, " + uploadPercentage.toFixed(2) + "% complete",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: "Installing uploaded software...",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response) {
|
||||
axios
|
||||
.post("/utils/offlineUpdate", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
onUploadProgress: ({ progress }) => {
|
||||
const uploadPercentage = (progress || 0) * 100.0;
|
||||
if (uploadPercentage < 99.5) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
message: "New Software Upload in Process, " + uploadPercentage.toFixed(2) + "% complete",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
message: "Installing uploaded software...",
|
||||
color: "secondary",
|
||||
timeout: -1
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const exportLogFile = ref();
|
||||
@@ -154,33 +161,34 @@ const handleSettingsImport = () => {
|
||||
break;
|
||||
}
|
||||
|
||||
axios.post(`/settings${settingsEndpoint}`, formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" }
|
||||
})
|
||||
.then(response => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
});
|
||||
}
|
||||
axios
|
||||
.post(`/settings${settingsEndpoint}`, formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" }
|
||||
})
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
showImportDialog.value = false;
|
||||
importType.value = -1;
|
||||
@@ -189,99 +197,52 @@ const handleSettingsImport = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
dark
|
||||
class="mb-3 pr-6 pb-3"
|
||||
style="background-color: #006492;"
|
||||
>
|
||||
<v-card dark class="mb-3 pr-6 pb-3" style="background-color: #006492">
|
||||
<v-card-title>Device Control</v-card-title>
|
||||
<div class="ml-5">
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
lg="4"
|
||||
md="6"
|
||||
>
|
||||
<v-btn
|
||||
color="red"
|
||||
@click="restartProgram"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-restart
|
||||
</v-icon>
|
||||
<v-col cols="12" lg="4" md="6">
|
||||
<v-btn color="red" @click="restartProgram">
|
||||
<v-icon left> mdi-restart </v-icon>
|
||||
Restart PhotonVision
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
lg="4"
|
||||
md="6"
|
||||
>
|
||||
<v-btn
|
||||
color="red"
|
||||
@click="restartDevice"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-restart-alert
|
||||
</v-icon>
|
||||
<v-col cols="12" lg="4" md="6">
|
||||
<v-btn color="red" @click="restartDevice">
|
||||
<v-icon left> mdi-restart-alert </v-icon>
|
||||
Restart Device
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
lg="4"
|
||||
>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
@click="openOfflineUpdatePrompt"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-upload
|
||||
</v-icon>
|
||||
<v-col cols="12" lg="4">
|
||||
<v-btn color="secondary" @click="openOfflineUpdatePrompt">
|
||||
<v-icon left> mdi-upload </v-icon>
|
||||
Offline Update
|
||||
</v-btn>
|
||||
<input
|
||||
ref="offlineUpdate"
|
||||
type="file"
|
||||
accept=".jar"
|
||||
style="display: none;"
|
||||
@change="handleOfflineUpdate"
|
||||
>
|
||||
<input ref="offlineUpdate" type="file" accept=".jar" style="display: none" @change="handleOfflineUpdate" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider style="margin: 12px 0;" />
|
||||
<v-divider style="margin: 12px 0" />
|
||||
<v-row>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
@click="() => showImportDialog = true"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-import
|
||||
</v-icon>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-btn color="secondary" @click="() => (showImportDialog = true)">
|
||||
<v-icon left> mdi-import </v-icon>
|
||||
Import Settings
|
||||
</v-btn>
|
||||
<v-dialog
|
||||
v-model="showImportDialog"
|
||||
width="600"
|
||||
@input="() => {
|
||||
importType = -1;
|
||||
importFile = null;
|
||||
}"
|
||||
@input="
|
||||
() => {
|
||||
importType = -1;
|
||||
importFile = null;
|
||||
}
|
||||
"
|
||||
>
|
||||
<v-card
|
||||
color="primary"
|
||||
dark
|
||||
>
|
||||
<v-card color="primary" dark>
|
||||
<v-card-title>Import Settings</v-card-title>
|
||||
<v-card-text>
|
||||
Upload and apply previously saved or exported PhotonVision settings to this device
|
||||
<v-row
|
||||
class="mt-6 ml-4"
|
||||
>
|
||||
<v-row class="mt-6 ml-4">
|
||||
<cv-select
|
||||
v-model="importType"
|
||||
label="Type"
|
||||
@@ -290,14 +251,12 @@ const handleSettingsImport = () => {
|
||||
:select-cols="10"
|
||||
/>
|
||||
</v-row>
|
||||
<v-row
|
||||
class="mt-6 ml-4 mr-8"
|
||||
>
|
||||
<v-row class="mt-6 ml-4 mr-8">
|
||||
<v-file-input
|
||||
:disabled="importType === -1"
|
||||
:error-messages="importType === -1 ? 'Settings type not selected' : ''"
|
||||
:accept="importType === ImportType.AllSettings ? '.zip' : '.json'"
|
||||
@change="(file) => importFile = file"
|
||||
@change="(file) => (importFile = file)"
|
||||
/>
|
||||
</v-row>
|
||||
<v-row
|
||||
@@ -305,14 +264,8 @@ const handleSettingsImport = () => {
|
||||
style="display: flex; align-items: center; justify-content: center"
|
||||
align="center"
|
||||
>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
:disabled="importFile === null"
|
||||
@click="handleSettingsImport"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-import
|
||||
</v-icon>
|
||||
<v-btn color="secondary" :disabled="importFile === null" @click="handleSettingsImport">
|
||||
<v-icon left> mdi-import </v-icon>
|
||||
Import Settings
|
||||
</v-btn>
|
||||
</v-row>
|
||||
@@ -320,17 +273,9 @@ const handleSettingsImport = () => {
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
@click="openExportSettingsPrompt"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-export
|
||||
</v-icon>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-btn color="secondary" @click="openExportSettingsPrompt">
|
||||
<v-icon left> mdi-export </v-icon>
|
||||
Export Settings
|
||||
</v-btn>
|
||||
<a
|
||||
@@ -341,17 +286,9 @@ const handleSettingsImport = () => {
|
||||
target="_blank"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
@click="openExportLogsPrompt"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-download
|
||||
</v-icon>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-btn color="secondary" @click="openExportLogsPrompt">
|
||||
<v-icon left> mdi-download </v-icon>
|
||||
Download Current Log
|
||||
|
||||
<!-- Special hidden link that gets 'clicked' when the user exports journalctl logs -->
|
||||
@@ -364,17 +301,9 @@ const handleSettingsImport = () => {
|
||||
/>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<v-btn
|
||||
color="secondary"
|
||||
@click="useStateStore().showLogModal = true"
|
||||
>
|
||||
<v-icon left>
|
||||
mdi-eye
|
||||
</v-icon>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-btn color="secondary" @click="useStateStore().showLogModal = true">
|
||||
<v-icon left> mdi-eye </v-icon>
|
||||
Show log viewer
|
||||
</v-btn>
|
||||
</v-col>
|
||||
|
||||
@@ -4,11 +4,7 @@ import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
dark
|
||||
class="mb-3 pr-6 pb-3"
|
||||
style="background-color: #006492;"
|
||||
>
|
||||
<v-card dark class="mb-3 pr-6 pb-3" style="background-color: #006492">
|
||||
<v-card-title>LED Control</v-card-title>
|
||||
<div class="ml-5">
|
||||
<cv-slider
|
||||
@@ -18,7 +14,7 @@ import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
|
||||
:slider-cols="12"
|
||||
:min="0"
|
||||
:max="100"
|
||||
@input="args => useSettingsStore().changeLEDBrightness(args)"
|
||||
@input="(args) => useSettingsStore().changeLEDBrightness(args)"
|
||||
/>
|
||||
</div>
|
||||
</v-card>
|
||||
|
||||
@@ -5,27 +5,28 @@ import { useStateStore } from "@/stores/StateStore";
|
||||
import CvIcon from "@/components/common/cv-icon.vue";
|
||||
|
||||
interface MetricItem {
|
||||
header: string,
|
||||
value?: string
|
||||
header: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
const generalMetrics = computed<MetricItem[]>(() => [
|
||||
{
|
||||
header: "Version",
|
||||
value: useSettingsStore().general.version || "Unknown"
|
||||
},
|
||||
{
|
||||
header: "Hardware Model",
|
||||
value: useSettingsStore().general.hardwareModel || "Unknown"
|
||||
},
|
||||
{
|
||||
header: "Platform",
|
||||
value: useSettingsStore().general.hardwarePlatform || "Unknown"
|
||||
},
|
||||
{
|
||||
header: "GPU Acceleration",
|
||||
value: useSettingsStore().general.gpuAcceleration || "Unknown"
|
||||
}]);
|
||||
{
|
||||
header: "Version",
|
||||
value: useSettingsStore().general.version || "Unknown"
|
||||
},
|
||||
{
|
||||
header: "Hardware Model",
|
||||
value: useSettingsStore().general.hardwareModel || "Unknown"
|
||||
},
|
||||
{
|
||||
header: "Platform",
|
||||
value: useSettingsStore().general.hardwarePlatform || "Unknown"
|
||||
},
|
||||
{
|
||||
header: "GPU Acceleration",
|
||||
value: useSettingsStore().general.gpuAcceleration || "Unknown"
|
||||
}
|
||||
]);
|
||||
const platformMetrics = computed<MetricItem[]>(() => [
|
||||
{
|
||||
header: "CPU Temp",
|
||||
@@ -33,15 +34,21 @@ const platformMetrics = computed<MetricItem[]>(() => [
|
||||
},
|
||||
{
|
||||
header: "CPU Usage",
|
||||
value: useSettingsStore().metrics.cpuUtil === undefined ? "Unknown" :`${useSettingsStore().metrics.cpuUtil}%`
|
||||
value: useSettingsStore().metrics.cpuUtil === undefined ? "Unknown" : `${useSettingsStore().metrics.cpuUtil}%`
|
||||
},
|
||||
{
|
||||
header: "CPU Memory Usage",
|
||||
value: useSettingsStore().metrics.ramUtil === undefined || useSettingsStore().metrics.cpuMem === undefined ? "Unknown" : `${useSettingsStore().metrics.ramUtil || "Unknown"}MB of ${useSettingsStore().metrics.cpuMem}MB`
|
||||
value:
|
||||
useSettingsStore().metrics.ramUtil === undefined || useSettingsStore().metrics.cpuMem === undefined
|
||||
? "Unknown"
|
||||
: `${useSettingsStore().metrics.ramUtil || "Unknown"}MB of ${useSettingsStore().metrics.cpuMem}MB`
|
||||
},
|
||||
{
|
||||
header: "GPU Memory Usage",
|
||||
value: useSettingsStore().metrics.gpuMemUtil === undefined || useSettingsStore().metrics.gpuMem === undefined ? "Unknown" : `${useSettingsStore().metrics.gpuMemUtil}MB of ${useSettingsStore().metrics.gpuMem}MB`
|
||||
value:
|
||||
useSettingsStore().metrics.gpuMemUtil === undefined || useSettingsStore().metrics.gpuMem === undefined
|
||||
? "Unknown"
|
||||
: `${useSettingsStore().metrics.gpuMemUtil}MB of ${useSettingsStore().metrics.gpuMem}MB`
|
||||
},
|
||||
{
|
||||
header: "CPU Throttling",
|
||||
@@ -60,28 +67,28 @@ const platformMetrics = computed<MetricItem[]>(() => [
|
||||
const metricsLastFetched = ref("Never");
|
||||
const fetchMetrics = () => {
|
||||
useSettingsStore()
|
||||
.requestMetricsUpdate()
|
||||
.catch(error => {
|
||||
if(error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Unable to fetch Metrics! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to fetch Metrics."
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
const pad = (num: number): string => {
|
||||
return String(num).padStart(2, "0");
|
||||
};
|
||||
.requestMetricsUpdate()
|
||||
.catch((error) => {
|
||||
if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Unable to fetch Metrics! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to fetch Metrics."
|
||||
});
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
const pad = (num: number): string => {
|
||||
return String(num).padStart(2, "0");
|
||||
};
|
||||
|
||||
const date = new Date();
|
||||
metricsLastFetched.value = `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||
});
|
||||
const date = new Date();
|
||||
metricsLastFetched.value = `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
||||
});
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
@@ -90,47 +97,24 @@ onBeforeMount(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
dark
|
||||
class="mb-3 pr-6 pb-3"
|
||||
style="background-color: #006492;"
|
||||
>
|
||||
<v-card dark class="mb-3 pr-6 pb-3" style="background-color: #006492">
|
||||
<v-card-title style="display: flex; justify-content: space-between">
|
||||
<span>Stats</span>
|
||||
<cv-icon
|
||||
icon-name="mdi-reload"
|
||||
color="white"
|
||||
tooltip="Reload Metrics"
|
||||
hover
|
||||
@click="fetchMetrics"
|
||||
/>
|
||||
<cv-icon icon-name="mdi-reload" color="white" tooltip="Reload Metrics" hover @click="fetchMetrics" />
|
||||
</v-card-title>
|
||||
<v-row class="pt-2 pa-4 ma-0 ml-5 pb-1">
|
||||
<v-card-subtitle
|
||||
class="ma-0 pa-0 pb-2"
|
||||
style="font-size: 16px"
|
||||
>
|
||||
General Metrics
|
||||
</v-card-subtitle>
|
||||
<v-card-subtitle class="ma-0 pa-0 pb-2" style="font-size: 16px"> General Metrics </v-card-subtitle>
|
||||
<v-simple-table class="metrics-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
v-for="(item, itemIndex) in generalMetrics"
|
||||
:key="itemIndex"
|
||||
class="metric-item metric-item-title"
|
||||
>
|
||||
<th v-for="(item, itemIndex) in generalMetrics" :key="itemIndex" class="metric-item metric-item-title">
|
||||
{{ item.header }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
v-for="(item, itemIndex) in generalMetrics"
|
||||
:key="itemIndex"
|
||||
class="metric-item"
|
||||
>
|
||||
<td v-for="(item, itemIndex) in generalMetrics" :key="itemIndex" class="metric-item">
|
||||
{{ item.value }}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -138,31 +122,18 @@ onBeforeMount(() => {
|
||||
</v-simple-table>
|
||||
</v-row>
|
||||
<v-row class="pa-4 ma-0 ml-5">
|
||||
<v-card-subtitle
|
||||
class="ma-0 pa-0 pb-2"
|
||||
style="font-size: 16px"
|
||||
>
|
||||
Hardware Metrics
|
||||
</v-card-subtitle>
|
||||
<v-card-subtitle class="ma-0 pa-0 pb-2" style="font-size: 16px"> Hardware Metrics </v-card-subtitle>
|
||||
<v-simple-table class="metrics-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
v-for="(item, itemIndex) in platformMetrics"
|
||||
:key="itemIndex"
|
||||
class="metric-item metric-item-title"
|
||||
>
|
||||
<th v-for="(item, itemIndex) in platformMetrics" :key="itemIndex" class="metric-item metric-item-title">
|
||||
{{ item.header }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
v-for="(item, itemIndex) in platformMetrics"
|
||||
:key="itemIndex"
|
||||
class="metric-item"
|
||||
>
|
||||
<td v-for="(item, itemIndex) in platformMetrics" :key="itemIndex" class="metric-item">
|
||||
<span v-if="useSettingsStore().metrics.cpuUtil !== undefined">{{ item.value }}</span>
|
||||
<span v-else>---</span>
|
||||
</td>
|
||||
@@ -177,7 +148,7 @@ onBeforeMount(() => {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.metrics-table{
|
||||
.metrics-table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
border-radius: 5px;
|
||||
@@ -203,7 +174,8 @@ onBeforeMount(() => {
|
||||
}
|
||||
|
||||
.v-data-table {
|
||||
thead, tbody {
|
||||
thead,
|
||||
tbody {
|
||||
background-color: #006492;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ const isValidNetworkTablesIP = (v: string | undefined): boolean => {
|
||||
// Check if it is a team number longer than 5 digits
|
||||
const badTeamNumberRegex = /^[0-9]{5,}$/;
|
||||
|
||||
if(v === undefined) return false;
|
||||
if (v === undefined) return false;
|
||||
if (teamNumberRegex.test(v)) return true;
|
||||
if (isValidIPv4(v)) return true;
|
||||
// need to check these before the hostname. "0" and "99999" are valid hostnames, but we don't want to allow then
|
||||
@@ -26,77 +26,80 @@ const isValidIPv4 = (v: string | undefined) => {
|
||||
// https://stackoverflow.com/a/17871737
|
||||
const ipv4Regex = /^((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])$/;
|
||||
|
||||
if(v === undefined) return false;
|
||||
if (v === undefined) return false;
|
||||
return ipv4Regex.test(v);
|
||||
};
|
||||
const isValidHostname = (v: string | undefined) => {
|
||||
// https://stackoverflow.com/a/18494710
|
||||
const hostnameRegex = /^([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*)+(\.([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*))*$/;
|
||||
|
||||
if(v === undefined) return false;
|
||||
if (v === undefined) return false;
|
||||
return hostnameRegex.test(v);
|
||||
};
|
||||
|
||||
const saveGeneralSettings = () => {
|
||||
const changingStaticIp = useSettingsStore().network.connectionType === NetworkConnectionType.Static;
|
||||
|
||||
useSettingsStore().saveGeneralSettings()
|
||||
.then(response => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
if(error.response) {
|
||||
if (error.status === 504 || changingStaticIp) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: `Connection lost! Try the new static IP at ${useSettingsStore().network.staticIp}:5800 or ${useSettingsStore().network.hostname}:5800?`
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
}
|
||||
} else if(error.request) {
|
||||
useSettingsStore()
|
||||
.saveGeneralSettings()
|
||||
.then((response) => {
|
||||
useStateStore().showSnackbarMessage({
|
||||
message: response.data.text || response.data,
|
||||
color: "success"
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.status === 504 || changingStaticIp) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
message: `Connection lost! Try the new static IP at ${useSettingsStore().network.staticIp}:5800 or ${
|
||||
useSettingsStore().network.hostname
|
||||
}:5800?`
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
message: error.response.data.text || error.response.data
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (error.request) {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "Error while trying to process the request! The backend didn't respond."
|
||||
});
|
||||
} else {
|
||||
useStateStore().showSnackbarMessage({
|
||||
color: "error",
|
||||
message: "An error occurred while trying to process the request."
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
dark
|
||||
class="mb-3 pr-6 pb-3"
|
||||
style="background-color: #006492;"
|
||||
>
|
||||
<v-card dark class="mb-3 pr-6 pb-3" style="background-color: #006492">
|
||||
<v-card-title>Networking</v-card-title>
|
||||
<div class="ml-5">
|
||||
<v-form
|
||||
ref="form"
|
||||
v-model="settingsValid"
|
||||
>
|
||||
<v-form ref="form" v-model="settingsValid">
|
||||
<cv-input
|
||||
v-model="useSettingsStore().network.ntServerAddress"
|
||||
label="Team Number/NetworkTables Server Address"
|
||||
tooltip="Enter the Team Number or the IP address of the NetworkTables Server"
|
||||
:label-cols="3"
|
||||
:disabled="useSettingsStore().network.runNTServer"
|
||||
:rules="[v => isValidNetworkTablesIP(v) || 'The NetworkTables Server Address must be a valid Team Number, IP address, or Hostname']"
|
||||
:rules="[
|
||||
(v) =>
|
||||
isValidNetworkTablesIP(v) ||
|
||||
'The NetworkTables Server Address must be a valid Team Number, IP address, or Hostname'
|
||||
]"
|
||||
/>
|
||||
<v-banner
|
||||
v-show="!isValidNetworkTablesIP(useSettingsStore().network.ntServerAddress) && !useSettingsStore().network.runNTServer"
|
||||
v-show="
|
||||
!isValidNetworkTablesIP(useSettingsStore().network.ntServerAddress) &&
|
||||
!useSettingsStore().network.runNTServer
|
||||
"
|
||||
rounded
|
||||
color="red"
|
||||
text-color="white"
|
||||
@@ -110,22 +113,22 @@ const saveGeneralSettings = () => {
|
||||
v-model="useSettingsStore().network.connectionType"
|
||||
label="IP Assignment Mode"
|
||||
tooltip="DHCP will make the radio (router) automatically assign an IP address; this may result in an IP address that changes across reboots. Static IP assignment means that you pick the IP address and it won't change."
|
||||
:input-cols="12-3"
|
||||
:list="['DHCP','Static']"
|
||||
:input-cols="12 - 3"
|
||||
:list="['DHCP', 'Static']"
|
||||
/>
|
||||
<cv-input
|
||||
v-if="useSettingsStore().network.connectionType === NetworkConnectionType.Static"
|
||||
v-model="useSettingsStore().network.staticIp"
|
||||
:input-cols="12-3"
|
||||
:input-cols="12 - 3"
|
||||
label="Static IP"
|
||||
:rules="[v => isValidIPv4(v) || 'Invalid IPv4 address']"
|
||||
:rules="[(v) => isValidIPv4(v) || 'Invalid IPv4 address']"
|
||||
/>
|
||||
<cv-input
|
||||
v-show="useSettingsStore().network.shouldManage"
|
||||
v-model="useSettingsStore().network.hostname"
|
||||
label="Hostname"
|
||||
:input-cols="12-3"
|
||||
:rules="[v => isValidHostname(v) || 'Invalid hostname']"
|
||||
:input-cols="12 - 3"
|
||||
:rules="[(v) => isValidHostname(v) || 'Invalid hostname']"
|
||||
/>
|
||||
<cv-switch
|
||||
v-model="useSettingsStore().network.runNTServer"
|
||||
@@ -147,7 +150,7 @@ const saveGeneralSettings = () => {
|
||||
<v-btn
|
||||
color="accent"
|
||||
:class="useSettingsStore().network.runNTServer ? 'mt-3' : ''"
|
||||
style="color: black; width: 100%;"
|
||||
style="color: black; width: 100%"
|
||||
:disabled="!settingsValid && !useSettingsStore().network.runNTServer"
|
||||
@click="saveGeneralSettings"
|
||||
>
|
||||
|
||||
@@ -21,7 +21,12 @@ export class AutoReconnectingWebsocket {
|
||||
* @param onData decoded websocket message data consumer. The data is automatically decoded by msgpack.
|
||||
* @param onDisconnect action to run on websocket disconnection (when the websocket changes to the CLOSED state)
|
||||
*/
|
||||
constructor(serverAddress: string | URL, onConnect: () => void, onData: (data: IncomingWebsocketData) => void, onDisconnect: () => void) {
|
||||
constructor(
|
||||
serverAddress: string | URL,
|
||||
onConnect: () => void,
|
||||
onData: (data: IncomingWebsocketData) => void,
|
||||
onDisconnect: () => void
|
||||
) {
|
||||
this.serverAddress = serverAddress;
|
||||
|
||||
this.onConnect = onConnect;
|
||||
@@ -41,8 +46,8 @@ export class AutoReconnectingWebsocket {
|
||||
*/
|
||||
send(data, encodeData = true) {
|
||||
// Only send data if the websocket is open
|
||||
if(this.isConnected()) {
|
||||
if(encodeData) {
|
||||
if (this.isConnected()) {
|
||||
if (encodeData) {
|
||||
this.websocket?.send(encode(data));
|
||||
} else {
|
||||
this.websocket?.send(data);
|
||||
|
||||
@@ -1,98 +1,101 @@
|
||||
export type HSV = [number, number, number]
|
||||
export type RGBA = [number, number, number, number] | Uint8ClampedArray
|
||||
export type HSV = [number, number, number];
|
||||
export type RGBA = [number, number, number, number] | Uint8ClampedArray;
|
||||
|
||||
export class ColorPicker {
|
||||
public hsvData: HSV;
|
||||
public hsvData: HSV;
|
||||
|
||||
constructor(pixelData: RGBA) {
|
||||
this.hsvData = this.RGBtoHSV(pixelData);
|
||||
constructor(pixelData: RGBA) {
|
||||
this.hsvData = this.RGBtoHSV(pixelData);
|
||||
}
|
||||
|
||||
public selectedColorRange() {
|
||||
return this.widenRange([[...this.hsvData], [...this.hsvData]]);
|
||||
}
|
||||
|
||||
public expandColorRange(currentRange: [HSV, HSV]) {
|
||||
const widenedHSV = this.widenRange([[...this.hsvData], [...this.hsvData]]);
|
||||
return this.createRange(currentRange.concat(widenedHSV));
|
||||
}
|
||||
|
||||
public shrinkColorRange(currentRange: [HSV, HSV]) {
|
||||
const widenedHSV = this.widenRange([[...this.hsvData], [...this.hsvData]]);
|
||||
|
||||
//Tries to shrink the lower part of to widened HSV
|
||||
if (!this.shrinkRange(currentRange, widenedHSV[0])) {
|
||||
//If the prev attempt failed, try to shrink the higher part of to widened HSV
|
||||
this.shrinkRange(currentRange, widenedHSV[1]);
|
||||
}
|
||||
|
||||
public selectedColorRange() {
|
||||
return this.widenRange([[...this.hsvData], [...this.hsvData]]);
|
||||
return currentRange;
|
||||
}
|
||||
|
||||
private createRange(range: HSV[]): [HSV, HSV] {
|
||||
const newRange: [HSV, HSV] = [
|
||||
[0, 0, 0],
|
||||
[0, 0, 0]
|
||||
];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
newRange[0][i] = range[0][i];
|
||||
newRange[1][i] = range[0][i];
|
||||
for (let j = range.length - 1; j >= 0; j--) {
|
||||
newRange[0][i] = Math.min(range[j][i], newRange[0][i]);
|
||||
newRange[1][i] = Math.max(range[j][i], newRange[1][i]);
|
||||
}
|
||||
}
|
||||
return newRange;
|
||||
}
|
||||
|
||||
private widenRange(range: [HSV, HSV]): [HSV, HSV] {
|
||||
const expanded: [HSV, HSV] = [
|
||||
[0, 0, 0],
|
||||
[0, 0, 0]
|
||||
];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
//Expanding the range by 10
|
||||
expanded[0][i] = Math.max(0, range[0][i] - 10);
|
||||
expanded[1][i] = Math.min(255, range[1][i] + 10);
|
||||
}
|
||||
expanded[1][0] = Math.min(180, expanded[1][0]); //h is up to 180
|
||||
return expanded;
|
||||
}
|
||||
|
||||
private shrinkRange(range: [HSV, HSV], color: HSV): boolean {
|
||||
for (let i = 0; i < color.length; i++) {
|
||||
if (!(range[0][i] <= color[i] && color[i] <= range[1][i])) return false;
|
||||
}
|
||||
|
||||
public expandColorRange(currentRange: [HSV, HSV]) {
|
||||
const widenedHSV = this.widenRange([[...this.hsvData], [...this.hsvData]]);
|
||||
return this.createRange(currentRange.concat(widenedHSV));
|
||||
for (let i = 0; i < color.length; i++) {
|
||||
if (color[i] - range[0][i] < range[1][i] - color[i]) {
|
||||
//shrink from min side
|
||||
range[0][i] = Math.min(range[0][i] + 10, range[1][i]);
|
||||
} else {
|
||||
//shrink from max side
|
||||
range[1][i] = Math.max(range[1][i] - 10, range[0][i]);
|
||||
}
|
||||
}
|
||||
|
||||
public shrinkColorRange(currentRange: [HSV, HSV]) {
|
||||
const widenedHSV = this.widenRange([[...this.hsvData], [...this.hsvData]]);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Tries to shrink the lower part of to widened HSV
|
||||
if(!this.shrinkRange(currentRange, widenedHSV[0])) {
|
||||
//If the prev attempt failed, try to shrink the higher part of to widened HSV
|
||||
this.shrinkRange(currentRange, widenedHSV[1]);
|
||||
}
|
||||
private RGBtoHSV(rgba: RGBA): HSV {
|
||||
// Normalize RGB ranges
|
||||
let r = rgba[0],
|
||||
g = rgba[1],
|
||||
b = rgba[2];
|
||||
r = r / 255;
|
||||
g = g / 255;
|
||||
b = b / 255;
|
||||
|
||||
return currentRange;
|
||||
}
|
||||
|
||||
private createRange(range: HSV[]): [HSV, HSV] {
|
||||
const newRange: [HSV, HSV] = [[0, 0, 0], [0, 0, 0]];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
newRange[0][i] = range[0][i];
|
||||
newRange[1][i] = range[0][i];
|
||||
for (let j = range.length - 1; j >= 0; j--) {
|
||||
newRange[0][i] = Math.min(range[j][i], newRange[0][i]);
|
||||
newRange[1][i] = Math.max(range[j][i], newRange[1][i]);
|
||||
}
|
||||
}
|
||||
return newRange;
|
||||
}
|
||||
|
||||
private widenRange(range: [HSV, HSV]): [HSV, HSV] {
|
||||
const expanded: [HSV, HSV] = [[0, 0, 0], [0, 0, 0]];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
//Expanding the range by 10
|
||||
expanded[0][i] = Math.max(0, range[0][i] - 10);
|
||||
expanded[1][i] = Math.min(255, range[1][i] + 10);
|
||||
}
|
||||
expanded[1][0] = Math.min(180, expanded[1][0]); //h is up to 180
|
||||
return expanded;
|
||||
}
|
||||
|
||||
private shrinkRange(range: [HSV, HSV], color: HSV): boolean {
|
||||
for (let i = 0; i < color.length; i++) {
|
||||
if (!(range[0][i] <= color[i] && color[i] <= range[1][i])) return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < color.length; i++) {
|
||||
if (color[i] - range[0][i] < range[1][i] - color[i]) {
|
||||
//shrink from min side
|
||||
range[0][i] = Math.min(range[0][i] + 10, range[1][i]);
|
||||
} else {
|
||||
//shrink from max side
|
||||
range[1][i] = Math.max(range[1][i] - 10, range[0][i]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private RGBtoHSV(rgba: RGBA ): HSV {
|
||||
// Normalize RGB ranges
|
||||
let r = rgba[0],
|
||||
g = rgba[1],
|
||||
b = rgba[2];
|
||||
r = r / 255;
|
||||
g = g / 255;
|
||||
b = b / 255;
|
||||
|
||||
const minRGB = Math.min(r, Math.min(g, b));
|
||||
const maxRGB = Math.max(r, Math.max(g, b));
|
||||
const d = (r === minRGB) ? g - b : ((b === minRGB) ? r - g : b - r);
|
||||
const h = (r === minRGB) ? 3 : ((b === minRGB) ? 1 : 5);
|
||||
let H = 30 * (h - d / (maxRGB - minRGB));
|
||||
let S = 255 * (maxRGB - minRGB) / maxRGB;
|
||||
let V = 255 * maxRGB;
|
||||
if (isNaN(H))
|
||||
H = 0;
|
||||
if (isNaN(S))
|
||||
S = 0;
|
||||
if (isNaN(V))
|
||||
V = 0;
|
||||
return [Math.round(H), Math.round(S), Math.round(V)];
|
||||
}
|
||||
const minRGB = Math.min(r, Math.min(g, b));
|
||||
const maxRGB = Math.max(r, Math.max(g, b));
|
||||
const d = r === minRGB ? g - b : b === minRGB ? r - g : b - r;
|
||||
const h = r === minRGB ? 3 : b === minRGB ? 1 : 5;
|
||||
let H = 30 * (h - d / (maxRGB - minRGB));
|
||||
let S = (255 * (maxRGB - minRGB)) / maxRGB;
|
||||
let V = 255 * maxRGB;
|
||||
if (isNaN(H)) H = 0;
|
||||
if (isNaN(S)) S = 0;
|
||||
if (isNaN(V)) V = 0;
|
||||
return [Math.round(H), Math.round(S), Math.round(V)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,39 +7,38 @@ import type { VuetifyThemeVariant } from "vuetify/types/services/theme";
|
||||
Vue.use(Vuetify);
|
||||
|
||||
const darkTheme: VuetifyThemeVariant = Object.freeze({
|
||||
primary: "#006492",
|
||||
secondary: "#39A4D5",
|
||||
accent: "#FFD843",
|
||||
background: "#232C37",
|
||||
error: "#FF5252",
|
||||
info: "#2196F3",
|
||||
success: "#4CAF50",
|
||||
warning: "#FFC107"
|
||||
primary: "#006492",
|
||||
secondary: "#39A4D5",
|
||||
accent: "#FFD843",
|
||||
background: "#232C37",
|
||||
error: "#FF5252",
|
||||
info: "#2196F3",
|
||||
success: "#4CAF50",
|
||||
warning: "#FFC107"
|
||||
});
|
||||
|
||||
const lightTheme: VuetifyThemeVariant = Object.freeze({
|
||||
primary: "#006492",
|
||||
secondary: "#39A4D5",
|
||||
accent: "#FFD843",
|
||||
background: "#232C37",
|
||||
error: "#FF5252",
|
||||
info: "#2196F3",
|
||||
success: "#4CAF50",
|
||||
warning: "#FFC107"
|
||||
|
||||
primary: "#006492",
|
||||
secondary: "#39A4D5",
|
||||
accent: "#FFD843",
|
||||
background: "#232C37",
|
||||
error: "#FF5252",
|
||||
info: "#2196F3",
|
||||
success: "#4CAF50",
|
||||
warning: "#FFC107"
|
||||
});
|
||||
|
||||
export default new Vuetify({
|
||||
theme: {
|
||||
themes: {
|
||||
light: lightTheme,
|
||||
dark: darkTheme
|
||||
}
|
||||
},
|
||||
breakpoint: {
|
||||
thresholds: {
|
||||
md: 1460,
|
||||
lg: 2000
|
||||
}
|
||||
theme: {
|
||||
themes: {
|
||||
light: lightTheme,
|
||||
dark: darkTheme
|
||||
}
|
||||
},
|
||||
breakpoint: {
|
||||
thresholds: {
|
||||
md: 1460,
|
||||
lg: 2000
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3,123 +3,120 @@ import type { LogMessage } from "@/types/SettingTypes";
|
||||
import type { AutoReconnectingWebsocket } from "@/lib/AutoReconnectingWebsocket";
|
||||
import type { PipelineResult } from "@/types/PhotonTrackingTypes";
|
||||
import type {
|
||||
WebsocketCalibrationData,
|
||||
WebsocketLogMessage,
|
||||
WebsocketNTUpdate,
|
||||
WebsocketPipelineResultUpdate
|
||||
WebsocketCalibrationData,
|
||||
WebsocketLogMessage,
|
||||
WebsocketNTUpdate,
|
||||
WebsocketPipelineResultUpdate
|
||||
} from "@/types/WebsocketDataTypes";
|
||||
|
||||
export interface NTConnectionStatus {
|
||||
connected: boolean,
|
||||
address?: string,
|
||||
clients?: number
|
||||
connected: boolean;
|
||||
address?: string;
|
||||
clients?: number;
|
||||
}
|
||||
|
||||
interface StateStore {
|
||||
backendConnected: boolean,
|
||||
websocket?: AutoReconnectingWebsocket,
|
||||
ntConnectionStatus: NTConnectionStatus,
|
||||
showLogModal: boolean,
|
||||
sidebarFolded: boolean,
|
||||
logMessages: LogMessage[]
|
||||
currentCameraIndex: number,
|
||||
backendConnected: boolean;
|
||||
websocket?: AutoReconnectingWebsocket;
|
||||
ntConnectionStatus: NTConnectionStatus;
|
||||
showLogModal: boolean;
|
||||
sidebarFolded: boolean;
|
||||
logMessages: LogMessage[];
|
||||
currentCameraIndex: number;
|
||||
|
||||
pipelineResults?: PipelineResult,
|
||||
pipelineResults?: PipelineResult;
|
||||
|
||||
colorPickingMode: boolean,
|
||||
colorPickingMode: boolean;
|
||||
|
||||
calibrationData: {
|
||||
imageCount: number,
|
||||
videoFormatIndex: number,
|
||||
minimumImageCount: number,
|
||||
hasEnoughImages: boolean
|
||||
},
|
||||
calibrationData: {
|
||||
imageCount: number;
|
||||
videoFormatIndex: number;
|
||||
minimumImageCount: number;
|
||||
hasEnoughImages: boolean;
|
||||
};
|
||||
|
||||
snackbarData: {
|
||||
show: boolean,
|
||||
message: string,
|
||||
color: string,
|
||||
timeout: number
|
||||
}
|
||||
snackbarData: {
|
||||
show: boolean;
|
||||
message: string;
|
||||
color: string;
|
||||
timeout: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const useStateStore = defineStore("state", {
|
||||
state: (): StateStore => {
|
||||
return {
|
||||
backendConnected: false,
|
||||
websocket: undefined,
|
||||
ntConnectionStatus: {
|
||||
connected: false
|
||||
},
|
||||
showLogModal: false,
|
||||
// Ignored if the display is too small
|
||||
sidebarFolded: localStorage.getItem("sidebarFolded") === null ? false : localStorage.getItem("sidebarFolded") === "true",
|
||||
logMessages: [],
|
||||
currentCameraIndex: 0,
|
||||
state: (): StateStore => {
|
||||
return {
|
||||
backendConnected: false,
|
||||
websocket: undefined,
|
||||
ntConnectionStatus: {
|
||||
connected: false
|
||||
},
|
||||
showLogModal: false,
|
||||
// Ignored if the display is too small
|
||||
sidebarFolded:
|
||||
localStorage.getItem("sidebarFolded") === null ? false : localStorage.getItem("sidebarFolded") === "true",
|
||||
logMessages: [],
|
||||
currentCameraIndex: 0,
|
||||
|
||||
pipelineResults: undefined,
|
||||
pipelineResults: undefined,
|
||||
|
||||
colorPickingMode: false,
|
||||
colorPickingMode: false,
|
||||
|
||||
calibrationData: {
|
||||
imageCount: 0,
|
||||
videoFormatIndex: 0,
|
||||
minimumImageCount: 12,
|
||||
hasEnoughImages: false
|
||||
},
|
||||
calibrationData: {
|
||||
imageCount: 0,
|
||||
videoFormatIndex: 0,
|
||||
minimumImageCount: 12,
|
||||
hasEnoughImages: false
|
||||
},
|
||||
|
||||
snackbarData: {
|
||||
show: false,
|
||||
message: "No Message",
|
||||
color: "info",
|
||||
timeout: 2000
|
||||
}
|
||||
};
|
||||
snackbarData: {
|
||||
show: false,
|
||||
message: "No Message",
|
||||
color: "info",
|
||||
timeout: 2000
|
||||
}
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
setSidebarFolded(value: boolean) {
|
||||
this.sidebarFolded = value;
|
||||
localStorage.setItem("sidebarFolded", Boolean(value).toString());
|
||||
},
|
||||
actions: {
|
||||
setSidebarFolded(value: boolean) {
|
||||
this.sidebarFolded = value;
|
||||
localStorage.setItem("sidebarFolded", Boolean(value).toString());
|
||||
},
|
||||
addLogFromWebsocket(data: WebsocketLogMessage) {
|
||||
this.logMessages.push({
|
||||
level: data.logMessage.logLevel,
|
||||
message: data.logMessage.logMessage
|
||||
});
|
||||
},
|
||||
updateNTConnectionStatusFromWebsocket(data: WebsocketNTUpdate) {
|
||||
this.ntConnectionStatus = {
|
||||
connected: data.connected,
|
||||
address: data.address,
|
||||
clients: data.clients
|
||||
};
|
||||
},
|
||||
updatePipelineResultsFromWebsocket(data: WebsocketPipelineResultUpdate) {
|
||||
for(const cameraIndex in data) {
|
||||
if(parseInt(cameraIndex) === this.currentCameraIndex) {
|
||||
this.pipelineResults = data[cameraIndex];
|
||||
}
|
||||
}
|
||||
},
|
||||
updateCalibrationStateValuesFromWebsocket(data: WebsocketCalibrationData) {
|
||||
this.calibrationData = {
|
||||
imageCount: data.count,
|
||||
videoFormatIndex: data.videoModeIndex,
|
||||
minimumImageCount: data.minCount,
|
||||
hasEnoughImages: data.hasEnough
|
||||
};
|
||||
},
|
||||
showSnackbarMessage(data: {
|
||||
message: string,
|
||||
color: string,
|
||||
timeout?: number
|
||||
}) {
|
||||
this.snackbarData = {
|
||||
show: true,
|
||||
message: data.message,
|
||||
color: data.color,
|
||||
timeout: data.timeout || 2000
|
||||
};
|
||||
addLogFromWebsocket(data: WebsocketLogMessage) {
|
||||
this.logMessages.push({
|
||||
level: data.logMessage.logLevel,
|
||||
message: data.logMessage.logMessage
|
||||
});
|
||||
},
|
||||
updateNTConnectionStatusFromWebsocket(data: WebsocketNTUpdate) {
|
||||
this.ntConnectionStatus = {
|
||||
connected: data.connected,
|
||||
address: data.address,
|
||||
clients: data.clients
|
||||
};
|
||||
},
|
||||
updatePipelineResultsFromWebsocket(data: WebsocketPipelineResultUpdate) {
|
||||
for (const cameraIndex in data) {
|
||||
if (parseInt(cameraIndex) === this.currentCameraIndex) {
|
||||
this.pipelineResults = data[cameraIndex];
|
||||
}
|
||||
}
|
||||
},
|
||||
updateCalibrationStateValuesFromWebsocket(data: WebsocketCalibrationData) {
|
||||
this.calibrationData = {
|
||||
imageCount: data.count,
|
||||
videoFormatIndex: data.videoModeIndex,
|
||||
minimumImageCount: data.minCount,
|
||||
hasEnoughImages: data.hasEnough
|
||||
};
|
||||
},
|
||||
showSnackbarMessage(data: { message: string; color: string; timeout?: number }) {
|
||||
this.snackbarData = {
|
||||
show: true,
|
||||
message: data.message,
|
||||
color: data.color,
|
||||
timeout: data.timeout || 2000
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { defineStore } from "pinia";
|
||||
import type {
|
||||
CalibrationBoardTypes,
|
||||
CameraCalibrationResult,
|
||||
CameraSettings,
|
||||
ConfigurableCameraSettings,
|
||||
RobotOffsetType,
|
||||
VideoFormat
|
||||
CalibrationBoardTypes,
|
||||
CameraCalibrationResult,
|
||||
CameraSettings,
|
||||
ConfigurableCameraSettings,
|
||||
RobotOffsetType,
|
||||
VideoFormat
|
||||
} from "@/types/SettingTypes";
|
||||
import { PlaceholderCameraSettings } from "@/types/SettingTypes";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
@@ -16,334 +16,372 @@ import type { PipelineType } from "@/types/PipelineTypes";
|
||||
import axios from "axios";
|
||||
|
||||
interface CameraSettingsStore {
|
||||
cameras: CameraSettings[]
|
||||
cameras: CameraSettings[];
|
||||
}
|
||||
|
||||
export const useCameraSettingsStore = defineStore("cameraSettings", {
|
||||
state: (): CameraSettingsStore => ({
|
||||
cameras: [
|
||||
PlaceholderCameraSettings
|
||||
]
|
||||
}),
|
||||
getters: {
|
||||
// TODO update types to update this value being undefined. This would be a decently large change.
|
||||
currentCameraSettings(): CameraSettings {
|
||||
return this.cameras[useStateStore().currentCameraIndex];
|
||||
},
|
||||
currentPipelineSettings(): ActivePipelineSettings {
|
||||
return this.currentCameraSettings.pipelineSettings;
|
||||
},
|
||||
currentPipelineType(): PipelineType {
|
||||
return this.currentPipelineSettings.pipelineType;
|
||||
},
|
||||
// This method only exists due to just how lazy I am and my dislike of consolidating the pipeline type enums (which mind you, suck as is)
|
||||
currentWebsocketPipelineType(): WebsocketPipelineType {
|
||||
return this.currentPipelineType - 2;
|
||||
},
|
||||
currentVideoFormat(): VideoFormat {
|
||||
return this.currentCameraSettings.validVideoFormats[this.currentPipelineSettings.cameraVideoModeIndex];
|
||||
},
|
||||
isCurrentVideoFormatCalibrated(): boolean {
|
||||
return this.currentCameraSettings.completeCalibrations.some(v =>
|
||||
v.resolution.width === this.currentVideoFormat.resolution.width
|
||||
&& v.resolution.height === this.currentVideoFormat.resolution.height);
|
||||
},
|
||||
cameraNames(): string[] {
|
||||
return this.cameras.map(c => c.nickname);
|
||||
},
|
||||
pipelineNames(): string[] {
|
||||
return this.currentCameraSettings.pipelineNicknames;
|
||||
},
|
||||
isDriverMode(): boolean {
|
||||
return this.currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.DriverMode;
|
||||
},
|
||||
isCalibrationMode(): boolean {
|
||||
return this.currentCameraSettings.currentPipelineIndex == WebsocketPipelineType.Calib3d;
|
||||
}
|
||||
state: (): CameraSettingsStore => ({
|
||||
cameras: [PlaceholderCameraSettings]
|
||||
}),
|
||||
getters: {
|
||||
// TODO update types to update this value being undefined. This would be a decently large change.
|
||||
currentCameraSettings(): CameraSettings {
|
||||
return this.cameras[useStateStore().currentCameraIndex];
|
||||
},
|
||||
actions: {
|
||||
updateCameraSettingsFromWebsocket(data: WebsocketCameraSettingsUpdate[]) {
|
||||
this.cameras = data.map<CameraSettings>((d) => ({
|
||||
nickname: d.nickname,
|
||||
fov: {
|
||||
value: d.fov,
|
||||
managedByVendor: !d.isFovConfigurable
|
||||
},
|
||||
stream: {
|
||||
inputPort: d.inputStreamPort,
|
||||
outputPort: d.outputStreamPort
|
||||
},
|
||||
validVideoFormats: Object.entries(d.videoFormatList)
|
||||
.sort(([firstKey], [secondKey]) => parseInt(firstKey) - parseInt(secondKey))
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
.map<VideoFormat>(([k, v], i) => ({
|
||||
resolution: {
|
||||
width: v.width,
|
||||
height: v.height
|
||||
},
|
||||
fps: v.fps,
|
||||
pixelFormat: v.pixelFormat,
|
||||
index: v.index || i,
|
||||
diagonalFOV: v.diagonalFOV,
|
||||
horizontalFOV: v.horizontalFOV,
|
||||
verticalFOV: v.verticalFOV,
|
||||
standardDeviation: v.standardDeviation,
|
||||
mean: v.mean
|
||||
})),
|
||||
completeCalibrations: d.calibrations.map<CameraCalibrationResult>(calib => ({
|
||||
resolution: {
|
||||
height: calib.height,
|
||||
width: calib.width
|
||||
},
|
||||
distCoeffs: calib.distCoeffs,
|
||||
standardDeviation: calib.standardDeviation,
|
||||
perViewErrors: calib.perViewErrors,
|
||||
intrinsics: calib.intrinsics
|
||||
})),
|
||||
pipelineNicknames: d.pipelineNicknames,
|
||||
currentPipelineIndex: d.currentPipelineIndex,
|
||||
pipelineSettings: d.currentPipelineSettings
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* Update the configurable camera settings.
|
||||
*
|
||||
* @param data camera settings to save.
|
||||
* @param updateStore whether or not to update the store. This is useful if the input field already models the store reference.
|
||||
* @param cameraIndex the index of the camera.
|
||||
*/
|
||||
updateCameraSettings(data: ConfigurableCameraSettings, updateStore = true, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
// The camera settings endpoint doesn't actually require all data, instead, it needs key data such as the FOV
|
||||
const payload = {
|
||||
settings: {
|
||||
...data
|
||||
},
|
||||
index: cameraIndex
|
||||
};
|
||||
if(updateStore) {
|
||||
this.currentCameraSettings.fov.value = data.fov;
|
||||
}
|
||||
return axios.post("/settings/camera", payload);
|
||||
},
|
||||
/**
|
||||
* Create a new Pipeline for the provided camera.
|
||||
*
|
||||
* @param newPipelineName the name of the new pipeline.
|
||||
* @param pipelineType the type of the new pipeline. Cannot be {@link WebsocketPipelineType.Calib3d} or {@link WebsocketPipelineType.DriverMode}.
|
||||
* @param cameraIndex the index of the camera
|
||||
*/
|
||||
createNewPipeline(newPipelineName: string, pipelineType: Exclude<WebsocketPipelineType, WebsocketPipelineType.Calib3d | WebsocketPipelineType.DriverMode>, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
addNewPipeline: [newPipelineName, pipelineType],
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Modify the settings of the currently selected pipeline of the provided camera.
|
||||
*
|
||||
* @param settings settings to modify. The type of the settings should match the currently selected pipeline type.
|
||||
* @param updateStore whether or not to update the store. This is useful if the input field already models the store reference.
|
||||
* @param cameraIndex the index of the camera
|
||||
*/
|
||||
changeCurrentPipelineSetting(settings: ActiveConfigurablePipelineSettings, updateStore = true, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
changePipelineSetting: {
|
||||
...settings,
|
||||
cameraIndex: cameraIndex
|
||||
}
|
||||
};
|
||||
if(updateStore) {
|
||||
this.changePipelineSettingsInStore(settings, cameraIndex);
|
||||
}
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
changePipelineSettingsInStore(settings: Partial<ActivePipelineSettings>, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
Object.entries(settings).forEach(([k, v]) => {
|
||||
this.cameras[cameraIndex].pipelineSettings[k] = v;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Change the nickname of the currently selected pipeline of the provided camera.
|
||||
*
|
||||
* @param newName the new nickname for the camera.
|
||||
* @param updateStore whether or not to update the store. This is useful if the input field already models the store reference.
|
||||
* @param cameraIndex the index of the camera
|
||||
*/
|
||||
changeCurrentPipelineNickname(newName: string, updateStore = true, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
changePipelineName: newName,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
if(updateStore) {
|
||||
this.cameras[cameraIndex].pipelineSettings.pipelineNickname = newName;
|
||||
}
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Modify the Pipeline type of the currently selected pipeline of the provided camera. This overwrites the current pipeline's settings when the backend resets the current pipeline settings.
|
||||
*
|
||||
* @param type the pipeline type to set. Cannot be {@link WebsocketPipelineType.Calib3d} or {@link WebsocketPipelineType.DriverMode}.
|
||||
* @param cameraIndex the index of the camera.
|
||||
*/
|
||||
changeCurrentPipelineType(type: Exclude<WebsocketPipelineType, WebsocketPipelineType.Calib3d | WebsocketPipelineType.DriverMode>, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
pipelineType: type,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Change the index of the pipeline of the currently selected camera.
|
||||
*
|
||||
* @param index pipeline index to set.
|
||||
* @param updateStore whether or not to update the store. This is useful if the input field already models the store reference.
|
||||
* @param cameraIndex the index of the camera.
|
||||
*/
|
||||
changeCurrentPipelineIndex(index: number, updateStore = true, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
currentPipeline: index,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
if(updateStore) {
|
||||
if(this.cameras[cameraIndex].currentPipelineIndex !== -1
|
||||
&& this.cameras[cameraIndex].currentPipelineIndex !== -2) {
|
||||
this.cameras[cameraIndex].lastPipelineIndex = this.cameras[cameraIndex].currentPipelineIndex;
|
||||
}
|
||||
this.cameras[cameraIndex].currentPipelineIndex = index;
|
||||
}
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Change the currently selected pipeline of the provided camera.
|
||||
*
|
||||
* @param cameraIndex the index of the camera's pipeline to change.
|
||||
*/
|
||||
deleteCurrentPipeline(cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
deleteCurrentPipeline: {},
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Duplicate the pipeline at the provided index.
|
||||
*
|
||||
* @param pipelineIndex index of the pipeline to duplicate.
|
||||
* @param cameraIndex the index of the camera.
|
||||
*/
|
||||
duplicatePipeline(pipelineIndex: number, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
duplicatePipeline: pipelineIndex,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Change the currently set camera
|
||||
*
|
||||
* @param cameraIndex the index of the camera to set as the current camera.
|
||||
* @param updateStore whether or not to update the store. This is useful if the input field already models the store reference.
|
||||
*/
|
||||
setCurrentCameraIndex(cameraIndex: number, updateStore = true) {
|
||||
const payload = {
|
||||
currentCamera: cameraIndex
|
||||
};
|
||||
if(updateStore) {
|
||||
useStateStore().currentCameraIndex = cameraIndex;
|
||||
}
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Change the nickname of the provided camera.
|
||||
*
|
||||
* @param newName the new nickname of the camera.
|
||||
* @param updateStore whether or not to update the store. This is useful if the input field already models the store reference.
|
||||
* @param cameraIndex the index of the camera.
|
||||
* @return HTTP request promise to the backend
|
||||
*/
|
||||
changeCameraNickname(newName: string, updateStore = true, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
name: newName,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
if(updateStore) {
|
||||
this.currentCameraSettings.nickname = newName;
|
||||
}
|
||||
return axios.post("/settings/camera/setNickname", payload);
|
||||
},
|
||||
/**
|
||||
* Start the 3D calibration process for the provided camera.
|
||||
*
|
||||
* @param calibrationInitData initialization calibration data.
|
||||
* @param cameraIndex the index of the camera.
|
||||
*/
|
||||
startPnPCalibration(calibrationInitData: {
|
||||
squareSizeIn: number,
|
||||
patternWidth: number,
|
||||
patternHeight: number,
|
||||
boardType: CalibrationBoardTypes
|
||||
}, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const stateCalibData = useStateStore().calibrationData;
|
||||
const payload = {
|
||||
startPnpCalibration: {
|
||||
count: stateCalibData.imageCount,
|
||||
minCount: stateCalibData.minimumImageCount,
|
||||
hasEnough: stateCalibData.hasEnoughImages,
|
||||
videoModeIndex: stateCalibData.videoFormatIndex,
|
||||
...calibrationInitData
|
||||
},
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* End the 3D calibration process for the provided camera.
|
||||
*
|
||||
* @param cameraIndex the index of the camera
|
||||
* @return HTTP request promise to the backend
|
||||
*/
|
||||
endPnPCalibration(cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
return axios.post("/calibration/end", { index: cameraIndex });
|
||||
},
|
||||
/**
|
||||
* Import calibration data that was computed using CalibDB.
|
||||
*
|
||||
* @param data Data from the uploaded CalibDB config
|
||||
* @param cameraIndex the index of the camera
|
||||
*/
|
||||
importCalibDB(data: { payload: string, filename: string }, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
...data,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
return axios.post("/calibration/importFromCalibDB", payload, { headers: { "Content-Type": "text/plain" } });
|
||||
},
|
||||
/**
|
||||
* Take a snapshot for the calibration processes
|
||||
*
|
||||
* @param takeSnapshot whether or not to take a snapshot. Defaults to true
|
||||
* @param cameraIndex the index of the camera that is currently in the calibration process
|
||||
*/
|
||||
takeCalibrationSnapshot(takeSnapshot = true, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
takeCalibrationSnapshot: takeSnapshot,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Set the robot offset mode type.
|
||||
*
|
||||
* @param type Offset type to take.
|
||||
* @param cameraIndex the index of the camera.
|
||||
*/
|
||||
takeRobotOffsetPoint(type: RobotOffsetType, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
robotOffsetPoint: type,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
}
|
||||
currentPipelineSettings(): ActivePipelineSettings {
|
||||
return this.currentCameraSettings.pipelineSettings;
|
||||
},
|
||||
currentPipelineType(): PipelineType {
|
||||
return this.currentPipelineSettings.pipelineType;
|
||||
},
|
||||
// This method only exists due to just how lazy I am and my dislike of consolidating the pipeline type enums (which mind you, suck as is)
|
||||
currentWebsocketPipelineType(): WebsocketPipelineType {
|
||||
return this.currentPipelineType - 2;
|
||||
},
|
||||
currentVideoFormat(): VideoFormat {
|
||||
return this.currentCameraSettings.validVideoFormats[this.currentPipelineSettings.cameraVideoModeIndex];
|
||||
},
|
||||
isCurrentVideoFormatCalibrated(): boolean {
|
||||
return this.currentCameraSettings.completeCalibrations.some(
|
||||
(v) =>
|
||||
v.resolution.width === this.currentVideoFormat.resolution.width &&
|
||||
v.resolution.height === this.currentVideoFormat.resolution.height
|
||||
);
|
||||
},
|
||||
cameraNames(): string[] {
|
||||
return this.cameras.map((c) => c.nickname);
|
||||
},
|
||||
pipelineNames(): string[] {
|
||||
return this.currentCameraSettings.pipelineNicknames;
|
||||
},
|
||||
isDriverMode(): boolean {
|
||||
return this.currentCameraSettings.currentPipelineIndex === WebsocketPipelineType.DriverMode;
|
||||
},
|
||||
isCalibrationMode(): boolean {
|
||||
return this.currentCameraSettings.currentPipelineIndex == WebsocketPipelineType.Calib3d;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
updateCameraSettingsFromWebsocket(data: WebsocketCameraSettingsUpdate[]) {
|
||||
this.cameras = data.map<CameraSettings>((d) => ({
|
||||
nickname: d.nickname,
|
||||
fov: {
|
||||
value: d.fov,
|
||||
managedByVendor: !d.isFovConfigurable
|
||||
},
|
||||
stream: {
|
||||
inputPort: d.inputStreamPort,
|
||||
outputPort: d.outputStreamPort
|
||||
},
|
||||
validVideoFormats: Object.entries(d.videoFormatList)
|
||||
.sort(([firstKey], [secondKey]) => parseInt(firstKey) - parseInt(secondKey))
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
.map<VideoFormat>(([k, v], i) => ({
|
||||
resolution: {
|
||||
width: v.width,
|
||||
height: v.height
|
||||
},
|
||||
fps: v.fps,
|
||||
pixelFormat: v.pixelFormat,
|
||||
index: v.index || i,
|
||||
diagonalFOV: v.diagonalFOV,
|
||||
horizontalFOV: v.horizontalFOV,
|
||||
verticalFOV: v.verticalFOV,
|
||||
standardDeviation: v.standardDeviation,
|
||||
mean: v.mean
|
||||
})),
|
||||
completeCalibrations: d.calibrations.map<CameraCalibrationResult>((calib) => ({
|
||||
resolution: {
|
||||
height: calib.height,
|
||||
width: calib.width
|
||||
},
|
||||
distCoeffs: calib.distCoeffs,
|
||||
standardDeviation: calib.standardDeviation,
|
||||
perViewErrors: calib.perViewErrors,
|
||||
intrinsics: calib.intrinsics
|
||||
})),
|
||||
pipelineNicknames: d.pipelineNicknames,
|
||||
currentPipelineIndex: d.currentPipelineIndex,
|
||||
pipelineSettings: d.currentPipelineSettings
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* Update the configurable camera settings.
|
||||
*
|
||||
* @param data camera settings to save.
|
||||
* @param updateStore whether or not to update the store. This is useful if the input field already models the store reference.
|
||||
* @param cameraIndex the index of the camera.
|
||||
*/
|
||||
updateCameraSettings(
|
||||
data: ConfigurableCameraSettings,
|
||||
updateStore = true,
|
||||
cameraIndex: number = useStateStore().currentCameraIndex
|
||||
) {
|
||||
// The camera settings endpoint doesn't actually require all data, instead, it needs key data such as the FOV
|
||||
const payload = {
|
||||
settings: {
|
||||
...data
|
||||
},
|
||||
index: cameraIndex
|
||||
};
|
||||
if (updateStore) {
|
||||
this.currentCameraSettings.fov.value = data.fov;
|
||||
}
|
||||
return axios.post("/settings/camera", payload);
|
||||
},
|
||||
/**
|
||||
* Create a new Pipeline for the provided camera.
|
||||
*
|
||||
* @param newPipelineName the name of the new pipeline.
|
||||
* @param pipelineType the type of the new pipeline. Cannot be {@link WebsocketPipelineType.Calib3d} or {@link WebsocketPipelineType.DriverMode}.
|
||||
* @param cameraIndex the index of the camera
|
||||
*/
|
||||
createNewPipeline(
|
||||
newPipelineName: string,
|
||||
pipelineType: Exclude<WebsocketPipelineType, WebsocketPipelineType.Calib3d | WebsocketPipelineType.DriverMode>,
|
||||
cameraIndex: number = useStateStore().currentCameraIndex
|
||||
) {
|
||||
const payload = {
|
||||
addNewPipeline: [newPipelineName, pipelineType],
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Modify the settings of the currently selected pipeline of the provided camera.
|
||||
*
|
||||
* @param settings settings to modify. The type of the settings should match the currently selected pipeline type.
|
||||
* @param updateStore whether or not to update the store. This is useful if the input field already models the store reference.
|
||||
* @param cameraIndex the index of the camera
|
||||
*/
|
||||
changeCurrentPipelineSetting(
|
||||
settings: ActiveConfigurablePipelineSettings,
|
||||
updateStore = true,
|
||||
cameraIndex: number = useStateStore().currentCameraIndex
|
||||
) {
|
||||
const payload = {
|
||||
changePipelineSetting: {
|
||||
...settings,
|
||||
cameraIndex: cameraIndex
|
||||
}
|
||||
};
|
||||
if (updateStore) {
|
||||
this.changePipelineSettingsInStore(settings, cameraIndex);
|
||||
}
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
changePipelineSettingsInStore(
|
||||
settings: Partial<ActivePipelineSettings>,
|
||||
cameraIndex: number = useStateStore().currentCameraIndex
|
||||
) {
|
||||
Object.entries(settings).forEach(([k, v]) => {
|
||||
this.cameras[cameraIndex].pipelineSettings[k] = v;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Change the nickname of the currently selected pipeline of the provided camera.
|
||||
*
|
||||
* @param newName the new nickname for the camera.
|
||||
* @param updateStore whether or not to update the store. This is useful if the input field already models the store reference.
|
||||
* @param cameraIndex the index of the camera
|
||||
*/
|
||||
changeCurrentPipelineNickname(
|
||||
newName: string,
|
||||
updateStore = true,
|
||||
cameraIndex: number = useStateStore().currentCameraIndex
|
||||
) {
|
||||
const payload = {
|
||||
changePipelineName: newName,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
if (updateStore) {
|
||||
this.cameras[cameraIndex].pipelineSettings.pipelineNickname = newName;
|
||||
}
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Modify the Pipeline type of the currently selected pipeline of the provided camera. This overwrites the current pipeline's settings when the backend resets the current pipeline settings.
|
||||
*
|
||||
* @param type the pipeline type to set. Cannot be {@link WebsocketPipelineType.Calib3d} or {@link WebsocketPipelineType.DriverMode}.
|
||||
* @param cameraIndex the index of the camera.
|
||||
*/
|
||||
changeCurrentPipelineType(
|
||||
type: Exclude<WebsocketPipelineType, WebsocketPipelineType.Calib3d | WebsocketPipelineType.DriverMode>,
|
||||
cameraIndex: number = useStateStore().currentCameraIndex
|
||||
) {
|
||||
const payload = {
|
||||
pipelineType: type,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Change the index of the pipeline of the currently selected camera.
|
||||
*
|
||||
* @param index pipeline index to set.
|
||||
* @param updateStore whether or not to update the store. This is useful if the input field already models the store reference.
|
||||
* @param cameraIndex the index of the camera.
|
||||
*/
|
||||
changeCurrentPipelineIndex(
|
||||
index: number,
|
||||
updateStore = true,
|
||||
cameraIndex: number = useStateStore().currentCameraIndex
|
||||
) {
|
||||
const payload = {
|
||||
currentPipeline: index,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
if (updateStore) {
|
||||
if (
|
||||
this.cameras[cameraIndex].currentPipelineIndex !== -1 &&
|
||||
this.cameras[cameraIndex].currentPipelineIndex !== -2
|
||||
) {
|
||||
this.cameras[cameraIndex].lastPipelineIndex = this.cameras[cameraIndex].currentPipelineIndex;
|
||||
}
|
||||
this.cameras[cameraIndex].currentPipelineIndex = index;
|
||||
}
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Change the currently selected pipeline of the provided camera.
|
||||
*
|
||||
* @param cameraIndex the index of the camera's pipeline to change.
|
||||
*/
|
||||
deleteCurrentPipeline(cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
deleteCurrentPipeline: {},
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Duplicate the pipeline at the provided index.
|
||||
*
|
||||
* @param pipelineIndex index of the pipeline to duplicate.
|
||||
* @param cameraIndex the index of the camera.
|
||||
*/
|
||||
duplicatePipeline(pipelineIndex: number, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
duplicatePipeline: pipelineIndex,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Change the currently set camera
|
||||
*
|
||||
* @param cameraIndex the index of the camera to set as the current camera.
|
||||
* @param updateStore whether or not to update the store. This is useful if the input field already models the store reference.
|
||||
*/
|
||||
setCurrentCameraIndex(cameraIndex: number, updateStore = true) {
|
||||
const payload = {
|
||||
currentCamera: cameraIndex
|
||||
};
|
||||
if (updateStore) {
|
||||
useStateStore().currentCameraIndex = cameraIndex;
|
||||
}
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Change the nickname of the provided camera.
|
||||
*
|
||||
* @param newName the new nickname of the camera.
|
||||
* @param updateStore whether or not to update the store. This is useful if the input field already models the store reference.
|
||||
* @param cameraIndex the index of the camera.
|
||||
* @return HTTP request promise to the backend
|
||||
*/
|
||||
changeCameraNickname(
|
||||
newName: string,
|
||||
updateStore = true,
|
||||
cameraIndex: number = useStateStore().currentCameraIndex
|
||||
) {
|
||||
const payload = {
|
||||
name: newName,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
if (updateStore) {
|
||||
this.currentCameraSettings.nickname = newName;
|
||||
}
|
||||
return axios.post("/settings/camera/setNickname", payload);
|
||||
},
|
||||
/**
|
||||
* Start the 3D calibration process for the provided camera.
|
||||
*
|
||||
* @param calibrationInitData initialization calibration data.
|
||||
* @param cameraIndex the index of the camera.
|
||||
*/
|
||||
startPnPCalibration(
|
||||
calibrationInitData: {
|
||||
squareSizeIn: number;
|
||||
patternWidth: number;
|
||||
patternHeight: number;
|
||||
boardType: CalibrationBoardTypes;
|
||||
},
|
||||
cameraIndex: number = useStateStore().currentCameraIndex
|
||||
) {
|
||||
const stateCalibData = useStateStore().calibrationData;
|
||||
const payload = {
|
||||
startPnpCalibration: {
|
||||
count: stateCalibData.imageCount,
|
||||
minCount: stateCalibData.minimumImageCount,
|
||||
hasEnough: stateCalibData.hasEnoughImages,
|
||||
videoModeIndex: stateCalibData.videoFormatIndex,
|
||||
...calibrationInitData
|
||||
},
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* End the 3D calibration process for the provided camera.
|
||||
*
|
||||
* @param cameraIndex the index of the camera
|
||||
* @return HTTP request promise to the backend
|
||||
*/
|
||||
endPnPCalibration(cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
return axios.post("/calibration/end", { index: cameraIndex });
|
||||
},
|
||||
/**
|
||||
* Import calibration data that was computed using CalibDB.
|
||||
*
|
||||
* @param data Data from the uploaded CalibDB config
|
||||
* @param cameraIndex the index of the camera
|
||||
*/
|
||||
importCalibDB(
|
||||
data: { payload: string; filename: string },
|
||||
cameraIndex: number = useStateStore().currentCameraIndex
|
||||
) {
|
||||
const payload = {
|
||||
...data,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
return axios.post("/calibration/importFromCalibDB", payload, { headers: { "Content-Type": "text/plain" } });
|
||||
},
|
||||
/**
|
||||
* Take a snapshot for the calibration processes
|
||||
*
|
||||
* @param takeSnapshot whether or not to take a snapshot. Defaults to true
|
||||
* @param cameraIndex the index of the camera that is currently in the calibration process
|
||||
*/
|
||||
takeCalibrationSnapshot(takeSnapshot = true, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
takeCalibrationSnapshot: takeSnapshot,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
},
|
||||
/**
|
||||
* Set the robot offset mode type.
|
||||
*
|
||||
* @param type Offset type to take.
|
||||
* @param cameraIndex the index of the camera.
|
||||
*/
|
||||
takeRobotOffsetPoint(type: RobotOffsetType, cameraIndex: number = useStateStore().currentCameraIndex) {
|
||||
const payload = {
|
||||
robotOffsetPoint: type,
|
||||
cameraIndex: cameraIndex
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,111 +1,106 @@
|
||||
import { defineStore } from "pinia";
|
||||
import type {
|
||||
GeneralSettings,
|
||||
LightingSettings,
|
||||
MetricData,
|
||||
NetworkSettings
|
||||
} from "@/types/SettingTypes";
|
||||
import type { GeneralSettings, LightingSettings, MetricData, NetworkSettings } from "@/types/SettingTypes";
|
||||
import { NetworkConnectionType } from "@/types/SettingTypes";
|
||||
import { useStateStore } from "@/stores/StateStore";
|
||||
import axios from "axios";
|
||||
import type { WebsocketSettingsUpdate } from "@/types/WebsocketDataTypes";
|
||||
|
||||
interface GeneralSettingsStore {
|
||||
general: GeneralSettings,
|
||||
network: NetworkSettings,
|
||||
lighting: LightingSettings,
|
||||
metrics: MetricData
|
||||
general: GeneralSettings;
|
||||
network: NetworkSettings;
|
||||
lighting: LightingSettings;
|
||||
metrics: MetricData;
|
||||
}
|
||||
|
||||
export const useSettingsStore = defineStore("settings", {
|
||||
state: (): GeneralSettingsStore => ({
|
||||
general: {
|
||||
version: undefined,
|
||||
gpuAcceleration: undefined,
|
||||
hardwareModel: undefined,
|
||||
hardwarePlatform: undefined
|
||||
},
|
||||
network: {
|
||||
ntServerAddress: "",
|
||||
shouldManage: true,
|
||||
connectionType: NetworkConnectionType.DHCP,
|
||||
staticIp: "",
|
||||
hostname: "photonvision",
|
||||
runNTServer: false
|
||||
},
|
||||
lighting: {
|
||||
supported: true,
|
||||
brightness: 0
|
||||
},
|
||||
metrics: {
|
||||
cpuTemp: undefined,
|
||||
cpuUtil: undefined,
|
||||
cpuMem: undefined,
|
||||
gpuMem: undefined,
|
||||
ramUtil: undefined,
|
||||
gpuMemUtil: undefined,
|
||||
cpuThr: undefined,
|
||||
cpuUptime: undefined,
|
||||
diskUtilPct: undefined
|
||||
}
|
||||
}),
|
||||
getters: {
|
||||
gpuAccelerationEnabled(): boolean {
|
||||
return this.general.gpuAcceleration !== undefined;
|
||||
}
|
||||
state: (): GeneralSettingsStore => ({
|
||||
general: {
|
||||
version: undefined,
|
||||
gpuAcceleration: undefined,
|
||||
hardwareModel: undefined,
|
||||
hardwarePlatform: undefined
|
||||
},
|
||||
actions: {
|
||||
requestMetricsUpdate() {
|
||||
return axios.post("/utils/publishMetrics");
|
||||
},
|
||||
updateMetricsFromWebsocket(data: Required<MetricData>) {
|
||||
this.metrics = {
|
||||
cpuTemp: data.cpuTemp || undefined,
|
||||
cpuUtil: data.cpuUtil || undefined,
|
||||
cpuMem: data.cpuMem || undefined,
|
||||
gpuMem: data.gpuMem || undefined,
|
||||
ramUtil: data.ramUtil || undefined,
|
||||
gpuMemUtil: data.gpuMemUtil || undefined,
|
||||
cpuThr: data.cpuThr || undefined,
|
||||
cpuUptime: data.cpuUptime || undefined,
|
||||
diskUtilPct: data.diskUtilPct || undefined
|
||||
};
|
||||
},
|
||||
updateGeneralSettingsFromWebsocket(data: WebsocketSettingsUpdate) {
|
||||
this.general = {
|
||||
version: data.general.version || undefined,
|
||||
hardwareModel: data.general.hardwareModel || undefined,
|
||||
hardwarePlatform: data.general.hardwarePlatform || undefined,
|
||||
gpuAcceleration: data.general.gpuAcceleration || undefined
|
||||
};
|
||||
this.lighting = data.lighting;
|
||||
this.network = data.networkSettings;
|
||||
},
|
||||
saveGeneralSettings() {
|
||||
const payload: Required<NetworkSettings> = {
|
||||
connectionType: this.network.connectionType,
|
||||
hostname: this.network.hostname,
|
||||
networkManagerIface: this.network.networkManagerIface || "",
|
||||
ntServerAddress: this.network.ntServerAddress,
|
||||
physicalInterface: this.network.physicalInterface || "",
|
||||
runNTServer: this.network.runNTServer,
|
||||
setDHCPcommand: this.network.setDHCPcommand || "",
|
||||
setStaticCommand: this.network.setStaticCommand || "",
|
||||
shouldManage: this.network.shouldManage,
|
||||
staticIp: this.network.staticIp
|
||||
};
|
||||
return axios.post("/settings/general", payload);
|
||||
},
|
||||
/**
|
||||
* Modify the brightness of the LEDs.
|
||||
*
|
||||
* @param brightness brightness to set [0, 100]
|
||||
*/
|
||||
changeLEDBrightness(brightness: number) {
|
||||
const payload = {
|
||||
enabledLEDPercentage: brightness
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
}
|
||||
network: {
|
||||
ntServerAddress: "",
|
||||
shouldManage: true,
|
||||
connectionType: NetworkConnectionType.DHCP,
|
||||
staticIp: "",
|
||||
hostname: "photonvision",
|
||||
runNTServer: false
|
||||
},
|
||||
lighting: {
|
||||
supported: true,
|
||||
brightness: 0
|
||||
},
|
||||
metrics: {
|
||||
cpuTemp: undefined,
|
||||
cpuUtil: undefined,
|
||||
cpuMem: undefined,
|
||||
gpuMem: undefined,
|
||||
ramUtil: undefined,
|
||||
gpuMemUtil: undefined,
|
||||
cpuThr: undefined,
|
||||
cpuUptime: undefined,
|
||||
diskUtilPct: undefined
|
||||
}
|
||||
}),
|
||||
getters: {
|
||||
gpuAccelerationEnabled(): boolean {
|
||||
return this.general.gpuAcceleration !== undefined;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
requestMetricsUpdate() {
|
||||
return axios.post("/utils/publishMetrics");
|
||||
},
|
||||
updateMetricsFromWebsocket(data: Required<MetricData>) {
|
||||
this.metrics = {
|
||||
cpuTemp: data.cpuTemp || undefined,
|
||||
cpuUtil: data.cpuUtil || undefined,
|
||||
cpuMem: data.cpuMem || undefined,
|
||||
gpuMem: data.gpuMem || undefined,
|
||||
ramUtil: data.ramUtil || undefined,
|
||||
gpuMemUtil: data.gpuMemUtil || undefined,
|
||||
cpuThr: data.cpuThr || undefined,
|
||||
cpuUptime: data.cpuUptime || undefined,
|
||||
diskUtilPct: data.diskUtilPct || undefined
|
||||
};
|
||||
},
|
||||
updateGeneralSettingsFromWebsocket(data: WebsocketSettingsUpdate) {
|
||||
this.general = {
|
||||
version: data.general.version || undefined,
|
||||
hardwareModel: data.general.hardwareModel || undefined,
|
||||
hardwarePlatform: data.general.hardwarePlatform || undefined,
|
||||
gpuAcceleration: data.general.gpuAcceleration || undefined
|
||||
};
|
||||
this.lighting = data.lighting;
|
||||
this.network = data.networkSettings;
|
||||
},
|
||||
saveGeneralSettings() {
|
||||
const payload: Required<NetworkSettings> = {
|
||||
connectionType: this.network.connectionType,
|
||||
hostname: this.network.hostname,
|
||||
networkManagerIface: this.network.networkManagerIface || "",
|
||||
ntServerAddress: this.network.ntServerAddress,
|
||||
physicalInterface: this.network.physicalInterface || "",
|
||||
runNTServer: this.network.runNTServer,
|
||||
setDHCPcommand: this.network.setDHCPcommand || "",
|
||||
setStaticCommand: this.network.setStaticCommand || "",
|
||||
shouldManage: this.network.shouldManage,
|
||||
staticIp: this.network.staticIp
|
||||
};
|
||||
return axios.post("/settings/general", payload);
|
||||
},
|
||||
/**
|
||||
* Modify the brightness of the LEDs.
|
||||
*
|
||||
* @param brightness brightness to set [0, 100]
|
||||
*/
|
||||
changeLEDBrightness(brightness: number) {
|
||||
const payload = {
|
||||
enabledLEDPercentage: brightness
|
||||
};
|
||||
useStateStore().websocket?.send(payload, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
export interface Pose {
|
||||
x: number,
|
||||
y: number,
|
||||
z: number,
|
||||
angle_z: number,
|
||||
qw: number,
|
||||
qx: number,
|
||||
qy: number,
|
||||
qz: number
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
angle_z: number;
|
||||
qw: number;
|
||||
qx: number;
|
||||
qy: number;
|
||||
qz: number;
|
||||
}
|
||||
|
||||
export interface PhotonTarget {
|
||||
yaw: number,
|
||||
pitch: number,
|
||||
skew: number,
|
||||
area: number,
|
||||
// -1 if not set
|
||||
ambiguity: number,
|
||||
// -1 if not set
|
||||
fiducialId: number,
|
||||
// undefined if 3d isn't enabled
|
||||
pose?: Pose
|
||||
yaw: number;
|
||||
pitch: number;
|
||||
skew: number;
|
||||
area: number;
|
||||
// -1 if not set
|
||||
ambiguity: number;
|
||||
// -1 if not set
|
||||
fiducialId: number;
|
||||
// undefined if 3d isn't enabled
|
||||
pose?: Pose;
|
||||
}
|
||||
|
||||
export interface PipelineResult {
|
||||
fps: number,
|
||||
latency: number,
|
||||
targets: PhotonTarget[]
|
||||
fps: number;
|
||||
latency: number;
|
||||
targets: PhotonTarget[];
|
||||
}
|
||||
|
||||
@@ -1,244 +1,277 @@
|
||||
import type { WebsocketNumberPair } from "@/types/WebsocketDataTypes";
|
||||
|
||||
export enum PipelineType {
|
||||
DriverMode=1,
|
||||
Reflective=2,
|
||||
ColoredShape=3,
|
||||
AprilTag=4,
|
||||
Aruco=5
|
||||
DriverMode = 1,
|
||||
Reflective = 2,
|
||||
ColoredShape = 3,
|
||||
AprilTag = 4,
|
||||
Aruco = 5
|
||||
}
|
||||
|
||||
export enum AprilTagFamily {
|
||||
Family36h11=0,
|
||||
Family25h9=1,
|
||||
Family16h5=2
|
||||
Family36h11 = 0,
|
||||
Family25h9 = 1,
|
||||
Family16h5 = 2
|
||||
}
|
||||
|
||||
export enum RobotOffsetPointMode {
|
||||
None=0,
|
||||
Single=1,
|
||||
Dual=2
|
||||
None = 0,
|
||||
Single = 1,
|
||||
Dual = 2
|
||||
}
|
||||
|
||||
export enum TargetModel {
|
||||
InfiniteRechargeHighGoalOuter=0,
|
||||
InfiniteRechargeHighGoalInner=1,
|
||||
DeepSpaceDualTarget=2,
|
||||
CircularPowerCell7in=3,
|
||||
RapidReactCircularCargoBall=4,
|
||||
StrongholdHighGoal=5,
|
||||
Apriltag_200mm=6,
|
||||
Aruco6in_16h5=7,
|
||||
Apriltag6in_16h5=8
|
||||
InfiniteRechargeHighGoalOuter = 0,
|
||||
InfiniteRechargeHighGoalInner = 1,
|
||||
DeepSpaceDualTarget = 2,
|
||||
CircularPowerCell7in = 3,
|
||||
RapidReactCircularCargoBall = 4,
|
||||
StrongholdHighGoal = 5,
|
||||
Apriltag_200mm = 6,
|
||||
Aruco6in_16h5 = 7,
|
||||
Apriltag6in_16h5 = 8
|
||||
}
|
||||
|
||||
export interface PipelineSettings {
|
||||
offsetRobotOffsetMode: RobotOffsetPointMode
|
||||
streamingFrameDivisor: number
|
||||
offsetDualPointBArea: number
|
||||
contourGroupingMode: number
|
||||
hsvValue: WebsocketNumberPair | [number, number]
|
||||
cameraGain: number
|
||||
cameraBlueGain: number
|
||||
cameraRedGain: number
|
||||
cornerDetectionSideCount: number
|
||||
contourRatio: WebsocketNumberPair | [number, number]
|
||||
contourTargetOffsetPointEdge: number
|
||||
pipelineNickname: string
|
||||
inputImageRotationMode: number
|
||||
contourArea: WebsocketNumberPair | [number, number]
|
||||
solvePNPEnabled: boolean
|
||||
contourFullness: WebsocketNumberPair | [number, number]
|
||||
pipelineIndex: number
|
||||
inputShouldShow: boolean
|
||||
cameraAutoExposure: boolean
|
||||
contourSpecklePercentage: number
|
||||
contourTargetOrientation: number
|
||||
targetModel: TargetModel
|
||||
cornerDetectionUseConvexHulls: boolean
|
||||
outputShouldShow: boolean
|
||||
outputShouldDraw: boolean
|
||||
offsetDualPointA: {x: number, y: number}
|
||||
offsetDualPointB: {x: number, y: number}
|
||||
hsvHue: WebsocketNumberPair | [number, number]
|
||||
ledMode: boolean
|
||||
hueInverted: boolean
|
||||
outputShowMultipleTargets: boolean
|
||||
contourSortMode: number
|
||||
cameraExposure: number
|
||||
offsetSinglePoint: {x: number, y: number}
|
||||
cameraBrightness: number
|
||||
offsetDualPointAArea: number
|
||||
cornerDetectionExactSideCount: boolean
|
||||
cameraVideoModeIndex: number
|
||||
cornerDetectionStrategy: number
|
||||
cornerDetectionAccuracyPercentage: number
|
||||
hsvSaturation: WebsocketNumberPair | [number, number]
|
||||
pipelineType: PipelineType
|
||||
contourIntersection: number
|
||||
offsetRobotOffsetMode: RobotOffsetPointMode;
|
||||
streamingFrameDivisor: number;
|
||||
offsetDualPointBArea: number;
|
||||
contourGroupingMode: number;
|
||||
hsvValue: WebsocketNumberPair | [number, number];
|
||||
cameraGain: number;
|
||||
cameraBlueGain: number;
|
||||
cameraRedGain: number;
|
||||
cornerDetectionSideCount: number;
|
||||
contourRatio: WebsocketNumberPair | [number, number];
|
||||
contourTargetOffsetPointEdge: number;
|
||||
pipelineNickname: string;
|
||||
inputImageRotationMode: number;
|
||||
contourArea: WebsocketNumberPair | [number, number];
|
||||
solvePNPEnabled: boolean;
|
||||
contourFullness: WebsocketNumberPair | [number, number];
|
||||
pipelineIndex: number;
|
||||
inputShouldShow: boolean;
|
||||
cameraAutoExposure: boolean;
|
||||
contourSpecklePercentage: number;
|
||||
contourTargetOrientation: number;
|
||||
targetModel: TargetModel;
|
||||
cornerDetectionUseConvexHulls: boolean;
|
||||
outputShouldShow: boolean;
|
||||
outputShouldDraw: boolean;
|
||||
offsetDualPointA: { x: number; y: number };
|
||||
offsetDualPointB: { x: number; y: number };
|
||||
hsvHue: WebsocketNumberPair | [number, number];
|
||||
ledMode: boolean;
|
||||
hueInverted: boolean;
|
||||
outputShowMultipleTargets: boolean;
|
||||
contourSortMode: number;
|
||||
cameraExposure: number;
|
||||
offsetSinglePoint: { x: number; y: number };
|
||||
cameraBrightness: number;
|
||||
offsetDualPointAArea: number;
|
||||
cornerDetectionExactSideCount: boolean;
|
||||
cameraVideoModeIndex: number;
|
||||
cornerDetectionStrategy: number;
|
||||
cornerDetectionAccuracyPercentage: number;
|
||||
hsvSaturation: WebsocketNumberPair | [number, number];
|
||||
pipelineType: PipelineType;
|
||||
contourIntersection: number;
|
||||
}
|
||||
export type ConfigurablePipelineSettings = Partial<Omit<PipelineSettings, "offsetDualPointAArea" | "cornerDetectionSideCount" | "pipelineNickname" | "pipelineIndex" | "pipelineType" | "cornerDetectionUseConvexHulls" | "offsetDualPointA" | "offsetDualPointB" | "ledMode" | "offsetSinglePoint" | "offsetDualPointBArea" | "cornerDetectionExactSideCount" | "cornerDetectionStrategy">>
|
||||
export type ConfigurablePipelineSettings = Partial<
|
||||
Omit<
|
||||
PipelineSettings,
|
||||
| "offsetDualPointAArea"
|
||||
| "cornerDetectionSideCount"
|
||||
| "pipelineNickname"
|
||||
| "pipelineIndex"
|
||||
| "pipelineType"
|
||||
| "cornerDetectionUseConvexHulls"
|
||||
| "offsetDualPointA"
|
||||
| "offsetDualPointB"
|
||||
| "ledMode"
|
||||
| "offsetSinglePoint"
|
||||
| "offsetDualPointBArea"
|
||||
| "cornerDetectionExactSideCount"
|
||||
| "cornerDetectionStrategy"
|
||||
>
|
||||
>;
|
||||
export const DefaultPipelineSettings: PipelineSettings = {
|
||||
offsetRobotOffsetMode: RobotOffsetPointMode.None,
|
||||
streamingFrameDivisor: 0,
|
||||
offsetDualPointBArea: 0,
|
||||
contourGroupingMode: 0,
|
||||
hsvValue: { first: 50, second: 255 },
|
||||
cameraBlueGain: 20,
|
||||
cameraRedGain: 11,
|
||||
cornerDetectionSideCount: 4,
|
||||
contourRatio: { first: 0, second: 20 },
|
||||
contourTargetOffsetPointEdge: 0,
|
||||
pipelineNickname: "Placeholder Pipeline",
|
||||
inputImageRotationMode: 0,
|
||||
contourArea: { first: 0, second: 100 },
|
||||
solvePNPEnabled: false,
|
||||
contourFullness: { first: 0, second: 100 },
|
||||
pipelineIndex: 0,
|
||||
inputShouldShow: false,
|
||||
cameraAutoExposure: false,
|
||||
contourSpecklePercentage: 5,
|
||||
contourTargetOrientation: 1,
|
||||
cornerDetectionUseConvexHulls: true,
|
||||
outputShouldShow: true,
|
||||
outputShouldDraw: true,
|
||||
offsetDualPointA: { x: 0, y: 0 },
|
||||
offsetDualPointB: { x: 0, y: 0 },
|
||||
hsvHue: { first: 50, second: 180 },
|
||||
hueInverted: false,
|
||||
contourSortMode: 0,
|
||||
offsetSinglePoint: { x: 0, y: 0 },
|
||||
cameraBrightness: 50,
|
||||
offsetDualPointAArea: 0,
|
||||
cornerDetectionExactSideCount: false,
|
||||
cameraVideoModeIndex: 0,
|
||||
cornerDetectionStrategy: 0,
|
||||
cornerDetectionAccuracyPercentage: 10,
|
||||
hsvSaturation: { first: 50, second: 255 },
|
||||
contourIntersection: 1,
|
||||
offsetRobotOffsetMode: RobotOffsetPointMode.None,
|
||||
streamingFrameDivisor: 0,
|
||||
offsetDualPointBArea: 0,
|
||||
contourGroupingMode: 0,
|
||||
hsvValue: { first: 50, second: 255 },
|
||||
cameraBlueGain: 20,
|
||||
cameraRedGain: 11,
|
||||
cornerDetectionSideCount: 4,
|
||||
contourRatio: { first: 0, second: 20 },
|
||||
contourTargetOffsetPointEdge: 0,
|
||||
pipelineNickname: "Placeholder Pipeline",
|
||||
inputImageRotationMode: 0,
|
||||
contourArea: { first: 0, second: 100 },
|
||||
solvePNPEnabled: false,
|
||||
contourFullness: { first: 0, second: 100 },
|
||||
pipelineIndex: 0,
|
||||
inputShouldShow: false,
|
||||
cameraAutoExposure: false,
|
||||
contourSpecklePercentage: 5,
|
||||
contourTargetOrientation: 1,
|
||||
cornerDetectionUseConvexHulls: true,
|
||||
outputShouldShow: true,
|
||||
outputShouldDraw: true,
|
||||
offsetDualPointA: { x: 0, y: 0 },
|
||||
offsetDualPointB: { x: 0, y: 0 },
|
||||
hsvHue: { first: 50, second: 180 },
|
||||
hueInverted: false,
|
||||
contourSortMode: 0,
|
||||
offsetSinglePoint: { x: 0, y: 0 },
|
||||
cameraBrightness: 50,
|
||||
offsetDualPointAArea: 0,
|
||||
cornerDetectionExactSideCount: false,
|
||||
cameraVideoModeIndex: 0,
|
||||
cornerDetectionStrategy: 0,
|
||||
cornerDetectionAccuracyPercentage: 10,
|
||||
hsvSaturation: { first: 50, second: 255 },
|
||||
contourIntersection: 1,
|
||||
|
||||
// These settings will be overridden by different pipeline types
|
||||
cameraGain: -1,
|
||||
targetModel: -1,
|
||||
ledMode: false,
|
||||
outputShowMultipleTargets: false,
|
||||
cameraExposure: -1,
|
||||
pipelineType: -1
|
||||
// These settings will be overridden by different pipeline types
|
||||
cameraGain: -1,
|
||||
targetModel: -1,
|
||||
ledMode: false,
|
||||
outputShowMultipleTargets: false,
|
||||
cameraExposure: -1,
|
||||
pipelineType: -1
|
||||
};
|
||||
|
||||
export interface ReflectivePipelineSettings extends PipelineSettings {
|
||||
pipelineType: PipelineType.Reflective
|
||||
contourFilterRangeY: number
|
||||
contourFilterRangeX: number
|
||||
pipelineType: PipelineType.Reflective;
|
||||
contourFilterRangeY: number;
|
||||
contourFilterRangeX: number;
|
||||
}
|
||||
export type ConfigurableReflectivePipelineSettings = Partial<Omit<ReflectivePipelineSettings, "pipelineType">> & ConfigurablePipelineSettings
|
||||
export type ConfigurableReflectivePipelineSettings = Partial<Omit<ReflectivePipelineSettings, "pipelineType">> &
|
||||
ConfigurablePipelineSettings;
|
||||
export const DefaultReflectivePipelineSettings: ReflectivePipelineSettings = {
|
||||
...DefaultPipelineSettings,
|
||||
cameraGain: 20,
|
||||
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
|
||||
ledMode: true,
|
||||
outputShowMultipleTargets: false,
|
||||
cameraExposure: 6,
|
||||
pipelineType: PipelineType.Reflective,
|
||||
...DefaultPipelineSettings,
|
||||
cameraGain: 20,
|
||||
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
|
||||
ledMode: true,
|
||||
outputShowMultipleTargets: false,
|
||||
cameraExposure: 6,
|
||||
pipelineType: PipelineType.Reflective,
|
||||
|
||||
contourFilterRangeY: 2,
|
||||
contourFilterRangeX: 2
|
||||
contourFilterRangeY: 2,
|
||||
contourFilterRangeX: 2
|
||||
};
|
||||
|
||||
export interface ColoredShapePipelineSettings extends PipelineSettings {
|
||||
pipelineType: PipelineType.ColoredShape
|
||||
erode: boolean
|
||||
cameraCalibration: null
|
||||
dilate: boolean
|
||||
circleAccuracy: number
|
||||
contourRadius: WebsocketNumberPair | [number, number]
|
||||
circleDetectThreshold: number
|
||||
accuracyPercentage: number
|
||||
contourShape: number
|
||||
contourPerimeter: WebsocketNumberPair | [number, number]
|
||||
minDist: number
|
||||
maxCannyThresh: number
|
||||
pipelineType: PipelineType.ColoredShape;
|
||||
erode: boolean;
|
||||
cameraCalibration: null;
|
||||
dilate: boolean;
|
||||
circleAccuracy: number;
|
||||
contourRadius: WebsocketNumberPair | [number, number];
|
||||
circleDetectThreshold: number;
|
||||
accuracyPercentage: number;
|
||||
contourShape: number;
|
||||
contourPerimeter: WebsocketNumberPair | [number, number];
|
||||
minDist: number;
|
||||
maxCannyThresh: number;
|
||||
}
|
||||
export type ConfigurableColoredShapePipelineSettings = Partial<Omit<ColoredShapePipelineSettings, "pipelineType" | "erode" | "cameraCalibration" | "dilate" | "minDist" >> & ConfigurablePipelineSettings
|
||||
export type ConfigurableColoredShapePipelineSettings = Partial<
|
||||
Omit<ColoredShapePipelineSettings, "pipelineType" | "erode" | "cameraCalibration" | "dilate" | "minDist">
|
||||
> &
|
||||
ConfigurablePipelineSettings;
|
||||
export const DefaultColoredShapePipelineSettings: ColoredShapePipelineSettings = {
|
||||
...DefaultPipelineSettings,
|
||||
cameraGain: 75,
|
||||
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
|
||||
ledMode: true,
|
||||
outputShowMultipleTargets: false,
|
||||
cameraExposure: 20,
|
||||
pipelineType: PipelineType.ColoredShape,
|
||||
...DefaultPipelineSettings,
|
||||
cameraGain: 75,
|
||||
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
|
||||
ledMode: true,
|
||||
outputShowMultipleTargets: false,
|
||||
cameraExposure: 20,
|
||||
pipelineType: PipelineType.ColoredShape,
|
||||
|
||||
erode: false,
|
||||
cameraCalibration: null,
|
||||
dilate: false,
|
||||
circleAccuracy: 20,
|
||||
contourRadius: { first: 0, second: 100 },
|
||||
circleDetectThreshold: 5,
|
||||
accuracyPercentage: 10,
|
||||
contourShape: 2,
|
||||
contourPerimeter: { first: 0, second: 1.7976931348623157e+308 },
|
||||
minDist: 20,
|
||||
maxCannyThresh: 90
|
||||
erode: false,
|
||||
cameraCalibration: null,
|
||||
dilate: false,
|
||||
circleAccuracy: 20,
|
||||
contourRadius: { first: 0, second: 100 },
|
||||
circleDetectThreshold: 5,
|
||||
accuracyPercentage: 10,
|
||||
contourShape: 2,
|
||||
contourPerimeter: { first: 0, second: 1.7976931348623157e308 },
|
||||
minDist: 20,
|
||||
maxCannyThresh: 90
|
||||
};
|
||||
|
||||
export interface AprilTagPipelineSettings extends PipelineSettings {
|
||||
pipelineType: PipelineType.AprilTag
|
||||
hammingDist: number
|
||||
numIterations: number
|
||||
decimate: number
|
||||
blur: number
|
||||
decisionMargin: number
|
||||
refineEdges: boolean
|
||||
debug: boolean
|
||||
threads: number
|
||||
tagFamily: AprilTagFamily
|
||||
pipelineType: PipelineType.AprilTag;
|
||||
hammingDist: number;
|
||||
numIterations: number;
|
||||
decimate: number;
|
||||
blur: number;
|
||||
decisionMargin: number;
|
||||
refineEdges: boolean;
|
||||
debug: boolean;
|
||||
threads: number;
|
||||
tagFamily: AprilTagFamily;
|
||||
}
|
||||
export type ConfigurableAprilTagPipelineSettings = Partial<Omit<AprilTagPipelineSettings, "pipelineType" | "hammingDist" | "debug">> & ConfigurablePipelineSettings
|
||||
export type ConfigurableAprilTagPipelineSettings = Partial<
|
||||
Omit<AprilTagPipelineSettings, "pipelineType" | "hammingDist" | "debug">
|
||||
> &
|
||||
ConfigurablePipelineSettings;
|
||||
export const DefaultAprilTagPipelineSettings: AprilTagPipelineSettings = {
|
||||
...DefaultPipelineSettings,
|
||||
cameraGain: 75,
|
||||
targetModel: TargetModel.Apriltag6in_16h5,
|
||||
ledMode: false,
|
||||
outputShowMultipleTargets: true,
|
||||
cameraExposure: 20,
|
||||
pipelineType: PipelineType.AprilTag,
|
||||
...DefaultPipelineSettings,
|
||||
cameraGain: 75,
|
||||
targetModel: TargetModel.Apriltag6in_16h5,
|
||||
ledMode: false,
|
||||
outputShowMultipleTargets: true,
|
||||
cameraExposure: 20,
|
||||
pipelineType: PipelineType.AprilTag,
|
||||
|
||||
hammingDist: 0,
|
||||
numIterations: 40,
|
||||
decimate: 1,
|
||||
blur: 0,
|
||||
decisionMargin: 35,
|
||||
refineEdges: true,
|
||||
debug: false,
|
||||
threads: 4,
|
||||
tagFamily: AprilTagFamily.Family16h5
|
||||
hammingDist: 0,
|
||||
numIterations: 40,
|
||||
decimate: 1,
|
||||
blur: 0,
|
||||
decisionMargin: 35,
|
||||
refineEdges: true,
|
||||
debug: false,
|
||||
threads: 4,
|
||||
tagFamily: AprilTagFamily.Family16h5
|
||||
};
|
||||
|
||||
export interface ArucoPipelineSettings extends PipelineSettings {
|
||||
pipelineType: PipelineType.Aruco
|
||||
decimate: number
|
||||
threads: number
|
||||
numIterations: number
|
||||
cornerAccuracy: number
|
||||
useAruco3: boolean
|
||||
pipelineType: PipelineType.Aruco;
|
||||
decimate: number;
|
||||
threads: number;
|
||||
numIterations: number;
|
||||
cornerAccuracy: number;
|
||||
useAruco3: boolean;
|
||||
}
|
||||
export type ConfigurableArucoPipelineSettings = Partial<Omit<ArucoPipelineSettings, "pipelineType">> & ConfigurablePipelineSettings
|
||||
export type ConfigurableArucoPipelineSettings = Partial<Omit<ArucoPipelineSettings, "pipelineType">> &
|
||||
ConfigurablePipelineSettings;
|
||||
export const DefaultArucoPipelineSettings: ArucoPipelineSettings = {
|
||||
...DefaultPipelineSettings,
|
||||
outputShowMultipleTargets: true,
|
||||
targetModel: TargetModel.Aruco6in_16h5,
|
||||
cameraExposure: -1,
|
||||
cameraAutoExposure: true,
|
||||
ledMode: false,
|
||||
pipelineType: PipelineType.Aruco,
|
||||
...DefaultPipelineSettings,
|
||||
outputShowMultipleTargets: true,
|
||||
targetModel: TargetModel.Aruco6in_16h5,
|
||||
cameraExposure: -1,
|
||||
cameraAutoExposure: true,
|
||||
ledMode: false,
|
||||
pipelineType: PipelineType.Aruco,
|
||||
|
||||
decimate: 1,
|
||||
threads: 2,
|
||||
numIterations: 100,
|
||||
cornerAccuracy: 25,
|
||||
useAruco3: true
|
||||
decimate: 1,
|
||||
threads: 2,
|
||||
numIterations: 100,
|
||||
cornerAccuracy: 25,
|
||||
useAruco3: true
|
||||
};
|
||||
|
||||
export type ActivePipelineSettings = ReflectivePipelineSettings | ColoredShapePipelineSettings | AprilTagPipelineSettings | ArucoPipelineSettings
|
||||
export type ActiveConfigurablePipelineSettings = ConfigurableReflectivePipelineSettings | ConfigurableColoredShapePipelineSettings | ConfigurableAprilTagPipelineSettings | ConfigurableArucoPipelineSettings
|
||||
export type ActivePipelineSettings =
|
||||
| ReflectivePipelineSettings
|
||||
| ColoredShapePipelineSettings
|
||||
| AprilTagPipelineSettings
|
||||
| ArucoPipelineSettings;
|
||||
export type ActiveConfigurablePipelineSettings =
|
||||
| ConfigurableReflectivePipelineSettings
|
||||
| ConfigurableColoredShapePipelineSettings
|
||||
| ConfigurableAprilTagPipelineSettings
|
||||
| ConfigurableArucoPipelineSettings;
|
||||
|
||||
@@ -1,152 +1,152 @@
|
||||
import { type ActivePipelineSettings, DefaultAprilTagPipelineSettings } from "@/types/PipelineTypes";
|
||||
|
||||
export interface GeneralSettings {
|
||||
version?: string
|
||||
gpuAcceleration?: string
|
||||
hardwareModel?: string
|
||||
hardwarePlatform?: string
|
||||
version?: string;
|
||||
gpuAcceleration?: string;
|
||||
hardwareModel?: string;
|
||||
hardwarePlatform?: string;
|
||||
}
|
||||
|
||||
export interface MetricData {
|
||||
cpuTemp?: string,
|
||||
cpuUtil?: string,
|
||||
cpuMem?: string,
|
||||
gpuMem?: string,
|
||||
ramUtil?: string
|
||||
gpuMemUtil?: string,
|
||||
cpuThr?: string,
|
||||
cpuUptime?: string,
|
||||
diskUtilPct?: string,
|
||||
cpuTemp?: string;
|
||||
cpuUtil?: string;
|
||||
cpuMem?: string;
|
||||
gpuMem?: string;
|
||||
ramUtil?: string;
|
||||
gpuMemUtil?: string;
|
||||
cpuThr?: string;
|
||||
cpuUptime?: string;
|
||||
diskUtilPct?: string;
|
||||
}
|
||||
|
||||
export enum NetworkConnectionType {
|
||||
DHCP = 0,
|
||||
Static = 1
|
||||
DHCP = 0,
|
||||
Static = 1
|
||||
}
|
||||
|
||||
export interface NetworkSettings {
|
||||
ntServerAddress: string
|
||||
connectionType: NetworkConnectionType,
|
||||
staticIp: string,
|
||||
hostname: string,
|
||||
runNTServer: boolean
|
||||
shouldManage: boolean,
|
||||
networkManagerIface?: string,
|
||||
physicalInterface?: string,
|
||||
setStaticCommand?: string,
|
||||
setDHCPcommand?: string
|
||||
ntServerAddress: string;
|
||||
connectionType: NetworkConnectionType;
|
||||
staticIp: string;
|
||||
hostname: string;
|
||||
runNTServer: boolean;
|
||||
shouldManage: boolean;
|
||||
networkManagerIface?: string;
|
||||
physicalInterface?: string;
|
||||
setStaticCommand?: string;
|
||||
setDHCPcommand?: string;
|
||||
}
|
||||
|
||||
export interface LightingSettings {
|
||||
supported: boolean,
|
||||
brightness: number
|
||||
supported: boolean;
|
||||
brightness: number;
|
||||
}
|
||||
|
||||
export enum LogLevel {
|
||||
ERROR=0,
|
||||
WARN=1,
|
||||
INFO=2,
|
||||
DEBUG=3,
|
||||
TRACE=4
|
||||
ERROR = 0,
|
||||
WARN = 1,
|
||||
INFO = 2,
|
||||
DEBUG = 3,
|
||||
TRACE = 4
|
||||
}
|
||||
|
||||
export interface LogMessage {
|
||||
level: LogLevel,
|
||||
message: string
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface Resolution {
|
||||
width: number,
|
||||
height: number
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface VideoFormat {
|
||||
resolution: Resolution
|
||||
fps: number,
|
||||
pixelFormat: string,
|
||||
index?: number,
|
||||
diagonalFOV?: number,
|
||||
horizontalFOV?: number,
|
||||
verticalFOV?: number,
|
||||
standardDeviation?: number,
|
||||
mean?: number
|
||||
resolution: Resolution;
|
||||
fps: number;
|
||||
pixelFormat: string;
|
||||
index?: number;
|
||||
diagonalFOV?: number;
|
||||
horizontalFOV?: number;
|
||||
verticalFOV?: number;
|
||||
standardDeviation?: number;
|
||||
mean?: number;
|
||||
}
|
||||
|
||||
export interface CameraCalibrationResult {
|
||||
resolution: Resolution
|
||||
distCoeffs: number[],
|
||||
standardDeviation: number,
|
||||
perViewErrors: number[],
|
||||
intrinsics: number[],
|
||||
resolution: Resolution;
|
||||
distCoeffs: number[];
|
||||
standardDeviation: number;
|
||||
perViewErrors: number[];
|
||||
intrinsics: number[];
|
||||
}
|
||||
|
||||
export interface ConfigurableCameraSettings {
|
||||
fov: number
|
||||
fov: number;
|
||||
}
|
||||
|
||||
export interface CameraSettings {
|
||||
nickname: string
|
||||
nickname: string;
|
||||
|
||||
fov: {
|
||||
value: number,
|
||||
managedByVendor: boolean
|
||||
}
|
||||
stream: {
|
||||
inputPort: number,
|
||||
outputPort: number
|
||||
}
|
||||
fov: {
|
||||
value: number;
|
||||
managedByVendor: boolean;
|
||||
};
|
||||
stream: {
|
||||
inputPort: number;
|
||||
outputPort: number;
|
||||
};
|
||||
|
||||
validVideoFormats: VideoFormat[]
|
||||
completeCalibrations: CameraCalibrationResult[]
|
||||
validVideoFormats: VideoFormat[];
|
||||
completeCalibrations: CameraCalibrationResult[];
|
||||
|
||||
lastPipelineIndex?: number,
|
||||
currentPipelineIndex: number,
|
||||
pipelineNicknames: string[],
|
||||
pipelineSettings: ActivePipelineSettings
|
||||
lastPipelineIndex?: number;
|
||||
currentPipelineIndex: number;
|
||||
pipelineNicknames: string[];
|
||||
pipelineSettings: ActivePipelineSettings;
|
||||
}
|
||||
|
||||
export const PlaceholderCameraSettings: CameraSettings = {
|
||||
nickname: "Placeholder Camera",
|
||||
fov: {
|
||||
value: 70,
|
||||
managedByVendor: true
|
||||
nickname: "Placeholder Camera",
|
||||
fov: {
|
||||
value: 70,
|
||||
managedByVendor: true
|
||||
},
|
||||
stream: {
|
||||
inputPort: 0,
|
||||
outputPort: 0
|
||||
},
|
||||
validVideoFormats: [
|
||||
{
|
||||
resolution: { width: 1920, height: 1080 },
|
||||
fps: 60,
|
||||
pixelFormat: "RGB"
|
||||
},
|
||||
stream: {
|
||||
inputPort: 0,
|
||||
outputPort: 0
|
||||
{
|
||||
resolution: { width: 1280, height: 720 },
|
||||
fps: 60,
|
||||
pixelFormat: "RGB"
|
||||
},
|
||||
validVideoFormats: [
|
||||
{
|
||||
resolution: { width: 1920, height: 1080 },
|
||||
fps: 60,
|
||||
pixelFormat: "RGB"
|
||||
},
|
||||
{
|
||||
resolution: { width: 1280, height: 720 },
|
||||
fps: 60,
|
||||
pixelFormat: "RGB"
|
||||
},
|
||||
{
|
||||
resolution: { width: 640, height: 480 },
|
||||
fps: 30,
|
||||
pixelFormat: "RGB"
|
||||
}
|
||||
],
|
||||
completeCalibrations: [],
|
||||
pipelineNicknames: ["Placeholder Pipeline"],
|
||||
lastPipelineIndex: 0,
|
||||
currentPipelineIndex: 0,
|
||||
pipelineSettings: DefaultAprilTagPipelineSettings
|
||||
{
|
||||
resolution: { width: 640, height: 480 },
|
||||
fps: 30,
|
||||
pixelFormat: "RGB"
|
||||
}
|
||||
],
|
||||
completeCalibrations: [],
|
||||
pipelineNicknames: ["Placeholder Pipeline"],
|
||||
lastPipelineIndex: 0,
|
||||
currentPipelineIndex: 0,
|
||||
pipelineSettings: DefaultAprilTagPipelineSettings
|
||||
};
|
||||
|
||||
export enum CalibrationBoardTypes {
|
||||
Chessboard=0,
|
||||
DotBoard=1
|
||||
Chessboard = 0,
|
||||
DotBoard = 1
|
||||
}
|
||||
|
||||
export enum RobotOffsetType {
|
||||
Clear=0,
|
||||
Single=1,
|
||||
DualFirst=2,
|
||||
DualSecond=3
|
||||
Clear = 0,
|
||||
Single = 1,
|
||||
DualFirst = 2,
|
||||
DualSecond = 3
|
||||
}
|
||||
|
||||
@@ -3,116 +3,122 @@ import type { ActivePipelineSettings } from "@/types/PipelineTypes";
|
||||
import type { LogLevel } from "@/types/SettingTypes";
|
||||
|
||||
export interface WebsocketLogMessage {
|
||||
logMessage: {
|
||||
logLevel: LogLevel,
|
||||
logMessage: string
|
||||
}
|
||||
logMessage: {
|
||||
logLevel: LogLevel;
|
||||
logMessage: string;
|
||||
};
|
||||
}
|
||||
export interface WebsocketSettingsUpdate {
|
||||
general: Required<GeneralSettings>,
|
||||
lighting: Required<LightingSettings>,
|
||||
networkSettings: NetworkSettings
|
||||
general: Required<GeneralSettings>;
|
||||
lighting: Required<LightingSettings>;
|
||||
networkSettings: NetworkSettings;
|
||||
}
|
||||
|
||||
export interface WebsocketNumberPair {
|
||||
first: number,
|
||||
second: number
|
||||
first: number;
|
||||
second: number;
|
||||
}
|
||||
|
||||
export interface WebsocketCompleteCalib {
|
||||
distCoeffs: number[],
|
||||
height: number,
|
||||
width: number,
|
||||
standardDeviation: number,
|
||||
perViewErrors: number[],
|
||||
intrinsics: number[]
|
||||
distCoeffs: number[];
|
||||
height: number;
|
||||
width: number;
|
||||
standardDeviation: number;
|
||||
perViewErrors: number[];
|
||||
intrinsics: number[];
|
||||
}
|
||||
|
||||
export type WebsocketVideoFormat = Record<number, {
|
||||
fps: number,
|
||||
height: number,
|
||||
width: number,
|
||||
pixelFormat: string,
|
||||
index?: number,
|
||||
diagonalFOV?: number,
|
||||
horizontalFOV?: number,
|
||||
verticalFOV?: number,
|
||||
standardDeviation?: number,
|
||||
mean?: number
|
||||
}>
|
||||
export type WebsocketVideoFormat = Record<
|
||||
number,
|
||||
{
|
||||
fps: number;
|
||||
height: number;
|
||||
width: number;
|
||||
pixelFormat: string;
|
||||
index?: number;
|
||||
diagonalFOV?: number;
|
||||
horizontalFOV?: number;
|
||||
verticalFOV?: number;
|
||||
standardDeviation?: number;
|
||||
mean?: number;
|
||||
}
|
||||
>;
|
||||
|
||||
export interface WebsocketCameraSettingsUpdate {
|
||||
calibrations: WebsocketCompleteCalib[],
|
||||
currentPipelineIndex: number,
|
||||
currentPipelineSettings: ActivePipelineSettings,
|
||||
fov: number,
|
||||
inputStreamPort: number,
|
||||
isFovConfigurable: boolean,
|
||||
nickname: string,
|
||||
outputStreamPort: number,
|
||||
pipelineNicknames: string[],
|
||||
videoFormatList: WebsocketVideoFormat
|
||||
calibrations: WebsocketCompleteCalib[];
|
||||
currentPipelineIndex: number;
|
||||
currentPipelineSettings: ActivePipelineSettings;
|
||||
fov: number;
|
||||
inputStreamPort: number;
|
||||
isFovConfigurable: boolean;
|
||||
nickname: string;
|
||||
outputStreamPort: number;
|
||||
pipelineNicknames: string[];
|
||||
videoFormatList: WebsocketVideoFormat;
|
||||
}
|
||||
export interface WebsocketNTUpdate {
|
||||
connected: boolean,
|
||||
address?: string,
|
||||
clients?: number
|
||||
connected: boolean;
|
||||
address?: string;
|
||||
clients?: number;
|
||||
}
|
||||
export type WebsocketPipelineResultUpdate = Record<number, {
|
||||
fps: number,
|
||||
latency: number,
|
||||
export type WebsocketPipelineResultUpdate = Record<
|
||||
number,
|
||||
{
|
||||
fps: number;
|
||||
latency: number;
|
||||
targets: {
|
||||
yaw: number,
|
||||
pitch: number,
|
||||
skew: number,
|
||||
area: number,
|
||||
ambiguity: number,
|
||||
fiducialId: number,
|
||||
pose: {
|
||||
"angle_z": number,
|
||||
"qw": number,
|
||||
"qx": number,
|
||||
"x": number,
|
||||
"qy": number,
|
||||
"y": number,
|
||||
"qz": number,
|
||||
"z": number
|
||||
},
|
||||
}[]
|
||||
}>
|
||||
yaw: number;
|
||||
pitch: number;
|
||||
skew: number;
|
||||
area: number;
|
||||
ambiguity: number;
|
||||
fiducialId: number;
|
||||
pose: {
|
||||
angle_z: number;
|
||||
qw: number;
|
||||
qx: number;
|
||||
x: number;
|
||||
qy: number;
|
||||
y: number;
|
||||
qz: number;
|
||||
z: number;
|
||||
};
|
||||
}[];
|
||||
}
|
||||
>;
|
||||
|
||||
export interface WebsocketCalibrationData {
|
||||
"patternWidth": number,
|
||||
"boardType": number,
|
||||
"hasEnough": boolean,
|
||||
"count": number,
|
||||
"minCount": number,
|
||||
"videoModeIndex": number,
|
||||
"patternHeight": number,
|
||||
"squareSizeIn": number
|
||||
patternWidth: number;
|
||||
boardType: number;
|
||||
hasEnough: boolean;
|
||||
count: number;
|
||||
minCount: number;
|
||||
videoModeIndex: number;
|
||||
patternHeight: number;
|
||||
squareSizeIn: number;
|
||||
}
|
||||
|
||||
export interface IncomingWebsocketData {
|
||||
log?: WebsocketLogMessage,
|
||||
settings?: WebsocketSettingsUpdate,
|
||||
cameraSettings?: WebsocketCameraSettingsUpdate[],
|
||||
ntConnectionInfo?: WebsocketNTUpdate,
|
||||
metrics?: Required<MetricData>,
|
||||
updatePipelineResult?: WebsocketPipelineResultUpdate,
|
||||
networkInfo?: {
|
||||
possibleRios: string[],
|
||||
deviceips: string[]
|
||||
}
|
||||
mutatePipelineSettings?: Partial<ActivePipelineSettings>,
|
||||
cameraIndex?: number // Sent when mutating pipeline settings to check against currently active
|
||||
calibrationData?: WebsocketCalibrationData,
|
||||
log?: WebsocketLogMessage;
|
||||
settings?: WebsocketSettingsUpdate;
|
||||
cameraSettings?: WebsocketCameraSettingsUpdate[];
|
||||
ntConnectionInfo?: WebsocketNTUpdate;
|
||||
metrics?: Required<MetricData>;
|
||||
updatePipelineResult?: WebsocketPipelineResultUpdate;
|
||||
networkInfo?: {
|
||||
possibleRios: string[];
|
||||
deviceips: string[];
|
||||
};
|
||||
mutatePipelineSettings?: Partial<ActivePipelineSettings>;
|
||||
cameraIndex?: number; // Sent when mutating pipeline settings to check against currently active
|
||||
calibrationData?: WebsocketCalibrationData;
|
||||
}
|
||||
|
||||
export enum WebsocketPipelineType {
|
||||
Calib3d=-2,
|
||||
DriverMode=-1,
|
||||
Reflective=0,
|
||||
ColoredShape=1,
|
||||
AprilTag=2,
|
||||
Aruco=3
|
||||
Calib3d = -2,
|
||||
DriverMode = -1,
|
||||
Reflective = 0,
|
||||
ColoredShape = 1,
|
||||
AprilTag = 2,
|
||||
Aruco = 3
|
||||
}
|
||||
|
||||
@@ -9,24 +9,24 @@ import { useStateStore } from "@/stores/StateStore";
|
||||
const cameraViewType = computed<number[]>({
|
||||
get: (): number[] => {
|
||||
// Only show the input stream in Color Picking Mode
|
||||
if(useStateStore().colorPickingMode) return [0];
|
||||
if (useStateStore().colorPickingMode) return [0];
|
||||
|
||||
// Only show the output stream in Driver Mode or Calibration Mode
|
||||
if(useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode) return [1];
|
||||
if (useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode) return [1];
|
||||
|
||||
const ret: number[] = [];
|
||||
if(useCameraSettingsStore().currentPipelineSettings.inputShouldShow) {
|
||||
if (useCameraSettingsStore().currentPipelineSettings.inputShouldShow) {
|
||||
ret.push(0);
|
||||
}
|
||||
if(useCameraSettingsStore().currentPipelineSettings.outputShouldShow) {
|
||||
if (useCameraSettingsStore().currentPipelineSettings.outputShouldShow) {
|
||||
ret.push(1);
|
||||
}
|
||||
|
||||
if(ret.length === 0) return [0];
|
||||
if (ret.length === 0) return [0];
|
||||
|
||||
return ret;
|
||||
},
|
||||
set: v => {
|
||||
set: (v) => {
|
||||
useCameraSettingsStore().currentPipelineSettings.inputShouldShow = v.includes(0);
|
||||
useCameraSettingsStore().currentPipelineSettings.outputShouldShow = v.includes(1);
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting({ inputShouldShow: v.includes(0) }, false);
|
||||
@@ -36,22 +36,12 @@ const cameraViewType = computed<number[]>({
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-row
|
||||
no-gutters
|
||||
class="pa-3"
|
||||
>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="7"
|
||||
>
|
||||
<v-row no-gutters class="pa-3">
|
||||
<v-col cols="12" md="7">
|
||||
<CamerasCard />
|
||||
<CalibrationCard />
|
||||
</v-col>
|
||||
<v-col
|
||||
class="pl-md-3 pt-3 pt-md-0"
|
||||
cols="12"
|
||||
md="5"
|
||||
>
|
||||
<v-col class="pl-md-3 pt-3 pt-md-0" cols="12" md="5">
|
||||
<CamerasView v-model="cameraViewType" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -10,57 +10,42 @@ import { useStateStore } from "@/stores/StateStore";
|
||||
const cameraViewType = computed<number[]>({
|
||||
get: (): number[] => {
|
||||
// Only show the input stream in Color Picking Mode
|
||||
if(useStateStore().colorPickingMode) return [0];
|
||||
if (useStateStore().colorPickingMode) return [0];
|
||||
|
||||
// Only show the output stream in Driver Mode or Calibration Mode
|
||||
if(useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode) return [1];
|
||||
if (useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode) return [1];
|
||||
|
||||
const ret: number[] = [];
|
||||
if(useCameraSettingsStore().currentPipelineSettings.inputShouldShow) {
|
||||
if (useCameraSettingsStore().currentPipelineSettings.inputShouldShow) {
|
||||
ret.push(0);
|
||||
}
|
||||
if(useCameraSettingsStore().currentPipelineSettings.outputShouldShow) {
|
||||
if (useCameraSettingsStore().currentPipelineSettings.outputShouldShow) {
|
||||
ret.push(1);
|
||||
}
|
||||
|
||||
if(ret.length === 0) return [0];
|
||||
if (ret.length === 0) return [0];
|
||||
|
||||
return ret;
|
||||
},
|
||||
set: v => {
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting({
|
||||
inputShouldShow: v.includes(0),
|
||||
outputShouldShow: v.includes(1)
|
||||
}, true);
|
||||
set: (v) => {
|
||||
useCameraSettingsStore().changeCurrentPipelineSetting(
|
||||
{
|
||||
inputShouldShow: v.includes(0),
|
||||
outputShouldShow: v.includes(1)
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container
|
||||
class="pa-3"
|
||||
fluid
|
||||
>
|
||||
<v-row
|
||||
no-gutters
|
||||
align="center"
|
||||
justify="center"
|
||||
>
|
||||
<v-col
|
||||
cols="12"
|
||||
class="pb-3 pr-lg-3"
|
||||
lg="8"
|
||||
align-self="stretch"
|
||||
>
|
||||
<v-container class="pa-3" fluid>
|
||||
<v-row no-gutters align="center" justify="center">
|
||||
<v-col cols="12" class="pb-3 pr-lg-3" lg="8" align-self="stretch">
|
||||
<CamerasCard v-model="cameraViewType" />
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
class="pb-3"
|
||||
lg="4"
|
||||
style="display: flex; flex-direction: column"
|
||||
align-self="stretch"
|
||||
>
|
||||
<v-col cols="12" class="pb-3" lg="4" style="display: flex; flex-direction: column" align-self="stretch">
|
||||
<CameraAndPipelineSelectCard />
|
||||
<StreamConfigCard v-model="cameraViewType" />
|
||||
</v-col>
|
||||
|
||||
@@ -3,27 +3,16 @@ const devMode = process.env.NODE_ENV === "development";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
style="overflow:hidden; height:100%; width:100%"
|
||||
>
|
||||
<div
|
||||
v-if="devMode"
|
||||
style="width: 100%; height: 100%; padding: 16px"
|
||||
>
|
||||
<div style="overflow: hidden; height: 100%; width: 100%">
|
||||
<div v-if="devMode" style="width: 100%; height: 100%; padding: 16px">
|
||||
<span style="color: white; font-weight: bold">
|
||||
PhotonClient is in development mode so the documentation page will not load.
|
||||
Please recompile in production mode with the documentation copied over after a full build.
|
||||
PhotonClient is in development mode so the documentation page will not load. Please recompile in production mode
|
||||
with the documentation copied over after a full build.
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
style="width: 100%; height: 100%"
|
||||
>
|
||||
<div v-else style="width: 100%; height: 100%">
|
||||
<!--suppress HtmlUnknownTarget -->
|
||||
<iframe
|
||||
src="docs/index.html"
|
||||
style="overflow:hidden; height:100%; width:100%; border: 0"
|
||||
/>
|
||||
<iframe src="docs/index.html" style="overflow: hidden; height: 100%; width: 100%; border: 0" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
<template>
|
||||
<div class="not-found-container">
|
||||
<div>
|
||||
<v-card-title style="color: white; padding-bottom: 36px; font-size: 42px;">
|
||||
<v-card-title style="color: white; padding-bottom: 36px; font-size: 42px">
|
||||
The page you are looking for isn't here
|
||||
</v-card-title>
|
||||
<v-card-subtitle style="color: white; font-size: 18px;">
|
||||
<v-card-subtitle style="color: white; font-size: 18px">
|
||||
Please use the sidebar to find what you are looking for
|
||||
</v-card-subtitle>
|
||||
</div>
|
||||
<img
|
||||
src="@/assets/images/notfound.webp"
|
||||
alt="missing-page"
|
||||
style="margin-top: 64px"
|
||||
>
|
||||
<img src="@/assets/images/notfound.webp" alt="missing-page" style="margin-top: 64px" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ public class NetworkTablesManager {
|
||||
|
||||
subMap.put("connected", ntInstance.isConnected());
|
||||
if (ntInstance.isConnected()) {
|
||||
var connections = getInstance().ntInstance.getConnections();
|
||||
var connections = ntInstance.getConnections();
|
||||
if (connections.length > 0) {
|
||||
subMap.put("address", connections[0].remote_ip + ":" + connections[0].remote_port);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user