diff --git a/photon-server/src/main/java/org/photonvision/vision/pipeline/PipelineProfiler.java b/photon-server/src/main/java/org/photonvision/vision/pipeline/PipelineProfiler.java
new file mode 100644
index 000000000..f77d4f128
--- /dev/null
+++ b/photon-server/src/main/java/org/photonvision/vision/pipeline/PipelineProfiler.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 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 .
+ */
+
+package org.photonvision.vision.pipeline;
+
+import org.photonvision.common.logging.LogGroup;
+import org.photonvision.common.logging.Logger;
+import org.photonvision.common.util.math.MathUtils;
+
+public class PipelineProfiler {
+ private static final Logger reflectiveLogger =
+ new Logger(ReflectivePipeline.class, LogGroup.VisionModule);
+ private static final Logger coloredShapeLogger =
+ new Logger(ColoredShapePipeline.class, LogGroup.VisionModule);
+
+ public static final int ReflectivePipeCount = 19;
+
+ /**
+ * Indices for Reflective profiling 0 - rotateImagePipe 1 - inputCopy (not a pipe) 2 -
+ * erodeDilatePipe 3 - hsvPipe 4 - findContoursPipe 5 - speckleRejectPipe 6 - filterContoursPipe 7
+ * - groupContoursPipe 8 - sortContoursPipe 9 - collect2dTargetsPipe 10 - cornerDetectionPipe
+ * (OPTIONAL) 11 - solvePNPPipe (OPTIONAL) 12 - outputMatPipe 13 - draw2dCrosshairPipe (on input)
+ * 14 - draw2dCrosshairPipe (on output) 15 - draw2dTargetsPipe (on input) 16 - draw2dTargetsPipe
+ * (on output) 17 - draw3dTargetsPipe (OPTIONAL, on input) 18 - draw3dTargetsPipe (OPTIONAL, on
+ * output)
+ */
+ private static final String[] ReflectivePipeNames =
+ new String[] {
+ "RotateImage",
+ "InputCopy",
+ "ErodeDilate",
+ "HSV",
+ "FindContours",
+ "SpeckleReject",
+ "FilterContours",
+ "GroupContours",
+ "SortContours",
+ "Collect2dTargets",
+ "CornerDetection",
+ "SolvePNP",
+ "OutputConversion",
+ "Draw2dCrosshairInput",
+ "Draw2dCrosshairOutput",
+ "Draw2dTargetsInput",
+ "Draw2dTargetsOutput",
+ "Draw3dTargetsInput",
+ "Draw3dTargetsOutput",
+ };
+
+ protected static String getReflectiveProfileString(long[] nanos) {
+ if (nanos.length != ReflectivePipeCount) {
+ return "Invalid data";
+ }
+
+ var sb = new StringBuilder("Profiling - ");
+ double totalMs = 0;
+ for (int i = 0; i < nanos.length; i++) {
+ if ((i == 10 || i == 11 || i == 17 || i == 18) && nanos[i] == 0) {
+ continue; // skip empty pipe profiles
+ }
+
+ sb.append(ReflectivePipeNames[i]);
+ sb.append(": ");
+ var ms = MathUtils.roundTo(nanos[i] / 1e+6, 3);
+ totalMs += ms;
+ sb.append(ms);
+ sb.append("ms");
+ // boolean isLast = (i + 1 == 17 && nanos[i+1] == 0) || i == 18;
+ // if (isLast) {
+ // var foo = "bar";
+ // } else {
+ sb.append(", ");
+ // }
+ }
+
+ sb.append("Total: ");
+ sb.append(MathUtils.roundTo(totalMs, 3));
+ sb.append("ms");
+
+ return sb.toString();
+ }
+
+ public static void printReflectiveProfile(long[] nanos) {
+ reflectiveLogger.trace(() -> getReflectiveProfileString(nanos));
+ }
+}
diff --git a/photon-server/src/main/java/org/photonvision/vision/pipeline/ReflectivePipeline.java b/photon-server/src/main/java/org/photonvision/vision/pipeline/ReflectivePipeline.java
index c7de98c21..7b3f06325 100644
--- a/photon-server/src/main/java/org/photonvision/vision/pipeline/ReflectivePipeline.java
+++ b/photon-server/src/main/java/org/photonvision/vision/pipeline/ReflectivePipeline.java
@@ -52,7 +52,6 @@ public class ReflectivePipeline extends CVPipeline hsvPipeResult = hsvPipe.run(rawInputMat);
sumPipeNanosElapsed += hsvPipeResult.nanosElapsed;
+ pipeProfileNanos[3] = pipeProfileNanos[3] = hsvPipeResult.nanosElapsed;
CVPipeResult> findContoursResult = findContoursPipe.run(hsvPipeResult.output);
- sumPipeNanosElapsed += findContoursResult.nanosElapsed;
+ sumPipeNanosElapsed += pipeProfileNanos[4] = findContoursResult.nanosElapsed;
CVPipeResult> speckleRejectResult =
speckleRejectPipe.run(findContoursResult.output);
- sumPipeNanosElapsed += speckleRejectResult.nanosElapsed;
+ sumPipeNanosElapsed += pipeProfileNanos[5] = speckleRejectResult.nanosElapsed;
CVPipeResult> filterContoursResult =
filterContoursPipe.run(speckleRejectResult.output);
- sumPipeNanosElapsed += filterContoursResult.nanosElapsed;
+ sumPipeNanosElapsed += pipeProfileNanos[6] = filterContoursResult.nanosElapsed;
CVPipeResult> groupContoursResult =
groupContoursPipe.run(filterContoursResult.output);
- sumPipeNanosElapsed += groupContoursResult.nanosElapsed;
+ sumPipeNanosElapsed += pipeProfileNanos[7] = groupContoursResult.nanosElapsed;
CVPipeResult> sortContoursResult =
sortContoursPipe.run(groupContoursResult.output);
- sumPipeNanosElapsed += sortContoursResult.nanosElapsed;
+ sumPipeNanosElapsed += pipeProfileNanos[8] = sortContoursResult.nanosElapsed;
CVPipeResult> collect2dTargetsResult =
collect2dTargetsPipe.run(sortContoursResult.output);
- sumPipeNanosElapsed += collect2dTargetsResult.nanosElapsed;
+ sumPipeNanosElapsed += pipeProfileNanos[9] = collect2dTargetsResult.nanosElapsed;
List targetList;
// 3d stuff
if (settings.solvePNPEnabled) {
var cornerDetectionResult = cornerDetectionPipe.run(collect2dTargetsResult.output);
- sumPipeNanosElapsed += cornerDetectionResult.nanosElapsed;
+ sumPipeNanosElapsed += pipeProfileNanos[10] = cornerDetectionResult.nanosElapsed;
var solvePNPResult = solvePNPPipe.run(cornerDetectionResult.output);
- sumPipeNanosElapsed += solvePNPResult.nanosElapsed;
+ sumPipeNanosElapsed += pipeProfileNanos[11] = solvePNPResult.nanosElapsed;
targetList = solvePNPResult.output;
} else {
+ pipeProfileNanos[10] = 0;
+ pipeProfileNanos[11] = 0;
targetList = collect2dTargetsResult.output;
}
// Convert single-channel HSV output mat to 3-channel BGR in preparation for streaming
var outputMatPipeResult = outputMatPipe.run(hsvPipeResult.output);
- sumPipeNanosElapsed += outputMatPipeResult.nanosElapsed;
+ sumPipeNanosElapsed += pipeProfileNanos[12] = outputMatPipeResult.nanosElapsed;
// Draw 2D Crosshair on input and output
var draw2dCrosshairResultOnInput = draw2dCrosshairPipe.run(Pair.of(rawInputMat, targetList));
- sumPipeNanosElapsed += draw2dCrosshairResultOnInput.nanosElapsed;
+ sumPipeNanosElapsed += pipeProfileNanos[13] = draw2dCrosshairResultOnInput.nanosElapsed;
var draw2dCrosshairResultOnOutput =
draw2dCrosshairPipe.run(Pair.of(hsvPipeResult.output, targetList));
- sumPipeNanosElapsed += draw2dCrosshairResultOnOutput.nanosElapsed;
+ sumPipeNanosElapsed += pipeProfileNanos[14] = draw2dCrosshairResultOnOutput.nanosElapsed;
// Draw 2D contours on input and output
- var draw2dContoursResultOnInput =
- draw2DTargetsPipe.run(Pair.of(rawInputMat, collect2dTargetsResult.output));
- sumPipeNanosElapsed += draw2dContoursResultOnInput.nanosElapsed;
+ var draw2dTargetsOnInput =
+ draw2dTargetsPipe.run(Pair.of(rawInputMat, collect2dTargetsResult.output));
+ sumPipeNanosElapsed += pipeProfileNanos[15] = draw2dTargetsOnInput.nanosElapsed;
- var draw2dContoursResultOnOutput =
- draw2DTargetsPipe.run(Pair.of(hsvPipeResult.output, collect2dTargetsResult.output));
- sumPipeNanosElapsed += draw2dContoursResultOnOutput.nanosElapsed;
+ var draw2dTargetsOnOutput =
+ draw2dTargetsPipe.run(Pair.of(hsvPipeResult.output, collect2dTargetsResult.output));
+ sumPipeNanosElapsed += pipeProfileNanos[16] = draw2dTargetsOnOutput.nanosElapsed;
// Draw 3D Targets on input and output if necessary
if (settings.solvePNPEnabled) {
var drawOnInputResult =
draw3dTargetsPipe.run(Pair.of(rawInputMat, collect2dTargetsResult.output));
- sumPipeNanosElapsed += drawOnInputResult.nanosElapsed;
+ sumPipeNanosElapsed += pipeProfileNanos[17] = drawOnInputResult.nanosElapsed;
var drawOnOutputResult =
draw3dTargetsPipe.run(Pair.of(hsvPipeResult.output, collect2dTargetsResult.output));
- sumPipeNanosElapsed += drawOnOutputResult.nanosElapsed;
+ sumPipeNanosElapsed += pipeProfileNanos[18] = drawOnOutputResult.nanosElapsed;
+ } else {
+ pipeProfileNanos[17] = 0;
+ pipeProfileNanos[18] = 0;
}
+ PipelineProfiler.printReflectiveProfile(pipeProfileNanos);
+
return new CVPipelineResult(
MathUtils.nanosToMillis(sumPipeNanosElapsed),
targetList,
diff --git a/photon-server/src/test/java/org/photonvision/vision/pipeline/PipelineProfilerTest.java b/photon-server/src/test/java/org/photonvision/vision/pipeline/PipelineProfilerTest.java
new file mode 100644
index 000000000..6c3ba7ca6
--- /dev/null
+++ b/photon-server/src/test/java/org/photonvision/vision/pipeline/PipelineProfilerTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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 .
+ */
+
+package org.photonvision.vision.pipeline;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class PipelineProfilerTest {
+
+ @Test
+ public void reflectiveProfile() {
+ long[] invalidNanos = new long[10];
+ long[] validNanos = new long[PipelineProfiler.ReflectivePipeCount];
+
+ for (int i = 0; i < validNanos.length; i++) {
+ validNanos[i] = (long) (i * 1e+6); // fill data
+ }
+
+ var invalidResult = PipelineProfiler.getReflectiveProfileString(invalidNanos);
+ var validResult = PipelineProfiler.getReflectiveProfileString(validNanos);
+
+ Assertions.assertEquals("Invalid data", invalidResult);
+ Assertions.assertTrue(validResult.contains("Total: 171.0ms"));
+ System.out.println(validResult);
+ }
+}