Convert to user selected camera matching (#1556)

This commit is contained in:
oh-yes-0-fps
2025-01-01 03:04:20 -05:00
committed by GitHub
parent b2e70a7257
commit 418eada0b5
67 changed files with 2710 additions and 1948 deletions

View File

@@ -1,20 +1,20 @@
<script setup lang="ts">
import { computed, inject, ref, onBeforeUnmount } from "vue";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { useStateStore } from "@/stores/StateStore";
import loadingImage from "@/assets/images/loading.svg";
import type { StyleValue } from "vue/types/jsx";
import PvIcon from "@/components/common/pv-icon.vue";
import type { UiCameraConfiguration } from "@/types/SettingTypes";
const props = defineProps<{
streamType: "Raw" | "Processed";
id: string;
cameraSettings: UiCameraConfiguration;
}>();
const emptyStreamSrc = "//:0";
const streamSrc = computed<string>(() => {
const port =
useCameraSettingsStore().currentCameraSettings.stream[props.streamType === "Raw" ? "inputPort" : "outputPort"];
const port = props.cameraSettings.stream[props.streamType === "Raw" ? "inputPort" : "outputPort"];
if (!useStateStore().backendConnected || port === 0) {
return emptyStreamSrc;
@@ -32,8 +32,12 @@ const streamStyle = computed<StyleValue>(() => {
});
const containerStyle = computed<StyleValue>(() => {
const resolution = useCameraSettingsStore().currentVideoFormat.resolution;
const rotation = useCameraSettingsStore().currentPipelineSettings.inputImageRotationMode;
if (props.cameraSettings.validVideoFormats.length === 0) {
return { aspectRatio: "1/1" };
}
const resolution =
props.cameraSettings.validVideoFormats[props.cameraSettings.pipelineSettings.cameraVideoModeIndex].resolution;
const rotation = props.cameraSettings.pipelineSettings.inputImageRotationMode;
if (rotation === 1 || rotation === 3) {
return {
aspectRatio: `${resolution.height}/${resolution.width}`
@@ -54,9 +58,9 @@ const overlayStyle = computed<StyleValue>(() => {
const handleCaptureClick = () => {
if (props.streamType === "Raw") {
useCameraSettingsStore().saveInputSnapshot();
props.cameraSettings.pipelineSettings[props.cameraSettings.currentPipelineIndex].saveInputSnapshot();
} else {
useCameraSettingsStore().saveOutputSnapshot();
props.cameraSettings.pipelineSettings[props.cameraSettings.currentPipelineIndex].saveOutputSnapshot();
}
};
const handlePopoutClick = () => {
@@ -69,6 +73,16 @@ const handleFullscreenRequest = () => {
};
const mjpgStream: any = ref(null);
const handleStreamError = () => {
if (streamSrc.value && streamSrc.value !== emptyStreamSrc) {
console.error("Error loading stream:", streamSrc.value, " Trying again.");
setTimeout(() => {
mjpgStream.value.src = streamSrc.value;
}, 100);
}
};
onBeforeUnmount(() => {
if (!mjpgStream.value) return;
mjpgStream.value["src"] = emptyStreamSrc;
@@ -79,7 +93,6 @@ onBeforeUnmount(() => {
<div class="stream-container" :style="containerStyle">
<img :src="loadingImage" class="stream-loading" />
<img
v-show="streamSrc !== emptyStreamSrc"
:id="id"
ref="mjpgStream"
class="stream-video"
@@ -87,6 +100,7 @@ onBeforeUnmount(() => {
:src="streamSrc"
:alt="streamDesc"
:style="streamStyle"
@error="handleStreamError"
/>
<div class="stream-overlay" :style="overlayStyle">
<pv-icon

View File

@@ -2,6 +2,9 @@
import { computed, getCurrentInstance } from "vue";
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
import { useStateStore } from "@/stores/StateStore";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { PlaceholderCameraSettings } from "@/types/SettingTypes";
import { useRoute } from "vue2-helpers/vue-router";
const compact = computed<boolean>({
get: () => {
@@ -14,6 +17,12 @@ const compact = computed<boolean>({
// Vuetify2 doesn't yet support the useDisplay API so this is required to access the prop when using the Composition API
const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.breakpoint.mdAndUp || false);
const needsCamerasConfigured = computed<boolean>(() => {
return (
useCameraSettingsStore().cameras.length === 0 || useCameraSettingsStore().cameras[0] === PlaceholderCameraSettings
);
});
</script>
<template>
@@ -35,14 +44,6 @@ 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-icon>
<v-icon>mdi-camera</v-icon>
</v-list-item-icon>
<v-list-item-content>
<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-icon>
<v-icon>mdi-cog</v-icon>
@@ -51,6 +52,26 @@ 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 ref="camerasTabOpener" link to="/cameras">
<v-list-item-icon>
<v-icon>mdi-camera</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Camera</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item
link
to="/cameraConfigs"
:class="{ cameraicon: needsCamerasConfigured && useRoute().path !== '/cameraConfigs' }"
>
<v-list-item-icon>
<v-icon :class="{ 'red--text': needsCamerasConfigured }">mdi-swap-horizontal-bold</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title :class="{ 'red--text': needsCamerasConfigured }">Camera Matching</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item link to="/docs">
<v-list-item-icon>
<v-icon>mdi-bookshelf</v-icon>
@@ -119,4 +140,18 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
height: 70px;
object-fit: contain;
}
.cameraicon {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%,
100% {
transform: scale(0.95);
}
50% {
transform: scale(1.05);
}
}
</style>

View File

@@ -20,6 +20,7 @@ const settingsValid = ref(true);
const getUniqueVideoFormatsByResolution = (): VideoFormat[] => {
const uniqueResolutions: VideoFormat[] = [];
if (useCameraSettingsStore().currentCameraSettings.validVideoFormats.length === 0) return uniqueResolutions;
useCameraSettingsStore().currentCameraSettings.validVideoFormats.forEach((format) => {
const index = uniqueResolutions.findIndex((v) => resolutionsAreEqual(v.resolution, format.resolution));
const contains = index != -1;
@@ -248,7 +249,7 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
</v-simple-table>
</v-row>
<v-divider />
<v-row style="display: flex; flex-direction: column" class="mt-4">
<v-row v-if="useCameraSettingsStore().isConnected" style="display: flex; flex-direction: column" class="mt-4">
<v-card-subtitle v-show="!isCalibrating" class="pl-3 pa-0 ma-0"> Configure New Calibration</v-card-subtitle>
<v-form ref="form" v-model="settingsValid" class="pl-4 mb-10 pr-5">
<!-- TODO: the default videoFormatIndex is 0, but the list of unique video mode indexes might not include 0. getUniqueVideoResolutionStrings indexing is also different from the normal video mode indexing -->

View File

@@ -57,6 +57,7 @@ const fpsTooLow = computed<boolean>(() => {
</div>
<div>
<v-chip
v-if="useCameraSettingsStore().currentCameraSettings.isConnected"
label
:color="fpsTooLow ? 'error' : 'transparent'"
:text-color="fpsTooLow ? '#C7EA46' : '#ff4d00'"
@@ -67,6 +68,9 @@ const fpsTooLow = computed<boolean>(() => {
{{ Math.min(Math.round(useStateStore().currentPipelineResults?.latency || 0), 9999) }} ms latency
</span>
</v-chip>
<v-chip v-else label color="transparent" text-color="red" style="font-size: 1rem; padding: 0; margin: 0">
<span class="pr-1"> Camera not connected </span>
</v-chip>
</div>
</div>
@@ -86,6 +90,7 @@ const fpsTooLow = computed<boolean>(() => {
<photon-camera-stream
v-if="value.includes(0)"
id="input-camera-stream"
:camera-settings="useCameraSettingsStore().currentCameraSettings"
stream-type="Raw"
style="max-width: 100%"
/>
@@ -94,6 +99,7 @@ const fpsTooLow = computed<boolean>(() => {
<photon-camera-stream
v-if="value.includes(1)"
id="output-camera-stream"
:camera-settings="useCameraSettingsStore().currentCameraSettings"
stream-type="Processed"
style="max-width: 100%"
/>

View File

@@ -0,0 +1,75 @@
<script setup lang="ts">
import { PVCameraInfo } from "@/types/SettingTypes";
const { camera, showTitle } = defineProps({
camera: {
type: PVCameraInfo,
required: true
},
showTitle: {
type: Boolean,
required: false,
default: true
}
});
const cameraInfoFor: any = (camera: PVCameraInfo) => {
if (camera.PVUsbCameraInfo) {
return camera.PVUsbCameraInfo;
}
if (camera.PVCSICameraInfo) {
return camera.PVCSICameraInfo;
}
if (camera.PVFileCameraInfo) {
return camera.PVFileCameraInfo;
}
return {};
};
</script>
<template>
<div>
<div v-if="showTitle === true">
<h3 v-if="camera.PVUsbCameraInfo" class="mb-3">USB Camera Info</h3>
<h3 v-if="camera.PVCSICameraInfo" class="mb-3">CSI Camera Info</h3>
<h3 v-if="camera.PVFileCameraInfo" class="mb-3">File Camera Info</h3>
</div>
<v-simple-table dense :style="{ backgroundColor: 'var(--v-primary-base)' }">
<tbody>
<tr v-if="cameraInfoFor(camera).dev !== undefined && cameraInfoFor(camera).dev !== null">
<td>Device Number:</td>
<td>{{ cameraInfoFor(camera).dev }}</td>
</tr>
<tr v-if="cameraInfoFor(camera).name !== undefined && cameraInfoFor(camera).name !== null">
<td>Name:</td>
<td>{{ cameraInfoFor(camera).name }}</td>
</tr>
<tr v-if="cameraInfoFor(camera).baseName !== undefined && cameraInfoFor(camera).baseName !== null">
<td>Base Name:</td>
<td>{{ cameraInfoFor(camera).baseName }}</td>
</tr>
<tr v-if="cameraInfoFor(camera).vendorId !== undefined && cameraInfoFor(camera).vendorId !== null">
<td>Vendor ID:</td>
<td>{{ cameraInfoFor(camera).vendorId }}</td>
</tr>
<tr v-if="cameraInfoFor(camera).productId !== undefined && cameraInfoFor(camera).productId !== null">
<td>Product ID:</td>
<td>{{ cameraInfoFor(camera).productId }}</td>
</tr>
<tr v-if="cameraInfoFor(camera).path !== undefined && cameraInfoFor(camera).path !== null">
<td>Path:</td>
<td style="word-break: break-all">{{ cameraInfoFor(camera).path }}</td>
</tr>
<tr v-if="cameraInfoFor(camera).otherPaths !== undefined && cameraInfoFor(camera).otherPaths !== null">
<td>Other Paths:</td>
<td>{{ cameraInfoFor(camera).otherPaths }}</td>
</tr>
<tr v-if="cameraInfoFor(camera).uniquePath !== undefined && cameraInfoFor(camera).uniquePath !== null">
<td>Unique Path:</td>
<td style="word-break: break-all">{{ cameraInfoFor(camera).uniquePath }}</td>
</tr>
</tbody>
</v-simple-table>
</div>
</template>

View File

@@ -0,0 +1,85 @@
<script setup lang="ts">
import { PVCameraInfo } from "@/types/SettingTypes";
const { saved, matched } = defineProps({
saved: {
type: PVCameraInfo,
required: true
},
matched: {
type: PVCameraInfo,
required: true
}
});
const cameraInfoFor = (camera: PVCameraInfo): any => {
if (camera.PVUsbCameraInfo) {
return camera.PVUsbCameraInfo;
}
if (camera.PVCSICameraInfo) {
return camera.PVCSICameraInfo;
}
if (camera.PVFileCameraInfo) {
return camera.PVFileCameraInfo;
}
return {};
};
</script>
<template>
<div>
<h3 v-if="saved.PVUsbCameraInfo" class="mb-3">USB Camera Info</h3>
<h3 v-if="saved.PVCSICameraInfo" class="mb-3">CSI Camera Info</h3>
<h3 v-if="saved.PVFileCameraInfo" class="mb-3">File Camera Info</h3>
<v-simple-table dense :style="{ backgroundColor: 'var(--v-primary-base)' }">
<tbody>
<tr>
<th></th>
<th>Saved</th>
<th>Matched</th>
</tr>
<tr v-if="cameraInfoFor(saved).dev !== undefined && cameraInfoFor(saved).dev !== null">
<td>Device Number:</td>
<td>{{ cameraInfoFor(saved).dev }}</td>
<td>{{ cameraInfoFor(matched).dev }}</td>
</tr>
<tr v-if="cameraInfoFor(saved).name !== undefined && cameraInfoFor(saved).name !== null">
<td>Name:</td>
<td>{{ cameraInfoFor(saved).name }}</td>
<td>{{ cameraInfoFor(matched).name }}</td>
</tr>
<tr v-if="cameraInfoFor(saved).baseName !== undefined && cameraInfoFor(saved).baseName !== null">
<td>Base Name:</td>
<td>{{ cameraInfoFor(saved).baseName }}</td>
<td>{{ cameraInfoFor(matched).baseName }}</td>
</tr>
<tr v-if="cameraInfoFor(saved).vendorId !== undefined && cameraInfoFor(saved).vendorId !== null">
<td>Vendor ID:</td>
<td>{{ cameraInfoFor(saved).vendorId }}</td>
<td>{{ cameraInfoFor(matched).vendorId }}</td>
</tr>
<tr v-if="cameraInfoFor(saved).productId !== undefined && cameraInfoFor(saved).productId !== null">
<td>Product ID:</td>
<td>{{ cameraInfoFor(saved).productId }}</td>
<td>{{ cameraInfoFor(matched).productId }}</td>
</tr>
<tr v-if="cameraInfoFor(saved).path !== undefined && cameraInfoFor(saved).path !== null">
<td>Path:</td>
<td style="word-break: break-all">{{ cameraInfoFor(saved).path }}</td>
<td style="word-break: break-all">{{ cameraInfoFor(matched).path }}</td>
</tr>
<tr v-if="cameraInfoFor(saved).otherPaths !== undefined && cameraInfoFor(saved).otherPaths !== null">
<td>Other Paths:</td>
<td>{{ cameraInfoFor(saved).otherPaths }}</td>
<td>{{ cameraInfoFor(matched).otherPaths }}</td>
</tr>
<tr v-if="cameraInfoFor(saved).uniquePath !== undefined && cameraInfoFor(saved).uniquePath !== null">
<td>Unique Path:</td>
<td style="word-break: break-all">{{ cameraInfoFor(saved).uniquePath }}</td>
<td style="word-break: break-all">{{ cameraInfoFor(matched).uniquePath }}</td>
</tr>
</tbody>
</v-simple-table>
</div>
</template>

View File

@@ -81,7 +81,7 @@ const localValue = computed({
type="number"
style="width: 45px"
:step="step"
hide-spin-buttons="true"
:hide-spin-buttons="true"
@keyup.enter="localValue = $event.target.value"
@blur="localValue = $event.target.value"
/>

View File

@@ -14,7 +14,8 @@ const props = withDefaults(
}>(),
{
disabled: false,
labelCols: 2
labelCols: 2,
switchCols: 8
}
);

View File

@@ -282,7 +282,11 @@ useCameraSettingsStore().$subscribe((mutation, state) => {
: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 ||
!useCameraSettingsStore().hasConnected
"
:items="pipelineNamesWrapper"
@input="(args) => useCameraSettingsStore().changeCurrentPipelineIndex(args, true)"
/>
@@ -349,7 +353,11 @@ useCameraSettingsStore().$subscribe((mutation, state) => {
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 ||
!useCameraSettingsStore().hasConnected
"
:items="pipelineTypesWrapper"
@input="showPipelineTypeChangeDialog = true"
/>

View File

@@ -49,6 +49,7 @@ const performanceRecommendation = computed<string>(() => {
</v-col>
<v-col class="align-self-center" style="text-align: right; margin-right: 12px; padding-left: 24px">
<v-chip
v-if="useCameraSettingsStore().currentCameraSettings.isConnected"
label
:color="fpsTooLow ? 'error' : 'transparent'"
:text-color="fpsTooLow ? '#C7EA46' : '#ff4d00'"
@@ -58,6 +59,9 @@ const performanceRecommendation = computed<string>(() => {
>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">
<span class="pr-1"> Camera not connected </span>
</v-chip>
</v-col>
<v-col
class="align-self-center"
@@ -82,10 +86,20 @@ const performanceRecommendation = computed<string>(() => {
<v-divider style="border-color: white" />
<v-row class="stream-viewer-container pa-3">
<v-col v-if="value.includes(0)" class="stream-view">
<photon-camera-stream id="input-camera-stream" stream-type="Raw" style="width: 100%; height: auto" />
<photon-camera-stream
id="input-camera-stream"
:camera-settings="useCameraSettingsStore().currentCameraSettings"
stream-type="Raw"
style="width: 100%; height: auto"
/>
</v-col>
<v-col v-if="value.includes(1)" class="stream-view">
<photon-camera-stream id="output-camera-stream" stream-type="Processed" style="width: 100%; height: auto" />
<photon-camera-stream
id="output-camera-stream"
:camera-settings="useCameraSettingsStore().currentCameraSettings"
stream-type="Processed"
style="width: 100%; height: auto"
/>
</v-col>
</v-row>
</v-card>

View File

@@ -145,31 +145,42 @@ onBeforeUpdate(() => {
<template>
<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-tabs
v-model="selectedTabs[tabGroupIndex]"
grow
background-color="primary"
dark
height="48"
slider-color="accent"
>
<v-tab v-for="(tabConfig, index) in tabGroupData" :key="index">
{{ tabConfig.tabName }}
</v-tab>
</v-tabs>
<div class="pl-4 pr-4 pt-4 pb-2">
<KeepAlive>
<Component :is="tabGroupData[selectedTabs[tabGroupIndex]].component" />
</KeepAlive>
</div>
</v-card>
</v-col>
<template v-if="!useCameraSettingsStore().hasConnected">
<v-col v-if="!useCameraSettingsStore().hasConnected" cols="12">
<v-card color="error">
<v-card-title class="white--text">
Camera has not connected. Please check your connection and try again.
</v-card-title>
</v-card>
</v-col>
</template>
<template v-else>
<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-tabs
v-model="selectedTabs[tabGroupIndex]"
grow
background-color="primary"
dark
height="48"
slider-color="accent"
>
<v-tab v-for="(tabConfig, index) in tabGroupData" :key="index">
{{ tabConfig.tabName }}
</v-tab>
</v-tabs>
<div class="pl-4 pr-4 pt-4 pb-2">
<KeepAlive>
<Component :is="tabGroupData[selectedTabs[tabGroupIndex]].component" />
</KeepAlive>
</div>
</v-card>
</v-col>
</template>
</v-row>
</template>

View File

@@ -38,11 +38,16 @@ const processingMode = computed<number>({
<v-col>
<p style="color: white">Processing Mode</p>
<v-btn-toggle v-model="processingMode" mandatory dark class="fill">
<v-btn color="secondary">
<v-btn color="secondary" :disabled="!useCameraSettingsStore().hasConnected">
<v-icon left>mdi-square-outline</v-icon>
<span>2D</span>
</v-btn>
<v-btn color="secondary" :disabled="!useCameraSettingsStore().isCurrentVideoFormatCalibrated">
<v-btn
color="secondary"
:disabled="
!useCameraSettingsStore().hasConnected || !useCameraSettingsStore().isCurrentVideoFormatCalibrated
"
>
<v-icon left>mdi-cube-outline</v-icon>
<span>3D</span>
</v-btn>