Allow configuring maximum target count (#2338)

This commit is contained in:
Wave Robotics - 2826
2026-02-01 18:19:13 -06:00
committed by GitHub
parent 77457219c7
commit 09e6d45e77
30 changed files with 239 additions and 85 deletions

View File

@@ -3,6 +3,7 @@ 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 PvSlider from "@/components/common/pv-slider.vue";
import { computed } from "vue";
import { RobotOffsetType } from "@/types/SettingTypes";
import { useStateStore } from "@/stores/StateStore";
@@ -58,14 +59,17 @@ const interactiveCols = computed(() =>
<template>
<div>
<pv-switch
v-model="useCameraSettingsStore().currentPipelineSettings.outputShowMultipleTargets"
label="Show Multiple Targets"
tooltip="If enabled, up to five targets will be displayed and sent via PhotonLib, instead of just one"
:disabled="isTagPipeline"
<pv-slider
v-model="useCameraSettingsStore().currentPipelineSettings.outputMaximumTargets"
label="Maximum Targets"
tooltip="The maximum number of targets to display and send."
:hidden="isTagPipeline"
:min="1"
:max="127"
:step="1"
:switch-cols="interactiveCols"
@update:modelValue="
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ outputShowMultipleTargets: value }, false)
(value) => useCameraSettingsStore().changeCurrentPipelineSetting({ outputMaximumTargets: value }, false)
"
/>
<pv-switch

View File

