Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bce2c40
added focus camera pipe
ElectricTurtle32 Nov 4, 2025
ee727d8
Made it into a real pipline
ElectricTurtle32 Nov 4, 2025
c2431b7
Added ui
ElectricTurtle32 Nov 4, 2025
f175ab3
Focus mode now outputs to UI, general cleanup.
ElectricTurtle32 Nov 10, 2025
ef4fb0b
Lots of formatting
ElectricTurtle32 Nov 10, 2025
95429a7
fix a few things
ElectricTurtle32 Nov 11, 2025
0e162af
added focus camera pipe
ElectricTurtle32 Nov 4, 2025
bcef45f
Made it into a real pipline
ElectricTurtle32 Nov 4, 2025
64f6731
Added ui
ElectricTurtle32 Nov 4, 2025
ac132fe
Focus mode now outputs to UI, general cleanup.
ElectricTurtle32 Nov 10, 2025
06765cc
Lots of formatting
ElectricTurtle32 Nov 10, 2025
315fc49
fix a few things
ElectricTurtle32 Nov 11, 2025
132be8e
Update docs/source/docs/quick-start/camera-focusing.md
ElectricTurtle32 Nov 11, 2025
fec2e9e
Fixed a lot of typos and formating. Reworked ui for focus mode.
ElectricTurtle32 Nov 12, 2025
aeffa0a
Merge branch 'camera-focus-tool' of https://github.com/ElectricTurtle…
ElectricTurtle32 Nov 12, 2025
b0fb7d5
Git merge things
ElectricTurtle32 Nov 12, 2025
56ea7c3
Hopefully fix build errors
ElectricTurtle32 Nov 12, 2025
1a0d0f2
Moved focus indicatorn to camera view
ElectricTurtle32 Nov 12, 2025
f492a77
Fix more merge things
ElectricTurtle32 Nov 13, 2025
ddb57db
Fix more merge things
ElectricTurtle32 Nov 13, 2025
ece7b42
Merge branch 'camera-focus-tool' of https://github.com/ElectricTurtle…
ElectricTurtle32 Nov 13, 2025
de5bf45
Fix typo
ElectricTurtle32 Nov 13, 2025
736347e
Merge branch 'main' into camera-focus-tool
ElectricTurtle32 Nov 13, 2025
99f3f41
Small requested changes
ElectricTurtle32 Nov 14, 2025
ac32a43
Fixxed padding in camera settings
ElectricTurtle32 Nov 14, 2025
c0d81ef
Update blank lines, fix docs.
ElectricTurtle32 Nov 16, 2025
e363744
Added warning to recalibrate camera after focusing & added warning to…
ElectricTurtle32 Nov 16, 2025
a6b36bd
Re-lint everthing
ElectricTurtle32 Nov 16, 2025
932dc9c
Added screenshot
ElectricTurtle32 Nov 16, 2025
cc1a523
Compress image
Gold856 Nov 16, 2025
5034c94
Changed wording in docs
ElectricTurtle32 Nov 16, 2025
6b33f3f
nitpicks
ElectricTurtle32 Nov 16, 2025
bdd5001
Merge branch 'camera-focus-tool' of https://github.com/ElectricTurtle…
ElectricTurtle32 Nov 16, 2025
001fa23
Typo
ElectricTurtle32 Nov 16, 2025
8937685
Update docs/source/docs/quick-start/camera-focusing.md
ElectricTurtle32 Nov 16, 2025
6da2c5c
Update docs/source/docs/quick-start/camera-focusing.md
ElectricTurtle32 Nov 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/source/docs/quick-start/camera-focusing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Camera Focusing

## Prepare Camera
:::{warning}
Refocusing your camera **will** make your calibration inaccurate, make sure to recalibrate after focusing.
:::
To ensure that your camera is focused properly, mount it to a secure surface and ensure it does not move drastically. Point your camera at a detailed surface like a calibration board, and make sure that it not too close to the camera.

## Using Focus Mode
:::{important}
When you enable Focus Mode, it will assign a *Score* to the current focus, this score depends on your environment and the lighting. This score cannot be compared to a focus score collected from other environments.
:::
- In the Cameras tab, turn on Focus Mode.
- Rotate the lens on your camera to try and get the focus score as high as possible.
- Once you cannot get a higher score, this indicates that your camera is fully focused and can be set in place using glue if desired.

