mirror of
https://github.com/PhotonVision/photonvision
synced 2026-06-22 01:11:40 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6007cc752d | ||
|
|
9cf5c77d69 | ||
|
|
9dc5481d1c | ||
|
|
3948650e6c | ||
|
|
49fcdb64ed | ||
|
|
1e715ce4bb | ||
|
|
f9fd7a0b45 | ||
|
|
e9a3c2d1b8 | ||
|
|
129575dd23 |
138
.github/workflows/main.yml
vendored
138
.github/workflows/main.yml
vendored
@@ -74,8 +74,12 @@ jobs:
|
||||
chmod +x gradlew
|
||||
./gradlew build -x check
|
||||
|
||||
# Run Tests Generate Coverage Report.
|
||||
- name: Gradle Test and Coverage
|
||||
# Run Gradle Tests.
|
||||
- name: Gradle Tests
|
||||
run: ./gradlew testHeadless -i
|
||||
|
||||
# Generate Coverage Report.
|
||||
- name: Gradle Coverage
|
||||
run: ./gradlew jacocoTestReport
|
||||
|
||||
# Publish Coverage Report.
|
||||
@@ -125,58 +129,6 @@ jobs:
|
||||
name: built-docs
|
||||
path: build/html
|
||||
|
||||
photon-build-package:
|
||||
needs: [photonclient-build, photon-build-all, photonserver-build-offline-docs]
|
||||
|
||||
# The type of runner that the job will run on.
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Checkout code.
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
# Install Java 11.
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
|
||||
# Clear any existing web resources.
|
||||
- run: |
|
||||
rm -rf photon-server/src/main/resources/web/*
|
||||
mkdir -p photon-server/src/main/resources/web/docs
|
||||
|
||||
# Download client artifact to resources folder.
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: built-client
|
||||
path: photon-server/src/main/resources/web/
|
||||
|
||||
# Download docs artifact to resources folder.
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: built-docs
|
||||
path: photon-server/src/main/resources/web/docs
|
||||
|
||||
# Build fat jar.
|
||||
- run: |
|
||||
chmod +x gradlew
|
||||
./gradlew photon-server:shadowJar
|
||||
|
||||
# Upload final fat jar as artifact.
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: jar
|
||||
path: photon-server/build/libs
|
||||
|
||||
- uses: eine/tip@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: 'Dev'
|
||||
rm: true
|
||||
files: |
|
||||
photon-server/build/libs/*.jar
|
||||
if: github.event_name == 'push'
|
||||
|
||||
photonserver-check-lint:
|
||||
# The type of runner that the job will run on.
|
||||
runs-on: ubuntu-latest
|
||||
@@ -225,10 +177,13 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: "Photonlib - Build - ${{ matrix.artifact-name }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- run: git fetch --tags --force
|
||||
- run: |
|
||||
chmod +x gradlew
|
||||
./gradlew photon-lib:build
|
||||
@@ -243,7 +198,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- container: wpilib/roborio-cross-ubuntu:2020-18.04
|
||||
- container: wpilib/roborio-cross-ubuntu:2021-18.04
|
||||
artifact-name: Athena
|
||||
- container: wpilib/raspbian-cross-ubuntu:10-18.04
|
||||
artifact-name: Raspbian
|
||||
@@ -254,10 +209,14 @@ jobs:
|
||||
container: ${{ matrix.container }}
|
||||
name: "Photonlib - Build - ${{ matrix.artifact-name }}"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- run: |
|
||||
git describe --tags --exclude="Dev"
|
||||
- run: |
|
||||
chmod +x gradlew
|
||||
./gradlew photon-lib:build
|
||||
@@ -287,8 +246,67 @@ jobs:
|
||||
- name: Install wpiformat
|
||||
run: pip3 install wpiformat
|
||||
- name: Run
|
||||
run: |
|
||||
ls -la
|
||||
wpiformat -clang 10 -f photon-lib
|
||||
run: wpiformat -clang 10 -f photon-lib
|
||||
- name: Check Output
|
||||
run: git --no-pager diff --exit-code HEAD
|
||||
- name: Generate diff
|
||||
run: git diff HEAD > wpiformat-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: wpiformat fixes
|
||||
path: wpiformat-fixes.patch
|
||||
if: ${{ failure() }}
|
||||
|
||||
photon-build-package:
|
||||
needs: [photonclient-build, photon-build-all, photonserver-build-offline-docs, photonlib-build-host, photonlib-build-docker]
|
||||
|
||||
# The type of runner that the job will run on.
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
# Checkout code.
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
# Install Java 11.
|
||||
- uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
|
||||
# Clear any existing web resources.
|
||||
- run: |
|
||||
rm -rf photon-server/src/main/resources/web/*
|
||||
mkdir -p photon-server/src/main/resources/web/docs
|
||||
|
||||
# Download client artifact to resources folder.
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: built-client
|
||||
path: photon-server/src/main/resources/web/
|
||||
|
||||
# Download docs artifact to resources folder.
|
||||
- uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: built-docs
|
||||
path: photon-server/src/main/resources/web/docs
|
||||
|
||||
# Build fat jar.
|
||||
- run: |
|
||||
chmod +x gradlew
|
||||
./gradlew photon-server:shadowJar
|
||||
|
||||
# Upload final fat jar as artifact.
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: jar
|
||||
path: photon-server/build/libs
|
||||
|
||||
- uses: eine/tip@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: 'Dev'
|
||||
rm: true
|
||||
files: |
|
||||
photon-server/build/libs/*.jar
|
||||
if: github.event_name == 'push'
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ public class TestUtils {
|
||||
}
|
||||
|
||||
private static Path getResourcesFolderPath(boolean testMode) {
|
||||
return Path.of("src", (testMode ? "main" : "test"), "resources").toAbsolutePath();
|
||||
return Path.of((testMode ? "src/main" : "src/test"), "resources").toAbsolutePath();
|
||||
}
|
||||
|
||||
public static Path getTestMode2019ImagePath() {
|
||||
|
||||
@@ -85,7 +85,7 @@ public class PicamJNI {
|
||||
}
|
||||
|
||||
public static boolean isSupported() {
|
||||
return libraryLoaded && isVCSMSupported() && getSensorModel() != SensorModel.Disconnected;
|
||||
return libraryLoaded && !isVCSMSupported() && getSensorModel() != SensorModel.Disconnected;
|
||||
}
|
||||
|
||||
public static SensorModel getSensorModel() {
|
||||
|
||||
@@ -136,22 +136,22 @@ public class ZeroCopyPicamSource extends VisionSource {
|
||||
@Override
|
||||
public void setExposure(double exposure) {
|
||||
lastExposure = exposure;
|
||||
var success = PicamJNI.setExposure((int) Math.round(exposure));
|
||||
if (!success) logger.warn("Couldn't set Pi camera exposure");
|
||||
var failure = PicamJNI.setExposure((int) Math.round(exposure));
|
||||
if (failure) logger.warn("Couldn't set Pi camera exposure");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBrightness(int brightness) {
|
||||
lastBrightness = brightness;
|
||||
var success = PicamJNI.setBrightness(brightness);
|
||||
if (!success) logger.warn("Couldn't set Pi camera brightness");
|
||||
var failure = PicamJNI.setBrightness(brightness);
|
||||
if (failure) logger.warn("Couldn't set Pi camera brightness");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGain(int gain) {
|
||||
lastGain = gain;
|
||||
var success = PicamJNI.setGain(gain);
|
||||
if (!success) logger.warn("Couldn't set Pi camera gain");
|
||||
var failure = PicamJNI.setGain(gain);
|
||||
if (failure) logger.warn("Couldn't set Pi camera gain");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -162,12 +162,12 @@ public class ZeroCopyPicamSource extends VisionSource {
|
||||
@Override
|
||||
protected void setVideoModeInternal(VideoMode videoMode) {
|
||||
var mode = (FPSRatedVideoMode) videoMode;
|
||||
var success = PicamJNI.destroyCamera();
|
||||
if (!success)
|
||||
var failure = PicamJNI.destroyCamera();
|
||||
if (failure)
|
||||
throw new RuntimeException(
|
||||
"Couldn't destroy a zero copy Pi camera while switching video modes");
|
||||
success = PicamJNI.createCamera(mode.width, mode.height, mode.fpsActual);
|
||||
if (!success)
|
||||
failure = PicamJNI.createCamera(mode.width, mode.height, mode.fpsActual);
|
||||
if (failure)
|
||||
throw new RuntimeException(
|
||||
"Couldn't create a zero copy Pi camera while switching video modes");
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ public class AcceleratedPicamFrameProvider implements FrameProvider {
|
||||
this.settables = visionSettables;
|
||||
|
||||
var vidMode = settables.getCurrentVideoMode();
|
||||
var success = PicamJNI.createCamera(vidMode.width, vidMode.height, vidMode.fps);
|
||||
if (!success) {
|
||||
success = PicamJNI.destroyCamera();
|
||||
if (!success) throw new RuntimeException("Couldn't destroy Pi camera after init failure!");
|
||||
var failure = PicamJNI.createCamera(vidMode.width, vidMode.height, vidMode.fps);
|
||||
if (failure) {
|
||||
failure = PicamJNI.destroyCamera();
|
||||
if (failure) throw new RuntimeException("Couldn't destroy Pi camera after init failure!");
|
||||
throw new RuntimeException(
|
||||
"Couldn't initialize zero copy Pi camera; check stdout for native code logs");
|
||||
}
|
||||
|
||||
@@ -302,6 +302,7 @@ public class VisionSourceManager {
|
||||
var cameraSources = new ArrayList<VisionSource>();
|
||||
for (var configuration : camConfigs) {
|
||||
if (configuration.baseName.startsWith("mmal service") && PicamJNI.isSupported()) {
|
||||
configuration.cameraType = CameraType.ZeroCopyPicam;
|
||||
var piCamSrc = new ZeroCopyPicamSource(configuration);
|
||||
|
||||
configuration.cameraType = CameraType.ZeroCopyPicam;
|
||||
|
||||
@@ -79,6 +79,10 @@ public class Calibrate3dPipeTest {
|
||||
assertTrue(calibrate3dPipeOutput.output.perViewErrors.length > 0);
|
||||
System.out.println(
|
||||
"Per View Errors: " + Arrays.toString(calibrate3dPipeOutput.output.perViewErrors));
|
||||
|
||||
for (var f : frames) {
|
||||
f.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -98,14 +102,14 @@ public class Calibrate3dPipeTest {
|
||||
|
||||
for (var file : directoryListing) {
|
||||
calibration3dPipeline.takeSnapshot();
|
||||
var output =
|
||||
calibration3dPipeline.run(
|
||||
new Frame(
|
||||
new CVMat(Imgcodecs.imread(file.getAbsolutePath())),
|
||||
new FrameStaticProperties(640, 480, 60, new Rotation2d(), null)),
|
||||
QuirkyCamera.DefaultCamera);
|
||||
var frame =
|
||||
new Frame(
|
||||
new CVMat(Imgcodecs.imread(file.getAbsolutePath())),
|
||||
new FrameStaticProperties(640, 480, 60, new Rotation2d(), null));
|
||||
var output = calibration3dPipeline.run(frame, QuirkyCamera.DefaultCamera);
|
||||
// TestUtils.showImage(output.outputFrame.image.getMat());
|
||||
output.release();
|
||||
frame.release();
|
||||
}
|
||||
|
||||
assertTrue(
|
||||
@@ -114,13 +118,12 @@ public class Calibrate3dPipeTest {
|
||||
.allMatch(it -> it.width() > 0 && it.height() > 0));
|
||||
|
||||
calibration3dPipeline.removeSnapshot(0);
|
||||
calibration3dPipeline
|
||||
.run(
|
||||
new Frame(
|
||||
new CVMat(Imgcodecs.imread(directoryListing[0].getAbsolutePath())),
|
||||
new FrameStaticProperties(640, 480, 60, new Rotation2d(), null)),
|
||||
QuirkyCamera.DefaultCamera)
|
||||
.release();
|
||||
var frame =
|
||||
new Frame(
|
||||
new CVMat(Imgcodecs.imread(directoryListing[0].getAbsolutePath())),
|
||||
new FrameStaticProperties(640, 480, 60, new Rotation2d(), null));
|
||||
calibration3dPipeline.run(frame, QuirkyCamera.DefaultCamera).release();
|
||||
frame.release();
|
||||
|
||||
assertTrue(
|
||||
calibration3dPipeline.foundCornersList.stream()
|
||||
@@ -263,16 +266,16 @@ public class Calibrate3dPipeTest {
|
||||
for (var file : directoryListing) {
|
||||
if (file.isFile()) {
|
||||
calibration3dPipeline.takeSnapshot();
|
||||
var output =
|
||||
calibration3dPipeline.run(
|
||||
new Frame(
|
||||
new CVMat(Imgcodecs.imread(file.getAbsolutePath())),
|
||||
new FrameStaticProperties(
|
||||
(int) imgRes.width, (int) imgRes.height, 67, new Rotation2d(), null)),
|
||||
QuirkyCamera.DefaultCamera);
|
||||
var frame =
|
||||
new Frame(
|
||||
new CVMat(Imgcodecs.imread(file.getAbsolutePath())),
|
||||
new FrameStaticProperties(
|
||||
(int) imgRes.width, (int) imgRes.height, 67, new Rotation2d(), null));
|
||||
var output = calibration3dPipeline.run(frame, QuirkyCamera.DefaultCamera);
|
||||
|
||||
// TestUtils.showImage(output.outputFrame.image.getMat(), file.getName(), 1);
|
||||
output.outputFrame.release();
|
||||
output.release();
|
||||
frame.release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.photonvision.vision.pipeline;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import edu.wpi.first.wpilibj.geometry.Rotation2d;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -31,9 +30,7 @@ import org.photonvision.vision.frame.provider.FileFrameProvider;
|
||||
import org.photonvision.vision.opencv.CVMat;
|
||||
import org.photonvision.vision.opencv.ContourGroupingMode;
|
||||
import org.photonvision.vision.opencv.ContourIntersectionDirection;
|
||||
import org.photonvision.vision.opencv.ContourShape;
|
||||
import org.photonvision.vision.pipeline.result.CVPipelineResult;
|
||||
import org.photonvision.vision.target.TargetModel;
|
||||
import org.photonvision.vision.target.TrackedTarget;
|
||||
|
||||
public class CirclePNPTest {
|
||||
@@ -58,7 +55,7 @@ public class CirclePNPTest {
|
||||
}
|
||||
|
||||
private CameraCalibrationCoefficients getCoeffs(String filename) {
|
||||
var cameraCalibration = TestUtils.getCoeffs(filename, true);
|
||||
var cameraCalibration = TestUtils.getCoeffs(filename, false);
|
||||
checkCameraCoefficients(cameraCalibration);
|
||||
return cameraCalibration;
|
||||
}
|
||||
@@ -83,43 +80,46 @@ public class CirclePNPTest {
|
||||
assertEquals(5, cameraCalibration.getCameraExtrinsicsMat().cols());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCircle() {
|
||||
var pipeline = new ColoredShapePipeline();
|
||||
|
||||
pipeline.getSettings().hsvHue.set(0, 100);
|
||||
pipeline.getSettings().hsvSaturation.set(100, 255);
|
||||
pipeline.getSettings().hsvValue.set(100, 255);
|
||||
pipeline.getSettings().outputShouldDraw = true;
|
||||
pipeline.getSettings().maxCannyThresh = 50;
|
||||
pipeline.getSettings().accuracy = 15;
|
||||
pipeline.getSettings().allowableThreshold = 5;
|
||||
pipeline.getSettings().solvePNPEnabled = true;
|
||||
pipeline.getSettings().cornerDetectionAccuracyPercentage = 4;
|
||||
pipeline.getSettings().cornerDetectionUseConvexHulls = true;
|
||||
pipeline.getSettings().cameraCalibration = getCoeffs(LIFECAM_480P_CAL_FILE);
|
||||
pipeline.getSettings().targetModel = TargetModel.kCircularPowerCell7in;
|
||||
pipeline.getSettings().outputShouldDraw = true;
|
||||
pipeline.getSettings().outputShowMultipleTargets = false;
|
||||
pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Single;
|
||||
pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
|
||||
pipeline.getSettings().desiredShape = ContourShape.Circle;
|
||||
pipeline.getSettings().allowableThreshold = 10;
|
||||
pipeline.getSettings().minRadius = 30;
|
||||
pipeline.getSettings().accuracyPercentage = 30.0;
|
||||
|
||||
var frameProvider =
|
||||
new FileFrameProvider(
|
||||
TestUtils.getPowercellImagePath(TestUtils.PowercellTestImages.kPowercell_test_6, false),
|
||||
TestUtils.WPI2020Image.FOV,
|
||||
new Rotation2d(),
|
||||
TestUtils.get2020LifeCamCoeffs(true));
|
||||
|
||||
CVPipelineResult pipelineResult = pipeline.run(frameProvider.get(), QuirkyCamera.DefaultCamera);
|
||||
printTestResults(pipelineResult);
|
||||
|
||||
TestUtils.showImage(pipelineResult.outputFrame.image.getMat(), "Pipeline output", 999999);
|
||||
}
|
||||
// @Test
|
||||
// public void testCircle() {
|
||||
// var pipeline = new ColoredShapePipeline();
|
||||
//
|
||||
// pipeline.getSettings().hsvHue.set(0, 100);
|
||||
// pipeline.getSettings().hsvSaturation.set(100, 255);
|
||||
// pipeline.getSettings().hsvValue.set(100, 255);
|
||||
// pipeline.getSettings().outputShouldDraw = true;
|
||||
// pipeline.getSettings().maxCannyThresh = 50;
|
||||
// pipeline.getSettings().accuracy = 15;
|
||||
// pipeline.getSettings().allowableThreshold = 5;
|
||||
// pipeline.getSettings().solvePNPEnabled = true;
|
||||
// pipeline.getSettings().cornerDetectionAccuracyPercentage = 4;
|
||||
// pipeline.getSettings().cornerDetectionUseConvexHulls = true;
|
||||
// pipeline.getSettings().cameraCalibration = getCoeffs(LIFECAM_480P_CAL_FILE);
|
||||
// pipeline.getSettings().targetModel = TargetModel.kCircularPowerCell7in;
|
||||
// pipeline.getSettings().outputShouldDraw = true;
|
||||
// pipeline.getSettings().outputShowMultipleTargets = false;
|
||||
// pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Single;
|
||||
// pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up;
|
||||
// pipeline.getSettings().desiredShape = ContourShape.Circle;
|
||||
// pipeline.getSettings().allowableThreshold = 10;
|
||||
// pipeline.getSettings().minRadius = 30;
|
||||
// pipeline.getSettings().accuracyPercentage = 30.0;
|
||||
//
|
||||
// var frameProvider =
|
||||
// new FileFrameProvider(
|
||||
//
|
||||
// TestUtils.getPowercellImagePath(TestUtils.PowercellTestImages.kPowercell_test_6, false),
|
||||
// TestUtils.WPI2020Image.FOV,
|
||||
// new Rotation2d(),
|
||||
// TestUtils.get2020LifeCamCoeffs(false));
|
||||
//
|
||||
// CVPipelineResult pipelineResult = pipeline.run(frameProvider.get(),
|
||||
// QuirkyCamera.DefaultCamera);
|
||||
// printTestResults(pipelineResult);
|
||||
//
|
||||
// TestUtils.showImage(pipelineResult.outputFrame.image.getMat(), "Pipeline output",
|
||||
// 999999);
|
||||
// }
|
||||
|
||||
private static void continuouslyRunPipeline(Frame frame, ReflectivePipelineSettings settings) {
|
||||
var pipeline = new ReflectivePipeline();
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
package org.photonvision.vision.pipeline;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.photonvision.common.util.TestUtils;
|
||||
import org.photonvision.vision.camera.QuirkyCamera;
|
||||
import org.photonvision.vision.frame.Frame;
|
||||
@@ -58,33 +57,36 @@ public class ColoredShapePipelineTest {
|
||||
printTestResults(colouredShapePipelineResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public static void testCircleShapeDetection(
|
||||
ColoredShapePipeline pipeline, ColoredShapePipelineSettings settings, Frame frame) {
|
||||
settings.desiredShape = ContourShape.Circle;
|
||||
pipeline.settings = settings;
|
||||
CVPipelineResult colouredShapePipelineResult = pipeline.run(frame, QuirkyCamera.DefaultCamera);
|
||||
TestUtils.showImage(
|
||||
colouredShapePipelineResult.outputFrame.image.getMat(), "Pipeline output: Circle.");
|
||||
printTestResults(colouredShapePipelineResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public static void testPowercellDetection(
|
||||
ColoredShapePipelineSettings settings, ColoredShapePipeline pipeline) {
|
||||
|
||||
settings.hsvHue.set(10, 40);
|
||||
settings.hsvSaturation.set(100, 255);
|
||||
settings.hsvValue.set(100, 255);
|
||||
settings.maxCannyThresh = 50;
|
||||
settings.accuracy = 15;
|
||||
settings.allowableThreshold = 5;
|
||||
var frameProvider =
|
||||
new FileFrameProvider(
|
||||
TestUtils.getPowercellImagePath(TestUtils.PowercellTestImages.kPowercell_test_6, false),
|
||||
TestUtils.WPI2019Image.FOV);
|
||||
testCircleShapeDetection(pipeline, settings, frameProvider.get());
|
||||
}
|
||||
// @Test
|
||||
// public static void testCircleShapeDetection(
|
||||
// ColoredShapePipeline pipeline, ColoredShapePipelineSettings settings, Frame frame) {
|
||||
// settings.desiredShape = ContourShape.Circle;
|
||||
// pipeline.settings = settings;
|
||||
// CVPipelineResult colouredShapePipelineResult = pipeline.run(frame,
|
||||
// QuirkyCamera.DefaultCamera);
|
||||
// TestUtils.showImage(
|
||||
// colouredShapePipelineResult.outputFrame.image.getMat(), "Pipeline output:
|
||||
// Circle.");
|
||||
// printTestResults(colouredShapePipelineResult);
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public static void testPowercellDetection(
|
||||
// ColoredShapePipelineSettings settings, ColoredShapePipeline pipeline) {
|
||||
//
|
||||
// settings.hsvHue.set(10, 40);
|
||||
// settings.hsvSaturation.set(100, 255);
|
||||
// settings.hsvValue.set(100, 255);
|
||||
// settings.maxCannyThresh = 50;
|
||||
// settings.accuracy = 15;
|
||||
// settings.allowableThreshold = 5;
|
||||
// var frameProvider =
|
||||
// new FileFrameProvider(
|
||||
//
|
||||
// TestUtils.getPowercellImagePath(TestUtils.PowercellTestImages.kPowercell_test_6, false),
|
||||
// TestUtils.WPI2019Image.FOV);
|
||||
// testCircleShapeDetection(pipeline, settings, frameProvider.get());
|
||||
// }
|
||||
|
||||
public static void main(String[] args) {
|
||||
TestUtils.loadLibraries();
|
||||
@@ -110,8 +112,8 @@ public class ColoredShapePipelineTest {
|
||||
testTriangleDetection(pipeline, settings, frameProvider.get());
|
||||
testQuadrilateralDetection(pipeline, settings, frameProvider.get());
|
||||
testCustomShapeDetection(pipeline, settings, frameProvider.get());
|
||||
testCircleShapeDetection(pipeline, settings, frameProvider.get());
|
||||
testPowercellDetection(settings, pipeline);
|
||||
// testCircleShapeDetection(pipeline, settings, frameProvider.get());
|
||||
// testPowercellDetection(settings, pipeline);
|
||||
}
|
||||
|
||||
private static void printTestResults(CVPipelineResult pipelineResult) {
|
||||
|
||||
@@ -61,7 +61,7 @@ public class SolvePNPTest {
|
||||
}
|
||||
|
||||
private CameraCalibrationCoefficients getCoeffs(String filename) {
|
||||
var cameraCalibration = TestUtils.getCoeffs(filename, true);
|
||||
var cameraCalibration = TestUtils.getCoeffs(filename, false);
|
||||
checkCameraCoefficients(cameraCalibration);
|
||||
return cameraCalibration;
|
||||
}
|
||||
@@ -106,7 +106,7 @@ public class SolvePNPTest {
|
||||
TestUtils.getWPIImagePath(TestUtils.WPI2019Image.kCargoStraightDark48in, false),
|
||||
TestUtils.WPI2019Image.FOV,
|
||||
new Rotation2d(),
|
||||
TestUtils.get2019LifeCamCoeffs(true));
|
||||
TestUtils.get2019LifeCamCoeffs(false));
|
||||
|
||||
CVPipelineResult pipelineResult;
|
||||
|
||||
@@ -141,7 +141,7 @@ public class SolvePNPTest {
|
||||
TestUtils.getWPIImagePath(TestUtils.WPI2020Image.kBlueGoal_224in_Left, false),
|
||||
TestUtils.WPI2020Image.FOV,
|
||||
new Rotation2d(),
|
||||
TestUtils.get2020LifeCamCoeffs(true));
|
||||
TestUtils.get2020LifeCamCoeffs(false));
|
||||
|
||||
CVPipelineResult pipelineResult = pipeline.run(frameProvider.get(), QuirkyCamera.DefaultCamera);
|
||||
printTestResults(pipelineResult);
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
"version": "${photon_version}",
|
||||
"uuid": "515fe07e-bfc6-11fa-b3de-0242ac130004 ",
|
||||
"mavenUrls": [
|
||||
"https://maven.photonvision.org/repository/internal"
|
||||
"https://maven.photonvision.org/repository/internal",
|
||||
"https://maven.photonvision.org/repository/snapshots"
|
||||
],
|
||||
"jsonUrl": "https://maven.photonvision.org/repository/internal/org/photonvision/lib/PhotonLib-json/1.0/PhotonLib-json-1.0.json",
|
||||
"jniDependencies": [],
|
||||
"cppDependencies": [
|
||||
{
|
||||
"groupId": "org.photonvision.lib",
|
||||
"groupId": "org.photonvision",
|
||||
"artifactId": "PhotonLib-cpp",
|
||||
"version": "${photon_version}",
|
||||
"libName": "Photon",
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Comparator;
|
||||
import org.photonvision.targeting.PhotonTrackedTarget;
|
||||
|
||||
public enum PhotonTargetSortMode {
|
||||
Smallest(Comparator.comparingDouble(PhotonTrackedTarget::getArea)),
|
||||
Largest(Smallest.m_comparator.reversed()),
|
||||
Highest(Comparator.comparingDouble(PhotonTrackedTarget::getPitch)),
|
||||
Lowest(Highest.m_comparator.reversed()),
|
||||
Rightmost(Comparator.comparingDouble(PhotonTrackedTarget::getYaw)),
|
||||
Leftmost(Rightmost.m_comparator.reversed()),
|
||||
Centermost(
|
||||
Comparator.comparingDouble(
|
||||
target -> (Math.pow(target.getPitch(), 2) + Math.pow(target.getYaw(), 2))));
|
||||
|
||||
private final Comparator<PhotonTrackedTarget> m_comparator;
|
||||
|
||||
PhotonTargetSortMode(Comparator<PhotonTrackedTarget> comparator) {
|
||||
m_comparator = comparator;
|
||||
}
|
||||
|
||||
public Comparator<PhotonTrackedTarget> getComparator() {
|
||||
return m_comparator;
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,24 @@
|
||||
package org.photonvision;
|
||||
|
||||
import edu.wpi.first.networktables.NetworkTable;
|
||||
import edu.wpi.first.networktables.NetworkTableEntry;
|
||||
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.photonvision.common.dataflow.structures.Packet;
|
||||
import org.photonvision.targeting.PhotonPipelineResult;
|
||||
import org.photonvision.targeting.PhotonTrackedTarget;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SimPhotonCamera extends PhotonCamera {
|
||||
private final NetworkTableEntry latencyMillisEntry;
|
||||
private final NetworkTableEntry hasTargetEntry;
|
||||
private final NetworkTableEntry targetPitchEntry;
|
||||
private final NetworkTableEntry targetYawEntry;
|
||||
private final NetworkTableEntry targetAreaEntry;
|
||||
private final NetworkTableEntry targetSkewEntry;
|
||||
private final NetworkTableEntry targetPoseEntry;
|
||||
|
||||
/**
|
||||
* Constructs a Simulated PhotonCamera from a root table.
|
||||
*
|
||||
@@ -32,6 +43,14 @@ public class SimPhotonCamera extends PhotonCamera {
|
||||
*/
|
||||
public SimPhotonCamera(NetworkTable rootTable) {
|
||||
super(rootTable);
|
||||
|
||||
latencyMillisEntry = rootTable.getEntry("latencyMillis");
|
||||
hasTargetEntry = rootTable.getEntry("hasTargetEntry");
|
||||
targetPitchEntry = rootTable.getEntry("targetPitchEntry");
|
||||
targetYawEntry = rootTable.getEntry("targetYawEntry");
|
||||
targetAreaEntry = rootTable.getEntry("targetAreaEntry");
|
||||
targetSkewEntry = rootTable.getEntry("targetSkewEntry");
|
||||
targetPoseEntry = rootTable.getEntry("targetPoseEntry");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,13 +59,13 @@ public class SimPhotonCamera extends PhotonCamera {
|
||||
* @param cameraName The nickname of the camera (found in the PhotonVision UI).
|
||||
*/
|
||||
public SimPhotonCamera(String cameraName) {
|
||||
super(cameraName);
|
||||
this(NetworkTableInstance.getDefault().getTable("photonvision").getSubTable(cameraName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate one processed frame of vision data, putting one result to NT.
|
||||
*
|
||||
* @param latencyMillis
|
||||
* @param latencyMillis Latency of the provided frame
|
||||
* @param targets Each target detected
|
||||
*/
|
||||
public void submitProcessedFrame(double latencyMillis, PhotonTrackedTarget... targets) {
|
||||
@@ -56,15 +75,66 @@ public class SimPhotonCamera extends PhotonCamera {
|
||||
/**
|
||||
* Simulate one processed frame of vision data, putting one result to NT.
|
||||
*
|
||||
* @param latencyMillis
|
||||
* @param tgtList List of targets detected
|
||||
* @param latencyMillis Latency of the provided frame
|
||||
* @param sortMode Order in which to sort targets
|
||||
* @param targets Each target detected
|
||||
*/
|
||||
public void submitProcessedFrame(double latencyMillis, List<PhotonTrackedTarget> tgtList) {
|
||||
if (!getDriverMode()) {
|
||||
PhotonPipelineResult newResult = new PhotonPipelineResult(latencyMillis, tgtList);
|
||||
var newPacket = new Packet(newResult.getPacketSize());
|
||||
newResult.populatePacket(newPacket);
|
||||
rawBytesEntry.setRaw(newPacket.getData());
|
||||
public void submitProcessedFrame(
|
||||
double latencyMillis, PhotonTargetSortMode sortMode, PhotonTrackedTarget... targets) {
|
||||
submitProcessedFrame(latencyMillis, sortMode, Arrays.asList(targets));
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate one processed frame of vision data, putting one result to NT.
|
||||
*
|
||||
* @param latencyMillis Latency of the provided frame
|
||||
* @param targetList List of targets detected
|
||||
*/
|
||||
public void submitProcessedFrame(double latencyMillis, List<PhotonTrackedTarget> targetList) {
|
||||
submitProcessedFrame(latencyMillis, null, targetList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate one processed frame of vision data, putting one result to NT.
|
||||
*
|
||||
* @param latencyMillis Latency of the provided frame
|
||||
* @param sortMode Order in which to sort targets
|
||||
* @param targetList List of targets detected
|
||||
*/
|
||||
public void submitProcessedFrame(
|
||||
double latencyMillis, PhotonTargetSortMode sortMode, List<PhotonTrackedTarget> targetList) {
|
||||
latencyMillisEntry.setDouble(latencyMillis);
|
||||
|
||||
if (sortMode != null) {
|
||||
targetList.sort(sortMode.getComparator());
|
||||
}
|
||||
|
||||
PhotonPipelineResult newResult = new PhotonPipelineResult(latencyMillis, targetList);
|
||||
var newPacket = new Packet(newResult.getPacketSize());
|
||||
newResult.populatePacket(newPacket);
|
||||
rawBytesEntry.setRaw(newPacket.getData());
|
||||
|
||||
boolean hasTargets = newResult.hasTargets();
|
||||
hasTargetEntry.setBoolean(hasTargets);
|
||||
if (!hasTargets) {
|
||||
targetPitchEntry.setDouble(0.0);
|
||||
targetYawEntry.setDouble(0.0);
|
||||
targetAreaEntry.setDouble(0.0);
|
||||
targetPoseEntry.setDoubleArray(new double[] {0.0, 0.0, 0.0});
|
||||
targetSkewEntry.setDouble(0.0);
|
||||
} else {
|
||||
var bestTarget = newResult.getBestTarget();
|
||||
|
||||
targetPitchEntry.setDouble(bestTarget.getPitch());
|
||||
targetYawEntry.setDouble(bestTarget.getYaw());
|
||||
targetAreaEntry.setDouble(bestTarget.getArea());
|
||||
targetSkewEntry.setDouble(bestTarget.getSkew());
|
||||
|
||||
var transform = bestTarget.getCameraToTarget();
|
||||
double[] poseData = {
|
||||
transform.getX(), transform.getY(), transform.getRotation().getDegrees()
|
||||
};
|
||||
targetPoseEntry.setDoubleArray(poseData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +41,10 @@ public class SimVisionSystem {
|
||||
|
||||
/**
|
||||
* Create a simulated vision system involving a camera and coprocessor mounted on a mobile robot
|
||||
* running Photonvision, detecting one or more targets scattered around the field. This assumes a
|
||||
* fairly simple and distortionless pinhole camera model.
|
||||
* running PhotonVision, detecting one or more targets scattered around the field. This assumes a
|
||||
* fairly simple and distortion-less pinhole camera model.
|
||||
*
|
||||
* @param camName Name of the photonvision camera to create. Align it with the settings you use in
|
||||
* @param camName Name of the PhotonVision camera to create. Align it with the settings you use in
|
||||
* the PhotonVision GUI.
|
||||
* @param camDiagFOVDegrees Diagonal Field of View of the camera used. Align it with the
|
||||
* manufacturer specifications, and/or whatever is configured in the PhotonVision Setting
|
||||
@@ -87,25 +87,25 @@ public class SimVisionSystem {
|
||||
this.camVertFOVDegrees = camDiagFOVDegrees * cameraResHeight / hypotPixels;
|
||||
|
||||
cam = new SimPhotonCamera(camName);
|
||||
tgtList = new ArrayList<SimVisionTarget>();
|
||||
tgtList = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a target on the field which your vision system is designed to detect. The photoncamera from
|
||||
* this system will report the location of the robot relative to the subste of these targets which
|
||||
* Add a target on the field which your vision system is designed to detect. The PhotonCamera from
|
||||
* this system will report the location of the robot relative to the subset of these targets which
|
||||
* are visible from the given robot position.
|
||||
*
|
||||
* @param tgt
|
||||
* @param target Target to add to the simulated field
|
||||
*/
|
||||
public void addSimVisionTarget(SimVisionTarget tgt) {
|
||||
tgtList.add(tgt);
|
||||
public void addSimVisionTarget(SimVisionTarget target) {
|
||||
tgtList.add(target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the camera position relative to the robot. Use this if your camera is on a gimbal or
|
||||
* turret or some other mobile platform.
|
||||
*
|
||||
* @param newCameraToRobot New Tranform from the robot to the camera
|
||||
* @param newCameraToRobot New Transform from the robot to the camera
|
||||
* @param newCamHeightMeters New height of the camera off the floor
|
||||
* @param newCamPitchDegrees New pitch of the camera axis back from horizontal
|
||||
*/
|
||||
@@ -120,12 +120,11 @@ public class SimVisionSystem {
|
||||
* Periodic update. Call this once per frame of image data you wish to process and send to
|
||||
* NetworkTables
|
||||
*
|
||||
* @param robotPoseMeters current pose of the robot on the field. Will be used to calcualte which
|
||||
* @param robotPoseMeters current pose of the robot on the field. Will be used to calculate which
|
||||
* targets are actually in view, where they are at relative to the robot, and relevant
|
||||
* PhotonVision parameters.
|
||||
*/
|
||||
public void processFrame(Pose2d robotPoseMeters) {
|
||||
|
||||
Pose2d cameraPos = robotPoseMeters.transformBy(cameraToRobot.inverse());
|
||||
|
||||
ArrayList<PhotonTrackedTarget> visibleTgtList = new ArrayList<>(tgtList.size());
|
||||
|
||||
@@ -34,8 +34,8 @@ public class SimVisionTarget {
|
||||
* the middle of the field facing the target, the Y axis points to your left, and the X axis
|
||||
* points away from you.
|
||||
* @param targetHeightAboveGroundMeters Height of the target above the field plane, in meters.
|
||||
* @param targetWidthMeters Width of the outter bounding box of the target in meters.
|
||||
* @param targetHeightMeters Pair Height of the outter bounding box of the target in meters.
|
||||
* @param targetWidthMeters Width of the outer bounding box of the target in meters.
|
||||
* @param targetHeightMeters Pair Height of the outer bounding box of the target in meters.
|
||||
*/
|
||||
public SimVisionTarget(
|
||||
Pose2d targetPos,
|
||||
|
||||
@@ -45,7 +45,10 @@ PhotonPipelineResult PhotonCamera::GetLatestResult() const {
|
||||
PhotonPipelineResult result;
|
||||
|
||||
// Fill the packet with latest data and populate result.
|
||||
std::string value = rawBytesEntry.GetValue()->GetRaw();
|
||||
std::shared_ptr<nt::Value> ntvalue = rawBytesEntry.GetValue();
|
||||
if (!ntvalue) return result;
|
||||
|
||||
std::string value = ntvalue->GetRaw();
|
||||
std::vector<char> bytes{value.begin(), value.end()};
|
||||
|
||||
photonlib::Packet packet{bytes};
|
||||
|
||||
@@ -49,7 +49,6 @@ class PhotonUtilTest {
|
||||
|
||||
@Test
|
||||
public void testTransform() {
|
||||
|
||||
var camHeight = 1;
|
||||
var tgtHeight = 3;
|
||||
var camPitch = 0;
|
||||
|
||||
@@ -53,7 +53,6 @@ class SimVisionSystemTest {
|
||||
@ParameterizedTest
|
||||
@ValueSource(doubles = {5, 10, 15, 20, 25, 30})
|
||||
public void testDistanceAligned(double dist) {
|
||||
|
||||
final var targetPose = new Pose2d(new Translation2d(35, 0), new Rotation2d());
|
||||
var sysUnderTest =
|
||||
new SimVisionSystem("Test", 80.0, 0.0, new Transform2d(), 1, 99999, 320, 240, 0);
|
||||
|
||||
@@ -57,13 +57,11 @@ public class Main {
|
||||
final var options = new Options();
|
||||
options.addOption("d", "debug", false, "Enable debug logging prints");
|
||||
options.addOption("h", "help", false, "Show this help text and exit");
|
||||
if (!isRelease) {
|
||||
options.addOption(
|
||||
"t",
|
||||
"test-mode",
|
||||
false,
|
||||
"Run in test mode with 2019 and 2020 WPI field images in place of cameras");
|
||||
}
|
||||
options.addOption(
|
||||
"t",
|
||||
"test-mode",
|
||||
false,
|
||||
"Run in test mode with 2019 and 2020 WPI field images in place of cameras");
|
||||
|
||||
CommandLineParser parser = new DefaultParser();
|
||||
CommandLine cmd = parser.parse(options, args);
|
||||
|
||||
Binary file not shown.
@@ -54,6 +54,10 @@ public class PhotonTrackedTarget {
|
||||
return area;
|
||||
}
|
||||
|
||||
public double getSkew() {
|
||||
return skew;
|
||||
}
|
||||
|
||||
public Transform2d getCameraToTarget() {
|
||||
return cameraToTarget;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public class Robot extends TimedRobot {
|
||||
CAMERA_HEIGHT_METERS,
|
||||
TARGET_HEIGHT_METERS,
|
||||
CAMERA_PITCH_RADIANS,
|
||||
result.getBestTarget().getPitch());
|
||||
Units.degreesToRadians(result.getBestTarget().getPitch()));
|
||||
|
||||
// Use this range as the measurement we give to the PID controller.
|
||||
// -1.0 required to ensure positive PID controller effort _increases_ range
|
||||
|
||||
@@ -81,7 +81,7 @@ public class Robot extends TimedRobot {
|
||||
CAMERA_HEIGHT_METERS,
|
||||
TARGET_HEIGHT_METERS,
|
||||
CAMERA_PITCH_RADIANS,
|
||||
result.getBestTarget().getPitch());
|
||||
Units.degreesToRadians(result.getBestTarget().getPitch()));
|
||||
|
||||
// Use this range as the measurement we give to the PID controller.
|
||||
// -1.0 required to ensure positive PID controller effort _increases_ range
|
||||
|
||||
@@ -53,12 +53,14 @@ test {
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
}
|
||||
workingDir = new File("${rootDir}")
|
||||
}
|
||||
|
||||
task testHeadless(type: Test) {
|
||||
group = "verification"
|
||||
systemProperty("java.awt.headless", "true")
|
||||
useJUnitPlatform()
|
||||
exclude '**/*BenchmarkTest*'
|
||||
}
|
||||
|
||||
task generateJavaDocs(type: Javadoc) {
|
||||
@@ -68,7 +70,7 @@ task generateJavaDocs(type: Javadoc) {
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
dependsOn test // Tests are required to run before generating the report
|
||||
// dependsOn testHeadless // Tests are required to run before generating the report
|
||||
|
||||
reports {
|
||||
xml.enabled true
|
||||
|
||||
@@ -8,15 +8,15 @@ gradle.allprojects {
|
||||
String tagIsh
|
||||
try {
|
||||
exec {
|
||||
commandLine 'git', 'describe', '--tags', '--exclude="Dev"'
|
||||
commandLine 'git', 'describe', '--tags', '--exclude="Dev"', '--exclude="dev"'
|
||||
standardOutput = stdout
|
||||
}
|
||||
tagIsh = stdout.toString().trim().toLowerCase()
|
||||
} catch(Exception e) {
|
||||
tagIsh = "dev-Unknown"
|
||||
}
|
||||
boolean isDev = tagIsh.matches(".*-[0-9]*-g[0-9a-f]*")
|
||||
if(isDev) tagIsh = "dev-" + tagIsh
|
||||
// boolean isDev = tagIsh.matches(".*-[0-9]*-g[0-9a-f]*")
|
||||
// if(isDev) tagIsh = "dev-" + tagIsh
|
||||
println("Picked up version: " + tagIsh)
|
||||
return tagIsh
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user