Skip to content
22 changes: 22 additions & 0 deletions photon-client/src/components/common/pv-camera-info-card.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { useStateStore } from "@/stores/StateStore";
import { PVCameraInfo } from "@/types/SettingTypes";

const { camera } = defineProps({
Expand All @@ -8,6 +9,17 @@ const { camera } = defineProps({
}
});

const cameraInfoForUuid: any = (sourceUniqueName) => {
return (
useStateStore().vsmState.allConnectedCameras.find((it) => cameraInfoFor(it).uniqueName === sourceUniqueName) || {
PVFileCameraInfo: undefined,
PVCSICameraInfo: undefined,
PVUsbCameraInfo: undefined,
PVDuplicateCameraInfo: undefined
}
);
};

const cameraInfoFor: any = (camera: PVCameraInfo) => {
if (camera.PVUsbCameraInfo) {
return camera.PVUsbCameraInfo;
Expand All @@ -18,6 +30,9 @@ const cameraInfoFor: any = (camera: PVCameraInfo) => {
if (camera.PVFileCameraInfo) {
return camera.PVFileCameraInfo;
}
if (camera.PVDuplicateCameraInfo) {
return camera.PVDuplicateCameraInfo;
}
return {};
};
</script>
Expand All @@ -39,8 +54,15 @@ const cameraInfoFor: any = (camera: PVCameraInfo) => {
<td v-if="camera.PVUsbCameraInfo" class="mb-3">USB Camera</td>
<td v-else-if="camera.PVCSICameraInfo" class="mb-3">CSI Camera</td>
<td v-else-if="camera.PVFileCameraInfo" class="mb-3">File Camera</td>
<td v-else-if="camera.PVDuplicateCameraInfo" class="mb-3">Duplicate Camera</td>
<td v-else>Unidentified Camera Type</td>
</tr>
<tr
v-if="cameraInfoFor(camera).sourceUniqueName !== undefined && cameraInfoFor(camera).sourceUniqueName !== null"
>
<td>Source Camera:</td>
<td>{{ cameraInfoFor(cameraInfoForUuid(cameraInfoFor(camera).sourceUniqeName)).name }}</td>
</tr>
<tr v-if="cameraInfoFor(camera).baseName !== undefined && cameraInfoFor(camera).baseName !== null">
<td>Base Name:</td>
<td>{{ cameraInfoFor(camera).baseName }}</td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ const cancelChangePipelineType = () => {
showPipelineTypeChangeDialog.value = false;
};

// Pipeline duplication'
// Pipeline duplication
const duplicateCurrentPipeline = () => {
useCameraSettingsStore().duplicatePipeline(useCameraSettingsStore().currentCameraSettings.currentPipelineIndex);
};
Expand Down
24 changes: 22 additions & 2 deletions photon-client/src/components/dashboard/tabs/InputTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import { useStateStore } from "@/stores/StateStore";
import { getResolutionString } from "@/lib/PhotonUtils";
import { useDisplay } from "vuetify";

// Check if this camera is a duplicate (read-only input settings)
const isReadOnly = computed(() => useCameraSettingsStore().cameraIsDuplicate);
const sourceCameraName = computed(() => useCameraSettingsStore().getSourceCameraName);

// 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) => ({
Expand Down Expand Up @@ -72,18 +76,26 @@ const interactiveCols = computed(() =>

<template>
<div>
<v-alert v-if="isReadOnly" type="info" class="mb-4">
This is a duplicate camera. Input settings are controlled by
<strong>{{ sourceCameraName || "the source camera" }}</strong
>. For all intents and purposes, duplicate cameras are distinct cameras share only input settings and camera feed
with the camera that they duplicate. Camera calibrations are NOT shared between duplicate cameras and their source
camera, and must be imported manually.
</v-alert>
<pv-switch
v-model="useCameraSettingsStore().currentPipelineSettings.cameraAutoExposure"
label="Auto Exposure"
:switch-cols="interactiveCols"
:disabled="isReadOnly"
tooltip="Enables or Disables camera automatic adjustment for current lighting conditions"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraAutoExposure: args }, false)
"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraExposureRaw"
:disabled="useCameraSettingsStore().currentCameraSettings.pipelineSettings.cameraAutoExposure"
:disabled="useCameraSettingsStore().currentCameraSettings.pipelineSettings.cameraAutoExposure || isReadOnly"
label="Exposure"
tooltip="Directly controls how long the camera shutter remains open. Units are dependant on the underlying driver."
:min="useCameraSettingsStore().minExposureRaw"
Expand All @@ -100,6 +112,7 @@ const interactiveCols = computed(() =>
:min="0"
:max="100"
:slider-cols="interactiveCols"
:disabled="isReadOnly"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBrightness: args }, false)
"
Expand All @@ -112,6 +125,7 @@ const interactiveCols = computed(() =>
:min="0"
:max="100"
:slider-cols="interactiveCols"
:disabled="isReadOnly"
@update:modelValue="(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraGain: args }, false)"
/>
<pv-slider
Expand All @@ -121,6 +135,7 @@ const interactiveCols = computed(() =>
:min="0"
:max="100"
:slider-cols="interactiveCols"
:disabled="isReadOnly"
tooltip="Controls red automatic white balance gain, which affects how the camera captures colors in different conditions"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraRedGain: args }, false)
Expand All @@ -133,6 +148,7 @@ const interactiveCols = computed(() =>
:min="0"
:max="100"
:slider-cols="interactiveCols"
:disabled="isReadOnly"
tooltip="Controls blue automatic white balance gain, which affects how the camera captures colors in different conditions"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraBlueGain: args }, false)
Expand All @@ -142,14 +158,15 @@ const interactiveCols = computed(() =>
v-model="useCameraSettingsStore().currentPipelineSettings.cameraAutoWhiteBalance"
label="Auto White Balance"
:switch-cols="interactiveCols"
:disabled="isReadOnly"
tooltip="Enables or Disables camera automatic adjustment for current lighting conditions"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ cameraAutoWhiteBalance: args }, false)
"
/>
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.cameraWhiteBalanceTemp"
:disabled="useCameraSettingsStore().currentPipelineSettings.cameraAutoWhiteBalance"
:disabled="useCameraSettingsStore().currentPipelineSettings.cameraAutoWhiteBalance || isReadOnly"
label="White Balance Temperature"
:min="useCameraSettingsStore().minWhiteBalanceTemp"
:max="useCameraSettingsStore().maxWhiteBalanceTemp"
Expand All @@ -164,6 +181,7 @@ const interactiveCols = computed(() =>
tooltip="Rotates the camera stream. Rotation not available when camera has been calibrated."
:items="cameraRotations"
:select-cols="interactiveCols"
:disabled="isReadOnly"
@update:modelValue="
(args) => useCameraSettingsStore().changeCurrentPipelineSetting({ inputImageRotationMode: args }, false)
"
Expand All @@ -174,6 +192,7 @@ const interactiveCols = computed(() =>
tooltip="Resolution and FPS the camera should directly capture at"
:items="cameraResolutions"
:select-cols="interactiveCols"
:disabled="isReadOnly"
@update:modelValue="(args) => handleResolutionChange(args)"
/>
<pv-select
Expand All @@ -182,6 +201,7 @@ const interactiveCols = computed(() =>
tooltip="Resolution to which camera frames are downscaled for streaming to the dashboard"
:items="streamResolutions"
:select-cols="interactiveCols"
:disabled="isReadOnly"
@update:modelValue="(args) => handleStreamResolutionChange(args)"
/>
<pv-switch
Expand Down
25 changes: 24 additions & 1 deletion photon-client/src/stores/settings/CameraSettingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
},
hasConnected(): boolean {
return this.currentCameraSettings.hasConnected;
},
cameraIsDuplicate(): boolean {
return this.currentCameraSettings.isDuplicateCamera || false;
},
getSourceCameraName(): string | null {
if (!this.cameraIsDuplicate) return null;
return this.currentCameraSettings.sourceCameraNickname || null;
}
},
actions: {
Expand Down Expand Up @@ -147,7 +154,11 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
fpsLimit: d.fpsLimit,
isConnected: d.isConnected,
hasConnected: d.hasConnected,
mismatch: d.mismatch
mismatch: d.mismatch,
isDuplicateCamera: d.isDuplicateCamera || false,
sourceUniqueName: d.sourceUniqueName,
sourceCameraNickname: d.sourceCameraNickname,
inputSettingsReadOnly: d.inputSettingsReadOnly || false
};
return acc;
}, {});
Expand Down Expand Up @@ -363,6 +374,18 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
}
return axios.post("/settings/camera/setNickname", payload);
},
/**
* Create a duplicate camera from an existing source camera.
*
* @param sourceUniqueName the unique name of the source camera to duplicate.
* @return HTTP request promise to the backend with the new duplicate's uniqueName
*/
async createDuplicateCamera(sourceUniqueName: string) {
const payload = {
cameraUniqueName: sourceUniqueName
};
return axios.post("/utils/duplicateCamera", payload);
},
/**
* Start the 3D calibration process for the provided camera.
*
Expand Down
25 changes: 21 additions & 4 deletions photon-client/src/types/SettingTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ export type ConfigurableNetworkSettings = Omit<
export interface PVCameraInfoBase {
/*
Huge hack. In Jackson, this is set based on the underlying type -- this
then maps to one of the 3 subclasses here below. Not sure how to best deal with this.
then maps to one of the subclasses here below. Not sure how to best deal with this.
*/
cameraTypename: "PVUsbCameraInfo" | "PVCSICameraInfo" | "PVFileCameraInfo";
cameraTypename: "PVUsbCameraInfo" | "PVCSICameraInfo" | "PVFileCameraInfo" | "PVDuplicateCameraInfo";
}

