Compare commits

...

6 Commits

Author SHA1 Message Date
Matt
d9c2a382f1 Update build.yml (#1276) 2024-03-14 00:32:08 -05:00
Matt
e125632960 Free native resources in apriltag pipelines (#1272)
Addresses memory leak when switching between apriltag/aruco pipelines
2024-03-14 01:22:32 -04:00
Matt
d8f82bf9ee Opencv cal: CALIB_USE_LU and use camera focal length guess (#1268) 2024-03-09 08:31:54 -05:00
Matt
587ac478f4 Bump mrcal to include solver fixes (#1265) 2024-03-06 10:51:49 -05:00
Matt
bad676f67c Pipe cscore logs through photonvision (#1260)
This means we can see even more logs about mjpeg server status as well
2024-03-04 23:27:39 -05:00
Matt
71128d1569 Create smoketest mode (#1264)
Create test mode that exists after confirming libraries load OK
2024-03-04 23:24:23 -05:00
20 changed files with 369 additions and 50 deletions

View File

@@ -260,6 +260,83 @@ jobs:
with:
name: jar-${{ matrix.artifact-name }}
path: photon-server/build/libs
run-smoketest-native:
needs: [build-package]
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
artifact-name: jar-Linux
extraOpts: -Djdk.lang.Process.launchMechanism=vfork
- os: windows-latest
artifact-name: jar-Win64
extraOpts: ""
- os: macos-latest
artifact-name: jar-macOS
architecture: x64
runs-on: ${{ matrix.os }}
steps:
- name: Install Java 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: temurin
- uses: actions/download-artifact@v4
with:
name: ${{ matrix.artifact-name }}
# On linux, install mrcal packages
- run: |
sudo apt-get update
sudo apt-get install --yes libcholmod3 liblapack3 libsuitesparseconfig5
if: ${{ (matrix.os) == 'ubuntu-latest' }}
# and actually run the jar
- run: java -jar ${{ matrix.extraOpts }} *.jar --smoketest
if: ${{ (matrix.os) != 'windows-latest' }}
- run: ls *.jar | %{ Write-Host "Running $($_.Name)"; Start-Process "java" -ArgumentList "-jar `"$($_.FullName)`" --smoketest" -NoNewWindow -Wait; break }
if: ${{ (matrix.os) == 'windows-latest' }}
run-smoketest-chroot:
needs: [build-package]
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
artifact-name: LinuxArm64
image_suffix: RaspberryPi
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.4/photonvision_raspi.img.xz
cpu: cortex-a7
image_additional_mb: 0
extraOpts: -Djdk.lang.Process.launchMechanism=vfork
runs-on: ${{ matrix.os }}
name: smoketest-${{ matrix.image_suffix }}
steps:
- uses: actions/download-artifact@v4
with:
name: jar-${{ matrix.artifact-name }}
- uses: pguyot/arm-runner-action@v2
name: Run photon smoketest
id: generate_image
with:
base_image: ${{ matrix.image_url }}
image_additional_mb: ${{ matrix.image_additional_mb }}
optimize_image: yes
cpu: ${{ matrix.cpu }}
# We do _not_ wanna copy photon into the image. Bind mount instead
bind_mount_repository: true
# our image better have java installed already
commands: |
java -jar ${{ matrix.extraOpts }} *.jar --smoketest
build-image:
needs: [build-package]
@@ -290,13 +367,13 @@ jobs:
- os: ubuntu-latest
artifact-name: LinuxArm64
image_suffix: orangepi5
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.9/photonvision_opi5.img.xz
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.10/photonvision_opi5.img.xz
cpu: cortex-a8
image_additional_mb: 4096
- os: ubuntu-latest
artifact-name: LinuxArm64
image_suffix: orangepi5plus
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.9/photonvision_opi5plus.img.xz
image_url: https://github.com/PhotonVision/photon-image-modifier/releases/download/v2024.0.10/photonvision_opi5plus.img.xz
cpu: cortex-a8
image_additional_mb: 4096

View File

@@ -32,7 +32,7 @@ ext {
photonGlDriverLibVersion = "dev-v2023.1.0-9-g75fc678"
rknnVersion = "dev-v2024.0.0-64-gc0836a6"
frcYear = "2024"
mrcalVersion = "dev-v2024.0.0-7-gc976aaa";
mrcalVersion = "dev-v2024.0.0-18-gb903a09";
pubVersion = versionString

View File

@@ -23,5 +23,6 @@ public enum LogGroup {
VisionModule,
Data,
General,
Config
Config,
CSCore,
}

View File

@@ -99,6 +99,7 @@ public class Logger {
levelMap.put(LogGroup.Data, LogLevel.INFO);
levelMap.put(LogGroup.VisionModule, LogLevel.INFO);
levelMap.put(LogGroup.Config, LogLevel.INFO);
levelMap.put(LogGroup.CSCore, LogLevel.TRACE);
}
static {
@@ -194,7 +195,7 @@ public class Logger {
return logLevel.code <= levelMap.get(group).code;
}
private void log(String message, LogLevel level) {
void log(String message, LogLevel level) {
if (shouldLog(level)) {
log(message, level, group, className);
}

View File

@@ -0,0 +1,70 @@
/*
* 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.common.logging;
import edu.wpi.first.cscore.CameraServerJNI;
import java.nio.file.Path;
/** Redirect cscore logs to our logger */
public class PvCSCoreLogger {
private static PvCSCoreLogger INSTANCE;
public static PvCSCoreLogger getInstance() {
if (INSTANCE == null) {
INSTANCE = new PvCSCoreLogger();
}
return INSTANCE;
}
private Logger logger;
private PvCSCoreLogger() {
CameraServerJNI.setLogger(this::logMsg, 7);
this.logger = new Logger(getClass(), LogGroup.CSCore);
}
private void logMsg(int level, String file, int line, String msg) {
if (level == 20) {
logger.info(msg);
return;
}
file = Path.of(file).getFileName().toString();
String levelmsg;
LogLevel pvlevel;
if (level >= 50) {
levelmsg = "CRITICAL";
pvlevel = LogLevel.ERROR;
} else if (level >= 40) {
levelmsg = "ERROR";
pvlevel = LogLevel.ERROR;
} else if (level >= 30) {
levelmsg = "WARNING";
pvlevel = LogLevel.WARN;
} else if (level >= 20) {
levelmsg = "INFO";
pvlevel = LogLevel.INFO;
} else {
levelmsg = "DEBUG";
pvlevel = LogLevel.DEBUG;
}
logger.log(
"CS: " + levelmsg + " " + level + ": " + msg + " (" + file + ":" + line + ")", pvlevel);
}
}

View File

@@ -23,16 +23,37 @@ import java.util.Comparator;
import org.opencv.core.Mat;
import org.opencv.objdetect.ArucoDetector;
import org.opencv.objdetect.DetectorParameters;
import org.opencv.objdetect.Dictionary;
import org.opencv.objdetect.Objdetect;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.Logger;
import org.photonvision.vision.opencv.Releasable;
/** This class wraps an {@link ArucoDetector} for convenience. */
public class PhotonArucoDetector {
public class PhotonArucoDetector implements Releasable {
private static final Logger logger = new Logger(PhotonArucoDetector.class, LogGroup.VisionModule);
private final ArucoDetector detector =
new ArucoDetector(Objdetect.getPredefinedDictionary(Objdetect.DICT_APRILTAG_16h5));
private static class ArucoDetectorHack extends ArucoDetector {
public ArucoDetectorHack(Dictionary predefinedDictionary) {
super(predefinedDictionary);
}
// avoid double-free by keeping track of this ourselves (ew)
private boolean freed = false;
@Override
public void finalize() throws Throwable {
if (freed) {
return;
}
super.finalize();
freed = true;
}
}
private final ArucoDetectorHack detector =
new ArucoDetectorHack(Objdetect.getPredefinedDictionary(Objdetect.DICT_APRILTAG_16h5));
private final Mat ids = new Mat();
private final ArrayList<Mat> cornerMats = new ArrayList<>();
@@ -95,4 +116,16 @@ public class PhotonArucoDetector {
return results;
}
@Override
public void release() {
try {
detector.finalize();
} catch (Throwable e) {
logger.error("Exception destroying PhotonArucoDetector", e);
}
ids.release();
for (var m : cornerMats) m.release();
cornerMats.clear();
}
}

View File

@@ -33,7 +33,7 @@ import org.photonvision.vision.opencv.CVMat;
* path}.
*/
public class FileFrameProvider extends CpuImageProcessor {
public static final int MAX_FPS = 5;
public static final int MAX_FPS = 10;
private static int count = 0;
private final int thisIndex = count++;

View File

@@ -21,11 +21,13 @@ import edu.wpi.first.apriltag.AprilTagDetection;
import edu.wpi.first.apriltag.AprilTagDetector;
import java.util.List;
import org.photonvision.vision.opencv.CVMat;
import org.photonvision.vision.opencv.Releasable;
import org.photonvision.vision.pipe.CVPipe;
public class AprilTagDetectionPipe
extends CVPipe<CVMat, List<AprilTagDetection>, AprilTagDetectionPipeParams> {
private final AprilTagDetector m_detector = new AprilTagDetector();
extends CVPipe<CVMat, List<AprilTagDetection>, AprilTagDetectionPipeParams>
implements Releasable {
private AprilTagDetector m_detector = new AprilTagDetector();
public AprilTagDetectionPipe() {
super();
@@ -40,6 +42,10 @@ public class AprilTagDetectionPipe
return List.of();
}
if (m_detector == null) {
throw new RuntimeException("Apriltag detector was released!");
}
var ret = m_detector.detect(in.getMat());
if (ret == null) {
@@ -60,4 +66,10 @@ public class AprilTagDetectionPipe
super.setParams(newParams);
}
@Override
public void release() {
m_detector.close();
m_detector = null;
}
}

View File

@@ -25,13 +25,15 @@ import org.opencv.calib3d.Calib3d;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
import org.photonvision.vision.opencv.Releasable;
import org.photonvision.vision.pipe.CVPipe;
public class AprilTagPoseEstimatorPipe
extends CVPipe<
AprilTagDetection,
AprilTagPoseEstimate,
AprilTagPoseEstimatorPipe.AprilTagPoseEstimatorPipeParams> {
AprilTagPoseEstimatorPipe.AprilTagPoseEstimatorPipeParams>
implements Releasable {
private final AprilTagPoseEstimator m_poseEstimator =
new AprilTagPoseEstimator(new AprilTagPoseEstimator.Config(0, 0, 0, 0, 0));
@@ -92,6 +94,11 @@ public class AprilTagPoseEstimatorPipe
super.setParams(newParams);
}
@Override
public void release() {
temp.release();
}
public static class AprilTagPoseEstimatorPipeParams {
final AprilTagPoseEstimator.Config config;
final CameraCalibrationCoefficients calibration;

View File

@@ -29,10 +29,12 @@ import org.opencv.objdetect.Objdetect;
import org.photonvision.vision.aruco.ArucoDetectionResult;
import org.photonvision.vision.aruco.PhotonArucoDetector;
import org.photonvision.vision.opencv.CVMat;
import org.photonvision.vision.opencv.Releasable;
import org.photonvision.vision.pipe.CVPipe;
public class ArucoDetectionPipe
extends CVPipe<CVMat, List<ArucoDetectionResult>, ArucoDetectionPipeParams> {
extends CVPipe<CVMat, List<ArucoDetectionResult>, ArucoDetectionPipeParams>
implements Releasable {
// ArucoDetector wrapper class
private final PhotonArucoDetector photonDetector = new PhotonArucoDetector();
@@ -131,4 +133,9 @@ public class ArucoDetectionPipe
var pt2 = new Point(corner.x + windowSize, corner.y + windowSize);
Imgproc.rectangle(outputMat, pt1, pt2, new Scalar(0, 0, 255), thickness);
}
@Override
public void release() {
photonDetector.release();
}
}

View File

@@ -32,13 +32,15 @@ import org.opencv.core.MatOfPoint3f;
import org.opencv.core.Point3;
import org.photonvision.vision.aruco.ArucoDetectionResult;
import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
import org.photonvision.vision.opencv.Releasable;
import org.photonvision.vision.pipe.CVPipe;
public class ArucoPoseEstimatorPipe
extends CVPipe<
ArucoDetectionResult,
AprilTagPoseEstimate,
ArucoPoseEstimatorPipe.ArucoPoseEstimatorPipeParams> {
ArucoPoseEstimatorPipe.ArucoPoseEstimatorPipeParams>
implements Releasable {
// image points of marker corners
private final MatOfPoint2f imagePoints = new MatOfPoint2f(Mat.zeros(4, 1, CvType.CV_32FC2));
// rvec/tvec estimations from solvepnp
@@ -117,6 +119,18 @@ public class ArucoPoseEstimatorPipe
super.setParams(newParams);
}
@Override
public void release() {
imagePoints.release();
for (var m : rvecs) m.release();
rvecs.clear();
for (var m : tvecs) m.release();
tvecs.clear();
rvec.release();
tvec.release();
reprojectionErrors.release();
}
public static class ArucoPoseEstimatorPipeParams {
final CameraCalibrationCoefficients calibration;
final double tagSize;

View File

@@ -38,13 +38,26 @@ import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
import org.photonvision.vision.calibration.CameraLensModel;
import org.photonvision.vision.calibration.JsonImageMat;
import org.photonvision.vision.calibration.JsonMatOfDouble;
import org.photonvision.vision.frame.FrameStaticProperties;
import org.photonvision.vision.pipe.CVPipe;
import org.photonvision.vision.pipe.impl.FindBoardCornersPipe.FindBoardCornersPipeResult;
public class Calibrate3dPipe
extends CVPipe<
List<FindBoardCornersPipe.FindBoardCornersPipeResult>,
Calibrate3dPipe.CalibrationInput,
CameraCalibrationCoefficients,
Calibrate3dPipe.CalibratePipeParams> {
public static class CalibrationInput {
final List<FindBoardCornersPipe.FindBoardCornersPipeResult> observations;
final FrameStaticProperties imageProps;
public CalibrationInput(
List<FindBoardCornersPipeResult> observations, FrameStaticProperties imageProps) {
this.observations = observations;
this.imageProps = imageProps;
}
}
// For logging
private static final Logger logger = new Logger(Calibrate3dPipe.class, LogGroup.General);
@@ -63,10 +76,9 @@ public class Calibrate3dPipe
* @return Result of processing.
*/
@Override
protected CameraCalibrationCoefficients process(
List<FindBoardCornersPipe.FindBoardCornersPipeResult> in) {
in =
in.stream()
protected CameraCalibrationCoefficients process(CalibrationInput in) {
var filteredIn =
in.observations.stream()
.filter(
it ->
it != null
@@ -79,17 +91,21 @@ public class Calibrate3dPipe
var start = System.nanoTime();
if (MrCalJNILoader.getInstance().isLoaded() && params.useMrCal) {
logger.debug("Calibrating with mrcal!");
ret = calibrateMrcal(in);
ret =
calibrateMrcal(
filteredIn, in.imageProps.horizontalFocalLength, in.imageProps.verticalFocalLength);
} else {
logger.debug("Calibrating with opencv!");
ret = calibrateOpenCV(in);
ret =
calibrateOpenCV(
filteredIn, in.imageProps.horizontalFocalLength, in.imageProps.verticalFocalLength);
}
var dt = System.nanoTime() - start;
if (ret != null)
logger.info(
"CALIBRATION SUCCESS for res "
+ in.get(0).size
+ in.observations.get(0).size
+ " in "
+ dt / 1e6
+ "ms! camMatrix: \n"
@@ -103,7 +119,7 @@ public class Calibrate3dPipe
}
protected CameraCalibrationCoefficients calibrateOpenCV(
List<FindBoardCornersPipe.FindBoardCornersPipeResult> in) {
List<FindBoardCornersPipe.FindBoardCornersPipeResult> in, double fxGuess, double fyGuess) {
List<Mat> objPoints = in.stream().map(it -> it.objectPoints).collect(Collectors.toList());
List<Mat> imgPts = in.stream().map(it -> it.imagePoints).collect(Collectors.toList());
if (objPoints.size() != imgPts.size()) {
@@ -111,30 +127,32 @@ public class Calibrate3dPipe
return null;
}
Mat cameraMatrix = new Mat();
Mat cameraMatrix = new Mat(3, 3, CvType.CV_64F);
MatOfDouble distortionCoefficients = new MatOfDouble();
List<Mat> rvecs = new ArrayList<>();
List<Mat> tvecs = new ArrayList<>();
// RMS of the calibration
double calibrationAccuracy;
// initial camera matrix guess
double cx = (in.get(0).size.width / 2.0) - 0.5;
double cy = (in.get(0).size.width / 2.0) - 0.5;
cameraMatrix.put(0, 0, new double[] {fxGuess, 0, cx, 0, fyGuess, cy, 0, 0, 1});
try {
// FindBoardCorners pipe outputs all the image points, object points, and frames to calculate
// imageSize from, other parameters are output Mats
calibrationAccuracy =
Calib3d.calibrateCameraExtended(
objPoints,
imgPts,
new Size(in.get(0).size.width, in.get(0).size.height),
cameraMatrix,
distortionCoefficients,
rvecs,
tvecs,
stdDeviationsIntrinsics,
stdDeviationsExtrinsics,
perViewErrors);
Calib3d.calibrateCameraExtended(
objPoints,
imgPts,
new Size(in.get(0).size.width, in.get(0).size.height),
cameraMatrix,
distortionCoefficients,
rvecs,
tvecs,
stdDeviationsIntrinsics,
stdDeviationsExtrinsics,
perViewErrors,
Calib3d.CALIB_USE_LU + Calib3d.CALIB_USE_INTRINSIC_GUESS);
} catch (Exception e) {
logger.error("Calibration failed!", e);
e.printStackTrace();
@@ -164,13 +182,12 @@ public class Calibrate3dPipe
}
protected CameraCalibrationCoefficients calibrateMrcal(
List<FindBoardCornersPipe.FindBoardCornersPipeResult> in) {
List<FindBoardCornersPipe.FindBoardCornersPipeResult> in, double fxGuess, double fyGuess) {
List<MatOfPoint2f> corner_locations =
in.stream().map(it -> it.imagePoints).map(MatOfPoint2f::new).collect(Collectors.toList());
int imageWidth = (int) in.get(0).size.width;
int imageHeight = (int) in.get(0).size.height;
final double FOCAL_LENGTH_GUESS = 1200;
MrCalResult result =
MrCalJNI.calibrateCamera(
@@ -180,7 +197,7 @@ public class Calibrate3dPipe
params.squareSize,
imageWidth,
imageHeight,
FOCAL_LENGTH_GUESS);
(fxGuess + fyGuess) / 2.0);
// intrinsics are fx fy cx cy from mrcal
JsonMatOfDouble cameraMatrixMat =

View File

@@ -36,6 +36,7 @@ import org.photonvision.vision.frame.FrameThresholdType;
import org.photonvision.vision.opencv.CVMat;
import org.photonvision.vision.opencv.ImageRotationMode;
import org.photonvision.vision.pipe.CVPipe.CVPipeResult;
import org.photonvision.vision.pipe.impl.Calibrate3dPipe.CalibrationInput;
import org.photonvision.vision.pipe.impl.FindBoardCornersPipe.FindBoardCornersPipeResult;
import org.photonvision.vision.pipeline.CVPipeline;
import org.photonvision.vision.pipeline.Calibration3dPipelineSettings;
@@ -176,7 +177,8 @@ public class Calibrate3dPipeline
/*Pass the board corners to the pipe, which will check again to see if all boards are valid
and returns the corresponding image and object points*/
calibrationOutput = calibrate3dPipe.run(foundCornersList);
calibrationOutput =
calibrate3dPipe.run(new CalibrationInput(foundCornersList, frameStaticProperties));
this.calibrating = false;
@@ -229,4 +231,9 @@ public class Calibrate3dPipeline
public CameraCalibrationCoefficients cameraCalibrationCoefficients() {
return calibrationOutput.output;
}
@Override
public void release() {
// we never actually need to give resources up since pipelinemanager only makes one of us
}
}

View File

@@ -221,4 +221,11 @@ public class AprilTagPipeline extends CVPipeline<CVPipelineResult, AprilTagPipel
return new CVPipelineResult(sumPipeNanosElapsed, fps, targetList, multiTagResult, frame);
}
@Override
public void release() {
aprilTagDetectionPipe.release();
singleTagPoseEstimatorPipe.release();
super.release();
}
}

View File

@@ -60,8 +60,8 @@ import org.photonvision.vision.target.TrackedTarget;
import org.photonvision.vision.target.TrackedTarget.TargetCalculationParameters;
public class ArucoPipeline extends CVPipeline<CVPipelineResult, ArucoPipelineSettings> {
private final ArucoDetectionPipe arucoDetectionPipe = new ArucoDetectionPipe();
private final ArucoPoseEstimatorPipe singleTagPoseEstimatorPipe = new ArucoPoseEstimatorPipe();
private ArucoDetectionPipe arucoDetectionPipe = new ArucoDetectionPipe();
private ArucoPoseEstimatorPipe singleTagPoseEstimatorPipe = new ArucoPoseEstimatorPipe();
private final MultiTargetPNPPipe multiTagPNPPipe = new MultiTargetPNPPipe();
private final CalculateFPSPipe calculateFPSPipe = new CalculateFPSPipe();
@@ -250,4 +250,13 @@ public class ArucoPipeline extends CVPipeline<CVPipelineResult, ArucoPipelineSet
windowSize,
constant);
}
@Override
public void release() {
arucoDetectionPipe.release();
singleTagPoseEstimatorPipe.release();
arucoDetectionPipe = null;
singleTagPoseEstimatorPipe = null;
super.release();
}
}

View File

@@ -34,6 +34,9 @@ public abstract class CVPipeline<R extends CVPipelineResult, S extends CVPipelin
private final FrameThresholdType thresholdType;
// So releaseable doesn't keep track of if we double-free something. so (ew) remember that here
protected volatile boolean released = false;
public CVPipeline(FrameThresholdType thresholdType) {
this.thresholdType = thresholdType;
}
@@ -64,6 +67,9 @@ public abstract class CVPipeline<R extends CVPipelineResult, S extends CVPipelin
}
public R run(Frame frame, QuirkyCamera cameraQuirks) {
if (released) {
throw new RuntimeException("Pipeline use-after-free!");
}
if (settings == null) {
throw new RuntimeException("No settings provided for pipeline!");
}
@@ -85,5 +91,7 @@ public abstract class CVPipeline<R extends CVPipelineResult, S extends CVPipelin
* switch. Stubbed out, but override if needed.
*/
@Override
public void release() {}
public void release() {
released = true;
}
}

View File

@@ -88,4 +88,9 @@ public class DriverModePipeline
fps,
new Frame(frame.processedImage, frame.colorImage, frame.type, frame.frameStaticProperties));
}
@Override
public void release() {
// we never actually need to give resources up since pipelinemanager only makes one of us
}
}

View File

@@ -131,5 +131,6 @@ public class ObjectDetectionPipeline
@Override
public void release() {
rknnPipe.release();
super.release();
}
}

View File

@@ -148,6 +148,7 @@ public class PipelineManager {
* @return The currently active pipeline.
*/
public CVPipeline getCurrentPipeline() {
updatePipelineFromRequested();
if (currentPipelineIndex < 0) {
switch (currentPipelineIndex) {
case CAL_3D_INDEX:
@@ -170,6 +171,8 @@ public class PipelineManager {
return getPipelineSettings(currentPipelineIndex);
}
private volatile int requestedIndex = 0;
/**
* Internal method for setting the active pipeline. <br>
* <br>
@@ -179,6 +182,22 @@ public class PipelineManager {
* @param newIndex Index of pipeline to be active
*/
private void setPipelineInternal(int newIndex) {
requestedIndex = newIndex;
}
/**
* Based on a requested pipeline index, create/destroy pipelines as necessary. We do this as a
* side effect of the main thread that calls getCurrentPipeline to avoid race conditions between
* server threads and the VisionRunner TODO: this should be refactored. Shame Java doesn't have
* RAII
*/
private void updatePipelineFromRequested() {
int newIndex = requestedIndex;
if (newIndex == currentPipelineIndex) {
// nothing to do, probably no change -- give up
return;
}
if (newIndex < 0 && currentPipelineIndex >= 0) {
// Transitioning to a built-in pipe, save off the current user one
lastUserPipelineIdx = currentPipelineIndex;
@@ -189,8 +208,8 @@ public class PipelineManager {
return;
}
// Cleanup potential old native resources before swapping over
if (currentUserPipeline != null) {
// Cleanup potential old native resources before swapping over for user pipelines
if (currentUserPipeline != null && !(newIndex < 0)) {
currentUserPipeline.release();
}

View File

@@ -35,6 +35,7 @@ import org.photonvision.common.hardware.Platform;
import org.photonvision.common.logging.LogGroup;
import org.photonvision.common.logging.LogLevel;
import org.photonvision.common.logging.Logger;
import org.photonvision.common.logging.PvCSCoreLogger;
import org.photonvision.common.networking.NetworkManager;
import org.photonvision.common.util.TestUtils;
import org.photonvision.common.util.numbers.IntegerCouple;
@@ -65,6 +66,7 @@ public class Main {
private static final boolean isRelease = PhotonVersion.isRelease;
private static boolean isTestMode = false;
private static boolean isSmoketest = false;
private static Path testModeFolder = null;
private static boolean printDebugLogs;
@@ -90,6 +92,11 @@ public class Main {
"clear-config",
false,
"Clears PhotonVision pipeline and networking settings. Preserves log files");
options.addOption(
"s",
"smoketest",
false,
"Exit Photon after loading native libraries and camera configs, but before starting up camera runners");
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args);
@@ -127,6 +134,10 @@ public class Main {
if (cmd.hasOption("clear-config")) {
ConfigManager.getInstance().clearConfig();
}
if (cmd.hasOption("smoketest")) {
isSmoketest = true;
}
}
return true;
}
@@ -266,7 +277,7 @@ public class Main {
CameraConfiguration camConf2024 =
ConfigManager.getInstance().getConfig().getCameraConfigurations().get("WPI2024");
if (camConf2024 == null || true) {
if (camConf2024 == null) {
camConf2024 =
new CameraConfiguration(
"WPI2024",
@@ -337,11 +348,17 @@ public class Main {
public static void main(String[] args) {
try {
TestUtils.loadLibraries();
logger.info("Native libraries loaded.");
boolean success = TestUtils.loadLibraries();
if (!success) {
logger.error("Failed to load native libraries! Giving up :(");
System.exit(1);
}
} catch (Exception e) {
logger.error("Failed to load native libraries!", e);
System.exit(1);
}
logger.info("Native libraries loaded.");
try {
if (Platform.isRaspberryPi()) {
@@ -393,6 +410,8 @@ public class Main {
+ Platform.getPlatformName()
+ (Platform.isRaspberryPi() ? (" (Pi " + PiVersion.getPiVersion() + ")") : ""));
PvCSCoreLogger.getInstance();
logger.debug("Loading ConfigManager...");
ConfigManager.getInstance().load(); // init config manager
ConfigManager.getInstance().requestSave();
@@ -412,6 +431,11 @@ public class Main {
NeuralNetworkModelManager.getInstance()
.initialize(ConfigManager.getInstance().getModelsDirectory());
if (isSmoketest) {
logger.info("PhotonVision base functionality loaded -- smoketest complete");
System.exit(0);
}
if (!isTestMode) {
logger.debug("Loading VisionSourceManager...");
VisionSourceManager.getInstance()