Vue 3 Upgrade (#1900)

## Description

Upgrades to Vue 3 and necessary associated dependencies. Also fixes some
issues with the layout and adds validation for object detection models.

Closes #885, closes #1943, closes #1449.
## Meta

Merge checklist:
- [x] Pull Request title is [short, imperative
summary](https://cbea.ms/git-commit/) of proposed changes
- [x] The description documents the _what_ and _why_
- [ ] If this PR changes behavior or adds a feature, user documentation
is updated
- [ ] If this PR touches photon-serde, all messages have been
regenerated and hashes have not changed unexpectedly
- [ ] If this PR touches configuration, this is backwards compatible
with settings back to v2024.3.1
- [ ] If this PR touches pipeline settings or anything related to data
exchange, the frontend typing is updated
- [ ] If this PR addresses a bug, a regression test for it is added

---------

Co-authored-by: Matt M <matthew.morley.ca@gmail.com>
Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
Co-authored-by: samfreund <techguy763@gmail.com>
This commit is contained in:
Graham
2025-05-06 18:21:41 -04:00
committed by GitHub
parent 29f76bc1c3
commit bec8092660
54 changed files with 1661 additions and 1843 deletions

View File

@@ -249,7 +249,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
v-model="useStateStore().currentCameraUniqueName"
label="Camera"
:items="wrappedCameras"
@input="changeCurrentCameraUniqueName"
@update:modelValue="changeCurrentCameraUniqueName"
/>
<pv-input
v-else
@@ -270,7 +270,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
:disabled="checkCameraName(currentCameraName) !== true"
@click="() => saveCameraNameEdit(currentCameraName)"
/>
<pv-icon icon-name="mdi-cancel" color="red darken-2" @click="cancelCameraNameEdit" />
<pv-icon icon-name="mdi-cancel" color="red-darken-2" @click="cancelCameraNameEdit" />
</div>
<pv-icon
v-else
@@ -285,7 +285,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
<v-col cols="10" class="pa-0">
<pv-select
v-if="!isPipelineNameEdit"
:value="useCameraSettingsStore().currentCameraSettings.currentPipelineIndex"
:model-value="useCameraSettingsStore().currentCameraSettings.currentPipelineIndex"
label="Pipeline"
tooltip="Each pipeline runs on a camera output and stores a unique set of processing settings"
:disabled="
@@ -294,7 +294,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
!useCameraSettingsStore().hasConnected
"
:items="pipelineNamesWrapper"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineIndex(args, true)"
@update:modelValue="(args) => useCameraSettingsStore().changeCurrentPipelineIndex(args, true)"
/>
<pv-input
v-else
@@ -314,13 +314,13 @@ const wrappedCameras = computed<SelectItem[]>(() =>
:disabled="checkPipelineName(currentPipelineName) !== true"
@click="() => savePipelineNameEdit(currentPipelineName)"
/>
<pv-icon icon-name="mdi-cancel" color="red darken-2" @click="cancelPipelineNameEdit" />
<pv-icon icon-name="mdi-cancel" color="red-darken-2" @click="cancelPipelineNameEdit" />
</div>
<v-menu v-else-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-menu v-else-if="!useCameraSettingsStore().isDriverMode" offset="7">
<template #activator="{ props }">
<v-icon color="#c5c5c5" v-bind="props" @click="cancelPipelineNameEdit"> mdi-menu </v-icon>
</template>
<v-list dark dense color="primary">
<v-list density="compact" color="primary">
<v-list-item @click="startPipelineNameEdit">
<v-list-item-title>
<pv-icon color="#c5c5c5" :right="true" icon-name="mdi-pencil" tooltip="Edit pipeline name" />
@@ -333,7 +333,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
</v-list-item>
<v-list-item @click="showPipelineDeletionConfirmationDialog = true">
<v-list-item-title>
<pv-icon color="red darken-2" :right="true" icon-name="mdi-delete" tooltip="Delete pipeline" />
<pv-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="duplicateCurrentPipeline">
@@ -365,12 +365,12 @@ const wrappedCameras = computed<SelectItem[]>(() =>
!useCameraSettingsStore().hasConnected
"
:items="pipelineTypesWrapper"
@input="showPipelineTypeChangeDialog = true"
@update:modelValue="showPipelineTypeChangeDialog = true"
/>
</v-col>
</v-row>
<v-dialog v-model="showPipelineCreationDialog" dark persistent width="500">
<v-card dark color="primary">
<v-dialog v-model="showPipelineCreationDialog" persistent width="500">
<v-card color="primary">
<v-card-title> Create New Pipeline </v-card-title>
<v-card-text>
<pv-input
@@ -394,18 +394,18 @@ const wrappedCameras = computed<SelectItem[]>(() =>
<v-spacer />
<v-btn
color="#ffd843"
class="black--text"
:disabled="checkPipelineName(newPipelineName) !== true"
variant="flat"
@click="createNewPipeline"
>
Save
</v-btn>
<v-btn color="error" @click="cancelPipelineCreation"> Cancel </v-btn>
<v-btn color="error" variant="elevated" @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-dialog v-model="showPipelineDeletionConfirmationDialog" width="500">
<v-card color="primary">
<v-card-title> Pipeline Deletion Confirmation </v-card-title>
<v-card-text>
Are you sure you want to delete the pipeline
@@ -417,8 +417,13 @@ const wrappedCameras = computed<SelectItem[]>(() =>
<v-divider />
<v-card-actions>
<v-spacer />
<v-btn color="error" @click="confirmDeleteCurrentPipeline"> Yes, I'm sure </v-btn>
<v-btn color="#ffd843" class="black--text" @click="showPipelineDeletionConfirmationDialog = false">
<v-btn variant="flat" color="error" @click="confirmDeleteCurrentPipeline"> Yes, I'm sure </v-btn>
<v-btn
variant="flat"
color="#ffd843"
class="text-black"
@click="showPipelineDeletionConfirmationDialog = false"
>
No, take me back
</v-btn>
</v-card-actions>
@@ -435,8 +440,10 @@ const wrappedCameras = computed<SelectItem[]>(() =>
<v-divider />
<v-card-actions>
<v-spacer />
<v-btn color="error" @click="confirmChangePipelineType"> Yes, I'm sure </v-btn>
<v-btn color="#ffd843" class="black--text" @click="cancelChangePipelineType"> No, take me back </v-btn>
<v-btn color="error" variant="elevated" @click="confirmChangePipelineType"> Yes, I'm sure </v-btn>
<v-btn color="#ffd843" variant="elevated" class="text-black" @click="cancelChangePipelineType">
No, take me back
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>

View File

@@ -6,10 +6,7 @@ import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
import { PipelineType } from "@/types/PipelineTypes";
import PhotonCameraStream from "@/components/app/photon-camera-stream.vue";
defineProps<{
// TODO fully update v-model usage in custom components on Vue3 update
value: number[];
}>();
const value = defineModel<number[]>();
const driverMode = computed<boolean>({
get: () => useCameraSettingsStore().isDriverMode,
@@ -43,20 +40,21 @@ const performanceRecommendation = computed<string>(() => {
<template>
<v-card color="primary" height="100%" class="d-flex flex-column" dark>
<v-card-title class="justify-space-between align-center pt-3 pb-3">
<v-card-title class="justify-space-between align-center pt-1 pb-1 d-flex">
<span>Cameras</span>
<v-chip
v-if="useCameraSettingsStore().currentCameraSettings.isConnected"
label
:color="fpsTooLow ? 'error' : 'transparent'"
:text-color="fpsTooLow ? '#C7EA46' : '#ff4d00'"
:color="fpsTooLow ? 'error' : ''"
style="font-size: 1rem; padding: 0; margin: 0"
:variant="fpsTooLow ? 'tonal' : 'text'"
:style="{ color: fpsTooLow ? '#C7EA46' : '#ff4d00' }"
>
<span class="pr-1"
>Processing @ {{ Math.round(useStateStore().currentPipelineResults?.fps || 0) }}&nbsp;FPS &ndash;</span
><span>{{ performanceRecommendation }}</span>
</v-chip>
<v-chip v-else label color="transparent" text-color="red" style="font-size: 1rem; padding: 0; margin: 0">
<v-chip v-else label variant="text" color="red" style="font-size: 1rem; padding: 0; margin: 0">
<span class="pr-1"> Camera not connected </span>
</v-chip>
<v-switch
@@ -69,7 +67,7 @@ const performanceRecommendation = computed<string>(() => {
</v-card-title>
<v-divider class="ml-3 mr-3" />
<v-row class="stream-viewer-container pa-3 align-center">
<v-col v-if="value.includes(0)" class="stream-view">
<v-col v-if="value?.includes(0)" class="stream-view">
<photon-camera-stream
id="input-camera-stream"
:camera-settings="useCameraSettingsStore().currentCameraSettings"
@@ -77,7 +75,7 @@ const performanceRecommendation = computed<string>(() => {
style="width: 100%; height: auto"
/>
</v-col>
<v-col v-if="value.includes(1)" class="stream-view">
<v-col v-if="value?.includes(1)" class="stream-view">
<photon-camera-stream
id="output-camera-stream"
:camera-settings="useCameraSettingsStore().currentCameraSettings"

View File

@@ -1,6 +1,6 @@
<script setup lang="ts">
import type { Component } from "vue";
import { computed, getCurrentInstance, onBeforeUpdate, ref } from "vue";
import { computed, ref } from "vue";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { useStateStore } from "@/stores/StateStore";
import InputTab from "@/components/dashboard/tabs/InputTab.vue";
@@ -14,6 +14,7 @@ import TargetsTab from "@/components/dashboard/tabs/TargetsTab.vue";
import PnPTab from "@/components/dashboard/tabs/PnPTab.vue";
import Map3DTab from "@/components/dashboard/tabs/Map3DTab.vue";
import { WebsocketPipelineType } from "@/types/WebsocketDataTypes";
import { useDisplay } from "vuetify/lib/composables/display";
interface ConfigOption {
tabName: string;
@@ -64,15 +65,12 @@ const allTabs = Object.freeze({
});
const selectedTabs = ref([0, 0, 0, 0]);
const getTabGroups = (): ConfigOption[][] => {
const smAndDown = getCurrentInstance()?.proxy.$vuetify.breakpoint.smAndDown || false;
const mdAndDown = getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false;
const lgAndDown = getCurrentInstance()?.proxy.$vuetify.breakpoint.lgAndDown || false;
const xl = getCurrentInstance()?.proxy.$vuetify.breakpoint.xl || false;
const { smAndDown, mdAndDown, lgAndDown, xl } = useDisplay();
if (smAndDown || useCameraSettingsStore().isDriverMode || (mdAndDown && !useStateStore().sidebarFolded)) {
const getTabGroups = (): ConfigOption[][] => {
if (smAndDown.value || useCameraSettingsStore().isDriverMode) {
return [Object.values(allTabs)];
} else if (mdAndDown || !useStateStore().sidebarFolded) {
} else if (mdAndDown.value || !useStateStore().sidebarFolded) {
return [
[
allTabs.inputTab,
@@ -85,7 +83,7 @@ const getTabGroups = (): ConfigOption[][] => {
],
[allTabs.targetsTab, allTabs.pnpTab, allTabs.map3dTab]
];
} else if (lgAndDown) {
} else if (lgAndDown.value) {
return [
[allTabs.inputTab],
[
@@ -98,7 +96,7 @@ const getTabGroups = (): ConfigOption[][] => {
],
[allTabs.targetsTab, allTabs.pnpTab, allTabs.map3dTab]
];
} else if (xl) {
} else if (xl.value) {
return [
[allTabs.inputTab],
[allTabs.thresholdTab],
@@ -135,12 +133,12 @@ const tabGroups = computed<ConfigOption[][]>(() => {
.filter((it) => it.length); // Remove empty tab groups
});
onBeforeUpdate(() => {
const onBeforeTabUpdate = () => {
// Force the current tab to the input tab on driver mode change
if (useCameraSettingsStore().isDriverMode) {
selectedTabs.value[0] = 0;
}
});
};
</script>
<template>
@@ -148,7 +146,7 @@ onBeforeUpdate(() => {
<template v-if="!useCameraSettingsStore().hasConnected">
<v-col cols="12">
<v-card color="error">
<v-card-title class="white--text">
<v-card-title class="text-white">
Camera has not connected. Please check your connection and try again.
</v-card-title>
</v-card>
@@ -158,17 +156,12 @@ onBeforeUpdate(() => {
<v-col
v-for="(tabGroupData, tabGroupIndex) in tabGroups"
:key="tabGroupIndex"
:cols="tabGroupIndex == 1 && useCameraSettingsStore().currentPipelineSettings.doMultiTarget ? 7 : ''"
:class="tabGroupIndex !== tabGroups.length - 1 && 'pr-3'"
@vue:before-update="onBeforeTabUpdate"
>
<v-card color="primary" height="100%" class="pr-4 pl-4">
<v-tabs
v-model="selectedTabs[tabGroupIndex]"
grow
background-color="primary"
dark
height="48"
slider-color="accent"
>
<v-tabs v-model="selectedTabs[tabGroupIndex]" grow bg-color="primary" height="48" slider-color="accent">
<v-tab v-for="(tabConfig, index) in tabGroupData" :key="index">
{{ tabConfig.tabName }}
</v-tab>

View File

@@ -3,19 +3,7 @@ import { computed } from "vue";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { useStateStore } from "@/stores/StateStore";
const props = defineProps<{
// TODO fully update v-model usage in custom components on Vue3 update
value: number[];
}>();
const emit = defineEmits<{
(e: "input", value: number[]): void;
}>();
const localValue = computed({
get: () => props.value,
set: (v) => emit("input", v)
});
const value = defineModel<number[]>();
const processingMode = computed<number>({
get: () => (useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled ? 1 : 0),
@@ -32,14 +20,18 @@ const processingMode = computed<number>({
:disabled="useCameraSettingsStore().isDriverMode || useStateStore().colorPickingMode"
class="mt-3"
color="primary"
style="height: 100%; display: flex; flex-direction: column"
style="flex-grow: 1; display: flex; flex-direction: column"
>
<v-row class="pa-3 pb-0 align-center">
<v-col class="pa-4">
<p style="color: white">Processing Mode</p>
<v-btn-toggle v-model="processingMode" mandatory dark class="fill">
<v-btn color="secondary" :disabled="!useCameraSettingsStore().hasConnected">
<v-icon left>mdi-square-outline</v-icon>
<v-btn-toggle v-model="processingMode" mandatory base-color="surface-variant" class="fill w-100">
<v-btn
color="secondary"
:disabled="!useCameraSettingsStore().hasConnected"
class="w-50"
prepend-icon="mdi-square-outline"
>
<span>2D</span>
</v-btn>
<v-btn
@@ -47,8 +39,9 @@ const processingMode = computed<number>({
:disabled="
!useCameraSettingsStore().hasConnected || !useCameraSettingsStore().isCurrentVideoFormatCalibrated
"
class="w-50"
prepend-icon="mdi-cube-outline"
>
<v-icon left>mdi-cube-outline</v-icon>
<span>3D</span>
</v-btn>
</v-btn-toggle>
@@ -57,13 +50,13 @@ const processingMode = computed<number>({
<v-row class="pa-3 pt-0 align-center">
<v-col class="pa-4 pt-0">
<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 left class="mode-btn-icon">mdi-import</v-icon>
<v-btn-toggle v-model="value" :multiple="true" mandatory base-color="surface-variant" class="fill w-100">
<v-btn color="secondary" class="fill w-50">
<v-icon start class="mode-btn-icon">mdi-import</v-icon>
<span class="mode-btn-label">Raw</span>
</v-btn>
<v-btn color="secondary" class="fill">
<v-icon left class="mode-btn-icon">mdi-export</v-icon>
<v-btn color="secondary" class="fill w-50">
<v-icon start class="mode-btn-icon">mdi-export</v-icon>
<span class="mode-btn-label">Processed</span>
</v-btn>
</v-btn-toggle>
@@ -73,14 +66,8 @@ const processingMode = computed<number>({
</template>
<style scoped>
.v-btn-toggle.fill {
width: 100%;
height: 100%;
}
.v-btn-toggle.fill > .v-btn {
width: 50%;
height: 100%;
.v-btn--disabled {
background-color: #191919 !important;
}
th {

View File

@@ -3,22 +3,20 @@ import { PipelineType } from "@/types/PipelineTypes";
import PvSelect from "@/components/common/pv-select.vue";
import PvSlider from "@/components/common/pv-slider.vue";
import PvSwitch from "@/components/common/pv-switch.vue";
import { computed, getCurrentInstance } from "vue";
import { computed } from "vue";
import { useStateStore } from "@/stores/StateStore";
import type { ActivePipelineSettings } from "@/types/PipelineTypes";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { useDisplay } from "vuetify";
// TODO fix pipeline typing in order to fix this, the store settings call should be able to infer that only valid pipeline type settings are exposed based on pre-checks for the entire config section
// Defer reference to store access method
const currentPipelineSettings = computed<ActivePipelineSettings>(
() => useCameraSettingsStore().currentPipelineSettings
);
const { mdAndDown } = useDisplay();
const interactiveCols = computed(() =>
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
? 8
: 7
mdAndDown.value && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode) ? 8 : 7
);
</script>
@@ -29,7 +27,7 @@ const interactiveCols = computed(() =>
label="Target family"
:items="['AprilTag 36h11 (6.5in)', 'AprilTag 16h5 (6in)']"
:select-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ tagFamily: value }, false)"
@update:modelValue="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ tagFamily: value }, false)"
/>
<pv-slider
v-model="currentPipelineSettings.decimate"
@@ -38,7 +36,7 @@ const interactiveCols = computed(() =>
tooltip="Increases FPS at the expense of range by reducing image resolution initially"
:min="1"
:max="8"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ decimate: value }, false)"
@update:modelValue="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ decimate: value }, false)"
/>
<pv-slider
v-model="currentPipelineSettings.blur"
@@ -48,7 +46,7 @@ const interactiveCols = computed(() =>
:min="0"
:max="5"
:step="0.1"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ blur: value }, false)"
@update:modelValue="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ blur: value }, false)"
/>
<pv-slider
v-model="currentPipelineSettings.threads"
@@ -57,7 +55,7 @@ const interactiveCols = computed(() =>
tooltip="Number of threads spawned by the AprilTag detector"
:min="1"
:max="8"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ threads: value }, false)"
@update:modelValue="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ threads: value }, false)"
/>
<pv-slider
v-model="currentPipelineSettings.decisionMargin"
@@ -66,7 +64,9 @@ const interactiveCols = computed(() =>
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)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ decisionMargin: value }, false)
"
/>
<pv-slider
v-model="currentPipelineSettings.numIterations"
@@ -75,14 +75,18 @@ const interactiveCols = computed(() =>
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)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ numIterations: value }, false)
"
/>
<pv-switch
v-model="currentPipelineSettings.refineEdges"
:switch-cols="interactiveCols"
label="Refine Edges"
tooltip="Further refines the AprilTag corner position initial estimate, suggested left on"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ refineEdges: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ refineEdges: value }, false)
"
/>
</div>
</template>

View File

@@ -5,20 +5,18 @@ import PvSlider from "@/components/common/pv-slider.vue";
import PvSwitch from "@/components/common/pv-switch.vue";
import PvRangeSlider from "@/components/common/pv-range-slider.vue";
import PvSelect from "@/components/common/pv-select.vue";
import { computed, getCurrentInstance } from "vue";
import { computed } from "vue";
import { useStateStore } from "@/stores/StateStore";
import { useDisplay } from "vuetify";
// TODO fix pipeline typing in order to fix this, the store settings call should be able to infer that only valid pipeline type settings are exposed based on pre-checks for the entire config section
// Defer reference to store access method
const currentPipelineSettings = computed<ActivePipelineSettings>(
() => useCameraSettingsStore().currentPipelineSettings
);
const { mdAndDown } = useDisplay();
const interactiveCols = computed(() =>
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
? 8
: 7
mdAndDown.value && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode) ? 8 : 7
);
</script>
@@ -29,7 +27,7 @@ const interactiveCols = computed(() =>
label="Target family"
:items="['AprilTag Family 36h11', 'AprilTag Family 16h5']"
:select-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ tagFamily: value }, false)"
@update:modelValue="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ tagFamily: value }, false)"
/>
<pv-range-slider
v-model="currentPipelineSettings.threshWinSizes"
@@ -39,7 +37,9 @@ const interactiveCols = computed(() =>
:max="255"
:slider-cols="interactiveCols"
:step="2"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ threshWinSizes: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ threshWinSizes: value }, false)
"
/>
<pv-slider
v-model="currentPipelineSettings.threshStepSize"
@@ -49,7 +49,9 @@ const interactiveCols = computed(() =>
:min="2"
:max="128"
:step="1"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ threshStepSize: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ threshStepSize: value }, false)
"
/>
<pv-slider
v-model="currentPipelineSettings.threshConstant"
@@ -59,21 +61,27 @@ const interactiveCols = computed(() =>
:min="0"
:max="128"
:step="1"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ threshConstant: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ threshConstant: value }, false)
"
/>
<pv-switch
v-model="currentPipelineSettings.useCornerRefinement"
label="Refine Corners"
tooltip="Further refine the initial corners with subpixel accuracy."
:switch-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ useCornerRefinement: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ useCornerRefinement: value }, false)
"
/>
<pv-switch
v-model="currentPipelineSettings.debugThreshold"
label="Debug Threshold"
tooltip="Display the first threshold step to the color stream."
:switch-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ debugThreshold: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ debugThreshold: value }, false)
"
/>
</div>
</template>

View File

@@ -4,8 +4,9 @@ import { type ActivePipelineSettings, PipelineType } from "@/types/PipelineTypes
import PvRangeSlider from "@/components/common/pv-range-slider.vue";
import PvSelect from "@/components/common/pv-select.vue";
import PvSlider from "@/components/common/pv-slider.vue";
import { computed, getCurrentInstance } from "vue";
import { computed } from "vue";
import { useStateStore } from "@/stores/StateStore";
import { useDisplay } from "vuetify";
// TODO fix pipeline typing in order to fix this, the store settings call should be able to infer that only valid pipeline type settings are exposed based on pre-checks for the entire config section
// Defer reference to store access method
@@ -48,12 +49,9 @@ const contourRadius = computed<[number, number]>({
}
}
});
const { mdAndDown } = useDisplay();
const interactiveCols = computed(() =>
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
? 8
: 7
mdAndDown.value && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode) ? 8 : 7
);
</script>
@@ -65,7 +63,7 @@ const interactiveCols = computed(() =>
tooltip="Used to determine how to calculate target landmarks, as well as aspect ratio"
:items="['Portrait', 'Landscape']"
:select-cols="interactiveCols"
@input="
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOrientation: value }, false)
"
/>
@@ -75,7 +73,9 @@ const interactiveCols = computed(() =>
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)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourSortMode: value }, false)
"
/>
<pv-range-slider
v-model="contourArea"
@@ -84,7 +84,9 @@ const interactiveCols = computed(() =>
:max="100"
:slider-cols="interactiveCols"
:step="0.01"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourArea: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourArea: value }, false)
"
/>
<pv-range-slider
v-if="useCameraSettingsStore().currentPipelineType !== PipelineType.ColoredShape"
@@ -95,7 +97,9 @@ const interactiveCols = computed(() =>
:max="100"
:slider-cols="interactiveCols"
:step="0.1"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourRatio: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourRatio: value }, false)
"
/>
<pv-range-slider
v-if="useCameraSettingsStore().currentPipelineType === PipelineType.ColoredShape"
@@ -105,7 +109,9 @@ const interactiveCols = computed(() =>
:min="0"
:max="100"
:slider-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourFullness: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourFullness: value }, false)
"
/>
<pv-range-slider
v-if="currentPipelineSettings.pipelineType === PipelineType.ColoredShape"
@@ -115,7 +121,9 @@ const interactiveCols = computed(() =>
:min="0"
:max="4000"
:slider-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourPerimeter: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourPerimeter: value }, false)
"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.contourSpecklePercentage"
@@ -124,7 +132,7 @@ const interactiveCols = computed(() =>
:min="0"
:max="100"
:slider-cols="interactiveCols"
@input="
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourSpecklePercentage: value }, false)
"
/>
@@ -137,7 +145,9 @@ const interactiveCols = computed(() =>
:max="4"
:step="0.1"
:slider-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourFilterRangeX: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourFilterRangeX: value }, false)
"
/>
<pv-slider
v-model="currentPipelineSettings.contourFilterRangeY"
@@ -147,7 +157,9 @@ const interactiveCols = computed(() =>
:max="4"
:step="0.1"
:slider-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourFilterRangeY: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourFilterRangeY: value }, false)
"
/>
<pv-select
v-model="useCameraSettingsStore().currentPipelineSettings.contourGroupingMode"
@@ -155,7 +167,9 @@ const interactiveCols = computed(() =>
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)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourGroupingMode: value }, false)
"
/>
<pv-select
v-model="useCameraSettingsStore().currentPipelineSettings.contourIntersection"
@@ -164,7 +178,9 @@ const interactiveCols = computed(() =>
:select-cols="interactiveCols"
:items="['None', 'Up', 'Down', 'Left', 'Right']"
:disabled="useCameraSettingsStore().currentPipelineSettings.contourGroupingMode === 0"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourIntersection: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourIntersection: value }, false)
"
/>
</template>
<template v-else-if="currentPipelineSettings.pipelineType === PipelineType.ColoredShape">
@@ -174,7 +190,9 @@ const interactiveCols = computed(() =>
tooltip="The shape of targets to look for"
:select-cols="interactiveCols"
:items="['Circle', 'Polygon', 'Triangle', 'Quadrilateral']"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourShape: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourShape: value }, false)
"
/>
<pv-slider
v-if="currentPipelineSettings.contourShape >= 1"
@@ -185,7 +203,9 @@ const interactiveCols = computed(() =>
:min="0"
:max="100"
:slider-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ accuracyPercentage: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ accuracyPercentage: value }, false)
"
/>
<pv-slider
v-if="currentPipelineSettings.contourShape === 0"
@@ -196,7 +216,7 @@ const interactiveCols = computed(() =>
:min="1"
:max="100"
:slider-cols="interactiveCols"
@input="
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ circleDetectThreshold: value }, false)
"
/>
@@ -208,7 +228,9 @@ const interactiveCols = computed(() =>
:min="1"
:max="100"
:slider-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ maxCannyThresh: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ maxCannyThresh: value }, false)
"
/>
<pv-slider
v-if="currentPipelineSettings.contourShape === 0"
@@ -218,7 +240,9 @@ const interactiveCols = computed(() =>
:min="1"
:max="100"
:slider-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ circleAccuracy: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ circleAccuracy: value }, false)
"
/>
<pv-range-slider
v-if="currentPipelineSettings.contourShape === 0"
@@ -228,7 +252,9 @@ const interactiveCols = computed(() =>
:min="0"
:max="100"
:slider-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourRadius: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourRadius: value }, false)
"
/>
</template>
</div>