export interface PVUsbCameraInfo {
Expand Down Expand Up @@ -107,11 +107,20 @@ export interface PVFileCameraInfo {
uniquePath: string;
}

export interface PVDuplicateCameraInfo {
sourceUniqueName: string;
name: string;

// In Java, PVCameraInfo provides a uniquePath property so we can have one Source of Truth here
uniquePath: string;
}

// This camera info will only ever hold one of its members - the others should be undefined.
export class PVCameraInfo {
PVUsbCameraInfo: PVUsbCameraInfo | undefined;
PVCSICameraInfo: PVCSICameraInfo | undefined;
PVFileCameraInfo: PVFileCameraInfo | undefined;
PVDuplicateCameraInfo: PVDuplicateCameraInfo | undefined;
}

export interface VsmState {
Expand Down Expand Up @@ -279,6 +288,11 @@ export interface UiCameraConfiguration {
isConnected: boolean;
hasConnected: boolean;
mismatch: boolean;

isDuplicateCamera: boolean;
sourceUniqueName?: string;
sourceCameraNickname?: string;
inputSettingsReadOnly: boolean;
}

export interface CameraSettingsChangeRequest {
Expand Down Expand Up @@ -442,12 +456,15 @@ export const PlaceholderCameraSettings: UiCameraConfiguration = reactive({
uniquePath: "/dev/foobar2"
},
PVCSICameraInfo: undefined,
PVUsbCameraInfo: undefined
PVUsbCameraInfo: undefined,
PVDuplicateCameraInfo: undefined
},
fpsLimit: -1,
isConnected: true,
hasConnected: true,
mismatch: false
mismatch: false,
isDuplicateCamera: false,
inputSettingsReadOnly: false
});

export enum CalibrationBoardTypes {
Expand Down
4 changes: 4 additions & 0 deletions photon-client/src/types/WebsocketDataTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ export interface WebsocketCameraSettingsUpdate {
isConnected: boolean;
hasConnected: boolean;
mismatch: boolean;
isDuplicateCamera?: boolean;
sourceUniqueName?: string;
sourceCameraNickname?: string;
inputSettingsReadOnly?: boolean;
}
export interface WebsocketNTUpdate {
connected: boolean;
Expand Down
Loading
Loading