diff --git a/photon-client/src/views/CamerasView.vue b/photon-client/src/views/CamerasView.vue index dcc5bd4a4..4ade293da 100644 --- a/photon-client/src/views/CamerasView.vue +++ b/photon-client/src/views/CamerasView.vue @@ -299,6 +299,19 @@ Download Target + + + + mdi-upload + + Import From CalibDB + + @@ -375,6 +388,20 @@ + + + + + + {{ uploadSnackData.text }} + + @@ -406,6 +433,11 @@ export default { filteredVideomodeIndex: 0, settingsValid: true, unfilteredStreamDivisors: [1, 2, 4], + uploadSnackData: { + color: "success", + text: "", + }, + uploadSnack: false, } }, computed: { @@ -571,6 +603,57 @@ export default { }, }, methods: { + + readImportedCalibration(event) { + // let formData = new FormData(); + // formData.append("zipData", event.target.files[0]); + const filename = event.target.files[0].name; + + event.target.files[0].text().then(fileText => { + const data = { + "cameraIndex": this.$store.getters.currentCameraIndex, + "payload": fileText, + "filename": filename, + }; + + this.axios + .post("http://" + this.$address + "/api/calibration/import", data, { + headers: { "Content-Type": "text/plain" }, + }) + .then(() => { + this.uploadSnackData = { + color: "success", + text: + "Calibration imported successfully! PhotonVision will restart in the background...", + }; + this.uploadSnack = true; + }) + .catch((err) => { + if (err.response) { + this.uploadSnackData = { + color: "error", + text: + "Error while uploading calibration file! Could not process provided file.", + }; + } else if (err.request) { + this.uploadSnackData = { + color: "error", + text: + "Error while uploading calibration file! No respond to upload attempt.", + }; + } else { + this.uploadSnackData = { + color: "error", + text: "Error while uploading calibration file!", + }; + } + this.uploadSnack = true; + }); + + }) + + }, + closeDialog() { this.snack = false; this.calibrationInProgress = false; diff --git a/photon-core/src/main/java/org/photonvision/vision/calibration/CameraCalibrationCoefficients.java b/photon-core/src/main/java/org/photonvision/vision/calibration/CameraCalibrationCoefficients.java index c7d29393a..7df950533 100644 --- a/photon-core/src/main/java/org/photonvision/vision/calibration/CameraCalibrationCoefficients.java +++ b/photon-core/src/main/java/org/photonvision/vision/calibration/CameraCalibrationCoefficients.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; import org.opencv.core.Mat; import org.opencv.core.MatOfDouble; import org.opencv.core.Size; @@ -82,4 +83,43 @@ public class CameraCalibrationCoefficients implements Releasable { cameraIntrinsics.release(); distCoeffs.release(); } + + public static CameraCalibrationCoefficients parseFromCalibdbJson(JsonNode json) { + // camera_matrix is a row major, array of arrays + var cam_matrix = json.get("camera_matrix"); + + double[] cam_arr = + new double[] { + cam_matrix.get(0).get(0).doubleValue(), + cam_matrix.get(0).get(1).doubleValue(), + cam_matrix.get(0).get(2).doubleValue(), + cam_matrix.get(1).get(0).doubleValue(), + cam_matrix.get(1).get(1).doubleValue(), + cam_matrix.get(1).get(2).doubleValue(), + cam_matrix.get(2).get(0).doubleValue(), + cam_matrix.get(2).get(1).doubleValue(), + cam_matrix.get(2).get(2).doubleValue() + }; + + var dist_coefs = json.get("distortion_coefficients"); + + double[] dist_array = + new double[] { + dist_coefs.get(0).doubleValue(), + dist_coefs.get(1).doubleValue(), + dist_coefs.get(2).doubleValue(), + dist_coefs.get(3).doubleValue(), + dist_coefs.get(4).doubleValue(), + }; + + var cam_jsonmat = new JsonMat(3, 3, cam_arr); + var distortion_jsonmat = new JsonMat(1, 5, dist_array); + + var error = json.get("avg_reprojection_error").asDouble(); + var width = json.get("img_size").get(0).doubleValue(); + var height = json.get("img_size").get(1).doubleValue(); + + return new CameraCalibrationCoefficients( + new Size(width, height), cam_jsonmat, distortion_jsonmat, new double[] {error}, 0); + } } diff --git a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java index 631424e65..82dd66812 100644 --- a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java +++ b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModule.java @@ -586,4 +586,15 @@ public class VisionModule { logger.error("Cannot set target model of non-reflective pipe! Ignoring..."); } } + + public void addCalibrationToConfig(CameraCalibrationCoefficients newCalibration) { + if (newCalibration != null) { + logger.info("Got new calibration for " + newCalibration.resolution); + visionSource.getSettables().getConfiguration().addCalibration(newCalibration); + } else { + logger.error("Got null calibration?"); + } + + saveAndBroadcastAll(); + } } diff --git a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModuleChangeSubscriber.java b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModuleChangeSubscriber.java index e1d605d36..e16e4eeed 100644 --- a/photon-core/src/main/java/org/photonvision/vision/processes/VisionModuleChangeSubscriber.java +++ b/photon-core/src/main/java/org/photonvision/vision/processes/VisionModuleChangeSubscriber.java @@ -28,6 +28,7 @@ import org.photonvision.common.logging.LogGroup; import org.photonvision.common.logging.Logger; import org.photonvision.common.util.numbers.DoubleCouple; import org.photonvision.common.util.numbers.IntegerCouple; +import org.photonvision.vision.calibration.CameraCalibrationCoefficients; import org.photonvision.vision.pipeline.AdvancedPipelineSettings; import org.photonvision.vision.pipeline.PipelineType; import org.photonvision.vision.pipeline.UICalibrationData; @@ -114,6 +115,10 @@ public class VisionModuleChangeSubscriber extends DataChangeSubscriber { parentModule.setPipeline(idx); parentModule.saveAndBroadcastAll(); return; + case "calibrationUploaded": + if (newPropValue instanceof CameraCalibrationCoefficients) + parentModule.addCalibrationToConfig((CameraCalibrationCoefficients) newPropValue); + return; case "robotOffsetPoint": if (currentSettings instanceof AdvancedPipelineSettings) { var curAdvSettings = (AdvancedPipelineSettings) currentSettings; diff --git a/photon-server/src/main/java/org/photonvision/server/RequestHandler.java b/photon-server/src/main/java/org/photonvision/server/RequestHandler.java index 930b748f2..b611fc308 100644 --- a/photon-server/src/main/java/org/photonvision/server/RequestHandler.java +++ b/photon-server/src/main/java/org/photonvision/server/RequestHandler.java @@ -18,6 +18,7 @@ package org.photonvision.server; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import io.javalin.http.Context; import java.io.File; @@ -33,6 +34,9 @@ import java.util.Map; import org.apache.commons.io.FileUtils; import org.photonvision.common.configuration.ConfigManager; import org.photonvision.common.configuration.NetworkConfig; +import org.photonvision.common.dataflow.DataChangeDestination; +import org.photonvision.common.dataflow.DataChangeService; +import org.photonvision.common.dataflow.events.IncomingWebSocketEvent; import org.photonvision.common.dataflow.networktables.NetworkTablesManager; import org.photonvision.common.hardware.HardwareManager; import org.photonvision.common.hardware.Platform; @@ -42,6 +46,7 @@ import org.photonvision.common.networking.NetworkManager; import org.photonvision.common.util.ShellExec; import org.photonvision.common.util.TimedTaskManager; import org.photonvision.common.util.file.ProgramDirectoryUtilities; +import org.photonvision.vision.calibration.CameraCalibrationCoefficients; import org.photonvision.vision.processes.VisionModuleManager; import org.photonvision.vision.target.TargetModel; @@ -274,6 +279,44 @@ public class RequestHandler { } } + public static void importCalibrationFromCalibdb(Context ctx) { + var file = ctx.body(); + + if (file != null) { + // check if it's a JSON file + // Load using Jackson + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode actualObj = mapper.readTree(file); + + int cameraIndex = actualObj.get("cameraIndex").asInt(); + String filename = actualObj.get("filename").asText(); + var payload = mapper.readTree(actualObj.get("payload").asText()); + + var coeffs = CameraCalibrationCoefficients.parseFromCalibdbJson(payload); + + var uploadCalibrationEvent = + new IncomingWebSocketEvent( + DataChangeDestination.DCD_ACTIVEMODULE, + "calibrationUploaded", + coeffs, + (Integer) cameraIndex, + null); + DataChangeService.getInstance().publishEvent(uploadCalibrationEvent); + + ctx.status(200); + logger.info("Calibration added!"); + } catch (Exception e) { + logger.warn("Could not parse cal metaJSON!"); + e.printStackTrace(); + return; + } + } else { + ctx.status(500); + return; + } + } + public static void setCameraNickname(Context ctx) { try { var data = kObjectMapper.readValue(ctx.body(), HashMap.class); @@ -304,7 +347,8 @@ public class RequestHandler { public static void sendMetrics(Context ctx) { HardwareManager.getInstance().publishMetrics(); - // TimedTaskManager.getInstance().addOneShotTask(() -> RoborioFinder.getInstance().findRios(), + // TimedTaskManager.getInstance().addOneShotTask(() -> + // RoborioFinder.getInstance().findRios(), // 0); ctx.status(200); } diff --git a/photon-server/src/main/java/org/photonvision/server/Server.java b/photon-server/src/main/java/org/photonvision/server/Server.java index b84bf6635..a67dae8a1 100644 --- a/photon-server/src/main/java/org/photonvision/server/Server.java +++ b/photon-server/src/main/java/org/photonvision/server/Server.java @@ -93,6 +93,7 @@ public class Server { app.post("api/vision/pnpModel", RequestHandler::uploadPnpModel); app.post("api/sendMetrics", RequestHandler::sendMetrics); app.post("api/setCameraNickname", RequestHandler::setCameraNickname); + app.post("api/calibration/import", RequestHandler::importCalibrationFromCalibdb); app.start(port); }