View File

@@ -3,10 +3,11 @@ import PvSlider from "@/components/common/pv-slider.vue";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import PvSwitch from "@/components/common/pv-switch.vue";
import PvSelect from "@/components/common/pv-select.vue";
import { computed, getCurrentInstance } from "vue";
import { computed } from "vue";
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
import { useStateStore } from "@/stores/StateStore";
import { getResolutionString } from "@/lib/PhotonUtils";
import { useDisplay } from "vuetify";
// 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(() =>
@@ -62,12 +63,10 @@ const handleStreamResolutionChange = (value: number) => {
false
);
};
const { mdAndDown } = useDisplay();
const interactiveCols = computed(() =>
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
? 8
: 7
mdAndDown.value && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode) ? 8 : 7
);
</script>
@@ -75,11 +74,12 @@ const interactiveCols = computed(() =>
<div>
<pv-switch
v-model="useCameraSettingsStore().currentPipelineSettings.cameraAutoExposure"
class="pt-2"
label="Auto Exposure"
:switch-cols="interactiveCols"
tooltip="Enables or Disables camera automatic adjustment for current lighting conditions"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraAutoExposure: args }, false)"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraAutoExposure: args }, false)
"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraExposureRaw"
@@ -90,7 +90,9 @@ const interactiveCols = computed(() =>
:max="useCameraSettingsStore().maxExposureRaw"
:slider-cols="interactiveCols"
:step="1"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposureRaw: args }, false)"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraExposureRaw: args }, false)
"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraBrightness"
@@ -98,7 +100,9 @@ const interactiveCols = computed(() =>
:min="0"
:max="100"
:slider-cols="interactiveCols"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBrightness: args }, false)"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBrightness: args }, false)
"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraGain >= 0"
@@ -108,7 +112,7 @@ const interactiveCols = computed(() =>
:min="0"
:max="100"
:slider-cols="interactiveCols"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraGain: args }, false)"
@update:modelValue="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraGain: args }, false)"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraRedGain !== -1"
@@ -118,7 +122,9 @@ const interactiveCols = computed(() =>
: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)"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraRedGain: args }, false)
"
/>
<pv-slider
v-if="useCameraSettingsStore().currentPipelineSettings.cameraBlueGain !== -1"
@@ -128,14 +134,18 @@ const interactiveCols = computed(() =>
: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)"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBlueGain: args }, false)
"
/>
<pv-switch
v-model="useCameraSettingsStore().currentPipelineSettings.cameraAutoWhiteBalance"
label="Auto White Balance"
:switch-cols="interactiveCols"
tooltip="Enables or Disables camera automatic adjustment for current lighting conditions"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraAutoWhiteBalance: args }, false)"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraAutoWhiteBalance: args }, false)
"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraWhiteBalanceTemp"
@@ -144,7 +154,9 @@ const interactiveCols = computed(() =>
:min="useCameraSettingsStore().minWhiteBalanceTemp"
:max="useCameraSettingsStore().maxWhiteBalanceTemp"
:slider-cols="interactiveCols"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraWhiteBalanceTemp: args }, false)"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraWhiteBalanceTemp: args }, false)
"
/>
<pv-select
v-model="useCameraSettingsStore().currentPipelineSettings.inputImageRotationMode"
@@ -152,7 +164,9 @@ const interactiveCols = computed(() =>
tooltip="Rotates the camera stream. Rotation not available when camera has been calibrated."
:items="cameraRotations"
:select-cols="interactiveCols"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ inputImageRotationMode: args }, false)"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ inputImageRotationMode: args }, false)
"
/>
<pv-select
v-model="useCameraSettingsStore().currentPipelineSettings.cameraVideoModeIndex"
@@ -160,7 +174,7 @@ const interactiveCols = computed(() =>
tooltip="Resolution and FPS the camera should directly capture at"
:items="cameraResolutions"
:select-cols="interactiveCols"
@input="(args) => handleResolutionChange(args)"
@update:modelValue="(args) => handleResolutionChange(args)"
/>
<pv-select
v-model="useCameraSettingsStore().currentPipelineSettings.streamingFrameDivisor"
@@ -168,7 +182,7 @@ const interactiveCols = computed(() =>
tooltip="Resolution to which camera frames are downscaled for streaming to the dashboard"
:items="streamResolutions"
:select-cols="interactiveCols"
@input="(args) => handleStreamResolutionChange(args)"
@update:modelValue="(args) => handleStreamResolutionChange(args)"
/>
<pv-switch
v-if="useCameraSettingsStore().isDriverMode"
@@ -176,7 +190,7 @@ const interactiveCols = computed(() =>
label="Crosshair"
:switch-cols="interactiveCols"
tooltip="Enables or disables a crosshair overlay on the camera stream"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ crosshair: args }, false)"
@update:modelValue="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ crosshair: args }, false)"
/>
</div>
</template>

View File

@@ -11,7 +11,7 @@ const trackedTargets = computed<PhotonTarget[]>(() => useStateStore().currentPip
<div>
<v-row style="width: 100%">
<v-col>
<span class="white--text">Target Visualization</span>
<span class="text-white">Target Visualization</span>
</v-col>
</v-row>
<v-row style="width: 100%">

View File

@@ -2,9 +2,12 @@
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { type ObjectDetectionPipelineSettings, PipelineType } from "@/types/PipelineTypes";
import PvSlider from "@/components/common/pv-slider.vue";
import { computed, getCurrentInstance } from "vue";
import PvSelect from "@/components/common/pv-select.vue";
import PvRangeSlider from "@/components/common/pv-range-slider.vue";
import { computed } from "vue";
import { useStateStore } from "@/stores/StateStore";
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
import { useDisplay } from "vuetify";
// TODO fix pipeline typing in order to fix this, the store settings call should be able to infer that only valid pipeline type settings are exposed based on pre-checks for the entire config section
// Defer reference to store access method
@@ -22,11 +25,10 @@ const contourRatio = computed<[number, number]>({
set: (v) => (useCameraSettingsStore().currentPipelineSettings.contourRatio = v)
});
const { mdAndDown } = useDisplay();
const interactiveCols = computed(() =>
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
? 9
: 8
mdAndDown.value && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode) ? 9 : 8
);
// Filters out models that are not supported by the current backend, and returns a flattened list.
@@ -36,9 +38,12 @@ const supportedModels = computed(() => {
});
const selectedModel = computed({
get: () => supportedModels.value.indexOf(currentPipelineSettings.value.model),
get: () => {
const index = supportedModels.value.indexOf(currentPipelineSettings.value.model);
return index === -1 ? undefined : index;
},
set: (v) => {
useCameraSettingsStore().changeCurrentPipelineSetting({ model: supportedModels.value[v] }, false);
v && useCameraSettingsStore().changeCurrentPipelineSetting({ model: supportedModels.value[v] }, false);
}
});
</script>
@@ -61,7 +66,9 @@ const selectedModel = computed({
:min="0"
:max="1"
:step="0.01"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ confidence: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ confidence: value }, false)
"
/>
<pv-range-slider
v-model="contourArea"
@@ -70,7 +77,9 @@ const selectedModel = computed({
:max="100"
:slider-cols="interactiveCols"
:step="0.01"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourArea: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourArea: value }, false)
"
/>
<pv-range-slider
v-model="contourRatio"
@@ -80,7 +89,9 @@ const selectedModel = computed({
:max="100"
:slider-cols="interactiveCols"
:step="0.01"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourRatio: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourRatio: value }, false)
"
/>
<pv-select
v-model="useCameraSettingsStore().currentPipelineSettings.contourTargetOrientation"
@@ -88,7 +99,7 @@ const selectedModel = computed({
tooltip="Used to determine how to calculate target landmarks, as well as aspect ratio"
:items="['Portrait', 'Landscape']"
:select-cols="interactiveCols"
@input="
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOrientation: value }, false)
"
/>
@@ -98,7 +109,9 @@ const selectedModel = computed({
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)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourSortMode: value }, false)
"
/>
</div>
</template>

