diff --git a/photon-server/src/main/java/org/photonvision/common/util/java/TriConsumer.java b/photon-server/src/main/java/org/photonvision/common/util/java/TriConsumer.java
new file mode 100644
index 000000000..6f1dc67fb
--- /dev/null
+++ b/photon-server/src/main/java/org/photonvision/common/util/java/TriConsumer.java
@@ -0,0 +1,22 @@
+/*
+ * 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 .
+ */
+
+package org.photonvision.common.util.java;
+
+public interface TriConsumer {
+ void accept(T t, U u, V v);
+}
diff --git a/photon-server/src/main/java/org/photonvision/vision/frame/consumer/FileSaveFrameConsumer.java b/photon-server/src/main/java/org/photonvision/vision/frame/consumer/FileSaveFrameConsumer.java
index 105375233..61274e73c 100644
--- a/photon-server/src/main/java/org/photonvision/vision/frame/consumer/FileSaveFrameConsumer.java
+++ b/photon-server/src/main/java/org/photonvision/vision/frame/consumer/FileSaveFrameConsumer.java
@@ -24,6 +24,7 @@ import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Consumer;
import org.opencv.imgcodecs.Imgcodecs;
import org.photonvision.common.configuration.ConfigManager;
import org.photonvision.common.dataflow.networktables.NetworkTablesManager;
@@ -32,7 +33,7 @@ import org.photonvision.common.logging.Logger;
import org.photonvision.common.util.TimedTaskManager;
import org.photonvision.vision.frame.Frame;
-public class FileSaveFrameConsumer {
+public class FileSaveFrameConsumer implements Consumer {
// Formatters to generate unique, timestamped file names
private static String FILE_PATH = ConfigManager.getInstance().getImageSavePath().toString();
@@ -59,7 +60,7 @@ public class FileSaveFrameConsumer {
this.rootTable = NetworkTablesManager.getInstance().kRootTable;
updateCameraNickname(camNickname);
entry = subTable.getEntry(ntEntryName);
- entry.setBoolean(false);
+ entry.forceSetBoolean(false);
this.logger = new Logger(FileSaveFrameConsumer.class, this.camNickname, LogGroup.General);
}
@@ -68,8 +69,7 @@ public class FileSaveFrameConsumer {
if (lock.tryLock()) {
boolean curCommand = entry.getBoolean(false);
-
- if (curCommand == true && prevCommand == false) {
+ if (curCommand && !prevCommand) {
Date now = new Date();
String savefile =
FILE_PATH
@@ -81,12 +81,16 @@ public class FileSaveFrameConsumer {
+ tf.format(now)
+ FILE_EXTENSION;
- Imgcodecs.imwrite(savefile.toString(), frame.image.getMat());
+ Imgcodecs.imwrite(savefile, frame.image.getMat());
// Help the user a bit - set the NT entry back to false after 500ms
- TimedTaskManager.getInstance().addOneShotTask(() -> resetCommand(), CMD_RESET_TIME_MS);
+ TimedTaskManager.getInstance().addOneShotTask(this::resetCommand, CMD_RESET_TIME_MS);
logger.info("Saved new image at " + savefile);
+ } else if (!curCommand) {
+ // If the entry is currently false, set it again. This will make sure it shows up on the
+ // dashboard.
+ entry.forceSetBoolean(false);
}
prevCommand = curCommand;
diff --git a/photon-server/src/main/java/org/photonvision/vision/opencv/CVMat.java b/photon-server/src/main/java/org/photonvision/vision/opencv/CVMat.java
index 96a2612d4..be9103f1c 100644
--- a/photon-server/src/main/java/org/photonvision/vision/opencv/CVMat.java
+++ b/photon-server/src/main/java/org/photonvision/vision/opencv/CVMat.java
@@ -69,9 +69,14 @@ public class CVMat implements Releasable {
@Override
public void release() {
- int matNo = allMats.get(mat);
- allMats.remove(mat);
+ // If this mat is empty, all we can do is return
+ if (mat.empty()) return;
+
+ // If the mat isn't in the hashmap, we can't remove it
+ Integer matNo = allMats.get(mat);
+ if (matNo != null) allMats.remove(mat);
mat.release();
+
if (shouldPrint) {
logger.trace(() -> "CVMat" + matNo + " de-alloc - new count: " + allMats.size());
logger.trace(getStackTraceBuilder()::toString);
@@ -82,6 +87,11 @@ public class CVMat implements Releasable {
return mat;
}
+ @Override
+ public String toString() {
+ return "CVMat{" + mat.toString() + '}';
+ }
+
public static int getMatCount() {
return allMats.size();
}
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 8d6bfc7a6..4d322c689 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
@@ -158,9 +158,15 @@ public class ReflectivePipeline extends CVPipeline resultConsumers = new LinkedList<>();
private final LinkedList fpsLimitedResultConsumers = new LinkedList<>();
+ // Raw result consumers run before any drawing has been done by the OutputStreamPipeline
+ private final LinkedList>> rawResultConsumers =
+ new LinkedList<>();
private final NTDataPublisher ntConsumer;
private final UIDataPublisher uiDataConsumer;
protected final int moduleIndex;
@@ -175,7 +179,7 @@ public class VisionModule {
private void recreateFpsLimitedResultConsumers() {
// Important! These must come before the stream result consumers because the stream result
// consumers release the frame
- fpsLimitedResultConsumers.add(result -> inputFrameSaver.accept(result.inputFrame));
+ rawResultConsumers.add((in, out, tgts) -> inputFrameSaver.accept(in));
fpsLimitedResultConsumers.add(result -> outputFrameSaver.accept(result.outputFrame));
fpsLimitedResultConsumers.add(
@@ -249,6 +253,7 @@ public class VisionModule {
this.shouldRun = false;
}
if (shouldRun) {
+ consumeRawResults(inputFrame, outputFrame, targets);
try {
var osr = outputStreamPipeline.process(inputFrame, outputFrame, settings, targets);
consumeFpsLimitedResult(osr);
@@ -522,6 +527,13 @@ public class VisionModule {
}
}
+ /** Consume results prior to drawing on them. */
+ private void consumeRawResults(Frame inputFrame, Frame outputFrame, List targets) {
+ for (var c : rawResultConsumers) {
+ c.accept(inputFrame, outputFrame, targets);
+ }
+ }
+
public void setTargetModel(TargetModel targetModel) {
var settings = pipelineManager.getCurrentUserPipeline().getSettings();
if (settings instanceof ReflectivePipelineSettings) {