mirror of
https://github.com/PhotonVision/photonvision
synced 2026-07-06 03:31:41 +00:00
Added input and output frame file save routines (#134)
* Added input and output frame file save routines * First pass at review items * Revised logic to not crash on start and pass tests * Updated build.gradle to force line endings. Spotless passes now. * Reverted lineEndings to not force Unix. Gerth needs to fix up his dev pc. Co-authored-by: Banks T <btrout.dhrs@gmail.com>
This commit is contained in:
@@ -358,6 +358,12 @@ public class ConfigManager {
|
|||||||
return logFile.toPath();
|
return logFile.toPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Path getImageSavePath() {
|
||||||
|
var imgFilePath = Path.of(configDirectoryFile.toString(), "imgSaves").toFile();
|
||||||
|
if (!imgFilePath.exists()) imgFilePath.mkdirs();
|
||||||
|
return imgFilePath.toPath();
|
||||||
|
}
|
||||||
|
|
||||||
public void requestSave() {
|
public void requestSave() {
|
||||||
logger.trace("Requesting save...");
|
logger.trace("Requesting save...");
|
||||||
saveRequestTimestamp = System.currentTimeMillis();
|
saveRequestTimestamp = System.currentTimeMillis();
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.photonvision.vision.frame.consumer;
|
||||||
|
|
||||||
|
import edu.wpi.first.networktables.NetworkTable;
|
||||||
|
import edu.wpi.first.networktables.NetworkTableInstance;
|
||||||
|
import java.io.File;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import org.opencv.imgcodecs.Imgcodecs;
|
||||||
|
import org.photonvision.common.configuration.ConfigManager;
|
||||||
|
import org.photonvision.common.logging.LogGroup;
|
||||||
|
import org.photonvision.common.logging.Logger;
|
||||||
|
import org.photonvision.common.util.TimedTaskManager;
|
||||||
|
import org.photonvision.vision.frame.Frame;
|
||||||
|
|
||||||
|
public class FileSaveFrameConsumer {
|
||||||
|
|
||||||
|
// Formatters to generate unique, timestamped file names
|
||||||
|
private static String FILE_PATH = ConfigManager.getInstance().getImageSavePath().toString();
|
||||||
|
private static String FILE_EXTENSION = ".jpg";
|
||||||
|
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
DateFormat tf = new SimpleDateFormat("hhmmssSS");
|
||||||
|
private final String NT_SUFFIX = "SaveImgCmd";
|
||||||
|
private final String ntEntryName;
|
||||||
|
private NetworkTable subTable;
|
||||||
|
private final NetworkTable rootTable;
|
||||||
|
private final Logger logger;
|
||||||
|
private boolean prevCommand = false;
|
||||||
|
private String camNickname;
|
||||||
|
private String fnamePrefix;
|
||||||
|
private final long CMD_RESET_TIME_MS = 500;
|
||||||
|
// Helps prevent race conditions between user set & auto-reset logic
|
||||||
|
private ReentrantLock lock;
|
||||||
|
|
||||||
|
public FileSaveFrameConsumer(String camNickname, String streamPrefix) {
|
||||||
|
this.lock = new ReentrantLock();
|
||||||
|
this.fnamePrefix = camNickname + "_" + streamPrefix;
|
||||||
|
this.ntEntryName = streamPrefix + NT_SUFFIX;
|
||||||
|
this.rootTable = NetworkTableInstance.getDefault().getTable("/photonvision");
|
||||||
|
updateCameraNickname(camNickname);
|
||||||
|
this.logger = new Logger(FileSaveFrameConsumer.class, this.camNickname, LogGroup.General);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void accept(Frame frame) {
|
||||||
|
if (frame != null && !frame.image.getMat().empty()) {
|
||||||
|
|
||||||
|
if (lock.tryLock()) {
|
||||||
|
boolean curCommand = subTable.getEntry(ntEntryName).getBoolean(false);
|
||||||
|
|
||||||
|
if (curCommand == true && prevCommand == false) {
|
||||||
|
Date now = new Date();
|
||||||
|
String savefile =
|
||||||
|
FILE_PATH
|
||||||
|
+ File.separator
|
||||||
|
+ fnamePrefix
|
||||||
|
+ "_"
|
||||||
|
+ df.format(now)
|
||||||
|
+ "T"
|
||||||
|
+ tf.format(now)
|
||||||
|
+ FILE_EXTENSION;
|
||||||
|
|
||||||
|
Imgcodecs.imwrite(savefile.toString(), 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);
|
||||||
|
|
||||||
|
logger.info("Saved new image at " + savefile);
|
||||||
|
}
|
||||||
|
|
||||||
|
prevCommand = curCommand;
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetCommand() {
|
||||||
|
lock.lock();
|
||||||
|
this.subTable.getEntry(ntEntryName).setBoolean(false);
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeEntries() {
|
||||||
|
if (this.subTable != null) {
|
||||||
|
if (this.subTable.containsKey(ntEntryName)) {
|
||||||
|
this.subTable.delete(ntEntryName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateCameraNickname(String newCameraNickname) {
|
||||||
|
removeEntries();
|
||||||
|
this.camNickname = newCameraNickname;
|
||||||
|
this.subTable = rootTable.getSubTable(this.camNickname);
|
||||||
|
resetCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import org.photonvision.vision.calibration.CameraCalibrationCoefficients;
|
|||||||
import org.photonvision.vision.camera.CameraQuirk;
|
import org.photonvision.vision.camera.CameraQuirk;
|
||||||
import org.photonvision.vision.camera.QuirkyCamera;
|
import org.photonvision.vision.camera.QuirkyCamera;
|
||||||
import org.photonvision.vision.camera.USBCameraSource;
|
import org.photonvision.vision.camera.USBCameraSource;
|
||||||
|
import org.photonvision.vision.frame.consumer.FileSaveFrameConsumer;
|
||||||
import org.photonvision.vision.frame.consumer.MJPGFrameConsumer;
|
import org.photonvision.vision.frame.consumer.MJPGFrameConsumer;
|
||||||
import org.photonvision.vision.pipeline.ReflectivePipelineSettings;
|
import org.photonvision.vision.pipeline.ReflectivePipelineSettings;
|
||||||
import org.photonvision.vision.pipeline.UICalibrationData;
|
import org.photonvision.vision.pipeline.UICalibrationData;
|
||||||
@@ -71,6 +72,9 @@ public class VisionModule {
|
|||||||
MJPGFrameConsumer dashboardInputStreamer;
|
MJPGFrameConsumer dashboardInputStreamer;
|
||||||
MJPGFrameConsumer dashboardOutputStreamer;
|
MJPGFrameConsumer dashboardOutputStreamer;
|
||||||
|
|
||||||
|
FileSaveFrameConsumer inputFrameSaver;
|
||||||
|
FileSaveFrameConsumer outputFrameSaver;
|
||||||
|
|
||||||
public VisionModule(PipelineManager pipelineManager, VisionSource visionSource, int index) {
|
public VisionModule(PipelineManager pipelineManager, VisionSource visionSource, int index) {
|
||||||
logger =
|
logger =
|
||||||
new Logger(
|
new Logger(
|
||||||
@@ -98,6 +102,8 @@ public class VisionModule {
|
|||||||
createStreams();
|
createStreams();
|
||||||
fpsLimitedResultConsumers.add(result -> dashboardInputStreamer.accept(result.inputFrame));
|
fpsLimitedResultConsumers.add(result -> dashboardInputStreamer.accept(result.inputFrame));
|
||||||
fpsLimitedResultConsumers.add(result -> dashboardOutputStreamer.accept(result.outputFrame));
|
fpsLimitedResultConsumers.add(result -> dashboardOutputStreamer.accept(result.outputFrame));
|
||||||
|
fpsLimitedResultConsumers.add(result -> inputFrameSaver.accept(result.inputFrame));
|
||||||
|
fpsLimitedResultConsumers.add(result -> outputFrameSaver.accept(result.outputFrame));
|
||||||
|
|
||||||
ntConsumer =
|
ntConsumer =
|
||||||
new NTDataPublisher(
|
new NTDataPublisher(
|
||||||
@@ -151,7 +157,13 @@ public class VisionModule {
|
|||||||
visionSource.getSettables().getConfiguration().nickname + "-output", outputStreamPort);
|
visionSource.getSettables().getConfiguration().nickname + "-output", outputStreamPort);
|
||||||
dashboardInputStreamer =
|
dashboardInputStreamer =
|
||||||
new MJPGFrameConsumer(
|
new MJPGFrameConsumer(
|
||||||
visionSource.getSettables().getConfiguration().nickname + "-input", inputStreamPort);
|
visionSource.getSettables().getConfiguration().uniqueName + "-input", inputStreamPort);
|
||||||
|
|
||||||
|
inputFrameSaver =
|
||||||
|
new FileSaveFrameConsumer(visionSource.getSettables().getConfiguration().nickname, "input");
|
||||||
|
outputFrameSaver =
|
||||||
|
new FileSaveFrameConsumer(
|
||||||
|
visionSource.getSettables().getConfiguration().nickname, "output");
|
||||||
}
|
}
|
||||||
|
|
||||||
void setDriverMode(boolean isDriverMode) {
|
void setDriverMode(boolean isDriverMode) {
|
||||||
@@ -287,6 +299,8 @@ public class VisionModule {
|
|||||||
public void setCameraNickname(String newName) {
|
public void setCameraNickname(String newName) {
|
||||||
visionSource.getSettables().getConfiguration().nickname = newName;
|
visionSource.getSettables().getConfiguration().nickname = newName;
|
||||||
ntConsumer.updateCameraNickname(newName);
|
ntConsumer.updateCameraNickname(newName);
|
||||||
|
inputFrameSaver.updateCameraNickname(newName);
|
||||||
|
outputFrameSaver.updateCameraNickname(newName);
|
||||||
|
|
||||||
// rename streams
|
// rename streams
|
||||||
fpsLimitedResultConsumers.clear();
|
fpsLimitedResultConsumers.clear();
|
||||||
@@ -297,6 +311,8 @@ public class VisionModule {
|
|||||||
|
|
||||||
fpsLimitedResultConsumers.add(result -> dashboardInputStreamer.accept(result.inputFrame));
|
fpsLimitedResultConsumers.add(result -> dashboardInputStreamer.accept(result.inputFrame));
|
||||||
fpsLimitedResultConsumers.add(result -> dashboardOutputStreamer.accept(result.outputFrame));
|
fpsLimitedResultConsumers.add(result -> dashboardOutputStreamer.accept(result.outputFrame));
|
||||||
|
fpsLimitedResultConsumers.add(result -> inputFrameSaver.accept(result.inputFrame));
|
||||||
|
fpsLimitedResultConsumers.add(result -> outputFrameSaver.accept(result.outputFrame));
|
||||||
|
|
||||||
// Push new data to the UI
|
// Push new data to the UI
|
||||||
saveAndBroadcastAll();
|
saveAndBroadcastAll();
|
||||||
|
|||||||
Reference in New Issue
Block a user