View File

@@ -3,9 +3,10 @@ import PvSelect from "@/components/common/pv-select.vue";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { type ActivePipelineSettings, PipelineType, RobotOffsetPointMode } from "@/types/PipelineTypes";
import PvSwitch from "@/components/common/pv-switch.vue";
import { computed, getCurrentInstance } from "vue";
import { computed } from "vue";
import { RobotOffsetType } from "@/types/SettingTypes";
import { useStateStore } from "@/stores/StateStore";
import { useDisplay } from "vuetify";
const isTagPipeline = computed(
() =>
@@ -45,12 +46,10 @@ const offsetPoints = computed<MetricItem[]>(() => {
const currentPipelineSettings = computed<ActivePipelineSettings>(
() => useCameraSettingsStore().currentPipelineSettings
);
const { mdAndDown } = useDisplay();
const interactiveCols = computed(() =>
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
? 8
: 7
mdAndDown.value && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode) ? 8 : 7
);
</script>
@@ -62,7 +61,7 @@ const interactiveCols = computed(() =>
tooltip="If enabled, up to five targets will be displayed and sent via PhotonLib, instead of just one"
:disabled="isTagPipeline"
:switch-cols="interactiveCols"
@input="
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ outputShowMultipleTargets: value }, false)
"
/>
@@ -78,7 +77,9 @@ const interactiveCols = computed(() =>
tooltip="If enabled, all visible fiducial targets will be used to provide a single pose estimate from their combined model."
:switch-cols="interactiveCols"
:disabled="!isTagPipeline"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ doMultiTarget: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ doMultiTarget: value }, false)
"
/>
<pv-switch
v-if="
@@ -92,7 +93,9 @@ const interactiveCols = computed(() =>
tooltip="If disabled, visible fiducial targets used for multi-target estimation will not also be used for single-target estimation."
:switch-cols="interactiveCols"
:disabled="!isTagPipeline || !currentPipelineSettings.doMultiTarget"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ doSingleTargetAlways: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ doSingleTargetAlways: value }, false)
"
/>
<pv-select
v-model="useCameraSettingsStore().currentPipelineSettings.contourTargetOffsetPointEdge"
@@ -100,7 +103,7 @@ const interactiveCols = computed(() =>
tooltip="Changes where the 'center' of the target is (used for calculating e.g. pitch and yaw)"
:items="['Center', 'Top', 'Bottom', 'Left', 'Right']"
:select-cols="interactiveCols"
@input="
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOffsetPointEdge: value }, false)
"
/>
@@ -111,7 +114,7 @@ const interactiveCols = computed(() =>
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="
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ contourTargetOrientation: value }, false)
"
/>
@@ -121,7 +124,9 @@ const interactiveCols = computed(() =>
tooltip="Used to add an arbitrary offset to the location of the targeting crosshair"
:items="['None', 'Single Point', 'Dual Point']"
:select-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ offsetRobotOffsetMode: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ offsetRobotOffsetMode: value }, false)
"
/>
<table
v-if="useCameraSettingsStore().currentPipelineSettings.offsetRobotOffsetMode !== RobotOffsetPointMode.None"
@@ -148,10 +153,10 @@ const interactiveCols = computed(() =>
>
<v-col cols="6" class="pl-0">
<v-btn
small
size="small"
block
color="accent"
class="black--text"
class="text-black"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.Single)"
>
Take Point
@@ -159,9 +164,9 @@ const interactiveCols = computed(() =>
</v-col>
<v-col cols="6" class="pr-0">
<v-btn
small
size="small"
block
color="yellow darken-3"
color="yellow-darken-3"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.Clear)"
>
Clear All Points
@@ -174,10 +179,10 @@ const interactiveCols = computed(() =>
>
<v-col cols="6" lg="4" class="pl-0 pr-2">
<v-btn
small
size="small"
block
color="accent"
class="black--text"
class="text-black"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.DualFirst)"
>
Take First Point
@@ -185,10 +190,10 @@ const interactiveCols = computed(() =>
</v-col>
<v-col cols="6" lg="4" class="pl-2 pr-0 pr-lg-2">
<v-btn
small
size="small"
block
color="accent"
class="black--text"
class="text-black"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.DualSecond)"
>
Take Second Point
@@ -196,9 +201,9 @@ const interactiveCols = computed(() =>
</v-col>
<v-col cols="12" lg="4" class="pl-0 pl-lg-2 pr-0">
<v-btn
small
size="small"
block
color="yellow darken-3"
color="yellow-darken-3"
@click="useCameraSettingsStore().takeRobotOffsetPoint(RobotOffsetType.Clear)"
>
Clear All Points

View File

@@ -3,14 +3,13 @@ import PvSelect from "@/components/common/pv-select.vue";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { TargetModel } from "@/types/PipelineTypes";
import PvSlider from "@/components/common/pv-slider.vue";
import { computed, getCurrentInstance } from "vue";
import { computed } from "vue";
import { useStateStore } from "@/stores/StateStore";
import { useDisplay } from "vuetify";
const { mdAndDown } = useDisplay();
const interactiveCols = computed(() =>
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
? 9
: 8
mdAndDown.value && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode) ? 9 : 8
);
</script>
@@ -30,7 +29,9 @@ const interactiveCols = computed(() =>
{ name: '2025 Algae (16.25in)', value: TargetModel.ReefscapeAlgae }
]"
:select-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ targetModel: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ targetModel: value }, false)
"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cornerDetectionAccuracyPercentage"
@@ -39,7 +40,7 @@ const interactiveCols = computed(() =>
label="Contour simplification Percentage"
:min="0"
:max="100"
@input="
@update:modelValue="
(value) =>
useCameraSettingsStore().changeCurrentPipelineSetting({ cornerDetectionAccuracyPercentage: value }, false)
"

View File

@@ -34,8 +34,8 @@ const resetCurrentBuffer = () => {
<template>
<div>
<v-row align="start" class="pb-4">
<v-simple-table dense class="pt-2 pb-12">
<v-row class="pb-4">
<v-table density="compact" class="pt-2 pb-12 pl-3 pr-3">
<template #default>
<thead>
<tr>
@@ -44,24 +44,24 @@ const resetCurrentBuffer = () => {
currentPipelineSettings.pipelineType === PipelineType.AprilTag ||
currentPipelineSettings.pipelineType === PipelineType.Aruco
"
class="text-center white--text"
class="text-center text-white"
>
Fiducial ID
</th>
<template v-if="currentPipelineSettings.pipelineType === PipelineType.ObjectDetection">
<th class="text-center white--text">Class</th>
<th class="text-center white--text">Confidence</th>
<th class="text-center text-white">Class</th>
<th class="text-center text-white">Confidence</th>
</template>
<template v-if="!useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled">
<th class="text-center white--text">Pitch &theta;&deg;</th>
<th class="text-center white--text">Yaw &theta;&deg;</th>
<th class="text-center white--text">Skew &theta;&deg;</th>
<th class="text-center white--text">Area %</th>
<th class="text-center text-white">Pitch &theta;&deg;</th>
<th class="text-center text-white">Yaw &theta;&deg;</th>
<th class="text-center text-white">Skew &theta;&deg;</th>
<th class="text-center text-white">Area %</th>
</template>
<template v-else>
<th class="text-center white--text">X meters</th>
<th class="text-center white--text">Y meters</th>
<th class="text-center white--text">Z Angle &theta;&deg;</th>
<th class="text-center text-white">X meters</th>
<th class="text-center text-white">Y meters</th>
<th class="text-center text-white">Z Angle &theta;&deg;</th>
</template>
<template
v-if="
@@ -70,7 +70,7 @@ const resetCurrentBuffer = () => {
useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled
"
>
<th class="text-center white--text">Ambiguity Ratio</th>
<th class="text-center text-white">Ambiguity Ratio</th>
</template>
</tr>
</thead>
@@ -78,7 +78,7 @@ const resetCurrentBuffer = () => {
<tr
v-for="(target, index) in useStateStore().currentPipelineResults?.targets"
:key="index"
class="white--text"
class="text-white"
>
<td
v-if="
@@ -91,13 +91,13 @@ const resetCurrentBuffer = () => {
</td>
<td
v-if="currentPipelineSettings.pipelineType === PipelineType.ObjectDetection"
class="text-center white--text"
class="text-center text-white"
>
{{ useStateStore().currentPipelineResults?.classNames[target.classId] }}
</td>
<td
v-if="currentPipelineSettings.pipelineType === PipelineType.ObjectDetection"
class="text-center white--text"
class="text-center text-white"
>
{{ target.confidence.toFixed(2) }}
</td>
@@ -126,7 +126,7 @@ const resetCurrentBuffer = () => {
</tr>
</tbody>
</template>
</v-simple-table>
</v-table>
</v-row>
<v-container
v-if="
@@ -136,122 +136,123 @@ const resetCurrentBuffer = () => {
useCameraSettingsStore().isCurrentVideoFormatCalibrated &&
useCameraSettingsStore().currentPipelineSettings.solvePNPEnabled
"
class="pl-3 pr-3"
>
<v-row class="pb-4 white--text">
<v-row class="pb-4 text-white">
<v-card-subtitle class="ma-0 pa-0 pb-4" style="font-size: 16px"
>Multi-tag pose, field-to-camera</v-card-subtitle
>
<v-simple-table dense>
<v-table density="compact">
<template #default>
<thead>
<tr class="white--text">
<th class="text-center white--text">X meters</th>
<th class="text-center white--text">Y meters</th>
<th class="text-center white--text">Z meters</th>
<th class="text-center white--text">X Angle &theta;&deg;</th>
<th class="text-center white--text">Y Angle &theta;&deg;</th>
<th class="text-center white--text">Z Angle &theta;&deg;</th>
<th class="text-center white--text">Tags</th>
<tr class="text-white">
<th class="text-center text-white">X meters</th>
<th class="text-center text-white">Y meters</th>
<th class="text-center text-white">Z meters</th>
<th class="text-center text-white">X Angle &theta;&deg;</th>
<th class="text-center text-white">Y Angle &theta;&deg;</th>
<th class="text-center text-white">Z Angle &theta;&deg;</th>
<th class="text-center text-white">Tags</th>
</tr>
</thead>
<tbody v-show="useStateStore().currentPipelineResults?.multitagResult">
<tr>
<td class="text-center white--text">
<td class="text-center text-white">
{{ useStateStore().currentPipelineResults?.multitagResult?.bestTransform.x.toFixed(3) }}&nbsp;m
</td>
<td class="text-center white--text">
<td class="text-center text-white">
{{ useStateStore().currentPipelineResults?.multitagResult?.bestTransform.y.toFixed(3) }}&nbsp;m
</td>
<td class="text-center white--text">
<td class="text-center text-white">
{{ useStateStore().currentPipelineResults?.multitagResult?.bestTransform.z.toFixed(3) }}&nbsp;m
</td>
<td class="text-center white--text">
<td class="text-center text-white">
{{
toDeg(useStateStore().currentPipelineResults?.multitagResult?.bestTransform.angle_x || 0).toFixed(
2
)
}}&deg;
</td>
<td class="text-center white--text">
<td class="text-center text-white">
{{
toDeg(useStateStore().currentPipelineResults?.multitagResult?.bestTransform.angle_y || 0).toFixed(
2
)
}}&deg;
</td>
<td class="text-center white--text">
<td class="text-center text-white">
{{
toDeg(useStateStore().currentPipelineResults?.multitagResult?.bestTransform.angle_z || 0).toFixed(
2
)
}}&deg;
</td>
<td class="text-center white--text">
<td class="text-center text-white">
{{ useStateStore().currentPipelineResults?.multitagResult?.fiducialIDsUsed }}
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-table>
</v-row>
<v-row class="pb-4 white--text" style="display: flex; flex-direction: column">
<v-row class="pb-4 text-white" style="display: flex; flex-direction: column">
<v-card-subtitle class="ma-0 pa-0 pb-4 pr-4" style="font-size: 16px"
>Multi-tag pose standard deviation over the last
{{ useStateStore().currentMultitagBuffer?.length || "NaN" }}/100 samples
</v-card-subtitle>
<v-btn color="secondary" class="mb-4 mt-1" style="width: min-content" depressed @click="resetCurrentBuffer"
<v-btn color="secondary" class="mb-4 mt-1" style="width: min-content" variant="flat" @click="resetCurrentBuffer"
>Reset Samples</v-btn
>
<v-simple-table dense>
<v-table density="compact">
<template #default>
<thead>
<tr>
<th class="text-center white--text">X meters</th>
<th class="text-center white--text">Y meters</th>
<th class="text-center white--text">Z meters</th>
<th class="text-center white--text">X Angle &theta;&deg;</th>
<th class="text-center white--text">Y Angle &theta;&deg;</th>
<th class="text-center white--text">Z Angle &theta;&deg;</th>
<th class="text-center text-white">X meters</th>
<th class="text-center text-white">Y meters</th>
<th class="text-center text-white">Z meters</th>
<th class="text-center text-white">X Angle &theta;&deg;</th>
<th class="text-center text-white">Y Angle &theta;&deg;</th>
<th class="text-center text-white">Z Angle &theta;&deg;</th>
</tr>
</thead>
<tbody v-show="useStateStore().currentPipelineResults?.multitagResult">
<tr>
<td class="text-center white--text">
<td class="text-center text-white">
{{
calculateStdDev(useStateStore().currentMultitagBuffer?.map((v) => v.bestTransform.x) || []).toFixed(
5
)
}}&nbsp;m
</td>
<td class="text-center white--text">
<td class="text-center text-white">
{{
calculateStdDev(useStateStore().currentMultitagBuffer?.map((v) => v.bestTransform.y) || []).toFixed(
5
)
}}&nbsp;m
</td>
<td class="text-center white--text">
<td class="text-center text-white">
{{
calculateStdDev(useStateStore().currentMultitagBuffer?.map((v) => v.bestTransform.z) || []).toFixed(
5
)
}}&nbsp;m
</td>
<td class="text-center white--text">
<td class="text-center text-white">
{{
calculateStdDev(
useStateStore().currentMultitagBuffer?.map((v) => toDeg(v.bestTransform.angle_x)) || []
).toFixed(5)
}}&deg;
</td>
<td class="text-center white--text">
<td class="text-center text-white">
{{
calculateStdDev(
useStateStore().currentMultitagBuffer?.map((v) => toDeg(v.bestTransform.angle_y)) || []
).toFixed(5)
}}&deg;
</td>
<td class="text-center white--text">
<td class="text-center text-white">
{{
calculateStdDev(
useStateStore().currentMultitagBuffer?.map((v) => toDeg(v.bestTransform.angle_z)) || []
@@ -261,14 +262,14 @@ const resetCurrentBuffer = () => {
</tr>
</tbody>
</template>
</v-simple-table>
</v-table>
</v-row>
</v-container>
</div>
</template>
<style scoped lang="scss">
.v-data-table {
.v-table {
background-color: #006492 !important;
width: 100%;
font-size: 1rem !important;

View File

@@ -1,10 +1,11 @@
<script setup lang="ts">
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { computed, getCurrentInstance, onBeforeUnmount, onMounted } from "vue";
import { computed, onBeforeUnmount, onMounted } from "vue";
import PvRangeSlider from "@/components/common/pv-range-slider.vue";
import PvSwitch from "@/components/common/pv-switch.vue";
import { useStateStore } from "@/stores/StateStore";
import { ColorPicker, type HSV } from "@/lib/ColorPicker";
import { useDisplay } from "vuetify";
const averageHue = computed<number>(() => {
const isHueInverted = useCameraSettingsStore().currentPipelineSettings.hueInverted;
@@ -123,12 +124,10 @@ onBeforeUnmount(() => {
cameraStream.removeEventListener("click", handleStreamClick);
});
const { mdAndDown } = useDisplay();
const interactiveCols = computed(() =>
(getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndDown || false) &&
(!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode)
? 9
: 8
mdAndDown.value && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode) ? 9 : 8
);
</script>
@@ -144,7 +143,7 @@ const interactiveCols = computed(() =>
:max="180"
:slider-cols="interactiveCols"
:inverted="useCameraSettingsStore().currentPipelineSettings.hueInverted"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ hsvHue: value }, false)"
@update:modelValue="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ hsvHue: value }, false)"
/>
<pv-range-slider
id="sat-slider"
@@ -155,7 +154,9 @@ const interactiveCols = computed(() =>
:min="0"
:max="255"
:slider-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ hsvSaturation: value }, false)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ hsvSaturation: value }, false)
"
/>
<pv-range-slider
id="value-slider"
@@ -166,53 +167,55 @@ const interactiveCols = computed(() =>
:min="0"
:max="255"
:slider-cols="interactiveCols"
@input="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ hsvValue: value }, false)"
@update:modelValue="(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ hsvValue: value }, false)"
/>
<pv-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)"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ hueInverted: value }, false)
"
/>
<div>
<div class="white--text pt-3">Color Picker</div>
<div class="text-white pt-3">Color Picker</div>
<div class="d-flex pt-3">
<template v-if="!useStateStore().colorPickingMode">
<v-col cols="4" class="pl-0 pr-2">
<v-btn
small
size="small"
block
color="accent"
class="black--text"
class="text-black"
@click="enableColorPicking(useCameraSettingsStore().currentPipelineSettings.hueInverted ? 2 : 3)"
>
<v-icon left> mdi-minus </v-icon>
<v-icon start> mdi-minus </v-icon>
Shrink Range
</v-btn>
</v-col>
<v-col cols="4" class="pl-0 pr-0">
<v-btn color="accent" class="black--text" small block @click="enableColorPicking(1)">
<v-icon left> mdi-plus-minus </v-icon>
<v-btn color="accent" class="text-black" size="small" block @click="enableColorPicking(1)">
<v-icon start> mdi-plus-minus </v-icon>
{{ useCameraSettingsStore().currentPipelineSettings.hueInverted ? "Exclude" : "Set to" }} Average
</v-btn>
</v-col>
<v-col cols="4" class="pl-2 pr-0">
<v-btn
small
size="small"
block
color="accent"
class="black--text"
class="text-black"
@click="enableColorPicking(useCameraSettingsStore().currentPipelineSettings.hueInverted ? 3 : 2)"
>
<v-icon left> mdi-plus </v-icon>
<v-icon start> mdi-plus </v-icon>
Expand Range
</v-btn>
</v-col>
</template>
<template v-else>
<v-card-text class="pa-0 pt-3 pb-3">
<v-btn block color="accent" class="black--text" small @click="disableColorPicking"> Cancel </v-btn>
<v-btn block color="accent" class="text-black" size="small" @click="disableColorPicking"> Cancel </v-btn>
</v-card-text>
</template>
</div>
@@ -224,32 +227,32 @@ const interactiveCols = computed(() =>
.threshold-modifiers {
--averageHue: 0;
}
#hue-slider >>> .v-slider {
#hue-slider:deep(.v-slider__container) {
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 {
#sat-slider:deep(.v-slider__container) {
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 {
#value-slider:deep(.v-slider__container) {
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 {
:deep(.v-slider__thumb) {
outline: black solid thin;
}
.normal-slider >>> .v-slider__track-fill {
.normal-slider:deep(.v-slider__track-fill) {
outline: black solid thin;
}
.inverted-slider >>> .v-slider__track-background {
.inverted-slider:deep(.v-slider__track-background) {
outline: black solid thin;
}
</style>