@@ -66,7 +66,7 @@ export interface PipelineSettings {
hsvHue: WebsocketNumberPair | [number, number];
ledMode: boolean;
hueInverted: boolean;
outputShowMultipleTargets: boolean;
outputMaximumTargets: number;
contourSortMode: number;
cameraExposureRaw: number;
cameraMinExposureRaw: number;
@@ -108,7 +108,7 @@ export type ConfigurablePipelineSettings = Partial<
// Omitted settings are changed for all pipeline types
export const DefaultPipelineSettings: Omit<
PipelineSettings,
"cameraGain" | "targetModel" | "ledMode" | "outputShowMultipleTargets" | "cameraExposureRaw" | "pipelineType"
"cameraGain" | "targetModel" | "ledMode" | "cameraExposureRaw" | "pipelineType"
> = {
offsetRobotOffsetMode: RobotOffsetPointMode.None,
streamingFrameDivisor: 0,
@@ -137,6 +137,7 @@ export const DefaultPipelineSettings: Omit<
offsetDualPointB: { x: 0, y: 0 },
hsvHue: { first: 50, second: 180 },
hueInverted: false,
outputMaximumTargets: 20,
contourSortMode: 0,
offsetSinglePoint: { x: 0, y: 0 },
cameraBrightness: 50,
@@ -166,7 +167,7 @@ export const DefaultReflectivePipelineSettings: ReflectivePipelineSettings = {
cameraGain: 20,
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
ledMode: true,
outputShowMultipleTargets: false,
outputMaximumTargets: 20,
cameraExposureRaw: 6,
pipelineType: PipelineType.Reflective,
@@ -197,7 +198,7 @@ export const DefaultColoredShapePipelineSettings: ColoredShapePipelineSettings =
cameraGain: 75,
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
ledMode: true,
outputShowMultipleTargets: false,
outputMaximumTargets: 20,
cameraExposureRaw: 20,
pipelineType: PipelineType.ColoredShape,
@@ -237,10 +238,9 @@ export const DefaultAprilTagPipelineSettings: AprilTagPipelineSettings = {
cameraGain: 75,
targetModel: TargetModel.AprilTag6p5in_36h11,
ledMode: false,
outputShowMultipleTargets: true,
outputMaximumTargets: 127,
cameraExposureRaw: 20,
pipelineType: PipelineType.AprilTag,
hammingDist: 0,
numIterations: 40,
decimate: 1,
@@ -278,13 +278,12 @@ export type ConfigurableArucoPipelineSettings = Partial<Omit<ArucoPipelineSettin
export const DefaultArucoPipelineSettings: ArucoPipelineSettings = {
...DefaultPipelineSettings,
cameraGain: 75,
outputShowMultipleTargets: true,
outputMaximumTargets: 127,
targetModel: TargetModel.AprilTag6p5in_36h11,
cameraExposureRaw: -1,
cameraAutoExposure: true,
ledMode: false,
pipelineType: PipelineType.Aruco,
tagFamily: AprilTagFamily.Family36h11,
threshWinSizes: { first: 11, second: 91 },
threshStepSize: 40,
@@ -316,7 +315,7 @@ export const DefaultObjectDetectionPipelineSettings: ObjectDetectionPipelineSett
cameraGain: 20,
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
ledMode: true,
outputShowMultipleTargets: false,
outputMaximumTargets: 20,
cameraExposureRaw: 6,
confidence: 0.9,
nms: 0.45,
@@ -335,7 +334,7 @@ export const DefaultCalibration3dPipelineSettings: Calibration3dPipelineSettings
cameraGain: 20,
targetModel: TargetModel.InfiniteRechargeHighGoalOuter,
ledMode: true,
outputShowMultipleTargets: false,
outputMaximumTargets: 1,
cameraExposureRaw: 6,
drawAllSnapshots: false
};

View File

@@ -228,7 +228,8 @@ public class TestUtils {
kRobots,
kTag1_640_480,
kTag1_16h5_1280,
kTag_corner_1280;
kTag_corner_1280,
k36h11_stress_test;
public final Path path;
@@ -237,6 +238,7 @@ public class TestUtils {
var filename = this.toString().substring(1).toLowerCase();
var extension = ".jpg";
if (filename.equals("tag1_16h5_1280")) extension = ".png";
if (filename.equals("36h11_stress_test")) extension = ".png";
return Path.of("apriltag", filename + extension);
}

View File

@@ -23,8 +23,8 @@ import org.photonvision.vision.frame.FrameDivisor;
public class Draw2dAprilTagsPipe extends Draw2dTargetsPipe {
public static class Draw2dAprilTagsParams extends Draw2dTargetsPipe.Draw2dTargetsParams {
public Draw2dAprilTagsParams(
boolean shouldDraw, boolean showMultipleTargets, FrameDivisor divisor) {
super(shouldDraw, showMultipleTargets, divisor);
boolean shouldDraw, int outputMaximumTargets, FrameDivisor divisor) {
super(shouldDraw, outputMaximumTargets, divisor);
// We want to show the polygon, not the rotated box
this.showRotatedBox = false;
this.showMaximumBox = false;

View File

@@ -22,9 +22,8 @@ import org.photonvision.vision.frame.FrameDivisor;
public class Draw2dArucoPipe extends Draw2dTargetsPipe {
public static class Draw2dArucoParams extends Draw2dTargetsPipe.Draw2dTargetsParams {
public Draw2dArucoParams(
boolean shouldDraw, boolean showMultipleTargets, FrameDivisor divisor) {
super(shouldDraw, showMultipleTargets, divisor);
public Draw2dArucoParams(boolean shouldDraw, int outputMaximumTargets, FrameDivisor divisor) {
super(shouldDraw, outputMaximumTargets, divisor);
// We want to show the polygon, not the rotated box
this.showRotatedBox = false;
this.showMaximumBox = false;

View File

@@ -58,14 +58,10 @@ public class Draw2dTargetsPipe
var circleColor = ColorHelper.colorToScalar(params.circleColor);
var shapeColour = ColorHelper.colorToScalar(params.shapeOutlineColour);
for (int i = 0; i < (params.showMultipleTargets ? in.getSecond().size() : 1); i++) {
for (int i = 0; i < Math.min(params.outputMaximumTargets, in.getSecond().size()); i++) {
Point[] vertices = new Point[4];
MatOfPoint contour = new MatOfPoint();
if (i != 0 && !params.showMultipleTargets) {
break;
}
TrackedTarget target = in.getSecond().get(i);
RotatedRect r = target.getMinAreaRect();
@@ -233,8 +229,7 @@ public class Draw2dTargetsPipe
public Color shapeOutlineColour = Color.MAGENTA;
public Color textColor = Color.GREEN;
public Color circleColor = Color.RED;
public final boolean showMultipleTargets;
public int outputMaximumTargets;
public final boolean shouldDraw;
public final FrameDivisor divisor;
@@ -247,10 +242,9 @@ public class Draw2dTargetsPipe
return shape != null && shape.shape.equals(ContourShape.Circle);
}
public Draw2dTargetsParams(
boolean shouldDraw, boolean showMultipleTargets, FrameDivisor divisor) {
public Draw2dTargetsParams(boolean shouldDraw, int outputMaximumTargets, FrameDivisor divisor) {
this.shouldDraw = shouldDraw;
this.showMultipleTargets = showMultipleTargets;
this.outputMaximumTargets = outputMaximumTargets;
this.divisor = divisor;
}
}

View File

@@ -17,6 +17,7 @@
package org.photonvision.vision.pipeline;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import java.util.Objects;
import org.opencv.core.Point;
import org.photonvision.common.util.numbers.DoubleCouple;
@@ -41,7 +42,7 @@ public class AdvancedPipelineSettings extends CVPipelineSettings {
public boolean hueInverted = false;
public boolean outputShouldDraw = true;
public boolean outputShowMultipleTargets = false;
public int outputMaximumTargets = 20;
public DoubleCouple contourArea = new DoubleCouple(0.0, 100.0);
public DoubleCouple contourRatio = new DoubleCouple(0.0, 20.0);
@@ -90,6 +91,22 @@ public class AdvancedPipelineSettings extends CVPipelineSettings {
public int cornerDetectionSideCount = 4;
public double cornerDetectionAccuracyPercentage = 10;
/**
* Handles backward compatibility for the deprecated outputShowMultipleTargets property. When
* outputShowMultipleTargets is encountered during deserialization, it sets outputMaximumTargets
* appropriately. If outputShowMultipleTargets is false, outputMaximumTargets is set to 1.
*/
@JsonAnySetter
public void handleUnknownProperty(String name, Object value) {
// Handle the old showMultipleTargets property for backward compatibility
if ("outputShowMultipleTargets".equals(name) && value instanceof Boolean showMultipleTargets) {
if (!showMultipleTargets) {
// If showMultipleTargets is false, limit to 1 target
outputMaximumTargets = 1;
}
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -97,7 +114,7 @@ public class AdvancedPipelineSettings extends CVPipelineSettings {
if (!super.equals(o)) return false;
AdvancedPipelineSettings that = (AdvancedPipelineSettings) o;
return outputShouldDraw == that.outputShouldDraw
&& outputShowMultipleTargets == that.outputShowMultipleTargets
&& outputMaximumTargets == that.outputMaximumTargets
&& contourSpecklePercentage == that.contourSpecklePercentage
&& Double.compare(that.offsetDualPointAArea, offsetDualPointAArea) == 0
&& Double.compare(that.offsetDualPointBArea, offsetDualPointBArea) == 0
@@ -136,7 +153,7 @@ public class AdvancedPipelineSettings extends CVPipelineSettings {
hsvValue,
hueInverted,
outputShouldDraw,
outputShowMultipleTargets,
outputMaximumTargets,
contourArea,
contourRatio,
contourFullness,

View File

@@ -30,6 +30,9 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.dataflow.structures.Packet;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.util.math.MathUtils;
import org.photonvision.estimation.TargetModel;
import org.photonvision.targeting.MultiTargetPNPResult;
@@ -49,6 +52,8 @@ import org.photonvision.vision.target.TrackedTarget;
import org.photonvision.vision.target.TrackedTarget.TargetCalculationParameters;
public class AprilTagPipeline extends CVPipeline<CVPipelineResult, AprilTagPipelineSettings> {
private static final Logger logger = new Logger(AprilTagPipeline.class, LogGroup.VisionModule);
private final AprilTagDetectionPipe aprilTagDetectionPipe = new AprilTagDetectionPipe();
private final AprilTagPoseEstimatorPipe singleTagPoseEstimatorPipe =
new AprilTagPoseEstimatorPipe();
@@ -232,6 +237,12 @@ public class AprilTagPipeline extends CVPipeline<CVPipelineResult, AprilTagPipel
}
}
if (targetList.size() > Packet.MAX_ARRAY_LEN) {
logger.error(
"We have " + targetList.size() + " targets! Arbitrarily dropping some on the floor");
targetList = targetList.subList(0, Packet.MAX_ARRAY_LEN);
}
var fpsResult = calculateFPSPipe.run(null);
var fps = fpsResult.output;

View File

@@ -40,7 +40,6 @@ public class AprilTagPipelineSettings extends AdvancedPipelineSettings {
public AprilTagPipelineSettings() {
super();
pipelineType = PipelineType.AprilTag;
outputShowMultipleTargets = true;
targetModel = TargetModel.kAprilTag6p5in_36h11;
cameraExposureRaw = 20;
cameraAutoExposure = false;

View File

@@ -15,23 +15,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* 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 edu.wpi.first.apriltag.AprilTagPoseEstimate;
@@ -46,6 +29,9 @@ import org.opencv.core.Mat;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.Objdetect;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.dataflow.structures.Packet;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.util.math.MathUtils;
import org.photonvision.estimation.TargetModel;
import org.photonvision.targeting.MultiTargetPNPResult;
@@ -61,6 +47,8 @@ import org.photonvision.vision.target.TrackedTarget;
import org.photonvision.vision.target.TrackedTarget.TargetCalculationParameters;
public class ArucoPipeline extends CVPipeline<CVPipelineResult, ArucoPipelineSettings> {
private static final Logger logger = new Logger(ArucoPipeline.class, LogGroup.VisionModule);
private ArucoDetectionPipe arucoDetectionPipe = new ArucoDetectionPipe();
private ArucoPoseEstimatorPipe singleTagPoseEstimatorPipe = new ArucoPoseEstimatorPipe();
private final MultiTargetPNPPipe multiTagPNPPipe = new MultiTargetPNPPipe();
@@ -237,6 +225,12 @@ public class ArucoPipeline extends CVPipeline<CVPipelineResult, ArucoPipelineSet
}
}
if (targetList.size() > Packet.MAX_ARRAY_LEN) {
logger.error(
"We have " + targetList.size() + " targets! Arbitrarily dropping some on the floor");
targetList = targetList.subList(0, Packet.MAX_ARRAY_LEN);
}
var fpsResult = calculateFPSPipe.run(null);
var fps = fpsResult.output;

View File

@@ -45,7 +45,6 @@ public class ArucoPipelineSettings extends AdvancedPipelineSettings {
public ArucoPipelineSettings() {
super();
pipelineType = PipelineType.Aruco;
outputShowMultipleTargets = true;
targetModel = TargetModel.kAprilTag6p5in_36h11;
cameraExposureRaw = 20;
cameraAutoExposure = true;

View File

@@ -26,8 +26,6 @@ import org.photonvision.vision.pipeline.result.CVPipelineResult;
public abstract class CVPipeline<R extends CVPipelineResult, S extends CVPipelineSettings>
implements Releasable {
static final int MAX_MULTI_TARGET_RESULTS = 50;
protected S settings;
protected FrameStaticProperties frameStaticProperties;
protected QuirkyCamera cameraQuirks;

View File

@@ -101,11 +101,7 @@ public class ColoredShapePipeline
sortContoursPipe.setParams(
new SortContoursPipe.SortContoursParams(
settings.contourSortMode,
settings.outputShowMultipleTargets
? MAX_MULTI_TARGET_RESULTS // TODO don't hardcode?
: 1,
frameStaticProperties));
settings.contourSortMode, settings.outputMaximumTargets, frameStaticProperties));
collect2dTargetsPipe.setParams(
new Collect2dTargetsPipe.Collect2dTargetsParams(
@@ -131,7 +127,7 @@ public class ColoredShapePipeline
Draw2dTargetsPipe.Draw2dTargetsParams draw2DTargetsParams =
new Draw2dTargetsPipe.Draw2dTargetsParams(
settings.outputShouldDraw,
settings.outputShowMultipleTargets,
settings.outputMaximumTargets,
settings.streamingFrameDivisor);
draw2DTargetsParams.showShape = true;
draw2DTargetsParams.showMaximumBox = false;

View File

@@ -82,9 +82,7 @@ public class ObjectDetectionPipeline
sortContoursPipe.setParams(
new SortContoursPipe.SortContoursParams(
settings.contourSortMode,
settings.outputShowMultipleTargets ? MAX_MULTI_TARGET_RESULTS : 1,
frameStaticProperties));
settings.contourSortMode, settings.outputMaximumTargets, frameStaticProperties));
filterContoursPipe.setParams(
new FilterObjectDetectionsPipe.FilterContoursParams(

View File

@@ -29,7 +29,7 @@ public class ObjectDetectionPipelineSettings extends AdvancedPipelineSettings {
public ObjectDetectionPipelineSettings() {
super();
this.pipelineType = PipelineType.ObjectDetection; // TODO: FIX this
this.outputShowMultipleTargets = true;
this.outputMaximumTargets = 20;
cameraExposureRaw = 20;
cameraAutoExposure = false;
ledMode = false;

View File

@@ -58,19 +58,19 @@ public class OutputStreamPipeline {
draw2dTargetsPipe.setParams(
new Draw2dTargetsPipe.Draw2dTargetsParams(
settings.outputShouldDraw,
settings.outputShowMultipleTargets,
settings.outputMaximumTargets,
settings.streamingFrameDivisor));
draw2dAprilTagsPipe.setParams(
new Draw2dAprilTagsPipe.Draw2dAprilTagsParams(
settings.outputShouldDraw,
settings.outputShowMultipleTargets,
settings.outputMaximumTargets,
settings.streamingFrameDivisor));
draw2dArucoPipe.setParams(
new Draw2dArucoPipe.Draw2dArucoParams(
settings.outputShouldDraw,
settings.outputShowMultipleTargets,
settings.outputMaximumTargets,
settings.streamingFrameDivisor));
draw2dCrosshairPipe.setParams(

View File

@@ -85,9 +85,7 @@ public class ReflectivePipeline extends CVPipeline<CVPipelineResult, ReflectiveP
sortContoursPipe.setParams(
new SortContoursPipe.SortContoursParams(
settings.contourSortMode,
settings.outputShowMultipleTargets ? MAX_MULTI_TARGET_RESULTS : 1,
frameStaticProperties));
settings.contourSortMode, settings.outputMaximumTargets, frameStaticProperties));
collect2dTargetsPipe.setParams(
new Collect2dTargetsPipe.Collect2dTargetsParams(

View File

@@ -51,7 +51,7 @@ public class BenchmarkTest {
pipeline.getSettings().hsvSaturation.set(100, 255);
pipeline.getSettings().hsvValue.set(190, 255);
pipeline.getSettings().outputShouldDraw = true;
pipeline.getSettings().outputShowMultipleTargets = true;
pipeline.getSettings().outputMaximumTargets = 20;
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Dual;
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
@@ -105,7 +105,7 @@ public class BenchmarkTest {
pipeline.getSettings().hsvSaturation.set(100, 255);
pipeline.getSettings().hsvValue.set(190, 255);
pipeline.getSettings().outputShouldDraw = true;
pipeline.getSettings().outputShowMultipleTargets = true;
pipeline.getSettings().outputMaximumTargets = 20;
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Dual;
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;

View File

@@ -66,7 +66,7 @@ public class ShapeBenchmarkTest {
pipeline.getSettings().hsvSaturation.set(100, 255);
pipeline.getSettings().hsvValue.set(190, 255);
pipeline.getSettings().outputShouldDraw = true;
pipeline.getSettings().outputShowMultipleTargets = true;
pipeline.getSettings().outputMaximumTargets = 20;
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Single;
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
pipeline.getSettings().contourShape = ContourShape.Custom;
@@ -87,7 +87,7 @@ public class ShapeBenchmarkTest {
pipeline.getSettings().hsvSaturation.set(100, 255);
pipeline.getSettings().hsvValue.set(190, 255);
pipeline.getSettings().outputShouldDraw = true;
pipeline.getSettings().outputShowMultipleTargets = true;
pipeline.getSettings().outputMaximumTargets = 20;
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Single;
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
pipeline.getSettings().contourShape = ContourShape.Custom;
@@ -109,7 +109,7 @@ public class ShapeBenchmarkTest {
pipeline.getSettings().hsvSaturation.set(100, 255);
pipeline.getSettings().hsvValue.set(190, 255);
pipeline.getSettings().outputShouldDraw = true;
pipeline.getSettings().outputShowMultipleTargets = true;
pipeline.getSettings().outputMaximumTargets = 20;
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Single;
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
pipeline.getSettings().contourShape = ContourShape.Custom;
@@ -131,7 +131,7 @@ public class ShapeBenchmarkTest {
pipeline.getSettings().hsvSaturation.set(100, 255);
pipeline.getSettings().hsvValue.set(190, 255);
pipeline.getSettings().outputShouldDraw = true;
pipeline.getSettings().outputShowMultipleTargets = true;
pipeline.getSettings().outputMaximumTargets = 20;
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Single;
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
pipeline.getSettings().contourShape = ContourShape.Custom;

View File

@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import com.fasterxml.jackson.core.JsonProcessingException;
import edu.wpi.first.cscore.UsbCameraInfo;
import java.nio.file.Path;
import java.util.Collection;
import java.util.List;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Order;
@@ -33,9 +34,12 @@ import org.photonvision.common.configuration.NeuralNetworkModelManager.Family;
import org.photonvision.common.util.TestUtils;
import org.photonvision.vision.camera.CameraQuirk;
import org.photonvision.vision.camera.PVCameraInfo;
import org.photonvision.vision.pipeline.AdvancedPipelineSettings;
import org.photonvision.vision.pipeline.AprilTagPipelineSettings;
import org.photonvision.vision.pipeline.CVPipelineSettings;
import org.photonvision.vision.pipeline.ColoredShapePipelineSettings;
import org.photonvision.vision.pipeline.ObjectDetectionPipelineSettings;
import org.photonvision.vision.pipeline.PipelineType;
import org.photonvision.vision.pipeline.ReflectivePipelineSettings;
public class SQLConfigTest {
@@ -155,4 +159,41 @@ public class SQLConfigTest {
ConfigManager.INSTANCE = null;
}
@Test
public void testMaxDetectionsMigration() {
var folder = TestUtils.getConfigDirectoriesPath(false).resolve("2025.3.1-old-nnmm");
var cfgManager = new ConfigManager(folder, new SqlConfigProvider(folder));
// Replace global configmanager
ConfigManager.INSTANCE = cfgManager;
assertDoesNotThrow(cfgManager::load);
Collection<CameraConfiguration> cameraConfigs =
cfgManager.getConfig().getCameraConfigurations().values();
for (CameraConfiguration cc : cameraConfigs) {
for (CVPipelineSettings ps : cc.pipelineSettings) {
if (ps instanceof AdvancedPipelineSettings adps) {
AdvancedPipelineSettings finalPs = adps;
if (finalPs.pipelineType.equals(PipelineType.AprilTag)
|| finalPs.pipelineType.equals(PipelineType.Aruco)) {
// Tag pipelines don't have max detections, so skip
continue;
} else if (finalPs.pipelineNickname.equals("TEST MIGRATION")) {
// This is our colored shape pipeline that we set to 1 before saving
assertEquals(1, finalPs.outputMaximumTargets);
} else {
// All others should be at default 20
assertEquals(20, finalPs.outputMaximumTargets);
}
} else {
System.out.println("Skipping pipeline settings type: " + ps.getClass().getSimpleName());
}
}
}
ConfigManager.INSTANCE = null;
}
}

View File

@@ -137,4 +137,31 @@ public class AprilTagTest {
assertEquals(2, pose.getTranslation().getY(), 0.2);
assertEquals(0.0, pose.getTranslation().getZ(), 0.2);
}
@Test
public void testManyDetections() {
// Given a 36h11 pipeline
var pipeline = new AprilTagPipeline();
pipeline.getSettings().inputShouldShow = true;
pipeline.getSettings().outputShouldDraw = true;
pipeline.getSettings().solvePNPEnabled = true;
pipeline.getSettings().cornerDetectionAccuracyPercentage = 4;
pipeline.getSettings().cornerDetectionUseConvexHulls = true;
pipeline.getSettings().tagFamily = AprilTagFamily.kTag36h11;
pipeline.getSettings().outputMaximumTargets = 3; // bogus
// when we have a picture with 280 targets
var frameProvider =
new FileFrameProvider(
TestUtils.getApriltagImagePath(TestUtils.ApriltagTestImages.k36h11_stress_test, false),
TestUtils.WPI2020Image.FOV,
TestUtils.getCoeffs(TestUtils.LIMELIGHT_480P_CAL_FILE, false));
frameProvider.requestFrameThresholdType(pipeline.getThresholdType());
CVPipelineResult pipelineResult;
pipelineResult = pipeline.run(frameProvider.get(), QuirkyCamera.DefaultCamera);
// the pipeline will only give us Byte.MAX_VALUE many
assertEquals(Byte.MAX_VALUE, pipelineResult.targets.size());
}
}

View File

@@ -94,7 +94,7 @@ public class CirclePNPTest {
pipeline.getSettings().cameraCalibration = getCoeffs(LIFECAM_480P_CAL_FILE);
pipeline.getSettings().targetModel = TargetModel.kCircularPowerCell7in;
pipeline.getSettings().outputShouldDraw = true;
pipeline.getSettings().outputShowMultipleTargets = false;
pipeline.getSettings().outputMaximumTargets = 20;
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Single;
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
pipeline.getSettings().contourShape = ContourShape.Circle;
@@ -144,7 +144,7 @@ public class CirclePNPTest {
settings.hsvSaturation.set(100, 255);
settings.hsvValue.set(190, 255);
settings.outputShouldDraw = true;
settings.outputShowMultipleTargets = true;
settings.outputMaximumTargets = 20;
settings.contourGroupingMode = ContourGroupingMode.Dual;
settings.contourIntersection = ContourIntersectionDirection.Up;

View File

@@ -102,7 +102,7 @@ public class ColoredShapePipelineTest {
settings.hsvSaturation.set(100, 255);
settings.hsvValue.set(100, 255);
settings.outputShouldDraw = true;
settings.outputShowMultipleTargets = true;
settings.outputMaximumTargets = 20;
settings.contourGroupingMode = ContourGroupingMode.Single;
settings.contourIntersection = ContourIntersectionDirection.Up;
settings.contourShape = ContourShape.Triangle;

View File

@@ -0,0 +1,78 @@
/*
* 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 static org.junit.jupiter.api.Assertions.assertEquals;
import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.photonvision.common.LoadJNI;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.util.TestUtils;
import org.photonvision.vision.camera.QuirkyCamera;
import org.photonvision.vision.frame.provider.FileFrameProvider;
import org.photonvision.vision.opencv.ContourShape;
import org.photonvision.vision.pipe.impl.HSVPipe;
import org.photonvision.vision.pipeline.result.CVPipelineResult;
public class MaxDetectionsTest {
@Test
public void testMaxDetections() {
LoadJNI.loadLibraries();
ConfigManager.getInstance().load();
ColoredShapePipeline pipeline = new ColoredShapePipeline();
pipeline.settings.contourShape = ContourShape.Circle;
pipeline.settings.hsvHue.set(140, 160);
pipeline.settings.hsvSaturation.set(226, 246);
pipeline.settings.hsvValue.set(188, 208);
pipeline.settings.maxCannyThresh = 90;
pipeline.settings.circleAccuracy = 20;
pipeline.settings.circleDetectThreshold = 5;
Path path =
TestUtils.getResourcesFolderPath(false).resolve("testimages/polygons/ColoredShapeTest.png");
var frameProvider = new FileFrameProvider(path, TestUtils.WPI2019Image.FOV);
// VisionRunner normally does this
var hsvParams =
new HSVPipe.HSVParams(
pipeline.getSettings().hsvHue,
pipeline.getSettings().hsvSaturation,
pipeline.getSettings().hsvValue,
pipeline.getSettings().hueInverted);
frameProvider.requestHsvSettings(hsvParams);
frameProvider.requestFrameThresholdType(pipeline.getThresholdType());
CVPipelineResult result = pipeline.run(frameProvider.get(), QuirkyCamera.DefaultCamera);
TestUtils.showImage(result.inputAndOutputFrame.processedImage.getMat(), "Max Detections Test");
assertEquals(20, result.targets.size());
pipeline.settings.outputMaximumTargets = 5;
result = pipeline.run(frameProvider.get(), QuirkyCamera.DefaultCamera);
assertEquals(5, result.targets.size());
pipeline.settings.outputMaximumTargets = 50;
result = pipeline.run(frameProvider.get(), QuirkyCamera.DefaultCamera);
// 24 circles, but we only detect 22
assertEquals(22, result.targets.size());
}
}

View File

@@ -41,7 +41,7 @@ public class ReflectivePipelineTest {
pipeline.getSettings().hsvSaturation.set(100, 255);
pipeline.getSettings().hsvValue.set(190, 255);
pipeline.getSettings().outputShouldDraw = true;
pipeline.getSettings().outputShowMultipleTargets = true;
pipeline.getSettings().outputMaximumTargets = 20;
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Dual;
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
@@ -120,7 +120,7 @@ public class ReflectivePipelineTest {
settings.hsvSaturation.set(100, 255);
settings.hsvValue.set(190, 255);
settings.outputShouldDraw = true;
settings.outputShowMultipleTargets = true;
settings.outputMaximumTargets = 20;
settings.contourGroupingMode = ContourGroupingMode.Dual;
settings.contourIntersection = ContourIntersectionDirection.Up;

View File

@@ -89,7 +89,6 @@ public class SolvePNPTest {
pipeline.getSettings().hsvSaturation.set(100, 255);
pipeline.getSettings().hsvValue.set(190, 255);
pipeline.getSettings().outputShouldDraw = true;
pipeline.getSettings().outputShowMultipleTargets = true;
pipeline.getSettings().solvePNPEnabled = true;
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Dual;
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
@@ -225,7 +224,6 @@ public class SolvePNPTest {
settings.hsvSaturation.set(100, 255);
settings.hsvValue.set(190, 255);
settings.outputShouldDraw = true;
settings.outputShowMultipleTargets = true;
settings.contourGroupingMode = ContourGroupingMode.Dual;
settings.contourIntersection = ContourIntersectionDirection.Up;

View File

@@ -31,6 +31,8 @@ public class Packet {
// Read and write positions.
int readPos, writePos;
public static final int MAX_ARRAY_LEN = Byte.MAX_VALUE;
/**
* Constructs an empty packet. This buffer will dynamically expand if we need more data space.
*

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB