Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c836ba1
.gitignore change
Ruthie-FRC Mar 15, 2026
b309348
Add JsonTypeName annotation and initialize targetModel in ObjectDetec…
Ruthie-FRC Mar 15, 2026
f5c2b4e
Add 3D target solving functionality to ObjectDetectionPipeline
Ruthie-FRC Mar 15, 2026
e051e84
Reorder TargetModel enum values and update default target model in Ob…
Ruthie-FRC Mar 15, 2026
3d11b75
Remove pipeline type check for ObjectDetection in StreamConfigCard
Ruthie-FRC Mar 15, 2026
61ca96d
Fix PnP tab filtering logic in ConfigOptions to exclude when 3D is un…
Ruthie-FRC Mar 15, 2026
e26c4cc
Add object detection check to PnPTab for conditional rendering of slider
Ruthie-FRC Mar 15, 2026
2d3d923
Refactor RknnObjectDetector: update Cleaner instance to static and op…
Ruthie-FRC Mar 15, 2026
c7531de
Refactor RubikObjectDetector: update Cleaner instance to static and i…
Ruthie-FRC Mar 15, 2026
08412fd
Fix ObjectDetectionPipe: update frame variable in detect method for c…
Ruthie-FRC Mar 15, 2026
e0d6a0d
Refactor ObjectDetectionPipeline: streamline target corner handling f…
Ruthie-FRC Mar 15, 2026
82882f5
Refactor ObjectDetectionPipelineSettings: move targetModel assignment…
Ruthie-FRC Mar 15, 2026
83be30f
Add isSpherical method to TargetModel for spherical target detection
Ruthie-FRC Mar 15, 2026
5671000
lint frontend
Ruthie-FRC Mar 15, 2026
0584d28
Fix: add missing newline at end of file in multiple detector and pipe…
Ruthie-FRC Mar 15, 2026
a06bde6
Add isSpherical method to TargetModel for spherical target detection
Ruthie-FRC Mar 15, 2026
bb1ef6d
Refactor isSpherical method in TargetModel for improved readability
Ruthie-FRC Mar 15, 2026
62c889e
Merge branch 'main' into object-pose-estimation
Ruthie-FRC Mar 16, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,4 @@ photon-client/playwright-report/
photon-client/blob-report/
photon-client/playwright/.cache/
photon-client/playwright/.auth/
photon-client/package-lock.json
2 changes: 1 addition & 1 deletion photon-client/src/components/dashboard/ConfigOptions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const tabGroups = computed<ConfigOption[][]>(() => {
tabGroup.filter(
(tabConfig) =>
!(!allow3d && tabConfig.tabName === "3D") && //Filter out 3D tab any time 3D isn't calibrated
!((!allow3d || isAprilTag || isAruco || isObjectDetection) && tabConfig.tabName === "PnP") && //Filter out the PnP config tab if 3D isn't available, or we're doing AprilTags
!((!allow3d || isAprilTag || isAruco) && tabConfig.tabName === "PnP") && //Filter out the PnP config tab if 3D isn't available, or we're doing AprilTags
!((isAprilTag || isAruco || isObjectDetection) && tabConfig.tabName === "Threshold") && //Filter out threshold tab if we're doing AprilTags
!((isAprilTag || isAruco || isObjectDetection) && tabConfig.tabName === "Contours") && //Filter out contours if we're doing AprilTags
!(!isAprilTag && tabConfig.tabName === "AprilTag") && //Filter out apriltag unless we actually are doing AprilTags
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ const processingMode = computed<number>({
:disabled="
!useCameraSettingsStore().hasConnected ||
!useCameraSettingsStore().isCurrentVideoFormatCalibrated ||
useCameraSettingsStore().currentPipelineSettings.pipelineType == PipelineType.ObjectDetection ||
useCameraSettingsStore().currentPipelineSettings.pipelineType == PipelineType.ColoredShape
"
:variant="theme.global.name.value === 'LightTheme' ? 'elevated' : 'outlined'"
Expand Down
7 changes: 6 additions & 1 deletion photon-client/src/components/dashboard/tabs/PnPTab.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script setup lang="ts">
import PvSelect from "@/components/common/pv-select.vue";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { TargetModel } from "@/types/PipelineTypes";
import { PipelineType, TargetModel } from "@/types/PipelineTypes";
import PvSlider from "@/components/common/pv-slider.vue";
import { computed } from "vue";
import { useStateStore } from "@/stores/StateStore";
Expand All @@ -11,6 +11,10 @@ const { mdAndDown } = useDisplay();
const interactiveCols = computed(() =>
mdAndDown.value && (!useStateStore().sidebarFolded || useCameraSettingsStore().isDriverMode) ? 9 : 8
);

const isObjectDetection = computed(
() => useCameraSettingsStore().currentPipelineSettings.pipelineType === PipelineType.ObjectDetection
);
</script>

<template>
Expand All @@ -34,6 +38,7 @@ const interactiveCols = computed(() =>
"
/>
<pv-slider
v-if="!isObjectDetection"
v-model="useCameraSettingsStore().currentPipelineSettings.cornerDetectionAccuracyPercentage"
class="pt-2"
:slider-cols="interactiveCols"
Expand Down
10 changes: 5 additions & 5 deletions photon-client/src/types/PipelineTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ export enum TargetModel {
InfiniteRechargeHighGoalOuter = 2,
CircularPowerCell7in = 3,
RapidReactCircularCargoBall = 4,
AprilTag6in_16h5 = 5,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these reordered?

AprilTag6p5in_36h11 = 6,
ReefscapeAlgae = 7
ReefscapeAlgae = 5,
AprilTag6in_16h5 = 6,
AprilTag6p5in_36h11 = 7
}

export interface PipelineSettings {
Expand Down Expand Up @@ -313,8 +313,8 @@ export const DefaultObjectDetectionPipelineSettings: ObjectDetectionPipelineSett
...DefaultPipelineSettings,
pipelineType: PipelineType.ObjectDetection,
cameraGain: 20,
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
ledMode: true,
targetModel: TargetModel.ReefscapeAlgae,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that.... that was me being an idiot and forgetting how things work. sorry.

ledMode: false,
outputMaximumTargets: 20,
cameraExposureRaw: 6,
confidence: 0.9,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import java.awt.Color;
import java.lang.ref.Cleaner;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opencv.core.Mat;
Expand All @@ -33,8 +34,11 @@
public class RknnObjectDetector implements ObjectDetector {
private static final Logger logger = new Logger(RknnObjectDetector.class, LogGroup.General);

/** Cleaner instance to release the detector when it goes out of scope */
private final Cleaner cleaner = Cleaner.create();
/**
* Shared Cleaner instance for all RknnObjectDetector instances. Using a single static Cleaner
* avoids spawning a new daemon thread per detector object.
*/
private static final Cleaner cleaner = Cleaner.create();

/** Atomic boolean to ensure that the native object can only be released once. */
private AtomicBoolean released = new AtomicBoolean(false);
Expand Down Expand Up @@ -128,10 +132,11 @@ public List<NeuralNetworkPipeResult> detect(Mat in, double nmsThresh, double box
return List.of();
}

return scale.resizeDetections(
List.of(results).stream()
.map(it -> new NeuralNetworkPipeResult(it.rect, it.class_id, it.conf))
.toList());
var pipeResults = new ArrayList<NeuralNetworkPipeResult>(results.length);
for (var it : results) {
pipeResults.add(new NeuralNetworkPipeResult(it.rect, it.class_id, it.conf));
}
return scale.resizeDetections(pipeResults);
}

/** Thread-safe method to release the detector. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import java.awt.Color;
import java.lang.ref.Cleaner;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.opencv.core.Mat;
Expand All @@ -33,8 +34,11 @@
public class RubikObjectDetector implements ObjectDetector {
private static final Logger logger = new Logger(RubikObjectDetector.class, LogGroup.General);

/** Cleaner instance to release the detector when it goes out of scope */
private final Cleaner cleaner = Cleaner.create();
/**
* Shared Cleaner instance for all RubikObjectDetector instances. Using a single static Cleaner
* avoids spawning a new daemon thread per detector object.
*/
private static final Cleaner cleaner = Cleaner.create();

/** Atomic boolean to ensure that the native object can only be released once. */
private AtomicBoolean released = new AtomicBoolean(false);
Expand Down Expand Up @@ -113,10 +117,8 @@ public List<String> getClasses() {
@Override
public List<NeuralNetworkPipeResult> detect(Mat in, double nmsThresh, double boxThresh) {
if (!isValid()) {
logger.error(
"Detector is not initialized, and so it can't be released! Model: "
+ model.modelFile.getName());
return null;
logger.error("Detector is not initialized! Model: " + model.modelFile.getName());
return List.of();
}

// Resize the frame to the input size of the model
Expand All @@ -137,10 +139,11 @@ public List<NeuralNetworkPipeResult> detect(Mat in, double nmsThresh, double box
return List.of();
}

return scale.resizeDetections(
List.of(results).stream()
.map(it -> new NeuralNetworkPipeResult(it.rect, it.class_id, it.conf))
.toList());
var pipeResults = new ArrayList<NeuralNetworkPipeResult>(results.length);
for (var it : results) {
pipeResults.add(new NeuralNetworkPipeResult(it.rect, it.class_id, it.conf));
}
return scale.resizeDetections(pipeResults);
}

/** Thread-safe method to release the detector. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ protected List<NeuralNetworkPipeResult> process(CVMat in) {
return List.of();
}

return detector.detect(in.getMat(), params.nms(), params.confidence());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this changed? there needs to be great caution with where mat's get returned, we've had memory leak issues from things like this in the past

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will revert.

return detector.detect(frame, params.nms(), params.confidence());
}

public static record ObjectDetectionPipeParams(double confidence, double nms, Model model) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@

package org.photonvision.vision.pipeline;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.opencv.core.Point;
import org.photonvision.common.configuration.NeuralNetworkModelManager;
import org.photonvision.vision.frame.Frame;
import org.photonvision.vision.frame.FrameThresholdType;
Expand All @@ -40,6 +42,7 @@ public class ObjectDetectionPipeline
private final SortContoursPipe sortContoursPipe = new SortContoursPipe();
private final Collect2dTargetsPipe collect2dTargetsPipe = new Collect2dTargetsPipe();
private final FilterObjectDetectionsPipe filterContoursPipe = new FilterObjectDetectionsPipe();
private final SolvePNPPipe solvePNPPipe = new SolvePNPPipe();

private static final FrameThresholdType PROCESSING_TYPE = FrameThresholdType.NONE;

Expand Down Expand Up @@ -99,6 +102,10 @@ protected void setPipeParamsImpl() {
settings.contourTargetOffsetPointEdge,
settings.contourTargetOrientation,
frameStaticProperties));

solvePNPPipe.setParams(
new SolvePNPPipe.SolvePNPPipeParams(
frameStaticProperties.cameraCalibration, settings.targetModel));
}

@Override
Expand All @@ -125,11 +132,48 @@ protected CVPipelineResult process(Frame frame, ObjectDetectionPipelineSettings
collect2dTargetsPipe.run(sortContoursResult.output);
sumPipeNanosElapsed += collect2dTargetsResult.nanosElapsed;

List<TrackedTarget> targetList;

// 3d stuff
if (settings.solvePNPEnabled) {
var rectPoints = new Point[4];
collect2dTargetsResult.output.forEach(
target -> {
target.getMinAreaRect().points(rectPoints);
if (settings.targetModel.isSpherical()) {
// For symmetric (spherical) targets such as balls: the model presents
// the same circular silhouette from every angle, so any mapping of the
// 4 OBB corners to the 4 equatorial model points yields the same pose.
// Just hand the corners through as-is.
target.setTargetCorners(
Arrays.asList(rectPoints[0], rectPoints[1], rectPoints[2], rectPoints[3]));
} else {
// For non-symmetric targets the OBB side ratio indicates which face of
// the 3D object is visible. RotatedRect.points() returns corners in
// order (bottom-left, top-left, top-right, bottom-right); reorder to
// (bottom-left, bottom-right, top-right, top-left) to match the
// solvePNP model convention expected by CornerDetectionPipe.
// TODO: when TargetModel gains multi-face support for 3D non-symmetric
// objects, select the appropriate face's 3D corners based on the ratio
// of the OBB's width to its height before calling solvePNP.
target.setTargetCorners(
Arrays.asList(rectPoints[0], rectPoints[3], rectPoints[2], rectPoints[1]));
}
});

var solvePNPResult = solvePNPPipe.run(collect2dTargetsResult.output);
sumPipeNanosElapsed += solvePNPResult.nanosElapsed;

targetList = solvePNPResult.output;
} else {
targetList = collect2dTargetsResult.output;
}

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

return new CVPipelineResult(
frame.sequenceID, sumPipeNanosElapsed, fps, collect2dTargetsResult.output, frame, names);
frame.sequenceID, sumPipeNanosElapsed, fps, targetList, frame, names);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@

package org.photonvision.vision.pipeline;

import com.fasterxml.jackson.annotation.JsonTypeName;
import org.photonvision.common.configuration.NeuralNetworkModelManager;
import org.photonvision.common.configuration.NeuralNetworkModelsSettings;
import org.photonvision.vision.objects.Model;
import org.photonvision.vision.target.TargetModel;

@JsonTypeName("ObjectDetectionPipelineSettings")
public class ObjectDetectionPipelineSettings extends AdvancedPipelineSettings {
public double confidence;
public double nms; // non maximal suppression
Expand All @@ -33,6 +36,9 @@ public ObjectDetectionPipelineSettings() {
cameraExposureRaw = 20;
cameraAutoExposure = false;
ledMode = false;
// Use a spherical ball model by default: YOLO primarily detects game pieces like
// balls/cargo, which are symmetric from all sides and work well with solvePNP.
targetModel = TargetModel.k2025Algae;
confidence = .9;
nms = .45;
model =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ public MatOfPoint3f getVisualizationBoxTop() {
return visualizationBoxTop;
}

public boolean isSpherical() {
return switch (this) {
case kCircularPowerCell7in, k2022CircularCargoBall, k2025Algae -> true;
default -> false;
};
}

// public static TargetModel getCircleTarget(double Units.inchesToMeters(7)) {
// var corners =
// List.of(
Expand Down
Loading