```{image} images/focusModeExample.png
:scale: 50%
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/source/docs/quick-start/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ wiring
networking
camera-matching
camera-calibration
camera-focusing
quick-configure
```
15 changes: 14 additions & 1 deletion photon-client/src/components/cameras/CameraSettingsCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import PvSelect, { type SelectItem } from "@/components/common/pv-select.vue";
import PvInput from "@/components/common/pv-input.vue";
import PvNumberInput from "@/components/common/pv-number-input.vue";
import PvSwitch from "@/components/common/pv-switch.vue";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { useStateStore } from "@/stores/StateStore";
import { computed, ref, watchEffect } from "vue";
Expand All @@ -15,7 +16,14 @@ const tempSettingsStruct = ref<CameraSettingsChangeRequest>({
fov: useCameraSettingsStore().currentCameraSettings.fov.value,
quirksToChange: Object.assign({}, useCameraSettingsStore().currentCameraSettings.cameraQuirks.quirks)
});

const focusMode = computed<boolean>({
get: () => useCameraSettingsStore().isFocusMode,
set: (v) =>
useCameraSettingsStore().changeCurrentPipelineIndex(
v ? -3 : useCameraSettingsStore().currentCameraSettings.lastPipelineIndex || 0,
true
)
});
const arducamSelectWrapper = computed<number>({
get: () => {
if (tempSettingsStruct.value.quirksToChange.ArduOV9281Controls) return 1;
Expand Down Expand Up @@ -166,6 +174,11 @@ const wrappedCameras = computed<SelectItem[]>(() =>
]"
:select-cols="8"
/>
<pv-switch
v-model="focusMode"
tooltip="Enable Focus Mode to start focusing the lens on your camera"
label="Focus Mode"
></pv-switch>
</v-card-text>
<v-card-text class="d-flex pt-0">
<v-col cols="6" class="pa-0 pr-2">
Expand Down
21 changes: 19 additions & 2 deletions photon-client/src/components/cameras/CamerasView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ const fpsTooLow = computed<boolean>(() => {
<v-chip v-else label color="red" variant="text" style="font-size: 1rem; padding: 0; margin: 0">
<span class="pr-1">Camera not connected</span>
</v-chip>
<v-chip
v-if="useCameraSettingsStore().isFocusMode"
label
color="primary"
variant="text"
style="font-size: 1rem; padding: 0; margin: auto"
>
<span class="pr-1"> Focus: {{ Math.round(useStateStore().currentPipelineResults?.focus || 0) }} </span>
</v-chip>
<v-switch
v-model="driverMode"
:disabled="useCameraSettingsStore().isCalibrationMode || useCameraSettingsStore().pipelineNames.length === 0"
Expand Down Expand Up @@ -95,7 +104,11 @@ const fpsTooLow = computed<boolean>(() => {
color="buttonPassive"
class="fill"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:disabled="useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode"
:disabled="
useCameraSettingsStore().isDriverMode ||
useCameraSettingsStore().isCalibrationMode ||
useCameraSettingsStore().isFocusMode
"
>
<v-icon start class="mode-btn-icon" size="large">mdi-import</v-icon>
<span class="mode-btn-label">Raw</span>
Expand All @@ -104,7 +117,11 @@ const fpsTooLow = computed<boolean>(() => {
color="buttonPassive"
class="fill"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
:disabled="useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode"
:disabled="
useCameraSettingsStore().isDriverMode ||
useCameraSettingsStore().isCalibrationMode ||
useCameraSettingsStore().isFocusMode
"
>
<v-icon start class="mode-btn-icon" size="large">mdi-export</v-icon>
<span class="mode-btn-label">Processed</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ const pipelineNamesWrapper = computed<SelectItem[]>(() => {
if (useCameraSettingsStore().isDriverMode) {
pipelineNames.push({ name: "Driver Mode", value: WebsocketPipelineType.DriverMode });
}
if (useCameraSettingsStore().isFocusMode) {
pipelineNames.push({ name: "Focus Mode", value: WebsocketPipelineType.FocusCamera });
}
if (useCameraSettingsStore().isCalibrationMode) {
pipelineNames.push({ name: "3D Calibration Mode", value: WebsocketPipelineType.Calib3d });
}
Expand Down Expand Up @@ -177,6 +180,9 @@ const pipelineTypesWrapper = computed<{ name: string; value: number }[]>(() => {
if (useCameraSettingsStore().isDriverMode) {
pipelineTypes.push({ name: "Driver Mode", value: WebsocketPipelineType.DriverMode });
}
if (useCameraSettingsStore().isFocusMode) {
pipelineTypes.push({ name: "Focus Mode", value: WebsocketPipelineType.FocusCamera });
}
if (useCameraSettingsStore().isCalibrationMode) {
pipelineTypes.push({ name: "3D Calibration Mode", value: WebsocketPipelineType.Calib3d });
}
Expand All @@ -187,6 +193,7 @@ const pipelineType = ref<WebsocketPipelineType>(useCameraSettingsStore().current
const currentPipelineType = computed<WebsocketPipelineType>({
get: () => {
if (useCameraSettingsStore().isDriverMode) return WebsocketPipelineType.DriverMode;
if (useCameraSettingsStore().isFocusMode) return WebsocketPipelineType.FocusCamera;
if (useCameraSettingsStore().isCalibrationMode) return WebsocketPipelineType.Calib3d;
return pipelineType.value;
},
Expand Down Expand Up @@ -290,6 +297,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
tooltip="Each pipeline runs on a camera output and stores a unique set of processing settings"
:disabled="
useCameraSettingsStore().isDriverMode ||
useCameraSettingsStore().isFocusMode ||
useCameraSettingsStore().isCalibrationMode ||
!useCameraSettingsStore().hasConnected
"
Expand Down Expand Up @@ -366,6 +374,7 @@ const wrappedCameras = computed<SelectItem[]>(() =>
tooltip="Changes the pipeline type, which changes the type of processing that will happen on input frames"
:disabled="
useCameraSettingsStore().isDriverMode ||
useCameraSettingsStore().isFocusMode ||
useCameraSettingsStore().isCalibrationMode ||
!useCameraSettingsStore().hasConnected
"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ const processingMode = computed<number>({

<template>
<v-card
:disabled="useCameraSettingsStore().isDriverMode || useStateStore().colorPickingMode"
:disabled="
useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isFocusMode || useStateStore().colorPickingMode
"
class="mt-3 rounded-12"
color="surface"
style="flex-grow: 1; display: flex; flex-direction: column"
Expand Down
3 changes: 3 additions & 0 deletions photon-client/src/stores/settings/CameraSettingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
isCalibrationMode(): boolean {
return this.currentCameraSettings.currentPipelineIndex == WebsocketPipelineType.Calib3d;
},
isFocusMode(): boolean {
return this.currentCameraSettings.currentPipelineIndex == WebsocketPipelineType.FocusCamera;
},
isCSICamera(): boolean {
return this.currentCameraSettings.isCSICamera;
},
Expand Down
2 changes: 2 additions & 0 deletions photon-client/src/types/PhotonTrackingTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ export interface PipelineResult {
sequenceID: number;
fps: number;
latency: number;
// Focus pipeline
focus?: number;
targets: PhotonTarget[];
// undefined if multitag failed or non-tag pipeline
multitagResult?: MultitagResult;
Expand Down
1 change: 1 addition & 0 deletions photon-client/src/types/WebsocketDataTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export interface IncomingWebsocketData {
}

export enum WebsocketPipelineType {
FocusCamera = -3,
Calib3d = -2,
DriverMode = -1,
Reflective = 0,
Expand Down
9 changes: 7 additions & 2 deletions photon-client/src/views/CameraSettingsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ const cameraViewType = computed<number[]>({
// Only show the input stream in Color Picking Mode
if (useStateStore().colorPickingMode) return [0];

// Only show the output stream in Driver Mode or Calibration Mode
if (useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode) return [1];
// Only show the output stream in Driver Mode or Calibration Mode or Focus Mode
if (
useCameraSettingsStore().isDriverMode ||
useCameraSettingsStore().isCalibrationMode ||
useCameraSettingsStore().isFocusMode
)
return [1];

const ret: number[] = [];
if (useCameraSettingsStore().currentPipelineSettings.inputShouldShow) {
Expand Down
9 changes: 7 additions & 2 deletions photon-client/src/views/DashboardView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ const cameraViewType = computed<number[]>({
// Only show the input stream in Color Picking Mode
if (useStateStore().colorPickingMode) return [0];

// Only show the output stream in Driver Mode or Calibration Mode
if (useCameraSettingsStore().isDriverMode || useCameraSettingsStore().isCalibrationMode) return [1];
// Only show the output stream in Driver Mode or Calibration Mode or Focus Mode
if (
useCameraSettingsStore().isDriverMode ||
useCameraSettingsStore().isCalibrationMode ||
useCameraSettingsStore().isFocusMode
)
return [1];

const ret: number[] = [];
if (useCameraSettingsStore().currentPipelineSettings.inputShouldShow) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.photonvision.common.util.SerializationUtils;
import org.photonvision.vision.pipeline.result.CVPipelineResult;
import org.photonvision.vision.pipeline.result.CalibrationPipelineResult;
import org.photonvision.vision.pipeline.result.FocusPipelineResult;

public class UIDataPublisher implements CVPipelineResultConsumer {
private static final Logger logger = new Logger(UIDataPublisher.class, LogGroup.VisionModule);
Expand Down Expand Up @@ -77,6 +78,10 @@ public void accept(CVPipelineResult result) {
var uiMap = new HashMap<String, HashMap<String, Object>>();
uiMap.put(uniqueName, dataMap);

if (result instanceof FocusPipelineResult focusResult) {
dataMap.put("focus", focusResult.focus);
}

DataChangeService.getInstance()
.publishEvent(OutgoingUIEvent.wrappedOf("updatePipelineResult", uiMap));
lastUIResultUpdateTime = now;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package org.photonvision.vision.pipe.impl;

import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfDouble;
import org.opencv.imgproc.Imgproc;
import org.photonvision.vision.pipe.CVPipe;

public class FocusPipe extends CVPipe<Mat, FocusPipe.FocusResult, FocusPipe.FocusParams> {
private double maxVariance = 0.0;

@Override
protected FocusResult process(Mat in) {
var outputMat = new Mat();

Imgproc.Laplacian(in, outputMat, CvType.CV_64F, 3);

var mean = new MatOfDouble();
var stddev = new MatOfDouble();
Core.meanStdDev(outputMat, mean, stddev);
var sd = stddev.get(0, 0)[0];
var variance = sd * sd;

return new FocusResult(outputMat, variance);
}

public static class FocusResult {
public final Mat frame;
public final double variance;

public FocusResult(Mat frame, double variance) {
this.frame = frame;
this.variance = variance;
}
}

public static class FocusParams {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright (C) Photon Vision.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package org.photonvision.vision.pipeline;

import org.opencv.core.Mat;
import org.photonvision.common.util.math.MathUtils;
import org.photonvision.vision.frame.Frame;
import org.photonvision.vision.frame.FrameThresholdType;
import org.photonvision.vision.opencv.CVMat;
import org.photonvision.vision.pipe.impl.CalculateFPSPipe;
import org.photonvision.vision.pipe.impl.FocusPipe;
import org.photonvision.vision.pipe.impl.ResizeImagePipe;
import org.photonvision.vision.pipeline.result.FocusPipelineResult;

public class FocusPipeline extends CVPipeline<FocusPipelineResult, FocusPipelineSettings> {
private final FocusPipe focusPipe = new FocusPipe();
private final CalculateFPSPipe calculateFPSPipe = new CalculateFPSPipe();
private final ResizeImagePipe resizeImagePipe = new ResizeImagePipe();

private static final FrameThresholdType PROCESSING_TYPE = FrameThresholdType.NONE;

public FocusPipeline() {
super(PROCESSING_TYPE);
settings = new FocusPipelineSettings();
}

public FocusPipeline(FocusPipelineSettings settings) {
super(PROCESSING_TYPE);
this.settings = settings;
}

@Override
protected void setPipeParamsImpl() {
resizeImagePipe.setParams(
new ResizeImagePipe.ResizeImageParams(settings.streamingFrameDivisor));
}

@Override
public FocusPipelineResult process(Frame frame, FocusPipelineSettings settings) {
long totalNanos = 0;

var inputMat = frame.colorImage.getMat();
boolean emptyIn = inputMat.empty();
Mat displayMat = new Mat();
double variance = 0.0;

if (!emptyIn) {
totalNanos += resizeImagePipe.run(inputMat).nanosElapsed;

var focusResult = focusPipe.run(inputMat);
totalNanos += focusResult.nanosElapsed;
variance = focusResult.output.variance;
displayMat = focusResult.output.frame;
}

var fpsResult = calculateFPSPipe.run(null);
var fps = fpsResult.output;

var processedCVMat = new CVMat(displayMat);

return new FocusPipelineResult(
frame.sequenceID,
MathUtils.nanosToMillis(totalNanos),
fps,
new Frame(
frame.sequenceID,
frame.colorImage,
processedCVMat,
frame.type,
frame.frameStaticProperties),
variance);
}

@Override
public void release() {
// we never actually need to give resources up since pipelinemanager only makes
// one of us
}
}
Loading
Loading