From 508ccff7669070943ebcc43549dc7b0abbb3c0fd Mon Sep 17 00:00:00 2001 From: Banks Troutman Date: Thu, 19 Sep 2019 14:07:42 -0400 Subject: [PATCH] Full rewrite of camera system --- .../com/chameleonvision/CameraException.java | 25 +++ .../java/com/chameleonvision/FileHelper.java | 25 +++ .../main/java/com/chameleonvision/Main.java | 9 +- .../chameleonvision/NoCameraException.java | 7 - .../settings/SettingsManager.java | 180 +++--------------- .../chameleonvision/vision/CamVideoMode.java | 9 - .../com/chameleonvision/vision/Camera.java | 11 -- .../vision/GeneralSettings.java | 2 +- .../vision/camera/CamVideoMode.java | 25 +++ .../chameleonvision/vision/camera/Camera.java | 132 +++++++++++++ .../vision/camera/CameraManager.java | 133 +++++++++++++ .../vision/camera/CameraSerializer.java | 22 +++ .../vision/{ => camera}/CameraValues.java | 4 +- .../vision/process/CameraProcess.java | 70 ++++--- .../vision/process/VisionProcess.java | 3 +- .../java/com/chameleonvision/web/Server.java | 65 +++---- 16 files changed, 470 insertions(+), 252 deletions(-) create mode 100644 Main/src/main/java/com/chameleonvision/CameraException.java create mode 100644 Main/src/main/java/com/chameleonvision/FileHelper.java delete mode 100644 Main/src/main/java/com/chameleonvision/NoCameraException.java delete mode 100644 Main/src/main/java/com/chameleonvision/vision/CamVideoMode.java delete mode 100644 Main/src/main/java/com/chameleonvision/vision/Camera.java create mode 100644 Main/src/main/java/com/chameleonvision/vision/camera/CamVideoMode.java create mode 100644 Main/src/main/java/com/chameleonvision/vision/camera/Camera.java create mode 100644 Main/src/main/java/com/chameleonvision/vision/camera/CameraManager.java create mode 100644 Main/src/main/java/com/chameleonvision/vision/camera/CameraSerializer.java rename Main/src/main/java/com/chameleonvision/vision/{ => camera}/CameraValues.java (93%) diff --git a/Main/src/main/java/com/chameleonvision/CameraException.java b/Main/src/main/java/com/chameleonvision/CameraException.java new file mode 100644 index 000000000..6be5ec7a9 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/CameraException.java @@ -0,0 +1,25 @@ +package com.chameleonvision; + +public class CameraException extends Exception { + public enum CameraExceptionType { + NO_CAMERA, + BAD_CAMERA, + BAD_PIPELINE, + BAD_SETTING; + + @Override + public String toString() { + switch (this) { + case NO_CAMERA: return "No camera connected!"; + case BAD_CAMERA: return "Invalid camera!"; + case BAD_PIPELINE: return "Invalid pipeline!"; + case BAD_SETTING: return "Invalid camera/pipeline setting!"; + default: return "Unknown camera exception!"; + } + } + } + + public CameraException(CameraExceptionType camExceptionType) { + super(camExceptionType.toString()); + } +} diff --git a/Main/src/main/java/com/chameleonvision/FileHelper.java b/Main/src/main/java/com/chameleonvision/FileHelper.java new file mode 100644 index 000000000..8ddb39b05 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/FileHelper.java @@ -0,0 +1,25 @@ +package com.chameleonvision; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +public class FileHelper { + private FileHelper() {} // no construction, utility class + + public static void CheckPath(String path) { + if (path.equals("")) return; + Path realPath = Path.of(path); + CheckPath(realPath); + } + + public static void CheckPath(Path path) { + if (!Files.exists(path)) { + try { + Files.createDirectories(path); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/Main/src/main/java/com/chameleonvision/Main.java b/Main/src/main/java/com/chameleonvision/Main.java index 943d2b676..4c27705c7 100644 --- a/Main/src/main/java/com/chameleonvision/Main.java +++ b/Main/src/main/java/com/chameleonvision/Main.java @@ -1,6 +1,8 @@ package com.chameleonvision; import com.chameleonvision.settings.SettingsManager; +import com.chameleonvision.vision.camera.Camera; +import com.chameleonvision.vision.camera.CameraManager; import com.chameleonvision.vision.process.CameraProcess; import com.chameleonvision.web.Server; import edu.wpi.cscore.UsbCamera; @@ -9,11 +11,12 @@ import java.util.Map; public class Main { public static void main(String[] args) { + CameraManager.initializeCameras(); SettingsManager manager = SettingsManager.getInstance(); -// NetworkTableInstance.getDefault().startClientTeam(SettingsManager.GeneralSettings.team_number); - for (Map.Entry entry : SettingsManager.UsbCameras.entrySet()) { - new Thread(new CameraProcess(entry.getKey())).start(); + for (var camSet : CameraManager.getAllCamerasByName().entrySet()) { + new Thread(new CameraProcess(camSet.getValue())).start(); } + // NetworkTableInstance.getDefault().startClientTeam(SettingsManager.GeneralSettings.team_number); Server.main(8888); } } diff --git a/Main/src/main/java/com/chameleonvision/NoCameraException.java b/Main/src/main/java/com/chameleonvision/NoCameraException.java deleted file mode 100644 index 063da8f48..000000000 --- a/Main/src/main/java/com/chameleonvision/NoCameraException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.chameleonvision; - -public class NoCameraException extends Exception { - public NoCameraException(){ - super("No Camera Connected"); - } -} diff --git a/Main/src/main/java/com/chameleonvision/settings/SettingsManager.java b/Main/src/main/java/com/chameleonvision/settings/SettingsManager.java index c2f8954f9..3dcac648f 100644 --- a/Main/src/main/java/com/chameleonvision/settings/SettingsManager.java +++ b/Main/src/main/java/com/chameleonvision/settings/SettingsManager.java @@ -1,14 +1,15 @@ package com.chameleonvision.settings; -import com.chameleonvision.NoCameraException; +import com.chameleonvision.FileHelper; +import com.chameleonvision.CameraException; import java.io.*; import java.nio.file.*; -import com.chameleonvision.vision.CamVideoMode; -import com.chameleonvision.vision.Camera; +import com.chameleonvision.vision.camera.Camera; import com.chameleonvision.vision.GeneralSettings; import com.chameleonvision.vision.Pipeline; +import com.chameleonvision.vision.camera.CameraManager; import com.google.gson.Gson; import java.util.ArrayList; @@ -17,22 +18,17 @@ import java.util.List; import java.util.Map; import edu.wpi.cscore.*; -import org.opencv.videoio.VideoCapture; public class SettingsManager { private static SettingsManager instance; private SettingsManager() { InitiateGeneralSettings(); - InitiateCamerasInfo(); - InitiateUsbCameras(); - InitiateCameras(); - InitiateUsbCamerasSettings(); if (!Cameras.containsKey(GeneralSettings.curr_camera) && Cameras.size() > 0) { String camName = Cameras.keySet().stream().findFirst().get(); GeneralSettings.curr_camera = camName; - GeneralSettings.curr_pipeline = Cameras.get(camName).pipelines.keySet().stream().findFirst().get(); + GeneralSettings.curr_pipeline = CameraManager.getCamera(camName).getCurrentPipelineIndex(); } } @@ -47,18 +43,16 @@ public class SettingsManager { return instance; } - public static HashMap Cameras = new HashMap(); - public static HashMap UsbCameras = new HashMap(); - public static HashMap USBCamerasInfo = new HashMap(); + public static HashMap Cameras = new HashMap<>(); + public static HashMap UsbCameras = new HashMap<>(); public static com.chameleonvision.vision.GeneralSettings GeneralSettings; - public static HashMap CamerasCurrentPipeline = new HashMap(); - public static HashMap CameraPorts = new HashMap();//TODO Implement ports - private Path SettingsPath = Paths.get(System.getProperty("user.dir"), "Settings"); - private Path CamsPath = Paths.get(SettingsPath.toString(), "Cams"); + public static HashMap CamerasCurrentPipeline = new HashMap<>(); + // public static HashMap CameraPorts = new HashMap<>();//TODO Implement ports + public static final Path SettingsPath = Paths.get(System.getProperty("user.dir"), "Settings"); private void InitiateGeneralSettings() { - CheckPath(SettingsPath); + FileHelper.CheckPath(SettingsPath); try { GeneralSettings = new Gson().fromJson(new FileReader(Paths.get(SettingsPath.toString(), "Settings.json").toString()), com.chameleonvision.vision.GeneralSettings.class); } catch (FileNotFoundException e) { @@ -66,163 +60,33 @@ public class SettingsManager { } } - private void InitiateCamerasInfo() { - List TrueCameras = new ArrayList(); - UsbCameraInfo[] UsbDevices = UsbCamera.enumerateUsbCameras(); - for (var i = 0; i < UsbDevices.length; i++) { - var cap = new VideoCapture(UsbDevices[i].dev); - if (cap.isOpened()) { - TrueCameras.add(i); - cap.release(); - } - - } - for (var i : TrueCameras) { - var DeviceName = UsbDevices[i].name; - var suffix = 0; - while (USBCamerasInfo.containsKey(DeviceName)) { - suffix++; - DeviceName = String.format("%s(%s)", UsbDevices[i].name, suffix); - } - USBCamerasInfo.put(DeviceName, UsbDevices[i]); - } - } - - private void InitiateUsbCameras() { - for (Map.Entry entry : USBCamerasInfo.entrySet()) { - var device = entry.getValue(); - var name = entry.getKey(); - UsbCamera camera = new UsbCamera(name, device.dev); - UsbCameras.put(name, camera); - } - } - - private void InitiateCameras() { - CheckPath(CamsPath); - for (Map.Entry entry : USBCamerasInfo.entrySet()) { - var camPath = Paths.get(CamsPath.toString(), String.format("%s.json", entry.getKey())); - if (Files.exists(camPath)) { - try { - Camera cam = new Gson().fromJson(new FileReader(camPath.toString()), Camera.class); - Cameras.put(entry.getKey(), cam); - - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - } else { - CreateNewCam(entry.getKey()); - } - } - } - - private void InitiateUsbCamerasSettings() { - for (Map.Entry entry : UsbCameras.entrySet()) { - var cam = entry.getValue(); - var camName = entry.getKey(); - var camInfo = Cameras.get(camName); - cam.setPixelFormat(VideoMode.PixelFormat.valueOf(camInfo.camVideoMode.pixel_format)); - cam.setFPS(camInfo.camVideoMode.fps); - cam.setResolution(camInfo.camVideoMode.width, camInfo.camVideoMode.height); - } - } - - private void CheckPath(Path path) { - if (!Files.exists(path)) { - try { - Files.createDirectories(path); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - - // Creators - private void CreateNewCam(String CameraName) { - Camera cam = new Camera(); - var caminfo = USBCamerasInfo.get(CameraName); - cam.path = caminfo.path; - var videomode = UsbCameras.get(CameraName).enumerateVideoModes()[0]; - CamVideoMode CamVideoMode = new CamVideoMode(); - CamVideoMode.fps = videomode.fps; - CamVideoMode.height = videomode.height; - CamVideoMode.width = videomode.width; - CamVideoMode.pixel_format = videomode.pixelFormat.name(); - cam.camVideoMode = CamVideoMode; - cam.pipelines = new HashMap(); - cam.resolution = 0; - cam.FOV = 60.8; - Cameras.put(CameraName, cam); - - CreateNewPipeline(null,cam); - CreateNewPipeline(null,cam);//Created 2 pipeline for testing TODO add a create pipeline button - - } - - public void CreateNewPipeline(String PipeName,Camera cam) { - if (PipeName == null) { - var suffix = 0; - PipeName = "pipeline" + suffix; - while (cam.pipelines.containsKey(PipeName)) { - suffix++; - PipeName = "pipeline" + suffix; - } - } else if (cam.pipelines.containsKey(PipeName)) { - System.err.println("Pipeline Already Exists"); - } - cam.pipelines.put(PipeName, new Pipeline()); - } //Access Methods - public Pipeline GetCurrentPipeline() throws NoCameraException { - if (!GeneralSettings.curr_pipeline.equals("")) { - return Cameras.get(GeneralSettings.curr_camera).pipelines.get(GeneralSettings.curr_pipeline); - } - throw new NoCameraException(); - } - - public Camera GetCurrentCamera() throws NoCameraException { + public List GetResolutionList() throws CameraException { if (!GeneralSettings.curr_camera.equals("")) { - return Cameras.get(GeneralSettings.curr_camera); - } - throw new NoCameraException(); - } - - public UsbCamera GetCurrentUsbCamera() throws NoCameraException { - if (!GeneralSettings.curr_camera.equals("")) { - return UsbCameras.get(GeneralSettings.curr_camera); - } - throw new NoCameraException(); - } - - public List GetResolutionList() throws NoCameraException { - if (!GeneralSettings.curr_camera.equals("")) { - List list = new ArrayList(); - var cam = UsbCameras.get(GeneralSettings.curr_camera); + List list = new ArrayList<>(); + var cam = CameraManager.getCamera(GeneralSettings.curr_camera).UsbCam; for (var res : cam.enumerateVideoModes()) { list.add(String.format("%s X %s at %s fps", res.width, res.height, res.fps)); } return list; } - throw new NoCameraException(); + throw new CameraException(CameraException.CameraExceptionType.NO_CAMERA); } - public void SetCurrentCamera(String CamName) throws Exception { - if (Cameras.containsKey(CamName)) { - GeneralSettings.curr_camera = CamName; - GeneralSettings.curr_pipeline = GetCurrentCamera().pipelines.keySet().stream().findFirst().toString(); - } + public void updateCameraSetting(String cameraName, int pipelineNumber) { + GeneralSettings.curr_camera = cameraName; + GeneralSettings.curr_pipeline = pipelineNumber; } - public void SetCurrentPipeline(String PipelineName) throws Exception { - if (GetCurrentCamera().pipelines.containsKey(PipelineName)) { - GeneralSettings.curr_pipeline = PipelineName; - } + public void updatePipelineSetting(int pipelineNumber){ + GeneralSettings.curr_pipeline = pipelineNumber; } //Savers public void SaveSettings() { - SaveCameras(); + CameraManager.saveCameras(); SaveGeneralSettings(); } @@ -230,7 +94,7 @@ public class SettingsManager { for (Map.Entry entry : Cameras.entrySet()) { try { Gson gson = new Gson(); - FileWriter writer = new FileWriter(Paths.get(CamsPath.toString(), String.format("%s.json", entry.getKey())).toString()); + FileWriter writer = new FileWriter(Paths.get(CameraManager.CamConfigPath.toString(), String.format("%s.json", entry.getKey())).toString()); gson.toJson(entry.getValue(), writer); writer.flush(); writer.close(); diff --git a/Main/src/main/java/com/chameleonvision/vision/CamVideoMode.java b/Main/src/main/java/com/chameleonvision/vision/CamVideoMode.java deleted file mode 100644 index 7ba6f2468..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/CamVideoMode.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.chameleonvision.vision; - -public class CamVideoMode { - public int fps; - public int width; - public int height; - public String pixel_format; - -} diff --git a/Main/src/main/java/com/chameleonvision/vision/Camera.java b/Main/src/main/java/com/chameleonvision/vision/Camera.java deleted file mode 100644 index fde8e0326..000000000 --- a/Main/src/main/java/com/chameleonvision/vision/Camera.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.chameleonvision.vision; - -import java.util.HashMap; - -public class Camera { - public Double FOV = 60.8; - public String path = ""; - public HashMap pipelines; - public int resolution = 0; - public CamVideoMode camVideoMode; -} diff --git a/Main/src/main/java/com/chameleonvision/vision/GeneralSettings.java b/Main/src/main/java/com/chameleonvision/vision/GeneralSettings.java index c722d8ee8..b8d9ee485 100644 --- a/Main/src/main/java/com/chameleonvision/vision/GeneralSettings.java +++ b/Main/src/main/java/com/chameleonvision/vision/GeneralSettings.java @@ -8,7 +8,7 @@ public class GeneralSettings { public String netmask = ""; public String hostname = "Chameleon-vision"; public String curr_camera = ""; - public String curr_pipeline = ""; + public Integer curr_pipeline = null; } diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/CamVideoMode.java b/Main/src/main/java/com/chameleonvision/vision/camera/CamVideoMode.java new file mode 100644 index 000000000..e2a295c40 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/camera/CamVideoMode.java @@ -0,0 +1,25 @@ +package com.chameleonvision.vision.camera; + +import edu.wpi.cscore.VideoMode; + +public class CamVideoMode { + public final int fps; + public final int width; + public final int height; + public final String pixel_format; + + public CamVideoMode(VideoMode videoMode) { + fps = videoMode.fps; + width = videoMode.width; + height = videoMode.height; + pixel_format = videoMode.pixelFormat.name(); + } + + public VideoMode.PixelFormat getActualPixelFormat() { + return VideoMode.PixelFormat.valueOf(pixel_format); + } + + public boolean isEqualToVideoMode(VideoMode videoMode) { + return videoMode.fps == fps && videoMode.width == width && videoMode.height == height && videoMode.pixelFormat == getActualPixelFormat(); + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/Camera.java b/Main/src/main/java/com/chameleonvision/vision/camera/Camera.java new file mode 100644 index 000000000..f53fc0f51 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/camera/Camera.java @@ -0,0 +1,132 @@ +package com.chameleonvision.vision.camera; + +import com.chameleonvision.vision.Pipeline; +import edu.wpi.cscore.UsbCamera; +import edu.wpi.cscore.UsbCameraInfo; +import edu.wpi.cscore.VideoMode; + +import java.util.HashMap; +import java.util.stream.IntStream; + +public class Camera { + + private static double defaultFOV = 60.8; + + public final String name; + public final String path; + + public final UsbCamera UsbCam; + private final UsbCameraInfo UsbCamInfo; + private final VideoMode[] availableVideoModes; + + private double FOV; + + private CameraValues camVals; + private CamVideoMode camVideoMode; + + private int currentPipelineIndex; + private HashMap pipelines = new HashMap<>(); + + public Camera(String cameraName) { + this(cameraName, defaultFOV); + } + + public Camera(UsbCameraInfo usbCamInfo) { + this(usbCamInfo, defaultFOV); + } + + public Camera(String cameraName, double fov) { + this(CameraManager.AllUsbCameraInfosByName.get(cameraName), fov); + } + + public Camera(UsbCameraInfo usbCamInfo, double fov) { + UsbCamInfo = usbCamInfo; + FOV = fov; + name = usbCamInfo.name; + path = usbCamInfo.path; + + UsbCam = new UsbCamera(name, path); + + // set up video mode + availableVideoModes = UsbCam.enumerateVideoModes(); + setCamVideoMode(new CamVideoMode(availableVideoModes[0])); + } + + public void setCamVideoMode(int videoMode) { + setCamVideoMode(UsbCam.enumerateVideoModes()[videoMode]); + } + + private void setCamVideoMode(VideoMode videoMode) { + setCamVideoMode(new CamVideoMode(videoMode)); + } + + private void setCamVideoMode(CamVideoMode camVideoMode) { + this.camVideoMode = camVideoMode; + UsbCam.setPixelFormat(camVideoMode.getActualPixelFormat()); + UsbCam.setFPS(camVideoMode.fps); + UsbCam.setResolution(camVideoMode.width, camVideoMode.height); + camVals = new CameraValues(this); + // TODO: Automatically restart CameraProcess when resolution changes (not FPS) + } + + public void addPipeline() { + addPipeline(pipelines.size()); + } + + private void addPipeline(int pipelineNumber) { + if (pipelines.containsKey(pipelineNumber)) return; + pipelines.put(pipelineNumber, new Pipeline()); + } + + public void setCurrentPipelineIndex(int pipelineNumber) { + if (pipelineNumber - 1 > pipelines.size()) return; + currentPipelineIndex = pipelineNumber; + } + + public Pipeline getCurrentPipeline() { + return pipelines.get(currentPipelineIndex); + } + + public int getCurrentPipelineIndex() { + return currentPipelineIndex; + } + + public HashMap getPipelines() { + return pipelines; + } + + public CamVideoMode getVideoMode() { + return camVideoMode; + } + + public int getVideoModeIndex() { + return IntStream.range(0, availableVideoModes.length) + .filter(i -> camVideoMode.isEqualToVideoMode(availableVideoModes[i])) + .findFirst() + .orElse(-1); + } + + public void setFOV(double fov) { + FOV = fov; + camVals = new CameraValues(this); + } + + public double getFOV() { + return FOV; + } + + public void setBrightness(int brightness) { + getCurrentPipeline().brightness = brightness; + UsbCam.setBrightness(brightness); + } + + public int getBrightness() { + return getCurrentPipeline().brightness; + } + + public void setExposure(int exposure) { + getCurrentPipeline().exposure = exposure; + UsbCam.setExposureManual(exposure); + } + +} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/CameraManager.java b/Main/src/main/java/com/chameleonvision/vision/camera/CameraManager.java new file mode 100644 index 000000000..3cbb68b22 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/camera/CameraManager.java @@ -0,0 +1,133 @@ +package com.chameleonvision.vision.camera; + +import com.chameleonvision.FileHelper; +import com.chameleonvision.CameraException; +import com.chameleonvision.settings.SettingsManager; +import com.chameleonvision.vision.Pipeline; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import edu.wpi.cscore.UsbCamera; +import edu.wpi.cscore.UsbCameraInfo; +import org.opencv.videoio.VideoCapture; + +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class CameraManager { + + public static final Path CamConfigPath = Paths.get(SettingsManager.SettingsPath.toString(), "Cams"); + + // TODO: Fix suffix for camera + // TODO: throw a camera Exeption if no camera is connected + static HashMap AllUsbCameraInfosByName = new HashMap<>() {{ + var suffix = 0; + for (var info : UsbCamera.enumerateUsbCameras()) { + var cap = new VideoCapture(info.name); + if (cap.isOpened()) { + cap.release(); + var name = String.format("%s(%s)", info.name, suffix); + put(name, info); + suffix++; + } + } + }}; + + private static HashMap AllCamerasByName = new HashMap<>(); + + private static String currentCameraName; + + public static HashMap getAllCamerasByName() { return AllCamerasByName; } + + public static void initializeCameras() { + FileHelper.CheckPath(CamConfigPath); + for (var entry : AllUsbCameraInfosByName.entrySet()) { + var camPath = Paths.get(CamConfigPath.toString(), String.format("%s.json", entry.getKey())); + if (Files.exists(camPath)) { + try { + // TODO: Check if deserializing correctly, if not, add CameraDeserializer + var camJsonFile = new FileReader(camPath.toString()); + var gsonRead = new Gson().fromJson(camJsonFile, Camera.class); + AllCamerasByName.put(entry.getKey(), gsonRead); + } catch (FileNotFoundException ex) { + ex.printStackTrace(); + } + } else { + if (!addCamera(new Camera(entry.getKey()))) { + System.err.println("Failed to add camera! Already exists!"); + } + } + // TODO: Set currentCameraName from GeneralSettings instead of this + if (currentCameraName == null && AllCamerasByName.size() == 1) { // set current camera to first found + currentCameraName = AllCamerasByName.keySet().stream().findFirst().get(); + } + } + } + + private static boolean addCamera(Camera camera) { + if (AllCamerasByName.containsKey(camera.name)) return false; + camera.addPipeline(); + AllCamerasByName.put(camera.name, camera); + return true; + } + + public static Camera getCamera(String cameraName) { + return AllCamerasByName.get(cameraName); + } + + public static void setCurrentCamera(String cameraName) throws CameraException { + if (!AllCamerasByName.containsKey(cameraName)) throw new CameraException(CameraException.CameraExceptionType.BAD_CAMERA); + currentCameraName = cameraName; + SettingsManager.getInstance().updateCameraSetting(cameraName, getCurrentCamera().getCurrentPipelineIndex()); + } + + public static Camera getCurrentCamera() throws CameraException { + if (AllCamerasByName.size() == 0) throw new CameraException(CameraException.CameraExceptionType.NO_CAMERA); + var curCam = AllCamerasByName.get(currentCameraName); + if (curCam == null) throw new CameraException(CameraException.CameraExceptionType.BAD_CAMERA); + return curCam; + } + + public static void setCurrentPipeline(int pipelineNumber) throws CameraException { + if (!getCurrentCamera().getPipelines().containsKey(pipelineNumber)) throw new CameraException(CameraException.CameraExceptionType.BAD_PIPELINE); + getCurrentCamera().setCurrentPipelineIndex(pipelineNumber); + SettingsManager.getInstance().updatePipelineSetting(pipelineNumber); + } + + public static Pipeline getCurrentPipeline() throws CameraException { + return getCurrentCamera().getCurrentPipeline(); + } + + public static List getResolutionList() throws CameraException { + if (!currentCameraName.equals("")) { + List list = new ArrayList<>(); + var cam = CameraManager.getCamera(currentCameraName).UsbCam; + for (var res : cam.enumerateVideoModes()) { + list.add(String.format("%s X %s at %s fps", res.width, res.height, res.fps)); + } + return list; + } + throw new CameraException(CameraException.CameraExceptionType.NO_CAMERA); + } + + public static void saveCameras() { + for (var entry : AllCamerasByName.entrySet()) { + try { + Gson gson = new GsonBuilder().setPrettyPrinting().registerTypeAdapter(Camera.class, new CameraSerializer()).create(); + FileWriter writer = new FileWriter(Paths.get(CamConfigPath.toString(), String.format("%s.json", entry.getKey())).toString()); + gson.toJson(entry.getValue(), writer); + writer.flush(); + writer.close(); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/camera/CameraSerializer.java b/Main/src/main/java/com/chameleonvision/vision/camera/CameraSerializer.java new file mode 100644 index 000000000..dc712bb26 --- /dev/null +++ b/Main/src/main/java/com/chameleonvision/vision/camera/CameraSerializer.java @@ -0,0 +1,22 @@ +package com.chameleonvision.vision.camera; +import com.google.gson.*; + +import java.lang.reflect.Type; + +public class CameraSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Camera camera, Type type, JsonSerializationContext context) { + JsonObject obj = new JsonObject(); + obj.addProperty("FOV", camera.getFOV()); + obj.addProperty("path", camera.path); + obj.addProperty("name", camera.name); + + var pipelines = context.serialize(camera.getPipelines()); + obj.add("pipelines", pipelines); + + obj.addProperty("resolution", camera.getVideoModeIndex()); + obj.add("camVideoMode", context.serialize(camera.getVideoMode())); + + return obj; + } +} diff --git a/Main/src/main/java/com/chameleonvision/vision/CameraValues.java b/Main/src/main/java/com/chameleonvision/vision/camera/CameraValues.java similarity index 93% rename from Main/src/main/java/com/chameleonvision/vision/CameraValues.java rename to Main/src/main/java/com/chameleonvision/vision/camera/CameraValues.java index e6def334c..2190c139b 100644 --- a/Main/src/main/java/com/chameleonvision/vision/CameraValues.java +++ b/Main/src/main/java/com/chameleonvision/vision/camera/CameraValues.java @@ -1,4 +1,4 @@ -package com.chameleonvision.vision; +package com.chameleonvision.vision.camera; import org.apache.commons.math3.fraction.Fraction; import org.apache.commons.math3.util.FastMath; @@ -20,7 +20,7 @@ public class CameraValues { public final double VerticalFocalLength; public CameraValues(Camera camera) { - this(camera.camVideoMode.width, camera.camVideoMode.height, camera.FOV); + this(camera.getVideoMode().width, camera.getVideoMode().height, camera.getFOV()); } public CameraValues(int imageWidth, int imageHeight, double fov) { diff --git a/Main/src/main/java/com/chameleonvision/vision/process/CameraProcess.java b/Main/src/main/java/com/chameleonvision/vision/process/CameraProcess.java index f92a85a36..bd56015f7 100644 --- a/Main/src/main/java/com/chameleonvision/vision/process/CameraProcess.java +++ b/Main/src/main/java/com/chameleonvision/vision/process/CameraProcess.java @@ -1,8 +1,11 @@ package com.chameleonvision.vision.process; +import com.chameleonvision.CameraException; import com.chameleonvision.MemoryManager; import com.chameleonvision.settings.SettingsManager; -import com.chameleonvision.vision.CameraValues; +import com.chameleonvision.vision.camera.Camera; +import com.chameleonvision.vision.camera.CameraManager; +import com.chameleonvision.vision.camera.CameraValues; import com.chameleonvision.vision.Pipeline; import com.chameleonvision.web.Server; import edu.wpi.cscore.CvSink; @@ -17,7 +20,9 @@ import java.util.HashMap; import java.util.List; public class CameraProcess implements Runnable { - private String CameraName; + + private final Camera camera; + private final String cameraName; // NetworkTables private NetworkTableEntry ntPipelineEntry; @@ -48,34 +53,38 @@ public class CameraProcess implements Runnable { private Mat streamOutputMat = new Mat(); private Scalar contourRectColor = new Scalar(255, 0, 0); - private void ChangeCameraValues(int exposure, int brightness) { - SettingsManager.UsbCameras.get(CameraName).setBrightness(brightness); - SettingsManager.UsbCameras.get(CameraName).setExposureManual(exposure); + public void restartProcess() { + // TODO: Restart process and re-create cvPublish for new resolution } private void DriverModeListener(EntryNotification entryNotification) { if (entryNotification.value.getBoolean()) { - ChangeCameraValues(25, 15); + camera.setExposure(25); + camera.setBrightness(15); } else { - Pipeline pipeline = SettingsManager.Cameras.get(CameraName).pipelines.get(SettingsManager.CamerasCurrentPipeline.get(CameraName)); - ChangeCameraValues(pipeline.exposure, pipeline.brightness); + Pipeline pipeline = camera.getCurrentPipeline(); + camera.setExposure(pipeline.exposure); + camera.setBrightness(pipeline.brightness); } } private void PipelineListener(EntryNotification entryNotification) { - if (SettingsManager.Cameras.get(CameraName).pipelines.containsKey(entryNotification.value.getString())) { - SettingsManager.CamerasCurrentPipeline.put(CameraName, entryNotification.value.getString()); - Pipeline pipeline = SettingsManager.Cameras.get(CameraName).pipelines.get(SettingsManager.CamerasCurrentPipeline.get(CameraName)); - ChangeCameraValues(pipeline.exposure, pipeline.brightness); + if (camera.getPipelines().containsKey(entryNotification.value.getString())) { +// camera.setPntryNotification.value.getString()); + Pipeline pipeline = camera.getCurrentPipeline(); + + camera.setExposure(pipeline.exposure); + camera.setBrightness(pipeline.brightness); //TODO Send Pipeline change using websocket to client } else { - ntPipelineEntry.setString(SettingsManager.CamerasCurrentPipeline.get(CameraName)); + ntPipelineEntry.setString("pipeline" + camera.getCurrentPipelineIndex()); } } - public CameraProcess(String cameraName) { - CameraName = cameraName; - SettingsManager.CamerasCurrentPipeline.put(CameraName,SettingsManager.Cameras.get(CameraName).pipelines.keySet().stream().findFirst().toString()); + public CameraProcess(Camera processCam) { + camera = processCam; + this.cameraName = camera.name; + // NetworkTables NetworkTable ntTable = NetworkTableInstance.getDefault().getTable("/chameleon-vision/" + cameraName); ntPipelineEntry = ntTable.getEntry("Pipeline"); @@ -88,17 +97,16 @@ public class CameraProcess implements Runnable { ntDriverModeEntry.addListener(this::DriverModeListener, EntryListenerFlags.kUpdate); ntPipelineEntry.addListener(this::PipelineListener, EntryListenerFlags.kUpdate); ntDriverModeEntry.setBoolean(false); - ntPipelineEntry.setString(SettingsManager.CamerasCurrentPipeline.get(cameraName)); + ntPipelineEntry.setString("pipeline" + camera.getCurrentPipelineIndex()); // camera settings - camVals = new CameraValues(SettingsManager.Cameras.get(cameraName)); + camVals = new CameraValues(camera); visionProcess = new VisionProcess(camVals); // cscore setup CameraServer cs = CameraServer.getInstance(); - cvSink = cs.getVideo(SettingsManager.UsbCameras.get(cameraName)); + cvSink = cs.getVideo(camera.UsbCam); cvPublish = cs.putVideo(cameraName, camVals.ImageWidth, camVals.ImageHeight); - } private void drawContour(Mat inputMat, RotatedRect contourRect) { @@ -111,10 +119,18 @@ public class CameraProcess implements Runnable { Imgproc.circle(inputMat, contourRect.center, 3, contourRectColor); } - // TODO: Separate video output, contour drawing, data output to separate function, maybe even second thread + private void updateNetworkTables(PipelineResult pipelineResult) { + + } + + // TODO: Separate video output to separate function, maybe even second thread private PipelineResult runVisionProcess(Mat inputImage, Mat outputImage) { var pipelineResult = new PipelineResult(); + if (currentPipeline == null) { + return pipelineResult; + } + Scalar hsvLower = new Scalar(currentPipeline.hue.get(0), currentPipeline.saturation.get(0), currentPipeline.value.get(0)); Scalar hsvUpper = new Scalar(currentPipeline.hue.get(1), currentPipeline.saturation.get(1), currentPipeline.value.get(1)); @@ -164,9 +180,9 @@ public class CameraProcess implements Runnable { FoundContours.clear(); FilteredContours.clear(); GroupedContours.clear(); - currentPipeline = SettingsManager.Cameras.get(CameraName).pipelines.get(SettingsManager.CamerasCurrentPipeline.get(CameraName)); - -// System.out.println(SettingsManager.CamerasCurrentPipeline.get(CameraName)); + + currentPipeline = camera.getCurrentPipeline(); + // start fps counter right before grabbing input frame startTime = System.nanoTime(); TimeStamp = cvSink.grabFrame(cameraInputMat); @@ -183,10 +199,10 @@ public class CameraProcess implements Runnable { ntPitchEntry.setNumber(pipelineResult.Pitch); } ntTimeStampEntry.setNumber(TimeStamp); - if (CameraName.equals(SettingsManager.GeneralSettings.curr_camera)){ + if (cameraName.equals(SettingsManager.GeneralSettings.curr_camera) && pipelineResult.IsValid) { HashMap WebSend = new HashMap<>(); HashMap point = new HashMap<>(); - List center = new ArrayList(); + List center = new ArrayList<>(); center.add(pipelineResult.RawPoint.center.x); center.add(pipelineResult.RawPoint.center.y); point.put("pitch", pipelineResult.Pitch); @@ -200,7 +216,7 @@ public class CameraProcess implements Runnable { // calculate FPS after publishing output frame processTimeMs = (System.nanoTime() - startTime) * 1e-6; fps = 1000 / processTimeMs; - System.out.printf("%s - Process time: %fms, FPS: %.2f, FoundContours: %d, FilteredContours: %d, GroupedContours: %d\n",CameraName ,processTimeMs, fps, FoundContours.size(), FilteredContours.size(), GroupedContours.size()); + System.out.printf("%s - Process time: %fms, FPS: %.2f, FoundContours: %d, FilteredContours: %d, GroupedContours: %d\n", cameraName,processTimeMs, fps, FoundContours.size(), FilteredContours.size(), GroupedContours.size()); cameraInputMat.release(); hsvThreshMat.release(); diff --git a/Main/src/main/java/com/chameleonvision/vision/process/VisionProcess.java b/Main/src/main/java/com/chameleonvision/vision/process/VisionProcess.java index 2b7bbc43b..f54ff1552 100644 --- a/Main/src/main/java/com/chameleonvision/vision/process/VisionProcess.java +++ b/Main/src/main/java/com/chameleonvision/vision/process/VisionProcess.java @@ -1,13 +1,12 @@ package com.chameleonvision.vision.process; -import com.chameleonvision.vision.CameraValues; +import com.chameleonvision.vision.camera.CameraValues; import org.apache.commons.math3.util.FastMath; import org.jetbrains.annotations.NotNull; import org.opencv.core.*; import org.opencv.imgproc.*; import java.util.*; -import java.util.stream.Collectors; public class VisionProcess { diff --git a/Main/src/main/java/com/chameleonvision/web/Server.java b/Main/src/main/java/com/chameleonvision/web/Server.java index 89e7db432..351c833e5 100644 --- a/Main/src/main/java/com/chameleonvision/web/Server.java +++ b/Main/src/main/java/com/chameleonvision/web/Server.java @@ -1,7 +1,8 @@ package com.chameleonvision.web; -import com.chameleonvision.NoCameraException; +import com.chameleonvision.CameraException; import com.chameleonvision.settings.SettingsManager; +import com.chameleonvision.vision.camera.CameraManager; import edu.wpi.cscore.VideoException; import io.javalin.Javalin; import io.javalin.websocket.WsContext; @@ -15,7 +16,7 @@ import org.springframework.beans.BeanUtils; public class Server { - private static List users = new ArrayList(); + private static List users = new ArrayList<>(); public static void main(int port) { Javalin app = Javalin.create(); @@ -33,6 +34,9 @@ public class Server { }); ws.onMessage(ctx -> { broadcastMessage(ctx.message(), ctx); + + + JSONObject jsonObject = new JSONObject(ctx.message()); String key = null; var jsonKeySetArray = jsonObject.keySet().toArray(); @@ -44,7 +48,7 @@ public class Server { if (key == null) return; Object value = jsonObject.get(key); // System.out.printf("Got websocket json data: [%s, %s]\n", key, value); - if (!allFieldsToMap(SettingsManager.getInstance().GetCurrentPipeline()).containsKey(key)) { + if (!allFieldsToMap(CameraManager.getCurrentPipeline()).containsKey(key)) { //If field not in pipeline switch (key) { case "change_general_settings_values": @@ -54,44 +58,41 @@ public class Server { case "curr_camera": String newCamera = (String) value; System.out.printf("Changing camera to %s\n", newCamera); - SettingsManager.getInstance().SetCurrentCamera(newCamera); + CameraManager.setCurrentCamera(newCamera); //broadcastMessage((Map) new HashMap(){}.put("port",SettingsManager.CameraPorts.get(SettingsManager.GeneralSettings.curr_camera))); - broadcastMessage(SettingsManager.getInstance().GetCurrentCamera()); //TODO CHECK JSON FOR CAMERA CHANGE + broadcastMessage(CameraManager.getCurrentCamera()); //TODO CHECK JSON FOR CAMERA CHANGE break; case "curr_pipeline": String newPipeline = (String) value; + var pipelineNumber = Integer.parseInt(newPipeline.replace("pipeline", "")); System.out.printf("Changing pipeline to %s\n", newPipeline); - SettingsManager.getInstance().SetCurrentPipeline(newPipeline); - SettingsManager.CamerasCurrentPipeline.put(SettingsManager.GeneralSettings.curr_camera, newPipeline); - broadcastMessage(allFieldsToMap(SettingsManager.getInstance().GetCurrentPipeline())); + CameraManager.setCurrentPipeline(pipelineNumber); + broadcastMessage(allFieldsToMap(CameraManager.getCurrentPipeline())); break; case "resolution": - int newResolution = (int) value; - System.out.printf("Changing resolution mode to %d\n", newResolution); - SettingsManager.getInstance().GetCurrentCamera().resolution = newResolution; - SettingsManager.getInstance().SaveSettings(); + int newVideoMode = (int) value; + System.out.printf("Changing video mode to %d\n", newVideoMode); + CameraManager.getCurrentCamera().setCamVideoMode(newVideoMode); break; - case "fov": + case "FOV": double newFov = (double) value; System.out.printf("Changing FOV to %f\n", newFov); - SettingsManager.getInstance().GetCurrentCamera().FOV = newFov; - SettingsManager.getInstance().SaveSettings(); + CameraManager.getCurrentCamera().setFOV(newFov); break; default: System.out.printf("Unexpected value from websocket: [%s, %s]\n", key, value); break; } } else { - setField(SettingsManager.getInstance().GetCurrentPipeline(), key, value); + setField(CameraManager.getCurrentPipeline(), key, value); //Special cases for exposure and brightness //TODO maybe add listener for value changes instead of this special case switch (key) { case "exposure": int newExposure = (int) value; System.out.printf("Changing exposure to %d\n", newExposure); - SettingsManager.getInstance().GetCurrentPipeline().exposure = newExposure; try { - SettingsManager.getInstance().GetCurrentUsbCamera().setExposureManual(newExposure); + CameraManager.getCurrentCamera().setExposure(newExposure); } catch ( VideoException e) { @@ -102,8 +103,7 @@ public class Server { case "brightness": int newBrightness = (int) value; System.out.printf("Changing brightness to %d\n", newBrightness); - SettingsManager.getInstance().GetCurrentPipeline().brightness = newBrightness; - SettingsManager.getInstance().GetCurrentUsbCamera().setBrightness(newBrightness); + CameraManager.getCurrentCamera().setBrightness(newBrightness); break; } } @@ -155,30 +155,31 @@ public class Server { } private static Map allFieldsToMap(Object obj) { - Map map = new HashMap<>(); + Map map = new HashMap(); try { Field[] fields = obj.getClass().getFields(); - for (Field field : fields) + for (Field field : fields) { map.put(field.getName(), field.get(obj)); + } } catch (IllegalAccessException e) { - System.err.println("Illegal Access error:" + e.getStackTrace().toString()); + System.err.println("Illegal Access error:" + e.getStackTrace()); } return map; } private static void sendFullSettings() { - Map fullSettings = new HashMap<>(); //General settings - fullSettings.putAll(allFieldsToMap(SettingsManager.GeneralSettings)); - fullSettings.put("cameraList", SettingsManager.Cameras.keySet()); + Map fullSettings = new HashMap<>(allFieldsToMap(SettingsManager.GeneralSettings)); + fullSettings.put("cameraList", CameraManager.getAllCamerasByName().keySet()); try { - fullSettings.putAll(allFieldsToMap(SettingsManager.getInstance().GetCurrentPipeline())); - fullSettings.put("pipelineList", SettingsManager.getInstance().GetCurrentCamera().pipelines.keySet()); - fullSettings.put("resolutionList", SettingsManager.getInstance().GetResolutionList()); - fullSettings.put("resolution", SettingsManager.getInstance().GetCurrentCamera().resolution); - fullSettings.put("FOV", SettingsManager.getInstance().GetCurrentCamera().FOV); + var currentCamera = CameraManager.getCurrentCamera(); + fullSettings.putAll(allFieldsToMap(currentCamera.getCurrentPipeline())); + fullSettings.put("pipelineList", currentCamera.getPipelines().keySet()); + fullSettings.put("resolutionList", CameraManager.getResolutionList()); + fullSettings.put("resolution", currentCamera.getVideoModeIndex()); + fullSettings.put("FOV", currentCamera.getFOV()); // fullSettings.put("port", SettingsManager.CameraPorts.get(SettingsManager.GeneralSettings.curr_camera)); - } catch (NoCameraException e) { + } catch (CameraException e) { System.err.println("No camera found!"); //TODO: add message to ui to inform that there are no cameras }