diff --git a/.circleci/config.yml b/.circleci/config.yml index 1ac8a1966..c7b635ffb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,6 +18,37 @@ jobs: - dist + run_tests: + working_directory: ~/project/chameleon-server + docker: + - image: gradle:jdk12 + steps: + - checkout: + path: ~/project + #- restore_cache: + # key: v1-gradle-wrapper-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }} + #- restore_cache: + # key: v1-gradle-cache-{{ checksum "build.gradle" }} + - run: + name: list + command: ls + - run: + name: Run tests + command: ./gradlew test + #- save_cache: + # paths: + # - .gradle/wrapper + # key: v1-gradle-wrapper-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }} + #- save_cache: + # paths: + # - .gradle/caches + # key: v1-gradle-cache-{{ checksum "build.gradle" }} + - store_test_results: + path: build/test-results/test + - store_artifacts: # Upload test results for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ + path: build/test-results/test + when: always + build_jar: working_directory: ~/project/chameleon-server docker: @@ -50,7 +81,6 @@ jobs: - run: gradle spotlessApply - run: git --no-pager diff --exit-code HEAD - deploy: docker: - image: cibuilds/github:0.10 @@ -68,13 +98,15 @@ workflows: release: jobs: - format_diff_against_git + - run_tests - build_ui - build_jar: requires: + - format_diff_against_git + - run_tests - build_ui - deploy: requires: - - format_diff_against_git - build_jar filters: branches: diff --git a/chameleon-server/src/main/java/com/chameleonvision/common/util/TestUtils.java b/chameleon-server/src/main/java/com/chameleonvision/common/util/TestUtils.java index 5c5e4db4f..4c607a6da 100644 --- a/chameleon-server/src/main/java/com/chameleonvision/common/util/TestUtils.java +++ b/chameleon-server/src/main/java/com/chameleonvision/common/util/TestUtils.java @@ -1,15 +1,15 @@ package com.chameleonvision.common.util; import edu.wpi.cscore.CameraServerCvJNI; -import java.io.File; +import java.awt.*; import java.io.IOException; import java.nio.file.Path; -import java.util.Optional; import org.opencv.core.Mat; import org.opencv.highgui.HighGui; public class TestUtils { + @SuppressWarnings("unused") public enum WPI2019Image { kCargoAngledDark48in(1.2192), kCargoSideStraightDark36in(0.9144), @@ -26,11 +26,11 @@ public class TestUtils { public static double FOV = 68.5; public final double distanceMeters; - public final String path; + public final Path path; - String getPath() { + Path getPath() { var filename = this.toString().substring(1); - return "\\2019\\WPI\\" + filename + ".jpg"; + return Path.of("2019", "WPI", filename + ".jpg"); } WPI2019Image(double distanceMeters) { @@ -39,6 +39,7 @@ public class TestUtils { } } + @SuppressWarnings("unused") public enum WPI2020Image { kBlueGoal_060in_Center(1.524), kBlueGoal_084in_Center(2.1336), @@ -62,11 +63,11 @@ public class TestUtils { public static double FOV = 68.5; public final double distanceMeters; - public final String path; + public final Path path; - String getPath() { + Path getPath() { var filename = this.toString().substring(1).replace('_', '-'); - return "\\2020\\WPI\\" + filename + ".jpg"; + return Path.of("2020", "WPI", filename + ".jpg"); } WPI2020Image(double distanceMeters) { @@ -75,22 +76,24 @@ public class TestUtils { } } - private static Path getTestImagesPath() { - var folder = TestUtils.class.getClassLoader().getResource("testimages"); - return Optional.ofNullable(folder).map(url -> new File(url.getFile()).toPath()).orElse(null); + private static Path getResourcesFolderPath() { + return Path.of("src", "test", "resources").toAbsolutePath(); + } + + public static Path getTestImagesPath() { + return getResourcesFolderPath().resolve("testimages"); } public static Path getCalibrationPath() { - var folder = TestUtils.class.getClassLoader().getResource("calibration"); - return Optional.ofNullable(folder).map(url -> new File(url.getFile()).toPath()).orElse(null); + return getResourcesFolderPath().resolve("calibration"); } public static Path getWPIImagePath(WPI2020Image image) { - return Path.of(getTestImagesPath().toString(), image.path); + return getTestImagesPath().resolve(image.path); } public static Path getWPIImagePath(WPI2019Image image) { - return Path.of(getTestImagesPath().toString(), image.path); + return getTestImagesPath().resolve(image.path); } public static void loadLibraries() { @@ -104,9 +107,13 @@ public class TestUtils { private static int DefaultTimeoutMillis = 5000; public static void showImage(Mat frame, String title, int timeoutMs) { - HighGui.imshow(title, frame); - HighGui.waitKey(timeoutMs); - HighGui.destroyAllWindows(); + try { + HighGui.imshow(title, frame); + HighGui.waitKey(timeoutMs); + HighGui.destroyAllWindows(); + } catch (HeadlessException ignored) { + + } } public static void showImage(Mat frame, int timeoutMs) { diff --git a/chameleon-server/src/main/java/com/chameleonvision/common/vision/frame/provider/FileFrameProvider.java b/chameleon-server/src/main/java/com/chameleonvision/common/vision/frame/provider/FileFrameProvider.java index d3be0a7ae..83e74bdb8 100644 --- a/chameleon-server/src/main/java/com/chameleonvision/common/vision/frame/provider/FileFrameProvider.java +++ b/chameleon-server/src/main/java/com/chameleonvision/common/vision/frame/provider/FileFrameProvider.java @@ -32,7 +32,8 @@ public class FileFrameProvider implements FrameProvider { * @param fov The fov of the image. */ public FileFrameProvider(Path path, double fov) { - if (!Files.exists(path)) throw new RuntimeException("Invalid path for image!"); + if (!Files.exists(path)) + throw new RuntimeException("Invalid path for image: " + path.toAbsolutePath().toString()); m_path = path; m_fov = fov; diff --git a/chameleon-server/src/main/java/com/chameleonvision/common/vision/target/TargetCalculations.java b/chameleon-server/src/main/java/com/chameleonvision/common/vision/target/TargetCalculations.java new file mode 100644 index 000000000..d42accf11 --- /dev/null +++ b/chameleon-server/src/main/java/com/chameleonvision/common/vision/target/TargetCalculations.java @@ -0,0 +1,78 @@ +package com.chameleonvision.common.vision.target; + +import com.chameleonvision.common.util.numbers.DoubleCouple; +import org.apache.commons.math3.util.FastMath; +import org.opencv.core.Point; +import org.opencv.core.RotatedRect; + +public class TargetCalculations { + public static double calculateYaw( + double offsetCenterX, double targetCenterX, double horizontalFocalLength) { + return FastMath.toDegrees(FastMath.atan(offsetCenterX - targetCenterX) / horizontalFocalLength); + } + + public static double calculatePitch( + double offsetCenterY, double targetCenterY, double verticalFocalLength) { + return -FastMath.toDegrees(FastMath.atan(offsetCenterY - targetCenterY) / verticalFocalLength); + } + + public static Point calculateTargetOffsetPoint( + boolean isLandscape, TargetOffsetPointEdge offsetRegion, RotatedRect minAreaRect) { + Point[] vertices = new Point[4]; + + minAreaRect.points(vertices); + + Point bl = getMiddle(vertices[0], vertices[1]); + Point tl = getMiddle(vertices[1], vertices[2]); + Point tr = getMiddle(vertices[2], vertices[3]); + Point br = getMiddle(vertices[3], vertices[0]); + boolean orientation; + if (isLandscape) { + orientation = minAreaRect.size.width > minAreaRect.size.height; + } else { + orientation = minAreaRect.size.width < minAreaRect.size.height; + } + + switch (offsetRegion) { + case Top: + return orientation ? tl : tr; + case Bottom: + return orientation ? br : bl; + case Left: + return orientation ? bl : tl; + case Right: + return orientation ? tr : br; + default: + return minAreaRect.center; + } + } + + private static Point getMiddle(Point p1, Point p2) { + return new Point(((p1.x + p2.x) / 2), ((p1.y + p2.y) / 2)); + } + + public static Point calculateRobotOffsetPoint( + Point offsetPoint, + Point camCenterPoint, + DoubleCouple offsetEquationValues, + RobotOffsetPointMode offsetMode) { + switch (offsetMode) { + case None: + default: + return camCenterPoint; + case Single: + if (offsetPoint.x == 0 && offsetPoint.y == 0) { + return camCenterPoint; + } else { + return offsetPoint; + } + case Dual: + Point resultPoint = new Point(); + resultPoint.x = + (offsetPoint.x - offsetEquationValues.getFirst()) / offsetEquationValues.getSecond(); + resultPoint.y = + (offsetPoint.y * offsetEquationValues.getSecond()) + offsetEquationValues.getFirst(); + return resultPoint; + } + } +} diff --git a/chameleon-server/src/main/java/com/chameleonvision/common/vision/target/TrackedTarget.java b/chameleon-server/src/main/java/com/chameleonvision/common/vision/target/TrackedTarget.java index c4bfdfce7..7d73787e6 100644 --- a/chameleon-server/src/main/java/com/chameleonvision/common/vision/target/TrackedTarget.java +++ b/chameleon-server/src/main/java/com/chameleonvision/common/vision/target/TrackedTarget.java @@ -5,7 +5,6 @@ import com.chameleonvision.common.vision.opencv.Contour; import com.chameleonvision.common.vision.opencv.Releasable; import edu.wpi.first.wpilibj.geometry.Pose2d; import java.util.List; -import org.apache.commons.math3.util.FastMath; import org.opencv.core.Mat; import org.opencv.core.MatOfPoint2f; import org.opencv.core.Point; @@ -75,112 +74,26 @@ public class TrackedTarget implements Releasable { return m_approximateBoundingPolygon; } - private void calculateTargetOffsetPoint(boolean isLandscape, TargetOffsetPointEdge offsetRegion) { - Point[] vertices = new Point[4]; - - var minAreaRect = getMinAreaRect(); - minAreaRect.points(vertices); - - Point bl = getMiddle(vertices[0], vertices[1]); - Point tl = getMiddle(vertices[1], vertices[2]); - Point tr = getMiddle(vertices[2], vertices[3]); - Point br = getMiddle(vertices[3], vertices[0]); - boolean orientation; - if (isLandscape) { - orientation = minAreaRect.size.width > minAreaRect.size.height; - } else { - orientation = minAreaRect.size.width < minAreaRect.size.height; - } - - Point resultPoint = minAreaRect.center; - switch (offsetRegion) { - case Top: - { - resultPoint = orientation ? tl : tr; - break; - } - case Bottom: - { - resultPoint = orientation ? br : bl; - break; - } - case Left: - { - resultPoint = orientation ? bl : tl; - break; - } - case Right: - { - resultPoint = orientation ? tr : br; - break; - } - } - m_targetOffsetPoint = resultPoint; - } - - private void calculateRobotOffsetPoint( - Point offsetPoint, - Point camCenterPoint, - DoubleCouple offsetEquationValues, - RobotOffsetPointMode offsetMode) { - Point resultPoint = new Point(); - switch (offsetMode) { - case None: - resultPoint = camCenterPoint; - break; - case Single: - if (offsetPoint.x == 0 && offsetPoint.y == 0) { - resultPoint = camCenterPoint; - } else { - resultPoint = offsetPoint; - } - break; - case Dual: - resultPoint.x = - (offsetPoint.x - offsetEquationValues.getFirst()) / offsetEquationValues.getSecond(); - resultPoint.y = - (offsetPoint.y * offsetEquationValues.getSecond()) + offsetEquationValues.getFirst(); - break; - } - - m_robotOffsetPoint = resultPoint; - } - - private void calculatePitch(double verticalFocalLength) { - double contourCenterY = m_mainContour.getCenterPoint().y; - double targetCenterY = m_targetOffsetPoint.y; - m_pitch = - -FastMath.toDegrees(FastMath.atan((contourCenterY - targetCenterY) / verticalFocalLength)); - } - - private void calculateYaw(double horizontalFocalLength) { - double contourCenterX = m_mainContour.getCenterPoint().x; - double targetCenterX = m_targetOffsetPoint.x; - m_yaw = - FastMath.toDegrees(FastMath.atan((contourCenterX - targetCenterX) / horizontalFocalLength)); - } - - private void calculateArea(double imageArea) { - m_area = m_mainContour.getMinAreaRect().size.area() / imageArea; - } - - private Point getMiddle(Point p1, Point p2) { - return new Point(((p1.x + p2.x) / 2), ((p1.y + p2.y) / 2)); - } - public void calculateValues(TargetCalculationParameters params) { // this MUST happen in this exact order! - calculateTargetOffsetPoint(params.isLandscape, params.targetOffsetPointEdge); - calculateRobotOffsetPoint( - m_targetOffsetPoint, - params.cameraCenterPoint, - params.offsetEquationValues, - params.robotOffsetPointMode); + m_targetOffsetPoint = + TargetCalculations.calculateTargetOffsetPoint( + params.isLandscape, params.targetOffsetPointEdge, getMinAreaRect()); + m_robotOffsetPoint = + TargetCalculations.calculateRobotOffsetPoint( + m_targetOffsetPoint, + params.cameraCenterPoint, + params.offsetEquationValues, + params.robotOffsetPointMode); // order of this stuff doesnt matter though - calculatePitch(params.verticalFocalLength); - calculateYaw(params.horizontalFocalLength); - calculateArea(params.imageArea); + m_pitch = + TargetCalculations.calculatePitch( + m_targetOffsetPoint.y, m_robotOffsetPoint.y, params.verticalFocalLength); + m_yaw = + TargetCalculations.calculateYaw( + m_targetOffsetPoint.x, m_robotOffsetPoint.x, params.horizontalFocalLength); + m_area = m_mainContour.getMinAreaRect().size.area() / params.imageArea; } @Override diff --git a/chameleon-server/src/test/java/com/chameleonvision/common/vision/frame/provider/FileFrameProviderTest.java b/chameleon-server/src/test/java/com/chameleonvision/common/vision/frame/provider/FileFrameProviderTest.java new file mode 100644 index 000000000..6b8787505 --- /dev/null +++ b/chameleon-server/src/test/java/com/chameleonvision/common/vision/frame/provider/FileFrameProviderTest.java @@ -0,0 +1,96 @@ +package com.chameleonvision.common.vision.frame.provider; + +import static org.junit.jupiter.api.Assertions.*; + +import com.chameleonvision.common.util.TestUtils; +import com.chameleonvision.common.vision.frame.Frame; +import edu.wpi.cscore.CameraServerCvJNI; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class FileFrameProviderTest { + + @BeforeAll + public static void initPath() { + + try { + CameraServerCvJNI.forceLoad(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void TestFilesExist() { + assertTrue(Files.exists(TestUtils.getTestImagesPath())); + } + + @Test + public void Load2019ImageOnceTest() { + var goodFilePath = TestUtils.getWPIImagePath(TestUtils.WPI2019Image.kCargoStraightDark72in); + + assertTrue(Files.exists(goodFilePath)); + + FileFrameProvider goodFrameProvider = new FileFrameProvider(goodFilePath, 68.5); + + Frame goodFrame = goodFrameProvider.get(); + + int goodFrameCols = goodFrame.image.getMat().cols(); + int goodFrameRows = goodFrame.image.getMat().rows(); + + // 2019 Images are at 320x240 + assertEquals(320, goodFrameCols); + assertEquals(240, goodFrameRows); + + // TODO: find a way to skip this if a flag isn't set + TestUtils.showImage(goodFrame.image.getMat(), "2019"); + + var badFilePath = Paths.get("bad.jpg"); // this file does not exist + + FileFrameProvider badFrameProvider = null; + + try { + badFrameProvider = new FileFrameProvider(badFilePath, 68.5); + } catch (Exception e) { + // ignored + } + + assertNull(badFrameProvider); + } + + @Test + public void Load2020ImageOnceTest() { + var goodFilePath = TestUtils.getWPIImagePath(TestUtils.WPI2020Image.kBlueGoal_108in_Center); + + assertTrue(Files.exists(goodFilePath)); + + FileFrameProvider goodFrameProvider = new FileFrameProvider(goodFilePath, 68.5); + + Frame goodFrame = goodFrameProvider.get(); + + int goodFrameCols = goodFrame.image.getMat().cols(); + int goodFrameRows = goodFrame.image.getMat().rows(); + + // 2020 Images are at 640x480 + assertEquals(640, goodFrameCols); + assertEquals(480, goodFrameRows); + + // TODO: find a way to skip this if a flag isn't set + TestUtils.showImage(goodFrame.image.getMat(), "2020"); + + var badFilePath = Paths.get("bad.jpg"); // this file does not exist + + FileFrameProvider badFrameProvider = null; + + try { + badFrameProvider = new FileFrameProvider(badFilePath, 68.5); + } catch (Exception e) { + // ignored + } + + assertNull(badFrameProvider); + } +} diff --git a/chameleon-server/src/test/java/com/chameleonvision/common/vision/opencv/ContourTest.java b/chameleon-server/src/test/java/com/chameleonvision/common/vision/opencv/ContourTest.java new file mode 100644 index 000000000..3a2490e46 --- /dev/null +++ b/chameleon-server/src/test/java/com/chameleonvision/common/vision/opencv/ContourTest.java @@ -0,0 +1,54 @@ +package com.chameleonvision.common.vision.opencv; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.chameleonvision.common.util.TestUtils; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opencv.core.MatOfPoint; +import org.opencv.core.Point; + +public class ContourTest { + @BeforeEach + public void Init() { + TestUtils.loadLibraries(); + } + + @Test + public void simpleContourTest() { + var mat = new MatOfPoint(); + mat.fromList(List.of(new Point(0, 0), new Point(10, 0), new Point(10, 10), new Point(0, 10))); + var contour = new Contour(mat); + assertEquals(100, contour.getArea()); + assertEquals(40, contour.getPerimeter()); + assertEquals(new Point(5, 5), contour.getCenterPoint()); + } + + @Test + public void test2019() { + var firstMat = new MatOfPoint(); + // contour 0 and 1 data from kCargoStraightDark72in_HighRes + firstMat.fromList( + List.of( + new Point(1328, 976), + new Point(1272, 985), + new Point(1230, 832), + new Point(1326, 948), + new Point(1328, 971))); + + var secondMat = new MatOfPoint(); + secondMat.fromList( + List.of( + new Point(956, 832), + new Point(882, 978), + new Point(927, 810), + new Point(954, 821), + new Point(956, 825))); + var firstContour = new Contour(firstMat); + var secondContour = new Contour(secondMat); + boolean result = firstContour.isIntersecting(secondContour, ContourIntersectionDirection.Up); + assertTrue(result); + } +} diff --git a/chameleon-server/src/test/java/com/chameleonvision/common/vision/pipeline/ReflectivePipelineTest.java b/chameleon-server/src/test/java/com/chameleonvision/common/vision/pipeline/ReflectivePipelineTest.java index 9a9eb6e6f..f59f9ae31 100644 --- a/chameleon-server/src/test/java/com/chameleonvision/common/vision/pipeline/ReflectivePipelineTest.java +++ b/chameleon-server/src/test/java/com/chameleonvision/common/vision/pipeline/ReflectivePipelineTest.java @@ -32,15 +32,13 @@ public class ReflectivePipelineTest { public void test2019() { TestUtils.loadLibraries(); var pipeline = new ReflectivePipeline(); - - var settings = new ReflectivePipelineSettings(); - settings.hsvHue.set(60, 100); - settings.hsvSaturation.set(100, 255); - settings.hsvValue.set(190, 255); - settings.outputShowThresholded = true; - settings.outputShowMultipleTargets = true; - settings.contourGroupingMode = ContourGroupingMode.Dual; - settings.contourIntersection = ContourIntersectionDirection.Up; + pipeline.getSettings().hsvHue.set(60, 100); + pipeline.getSettings().hsvSaturation.set(100, 255); + pipeline.getSettings().hsvValue.set(190, 255); + pipeline.getSettings().outputShowThresholded = true; + pipeline.getSettings().outputShowMultipleTargets = true; + pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Dual; + pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up; var frameProvider = new FileFrameProvider( @@ -55,7 +53,7 @@ public class ReflectivePipelineTest { printTestResults(pipelineResult); Assertions.assertTrue(pipelineResult.hasTargets()); - Assertions.assertEquals(2, pipelineResult.targets.size()); + Assertions.assertEquals(2, pipelineResult.targets.size(), "Target count wrong!"); TestUtils.showImage(pipelineResult.outputFrame.image.getMat(), "Pipeline output"); } @@ -65,11 +63,10 @@ public class ReflectivePipelineTest { TestUtils.loadLibraries(); var pipeline = new ReflectivePipeline(); - var settings = new ReflectivePipelineSettings(); - settings.hsvHue.set(60, 100); - settings.hsvSaturation.set(200, 255); - settings.hsvValue.set(200, 255); - settings.outputShowThresholded = true; + pipeline.getSettings().hsvHue.set(60, 100); + pipeline.getSettings().hsvSaturation.set(200, 255); + pipeline.getSettings().hsvValue.set(200, 255); + pipeline.getSettings().outputShowThresholded = true; var frameProvider = new FileFrameProvider( diff --git a/chameleon-server/src/test/java/com/chameleonvision/common/vision/pipeline/SolvePNPTest.java b/chameleon-server/src/test/java/com/chameleonvision/common/vision/pipeline/SolvePNPTest.java index 56ec0f877..4229643b2 100644 --- a/chameleon-server/src/test/java/com/chameleonvision/common/vision/pipeline/SolvePNPTest.java +++ b/chameleon-server/src/test/java/com/chameleonvision/common/vision/pipeline/SolvePNPTest.java @@ -81,21 +81,17 @@ public class SolvePNPTest { public void test2019() { var pipeline = new ReflectivePipeline(); - var settings = new ReflectivePipelineSettings(); - settings.hsvHue.set(60, 100); - settings.hsvSaturation.set(100, 255); - settings.hsvValue.set(190, 255); - settings.outputShowThresholded = true; - settings.outputShowMultipleTargets = true; - settings.solvePNPEnabled = true; - settings.contourGroupingMode = ContourGroupingMode.Dual; - settings.contourIntersection = ContourIntersectionDirection.Up; - settings.cornerDetectionUseConvexHulls = true; - - settings.targetModel = TargetModel.get2019Target(); - settings.cameraCalibration = getCoeffs(LIFECAM_240P_CAL_FILE); - - pipeline.settings = settings; + pipeline.getSettings().hsvHue.set(60, 100); + pipeline.getSettings().hsvSaturation.set(100, 255); + pipeline.getSettings().hsvValue.set(190, 255); + pipeline.getSettings().outputShowThresholded = true; + pipeline.getSettings().outputShowMultipleTargets = true; + pipeline.getSettings().solvePNPEnabled = true; + pipeline.getSettings().contourGroupingMode = ContourGroupingMode.Dual; + pipeline.getSettings().contourIntersection = ContourIntersectionDirection.Up; + pipeline.getSettings().cornerDetectionUseConvexHulls = true; + pipeline.getSettings().targetModel = TargetModel.get2019Target(); + pipeline.getSettings().cameraCalibration = getCoeffs(LIFECAM_240P_CAL_FILE); var frameProvider = new FileFrameProvider( @@ -120,20 +116,16 @@ public class SolvePNPTest { public void test2020() { var pipeline = new ReflectivePipeline(); - var settings = new ReflectivePipelineSettings(); - settings.hsvHue.set(60, 100); - settings.hsvSaturation.set(100, 255); - settings.hsvValue.set(60, 255); - settings.outputShowThresholded = true; - settings.solvePNPEnabled = true; - settings.cornerDetectionAccuracyPercentage = 4; - settings.cornerDetectionUseConvexHulls = true; - settings.cameraCalibration = getCoeffs(LIFECAM_480P_CAL_FILE); - - settings.targetModel = TargetModel.get2020Target(36); - settings.cameraPitch = Rotation2d.fromDegrees(0.0); - - pipeline.settings = settings; + pipeline.getSettings().hsvHue.set(60, 100); + pipeline.getSettings().hsvSaturation.set(100, 255); + pipeline.getSettings().hsvValue.set(60, 255); + pipeline.getSettings().outputShowThresholded = true; + pipeline.getSettings().solvePNPEnabled = true; + pipeline.getSettings().cornerDetectionAccuracyPercentage = 4; + pipeline.getSettings().cornerDetectionUseConvexHulls = true; + pipeline.getSettings().cameraCalibration = getCoeffs(LIFECAM_480P_CAL_FILE); + pipeline.getSettings().targetModel = TargetModel.get2020Target(36); + pipeline.getSettings().cameraPitch = Rotation2d.fromDegrees(0.0); var frameProvider = new FileFrameProvider( @@ -175,6 +167,7 @@ public class SolvePNPTest { private static void continuouslyRunPipeline(Frame frame, ReflectivePipelineSettings settings) { var pipeline = new ReflectivePipeline(); pipeline.settings = settings; + while (true) { CVPipelineResult pipelineResult = pipeline.run(frame); printTestResults(pipelineResult); diff --git a/chameleon-server/src/test/java/com/chameleonvision/common/vision/target/TargetCalculationsTest.java b/chameleon-server/src/test/java/com/chameleonvision/common/vision/target/TargetCalculationsTest.java new file mode 100644 index 000000000..67d1a69eb --- /dev/null +++ b/chameleon-server/src/test/java/com/chameleonvision/common/vision/target/TargetCalculationsTest.java @@ -0,0 +1,39 @@ +package com.chameleonvision.common.vision.target; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Test; +import org.opencv.core.Point; +import org.opencv.core.Size; + +public class TargetCalculationsTest { + + private static Size imageSize = new Size(800, 600); + private static Point imageCenterPoint = new Point(imageSize.width / 2, imageSize.height / 2); + private static double CameraHorizontalFocalLength = 61; + private static double CameraVerticalFocalLength = 34.3; + + @Test + public void yawTest() { + var targetPixelOffsetX = 100; + var targetCenterPoint = new Point(imageCenterPoint.x + targetPixelOffsetX, imageCenterPoint.y); + + var yaw = + TargetCalculations.calculateYaw( + imageCenterPoint.x, targetCenterPoint.x, CameraHorizontalFocalLength); + + assertEquals(-1.466, yaw, 0.025, "Yaw not as expected"); + } + + @Test + public void pitchTest() { + var targetPixelOffsetY = 100; + var targetCenterPoint = new Point(imageCenterPoint.x, imageCenterPoint.y + targetPixelOffsetY); + + var pitch = + TargetCalculations.calculatePitch( + imageCenterPoint.y, targetCenterPoint.y, CameraVerticalFocalLength); + + assertEquals(2.607, pitch, 0.025, "Pitch not as expected"); + } +} diff --git a/chameleon-server/src/test/java/com/chameleonvision/common/vision/target/TrackedTargetTest.java b/chameleon-server/src/test/java/com/chameleonvision/common/vision/target/TrackedTargetTest.java new file mode 100644 index 000000000..5269c27e0 --- /dev/null +++ b/chameleon-server/src/test/java/com/chameleonvision/common/vision/target/TrackedTargetTest.java @@ -0,0 +1,54 @@ +package com.chameleonvision.common.vision.target; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.chameleonvision.common.util.TestUtils; +import com.chameleonvision.common.util.numbers.DoubleCouple; +import com.chameleonvision.common.vision.opencv.Contour; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.opencv.core.Mat; +import org.opencv.core.MatOfPoint; +import org.opencv.core.Point; +import org.opencv.core.Size; + +public class TrackedTargetTest { + @BeforeEach + public void Init() { + TestUtils.loadLibraries(); + } + + @Test + void axisTest() { + Mat background = new Mat(); + + MatOfPoint mat = new MatOfPoint(); + mat.fromList( + List.of( + new Point(400, 298), + new Point(426.22, 298), + new Point(426.22, 302), + new Point(400, 302))); // gives contour with center of 426, 300 + Contour contour = new Contour(mat); + var pTarget = new PotentialTarget(contour); + + var imageSize = new Size(800, 600); + + var setting = + new TrackedTarget.TargetCalculationParameters( + false, + TargetOffsetPointEdge.Center, + new Point(0, 0), + new Point(imageSize.width / 2, imageSize.height / 2), + new DoubleCouple(0.0, 0.0), + RobotOffsetPointMode.None, + 61, + 34.3, + imageSize.area()); + + var trackedTarget = new TrackedTarget(pTarget, setting); + assertEquals(1.4, trackedTarget.getYaw(), 0.025, "Yaw was incorrect"); + assertEquals(0, trackedTarget.getPitch(), 0.025, "Pitch was incorrect"); + } +}