From 4a3313e326863c44e84ec750ce3145c5ab461f86 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Fri, 3 Feb 2023 15:58:16 -0500 Subject: [PATCH 01/13] Remove unnecessary dependency --- src/main/java/org/baxter/disco/ocr/Cli.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index 5e9baf5..de4a56f 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -1,7 +1,6 @@ package org.baxter.disco.ocr; import java.io.File; -import java.io.PrintStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; -- 2.47.2 From 066aec9a7476f2ab5d77d231dd8f4cf8bd792ce0 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Mon, 6 Feb 2023 09:44:48 -0500 Subject: [PATCH 02/13] Continue overhaul - Found and resolved issue with random file organisation. This was a rather heavy undertaking, resulting in the changes to DataSaving, OpenCVFacade, and TesseractFacade. See API for specifics. - Start debugging GUI. Start currently only runs one iteration, and does not save final information out. Start/Stop button needs refining, and user feedback needs to be improved. --- dependency-reduced-pom.xml | 4 +- pom.xml | 4 +- src/main/java/org/baxter/disco/ocr/Cli.java | 35 ++++++++--- .../java/org/baxter/disco/ocr/DataSaving.java | 42 +++++-------- .../org/baxter/disco/ocr/GuiController.java | 14 ++++- .../java/org/baxter/disco/ocr/GuiModel.java | 63 +++++++++++++------ .../java/org/baxter/disco/ocr/GuiView.java | 32 +++++++++- .../org/baxter/disco/ocr/OpenCVFacade.java | 6 +- .../org/baxter/disco/ocr/TesseractFacade.java | 9 +-- 9 files changed, 138 insertions(+), 71 deletions(-) diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index c3c9cc5..8ed036b 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -4,7 +4,7 @@ org.baxter.disco ocr Disco OCR Accuracy Over Life Testing - 4.0.0-rc2 + 4.0.0-rc3 Testing Discos for long-term accuracy, using automated optical character recognition. Baxter International @@ -62,7 +62,7 @@ - org.baxter.disco.ocr.Cli + org.baxter.disco.ocr.GuiStarter diff --git a/pom.xml b/pom.xml index 614b1e1..ae6df4d 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.baxter.disco ocr - 4.0.0-rc2 + 4.0.0-rc3 jar Disco OCR Accuracy Over Life Testing Testing Discos for long-term accuracy, using automated optical character recognition. @@ -155,7 +155,7 @@ - org.baxter.disco.ocr.Cli + org.baxter.disco.ocr.GuiStarter diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index de4a56f..30ad3b4 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -595,43 +595,60 @@ public class Cli //{ DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation(),OpenCVFacade.getCameraNames().size()); boolean prime = false; + List cameraList = new ArrayList<>(); for(String cameraName : OpenCVFacade.getCameraNames()) { - if(cameraName != null) { /*println(cameraName);*/ } - else ErrorLogging.logError("Null camera!"); + //if(cameraName != null) { /*println(cameraName);*/ } + //else ErrorLogging.logError("Null camera!"); if(ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0) { prime = true; } + cameraList.add(cameraName); } ErrorLogging.logError("DEBUG: Waking devices."); fixture.iterationMovement(prime); fixture.pressButton(); fixture.iterationMovement(prime); ErrorLogging.logError("DEBUG: Starting tests..."); + Map resultMap = new HashMap<>(); + Map cameraToFile = new HashMap<>(); + for(String cameraName : cameraList) + { + cameraToFile.put(cameraName,new File("/dev/null")); + } for(int i = 0; i < localIterations; i++) { - Map resultMap = new HashMap<>(); while(!LOCK.tryLock()) {} fixture.iterationMovement(prime); LOCK.unlock(); - while(!LOCK.tryLock()) {} - List iteration = OpenCVFacade.singleIteration(); - LOCK.unlock(); - for(File file : iteration) + for(String cameraName : cameraList) { + while(!LOCK.tryLock()) {} + File file = OpenCVFacade.completeProcess(cameraName); + LOCK.unlock(); + while(!LOCK.tryLock()) {} + cameraToFile.replace(cameraName,file); + LOCK.unlock(); + } + for(String cameraName : cameraList) + { + while(!LOCK.tryLock()) {} + File file = cameraToFile.get(cameraName); + LOCK.unlock(); while(!LOCK.tryLock()) {} //ErrorLogging.logError("DEBUG: File passed to Tesseract: " + file.getAbsolutePath()); Double result = TesseractFacade.imageToDouble(file); LOCK.unlock(); while(!LOCK.tryLock()) {} - resultMap.put(file.getPath(),result); + resultMap.put(file,result); ErrorLogging.logError("DEBUG: Tesseract final output: " + result); LOCK.unlock(); } while(!LOCK.tryLock()) {} - DataSaving.writeValues(i,resultMap); + DataSaving.writeValues(i,resultMap,cameraToFile); LOCK.unlock(); + resultMap.clear(); } println("======================================="); println("Testing complete!"); diff --git a/src/main/java/org/baxter/disco/ocr/DataSaving.java b/src/main/java/org/baxter/disco/ocr/DataSaving.java index ddfb8ff..92b9634 100644 --- a/src/main/java/org/baxter/disco/ocr/DataSaving.java +++ b/src/main/java/org/baxter/disco/ocr/DataSaving.java @@ -16,7 +16,7 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook; * Facade for saving data out to a file. * * @author Blizzard Finnegan - * @version 2.0.0, 02 Feb. 2023 + * @version 3.0.0, 06 Feb. 2023 */ public class DataSaving { @@ -34,8 +34,6 @@ public class DataSaving */ private static File outputFile; - private static Map serials; - /** * Prepares writer to write to XLSX file. */ @@ -67,7 +65,6 @@ public class DataSaving outputWorkbook.write(outputStream); output = true; outputStream.close(); - serials = ConfigFacade.getSerials(); } catch(Exception e) { ErrorLogging.logError(e); } return output; @@ -80,50 +77,39 @@ public class DataSaving * * @return Returns whether values were saved successfully. */ - public static boolean writeValues(int cycle, Map inputMap) + public static boolean writeValues(int cycle, Map inputMap, Map cameraToFile) { boolean output = false; int startingRow = outputSheet.getLastRowNum(); Row row = outputSheet.createRow(++startingRow); - List imageLocations = new ArrayList<>(inputMap.keySet()); + List cameraNames = new ArrayList<>(cameraToFile.keySet()); //ErrorLogging.logError("DEBUG: image locations: " + imageLocations.toString()); List objectArray = new LinkedList<>(); cycle++; objectArray.add((double)cycle); - List serialList = new ArrayList<>(serials.keySet()); - for(String imageLocation : imageLocations) + for(String cameraName : cameraNames) { - String[] temp = imageLocation.split("-"); - //ErrorLogging.logError("DEBUG: Image location post-split:"); - //String println = ""; - //for(String val : temp) - //{ - // println = println + " " + val; - //} - //ErrorLogging.logError("DEBUG: " + println); - String cameraAndFile = temp[temp.length - 1]; - //ErrorLogging.logError("DEBUG: " + cameraAndFile); - - //Magic number explanation: - //The 4 used below is defined by the length of the file extension - //The files being saved are known to be JPEGs, so the file extension - //will be ".jpg", which is 4 characters long. - String cameraName = cameraAndFile.substring(0,cameraAndFile.length() - 4); - + File file = cameraToFile.get(cameraName); //ErrorLogging.logError("DEBUG: " + cameraName); String serialNumber = ConfigFacade.getSerial(cameraName); objectArray.add(serialNumber); - objectArray.add(imageLocation); - objectArray.add(inputMap.get(imageLocation)); + objectArray.add(file.getPath()); + objectArray.add(inputMap.get(file)); objectArray.add(" "); } int cellnum = 0; for(Object cellObject : objectArray) { Cell cell = row.createCell(cellnum++); - if(cellObject instanceof Double) cell.setCellValue((Double) cellObject); + if(cellObject instanceof Double) + { + Double cellValue = (Double)cellObject; + if(cellValue.equals(Double.NEGATIVE_INFINITY)) + cell.setCellValue("ERROR!"); + else cell.setCellValue(cellValue); + } else if(cellObject instanceof String) cell.setCellValue((String) cellObject); else { diff --git a/src/main/java/org/baxter/disco/ocr/GuiController.java b/src/main/java/org/baxter/disco/ocr/GuiController.java index 73c5f64..84404d0 100644 --- a/src/main/java/org/baxter/disco/ocr/GuiController.java +++ b/src/main/java/org/baxter/disco/ocr/GuiController.java @@ -51,7 +51,10 @@ public class GuiController { GuiModel.runTests(); } public static void testMotions() - { GuiModel.testMovement(); } + { + testingMotions(); + GuiModel.testMovement(); + } public static void updateStart() { @@ -60,6 +63,9 @@ public class GuiController if(ready) GuiView.getStart().setTooltip(new Tooltip("Start running automated testing.")); } + public static void startTests() + { GuiController.userUpdate("Starting tests..."); } + public static void updateIterations() { String newIterations = Integer.toString(GuiModel.getIterations()); @@ -95,8 +101,14 @@ public class GuiController public static void updatePrime() { GuiModel.updatePrime(); } + public static void setSerial(String cameraName, String serial) + { GuiModel.setSerial(cameraName,serial); } + public static String getIterationCount() { return Integer.toString(GuiModel.getIterations()); } + public static void calibrateCameras() + { GuiModel.calibrateCameras(); } + public static void closeModel() { GuiModel.close(); } } diff --git a/src/main/java/org/baxter/disco/ocr/GuiModel.java b/src/main/java/org/baxter/disco/ocr/GuiModel.java index 6d88604..afa7d65 100644 --- a/src/main/java/org/baxter/disco/ocr/GuiModel.java +++ b/src/main/java/org/baxter/disco/ocr/GuiModel.java @@ -34,6 +34,7 @@ public class GuiModel public static void setIterations(int iterations) { iterationCount = iterations; + GuiController.userUpdate("Iterations set to: " + iterationCount); GuiController.updateIterations(); } @@ -102,50 +103,68 @@ public class GuiModel public static void runTests() { - testingThread = new Thread(() -> - { + //testingThread = new Thread(() -> + //{ + GuiController.startTests(); DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation(),OpenCVFacade.getCameraNames().size()); boolean prime = false; + List cameraList = new ArrayList<>(); for(String cameraName : OpenCVFacade.getCameraNames()) { if(ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0) { prime = true; } + cameraList.add(cameraName); + } + fixture.iterationMovement(prime); + fixture.pressButton(); + fixture.iterationMovement(prime); + Map resultMap = new HashMap<>(); + Map cameraToFile = new HashMap<>(); + for(String cameraName : cameraList) + { + cameraToFile.put(cameraName,new File("/dev/null")); } for(int i = 0; i < iterationCount; i++) { - Map resultMap = new HashMap<>(); - LOCK.lock(); + while(!LOCK.tryLock()) {} fixture.iterationMovement(prime); LOCK.unlock(); - LOCK.lock(); - List iteration = OpenCVFacade.singleIteration(); - LOCK.unlock(); - for(File file : iteration) + for(String cameraName : cameraList) { - LOCK.lock(); + while(!LOCK.tryLock()) {} + File file = OpenCVFacade.completeProcess(cameraName); + LOCK.unlock(); + while(!LOCK.tryLock()) {} + cameraToFile.replace(cameraName,file); + LOCK.unlock(); + } + LOCK.unlock(); + for(String cameraName : cameraList) + { + while(!LOCK.tryLock()) {} + File file = cameraToFile.get(cameraName); + LOCK.unlock(); + while(!LOCK.tryLock()) {} Double result = TesseractFacade.imageToDouble(file); LOCK.unlock(); - LOCK.lock(); - String fileLocation = file.getAbsolutePath(); + while(!LOCK.tryLock()) {} + resultMap.put(file,result); LOCK.unlock(); - LOCK.lock(); - resultMap.put(fileLocation,result); - LOCK.unlock(); - LOCK.lock(); + while(!LOCK.tryLock()) {} ErrorLogging.logError("DEBUG: Tesseract final output: " + result); LOCK.unlock(); } - LOCK.lock(); - DataSaving.writeValues(i,resultMap); + while(!LOCK.tryLock()) {} + DataSaving.writeValues(i,resultMap,cameraToFile); LOCK.unlock(); GuiController.runningUpdate(i); } //println("======================================="); ErrorLogging.logError("Testing complete!"); - }); - testingThread.run(); + //}); + //testingThread.run(); } public static void close() @@ -157,4 +176,10 @@ public class GuiModel } public static void interruptTesting() { testingThread.interrupt(); } + + public static void setSerial(String cameraName, String serial) + { ConfigFacade.setSerial(cameraName,serial); } + + public static void calibrateCameras() + { fixture.goDown(); } } diff --git a/src/main/java/org/baxter/disco/ocr/GuiView.java b/src/main/java/org/baxter/disco/ocr/GuiView.java index 418dcf1..b78a01d 100644 --- a/src/main/java/org/baxter/disco/ocr/GuiView.java +++ b/src/main/java/org/baxter/disco/ocr/GuiView.java @@ -37,6 +37,8 @@ public class GuiView extends Application private static Button startButton; + private static Button stopButton; + private static Stage STAGE; public static void main(String[] args) { launch(args); } @@ -213,7 +215,12 @@ public class GuiView extends Application STOP.setTooltip(new Tooltip("Pauses current iteration.")); Button calibrateCamera = buttonBuilder("Calibrate Cameras",false); - calibrateCamera.setOnAction( (event) -> STAGE.setScene(CAMERA_MENU) ); + calibrateCamera.setOnAction( + (event) -> + { + GuiController.calibrateCameras(); + STAGE.setScene(CAMERA_MENU); + }); Button testMovement = buttonBuilder("Test Movement",false); testMovement.setOnAction( (event) -> GuiController.testMotions() ); @@ -254,9 +261,25 @@ public class GuiView extends Application VBox output = new VBox(); output.setAlignment(Pos.CENTER_LEFT); output.setSpacing(5.0); - //HBox serialNumber = userTextField("DUT Serial Number:","","Enter the serial number for the device under test."); + + HBox serialNumber = userTextField("DUT Serial Number:","","Enter the serial number for the device under test."); + + TextField field = null; + for(Node child : serialNumber.getChildren()) + { + if(child instanceof TextField) + { + field = (TextField)child; + break; + } + } + + field.setId("serial" + cameraName); + field.textProperty().addListener( + (observable, oldValue, newValue) -> GuiController.setSerial(cameraName, newValue)); + output.getChildren().addAll(cameraHeader(cameraName), - //serialNumber, + serialNumber, cameraView(cameraName)); return output; } @@ -410,18 +433,21 @@ public class GuiView extends Application defaults.setOnAction( (event) -> { GuiController.saveDefaults(); + GuiController.updateStart(); }); Button save = buttonBuilder("Save"); save.setOnAction( (event) -> { GuiController.save(); + GuiController.updateStart(); }); Button saveClose = buttonBuilder("Save and Close"); saveClose.setOnAction( (event) -> { GuiController.saveClose(); + GuiController.updateStart(); STAGE.setScene(MAIN_MENU); }); diff --git a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java index 19c1036..ddec0dc 100644 --- a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java +++ b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java @@ -180,7 +180,7 @@ public class OpenCVFacade { //ErrorLogging.logError("DEBUG: Showing image from camera: " + cameraName); //ErrorLogging.logError("DEBUG: camera location: " + cameraMap.get(cameraName).toString()); - File imageLocation = completeProcess(cameraName); + File imageLocation = completeProcess(cameraName,ConfigFacade.getImgSaveLocation() + "/config"); if(imageLocation == null) return null; //ErrorLogging.logError("DEBUG: Image processed successfully."); //ErrorLogging.logError("DEBUG: Image location: " + imageLocation.getAbsolutePath()); @@ -424,9 +424,9 @@ public class OpenCVFacade * * @return null if any error occurs; otherwise File of output image */ - private static File completeProcess(String cameraName) + public static File completeProcess(String cameraName) { - return completeProcess(cameraName,ConfigFacade.getImgSaveLocation() + "/config"); + return completeProcess(cameraName,ConfigFacade.getImgSaveLocation()); } /** diff --git a/src/main/java/org/baxter/disco/ocr/TesseractFacade.java b/src/main/java/org/baxter/disco/ocr/TesseractFacade.java index a761a52..4865d10 100644 --- a/src/main/java/org/baxter/disco/ocr/TesseractFacade.java +++ b/src/main/java/org/baxter/disco/ocr/TesseractFacade.java @@ -14,7 +14,7 @@ import org.bytedeco.tesseract.TessBaseAPI; * information for this specific testing aparatus. * * @author Blizzard Finnegan - * @version 1.1.0, 27 Jan. 2023 + * @version 2.0.0, 06 Feb. 2023 */ public class TesseractFacade { @@ -45,12 +45,12 @@ public class TesseractFacade * Converts an image file to a double. * * @param file File object of the image to be parsed by Tesseract. - * @return Double, as read from the image by Tesseract. Anomalous data returns -1. + * @return Double, as read from the image by Tesseract. Anomalous data returns Double.NEGATIVE_INFINITY */ public static double imageToDouble(File file) { //Set default output - double output = -1.0; + double output = Double.NEGATIVE_INFINITY; //Import image, parse image PIX importedImage = lept.pixRead(file.getAbsolutePath()); @@ -63,7 +63,8 @@ public class TesseractFacade try(Scanner sc = new Scanner(stringOutput.trim());) { /* - *Discos have error messages (LO, HI, POS, ?). Consider parsing as well. + * Discos have error messages (LO, HI, POS, ?). Consider parsing as well. + * Update on above note: Requires retraining Tesseract */ if(sc.hasNextDouble()) { -- 2.47.2 From 33ddd6e20f7ee3478516672a6f0bfe20ffa41c9a Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Mon, 6 Feb 2023 12:36:38 -0500 Subject: [PATCH 03/13] Finish documentation POM temporarily modified to start with Cli, as it was necessary for demonstration. --- dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- .../org/baxter/disco/ocr/GuiController.java | 124 +++++- .../java/org/baxter/disco/ocr/GuiModel.java | 112 ++++- .../java/org/baxter/disco/ocr/GuiView.java | 392 ++++++++++++++++-- 5 files changed, 582 insertions(+), 50 deletions(-) diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 8ed036b..15f3151 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -62,7 +62,7 @@ - org.baxter.disco.ocr.GuiStarter + org.baxter.disco.ocr.Cli diff --git a/pom.xml b/pom.xml index ae6df4d..37c2bd0 100644 --- a/pom.xml +++ b/pom.xml @@ -155,7 +155,7 @@ - org.baxter.disco.ocr.GuiStarter + org.baxter.disco.ocr.Cli diff --git a/src/main/java/org/baxter/disco/ocr/GuiController.java b/src/main/java/org/baxter/disco/ocr/GuiController.java index 84404d0..0a5ffc0 100644 --- a/src/main/java/org/baxter/disco/ocr/GuiController.java +++ b/src/main/java/org/baxter/disco/ocr/GuiController.java @@ -9,53 +9,116 @@ import javafx.scene.control.Tooltip; * Controller portion of MVC for Accuracy over Life test fixture. * Mostly wrapper interface between View and Model. * + * {@link GuiView}, GuiController, and {@link GuiModel} versions are tied together, and are referred to collectively as Gui. + * * @author Blizzard Finnegan - * @version 0.0.1, 01 Feb, 2023 + * @version 0.2.0, 01 Feb, 2023 */ public class GuiController { + /** + * Wrapper function to get available cameras. + * + * @return List[String] of the names of cameras + */ public static List getCameras() { return GuiModel.getCameras(); } + /** + * Wrapper function used to show an image in a separate window + * + * @param cameraName The camera whose image should be shown. + */ public static void showImage(String cameraName) { GuiModel.showImage(cameraName); } + /** + * Wrapper function to toggle cropping for a given camera + * + * @param cameraName The camera whose image should be shown. + */ public static void toggleCrop(String cameraName) { GuiModel.toggleCrop(cameraName); } + /** + * Wrapper function to toggle threshold for a given camera + * + * @param cameraName The camera whose image should be shown. + */ public static void toggleThreshold(String cameraName) { GuiModel.toggleThreshold(cameraName); } + /** + * Wrapper function to save the default config + */ public static void saveDefaults() { GuiModel.saveDefaults(); } + /** + * Wrapper function to save the current config + */ public static void save() { GuiModel.save(); } + /** + * Wrapper function to save the current config, and re-enable image processing if necessary. + */ public static void saveClose() { GuiModel.save(); GuiModel.enableProcessing(); } + /** + * Wrapper function to get a config value, for a given camera + * + * @param cameraName The name of the camera being inspected + * @param property The config property to be returned + * + * @return String of the value of the current object + */ public static String getConfigValue(String cameraName, ConfigProperties property) { return GuiModel.getConfigVal(cameraName,property); } + /** + * Wrapper function to set a config value for a given camera. + * + * @param cameraName The name of the camera being modified + * @param property The property to be modified + * @param value The new value to set the property to + */ public static void setConfigValue(String cameraName, ConfigProperties property, double value) { GuiModel.setConfigVal(cameraName,property,value); } + /** + * Setter for the number of iterations + * + * @param iterationCount The new iteration count to be saved + */ public static void setIterationCount(int iterationCount) { GuiModel.setIterations(iterationCount); } + /** + * Wrapper function to interrupt testing. + */ public static void interruptTests() { GuiModel.interruptTesting(); } + /** + * Wrapper function to run tests. + */ public static void runTests() { GuiModel.runTests(); } + /** + * Wrapper function to test the movement of the fixture. + */ public static void testMotions() { testingMotions(); GuiModel.testMovement(); } + /** + * If the Model is ready, set the view to allow the Start button to be pressed. + */ public static void updateStart() { boolean ready = GuiModel.isReady(); @@ -63,16 +126,27 @@ public class GuiController if(ready) GuiView.getStart().setTooltip(new Tooltip("Start running automated testing.")); } + /** + * Wrapper function to write to the user feedback Label, stating that the test is starting. + */ public static void startTests() - { GuiController.userUpdate("Starting tests..."); } + { userUpdate("Starting tests..."); } + /** + * Updates the View's state wit hthe current iteration count + */ public static void updateIterations() { String newIterations = Integer.toString(GuiModel.getIterations()); - GuiView.getIterationField().setPromptText(newIterations); GuiView.getIterationField().setText(newIterations); } + /** + * Update a given config value, given a camera + * + * @param cameraName The name of the camera being updated + * @param property The property being updated + */ public static void updateConfigValue(String cameraName, ConfigProperties property) { TextField field = GuiView.getField(cameraName,property); @@ -80,35 +154,79 @@ public class GuiController field.setPromptText(GuiModel.getConfigVal(cameraName,property)); } + /** + * Wrapper function to write the current iteration ot the user feedback Label. + * + * @param index The current iteration number + */ public static void runningUpdate(int index) { userUpdate("Running iteration " + index + "..."); } + /** + * Wrapper function used to set a custom message to the user. + * + * @param output What should be sent to the user. + */ public static void userUpdate(String output) { GuiView.getFeedbackText().setText(output); } + /** + * Wrapper function to tell the user that fixture movement testing has begun. + */ public static void testingMotions() { userUpdate("Testing fixture movement..."); } + /** + * Wrapper function to tell the user that fixture movement testing was successful. + */ public static void testingMotionSuccessful() { userUpdate("Fixture movement test successful!"); } + /** + * Wrapper function to tell the user that fixture movement testing failed, and where it failed. + * + * @param failurePoint Where the movement failed. + */ public static void testingMotionUnsuccessful(String failurePoint) { userUpdate("Fixture movement unsuccessful! Fail point: " + failurePoint);} + /** + * Wrapper function for the Model's pressButton function. + */ public static void pressButton() { GuiModel.pressButton(); } + /** + * Wrapper function used to update whether or not the DUTs should be primed before testing. + */ public static void updatePrime() { GuiModel.updatePrime(); } + /** + * Wrapper function to set the Serial for a given camera. + * + * @param cameraName The name of the camera to modify + * @param serial The serial of the DUT under the given camera + */ public static void setSerial(String cameraName, String serial) { GuiModel.setSerial(cameraName,serial); } + /** + * Getter for the current iteration count + * + * @return String of the current iteration count. + */ public static String getIterationCount() { return Integer.toString(GuiModel.getIterations()); } + /** + * Wrapper function around the GuiModel's calibrateCameras function + */ public static void calibrateCameras() { GuiModel.calibrateCameras(); } + /** + * Close function for the Model; used to end the program + */ public static void closeModel() { GuiModel.close(); } } diff --git a/src/main/java/org/baxter/disco/ocr/GuiModel.java b/src/main/java/org/baxter/disco/ocr/GuiModel.java index afa7d65..c9a8d61 100644 --- a/src/main/java/org/baxter/disco/ocr/GuiModel.java +++ b/src/main/java/org/baxter/disco/ocr/GuiModel.java @@ -12,25 +12,55 @@ import java.util.concurrent.locks.ReentrantLock; * Model portion of MVC for the Accuracy Over Life test fixture. * Primarily a wrapper around other classes, but does store some information. * + * {@link GuiView}, {@link GuiController}, and GuiModel versions are tied together, and are referred to collectively as Gui. + * * @author Blizzard Finnegan - * @version 0.0.2, 03 Feb, 2023 + * @version 0.2.0, 06 Feb, 2023 */ public class GuiModel { + /** + * Whether or not the backend is prepared to start running. + */ private static boolean readyToRun = false; + /** + * The number of iterations. + */ private static int iterationCount = 3; + /** + * The Lock object, used for multithreading of the testing function + */ public static final Lock LOCK = new ReentrantLock(); + /** + * The testing thread object + */ private static Thread testingThread = new Thread(); + /** + * The Movement Facade instance + */ private static final MovementFacade fixture = new MovementFacade(LOCK); + /** + * The function called to define the GUI as ready to start testing. + */ public static void ready() { readyToRun = true; GuiController.updateStart(); } + /** + * Getter for {@link #readyToRun} + * + * @return boolean of whether or not testing can be started + */ public static boolean isReady() { return readyToRun; } + /** + * Setter for the number of iterations + * + * @param iterations The number of times to run the tests + */ public static void setIterations(int iterations) { iterationCount = iterations; @@ -38,8 +68,18 @@ public class GuiModel GuiController.updateIterations(); } + /** + * Getter for the number of iterations + * + * @return int of the number of iterations to be perfomed. + */ public static int getIterations() { return iterationCount; } + /** + * Wrapper around the MovementFacade's testMotions function. + * + * Updates the GUI with whether the testing was successful. + */ public static void testMovement() { GuiController.testingMotions(); @@ -48,23 +88,52 @@ public class GuiModel else GuiController.testingMotionUnsuccessful("Unknown"); } + /** + * Getter for the list of cameras. + * + * @return List[String] of camera names. + */ public static List getCameras() { return new ArrayList<>(OpenCVFacade.getCameraNames()); } + /** + * Wrapper function for showing an image. + */ public static void showImage(String cameraName) { OpenCVFacade.showImage(cameraName); } + /** + * Setter for a given camera's config value + * + * @param cameraName Name of the camera to be configured + * @param property Property to be changed + * @param value New value for the given property + */ public static void setConfigVal(String cameraName, ConfigProperties property, double value) { ConfigFacade.setValue(cameraName,property,value); GuiController.updateConfigValue(cameraName,property); } + /** + * Getter for a given camera's config value + * + * @param cameraName Name of the camera to get the config value from + * @param property Property to get the value of + * + * @return String of the current value in the config + */ public static String getConfigVal(String cameraName, ConfigProperties property) { return Double.toString(ConfigFacade.getValue(cameraName,property)); } + /** + * Wrapper function around the MovementFacade's pressButton function. + */ public static void pressButton() { fixture.pressButton(); } + /** + * Function used to update whether or not cameras should be primed. + */ public static void updatePrime() { for(String cameraName : OpenCVFacade.getCameraNames()) @@ -74,6 +143,9 @@ public class GuiModel } } + /** + * Wrapper function to enable all image processing. + */ public static void enableProcessing() { for(String camera : getCameras()) @@ -83,24 +155,45 @@ public class GuiModel } } + /** + * Wrapper function to save the default config values. + */ public static void saveDefaults() { ConfigFacade.saveDefaultConfig(); } + /** + * Save the current config, and ensure it is loaded properly. + */ public static void save() { ConfigFacade.saveCurrentConfig(); ConfigFacade.loadConfig(); } + /** + * Toggles the threshold processing for the given camera. + * + * @param cameraName The name of the camera to be modified + */ public static void toggleThreshold(String cameraName) { boolean old = (ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) == 0.0 ); ConfigFacade.setValue(cameraName,ConfigProperties.THRESHOLD,(old ? 1 : 0)); } + /** + * Toggles the cropping of the image for the given camera. + * + * @param cameraName The name of the camera to be modified + */ public static void toggleCrop(String cameraName) { boolean old = (ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) == 0.0 ); ConfigFacade.setValue(cameraName,ConfigProperties.CROP,(old ? 1 : 0)); } + /** + * Function used to run all tests. + * + * Currently not working. Will need to rewrite. + */ public static void runTests() { //testingThread = new Thread(() -> @@ -167,6 +260,9 @@ public class GuiModel //testingThread.run(); } + /** + * Wrapper function to close everything. + */ public static void close() { ErrorLogging.logError("DEBUG: PROGRAM CLOSING."); @@ -175,11 +271,25 @@ public class GuiModel ErrorLogging.closeLogs(); } + /** + * Function used to interrupt the testing thread. + * + * As of Gui 0.2.0, this does not work properly. + */ public static void interruptTesting() { testingThread.interrupt(); } + /** + * Function to set the serial number for a given camera + * + * @param cameraName name of the camera to be modified + * @param serial serial number to be set + */ public static void setSerial(String cameraName, String serial) { ConfigFacade.setSerial(cameraName,serial); } + /** + * Function to force fixture down before starting to calibrate the cameras. + */ public static void calibrateCameras() { fixture.goDown(); } } diff --git a/src/main/java/org/baxter/disco/ocr/GuiView.java b/src/main/java/org/baxter/disco/ocr/GuiView.java index b78a01d..412305b 100644 --- a/src/main/java/org/baxter/disco/ocr/GuiView.java +++ b/src/main/java/org/baxter/disco/ocr/GuiView.java @@ -16,33 +16,95 @@ import javafx.stage.Stage; /** * View portion of MVC for the Accuracy Over Life test fixture. * + * GuiView, {@link GuiController}, and {@link GuiModel} versions are tied together, and are referred to collectively as Gui. + * * @author Blizzard Finnegan - * @version 0.0.1, 01 Feb, 2023 + * @version 0.2.0, 06 Feb, 2023 */ public class GuiView extends Application { + /** + * Scene used for the Main Menu. + */ public static final Scene MAIN_MENU; + + /** + * The base Node object for the Main menu; used to define window borders. + */ private static final AnchorPane MAIN_ANCHOR; + + /** + * The Node object within the {@link #MAIN_ANCHOR}, where all portions of the main menu are stored. + */ private static final Pane MAIN_PANE; + + /** + * Scene used for the camera configuration menu + */ public static final Scene CAMERA_MENU; + + /** + * The base Node object for the camera config menu; used to define window borders. + */ private static final AnchorPane CAMERA_ANCHOR; + + /** + * The node object within the {@link #CAMERA_ANCHOR}, where all portions of the camera config menu are stored. + */ private static final Pane CAMERA_PANE; + /** + * An easily-accessible map of text fields used in the Camera config menu. + * The outer map's keys are the respective cameras, with the inner map being config properties and the associated text field. + */ private static final Map> uiFields = new HashMap<>(); + /** + * An easily-accessible location for the user-feedback Text object. + */ private static Text userFeedback; + /** + * An easily accessible location for the TextField the user uses to set the current amount of iterations. + */ private static TextField iterationField; + /** + * Easily-accessible Button object for the start button. + */ private static Button startButton; - private static Button stopButton; + /* + * Easily-accessible Button object for the stop button. + */ + //private static Button stopButton; + /** + * The main Stage object, used in the GUI. + * + * The Stage object is analogous to the window generated. + */ private static Stage STAGE; + /** + * Value used for spacing within VBoxes and HBoxes + */ + private static double INTERNAL_SPACING = 5.0; + + /** + * Value used for window border spacing + */ + private static double EXTERNAL_SPACING = 10.0; + + /** + * The wrapper function to spawn a new JavaFX Stage. + */ public static void main(String[] args) { launch(args); } + /** + * Initialiser for the static objects. + */ static { ErrorLogging.logError("START OF PROGRAM"); @@ -51,10 +113,12 @@ public class GuiView extends Application MAIN_ANCHOR.setMinWidth(Double.NEGATIVE_INFINITY); MAIN_ANCHOR.setMinHeight(Double.NEGATIVE_INFINITY); MAIN_PANE = new Pane(); - AnchorPane.setTopAnchor(MAIN_PANE,10.0); - AnchorPane.setLeftAnchor(MAIN_PANE,10.0); - AnchorPane.setRightAnchor(MAIN_PANE,10.0); - AnchorPane.setBottomAnchor(MAIN_PANE,10.0); + + //Set the window border + AnchorPane.setTopAnchor(MAIN_PANE,EXTERNAL_SPACING); + AnchorPane.setLeftAnchor(MAIN_PANE,EXTERNAL_SPACING); + AnchorPane.setRightAnchor(MAIN_PANE,EXTERNAL_SPACING); + AnchorPane.setBottomAnchor(MAIN_PANE,EXTERNAL_SPACING); MAIN_ANCHOR.getChildren().add(MAIN_PANE); MAIN_MENU = new Scene(MAIN_ANCHOR); @@ -63,13 +127,16 @@ public class GuiView extends Application CAMERA_ANCHOR.setMinWidth(Double.NEGATIVE_INFINITY); CAMERA_ANCHOR.setMinHeight(Double.NEGATIVE_INFINITY); CAMERA_PANE = new Pane(); - AnchorPane.setTopAnchor(CAMERA_PANE,10.0); - AnchorPane.setLeftAnchor(CAMERA_PANE,10.0); - AnchorPane.setRightAnchor(CAMERA_PANE,10.0); - AnchorPane.setBottomAnchor(CAMERA_PANE,10.0); + + //Set the window border + AnchorPane.setTopAnchor(CAMERA_PANE,EXTERNAL_SPACING); + AnchorPane.setLeftAnchor(CAMERA_PANE,EXTERNAL_SPACING); + AnchorPane.setRightAnchor(CAMERA_PANE,EXTERNAL_SPACING); + AnchorPane.setBottomAnchor(CAMERA_PANE,EXTERNAL_SPACING); CAMERA_ANCHOR.getChildren().add(CAMERA_PANE); CAMERA_MENU = new Scene(CAMERA_ANCHOR); + //Initialise the camera fields map for(String camera : GuiModel.getCameras()) uiFields.put(camera, new HashMap<>()); } @@ -86,10 +153,16 @@ public class GuiView extends Application ErrorLogging.logError("Gui loading complete."); } + /** + * Camera Configuration Menu builder function. + * + * Creates a {@link VBox}, creates a {@link #cameraSetup(String)} object, and adds a separator between each camera. + * Finally, sets the created VBox to be the child of the {@link #CAMERA_PANE}, so it can be shown. + */ private static void cameraMenuBuilder() { VBox layout = new VBox(); - layout.setSpacing(5.0); + layout.setSpacing(INTERNAL_SPACING); layout.setAlignment(Pos.CENTER_LEFT); int index = 0; @@ -103,6 +176,12 @@ public class GuiView extends Application CAMERA_PANE.getChildren().add(layout); } + /** + * Main Menu builder function. + * + * Creates a VBox, fills it with the {@link #topHalf()}, a {@link Separator}, and the {@link #bottomHalf()} + * Finally, sets the created VBox to be the child of the {@link #MAIN_PANE}, so it can be shown. + */ private static void mainMenuBuilder() { VBox layout = new VBox(); @@ -113,10 +192,18 @@ public class GuiView extends Application } + /** + * Builder for the top half of the main menu. + * + * Creates a VBox, fills it with the {@link #topButtons()}, a {@link Separator}, the {@link #setupSection()}, + * the {@link #primeCheckbox()}, and the {@link #testFeedback()}. + * + * @return VBox described above + */ private static VBox topHalf() { VBox output = new VBox(); - output.setSpacing(5.0); + output.setSpacing(INTERNAL_SPACING); output.getChildren().addAll(topButtons(), new Separator(Orientation.HORIZONTAL), setupSection(), @@ -125,10 +212,18 @@ public class GuiView extends Application return output; } + /** + * Builder for the priming section of the main menu. + * + * Builds a pre-defined checkbox for setting whether the DUTs should be primed. + * + * @return CheckBox with a preset Tooltip, Id, and Listener. + */ private static CheckBox primeCheckbox() { CheckBox output = new CheckBox("Prime devices"); output.setTooltip(new Tooltip("This presses the button on the device under test twice for every iteration.")); + output.setSelected(true); output.setId("primeCheckbox"); output.selectedProperty().addListener( (obeservableValue, oldValue, newValue) -> @@ -138,10 +233,18 @@ public class GuiView extends Application return output; } + /** + * Builder for the user feedback section of the main menu. + * + * Creates an HBox, fills it with a {@link Label} and a {@link Text} used for communicating + * program status. + * + * @return HBox defined above + */ private static HBox testFeedback() { HBox output = new HBox(); - output.setSpacing(5.0); + output.setSpacing(INTERNAL_SPACING); Label textboxLabel = new Label("Test feedback: "); Text textbox = new Text("Awaiting input..."); userFeedback = textbox; @@ -151,6 +254,14 @@ public class GuiView extends Application return output; } + /** + * Builder function for the iteration count user input. + * + * Creates an HBox, filled with a Label and a TextField for user input. + * This TextField is used for setting the number of iterations to complete. + * + * @return HBox defined above + */ private static HBox setupSection() { HBox output = userTextField("Cycles:",GuiController.getIterationCount(), "Enter the number of times to test the devices in the fixture."); @@ -186,17 +297,40 @@ public class GuiView extends Application return output; } + /** + * Builder function for the top buttons of the Main Menu. + * + * Creates an HBox for the top buttons, then fills it with a + * - start Button + * - Runs the tests. As of Gui 0.2.0, this only partially runs the first portion of the test. + * - stop Button + * - Intended to stop the test. As of Gui 0.2.0, this has not yet been properly implemented. + * - calibrate cameras Button + * - Changes Scene to {@link #CAMERA_MENU}, allowing for camera setup. + * - test movement Button + * - Tests the movement of the fixture, informs the user of the test's success/failure + * - close Button + * - Closes the window, and the program. Note that as of Gui 0.2.0, this errors out the JVM + * + * @return HBox containing the above-listed buttons. + */ private static HBox topButtons() { + //Initial HBox creation HBox topButtons = new HBox(); - topButtons.setSpacing(5.0); + topButtons.setSpacing(INTERNAL_SPACING); topButtons.setAlignment(Pos.CENTER); topButtons.setMinWidth(Region.USE_COMPUTED_SIZE); topButtons.setMinHeight(Region.USE_COMPUTED_SIZE); + //Start button creation final Button START = buttonBuilder("Start",true); startButton = START; + + //Stop button created early, as it is affected by Start, and must be passed in final Button STOP = buttonBuilder("Stop",true); + + //Start button action and tooltip setting. START.setOnAction( (event) -> { START.setDisable(true); @@ -205,15 +339,16 @@ public class GuiView extends Application }); START.setTooltip(new Tooltip("Configure cameras to start the program.")); + //Stop button action and tooltip setting. STOP.setOnAction( (event) -> { GuiController.interruptTests(); START.setDisable(false); STOP.setDisable(true); }); - STOP.setTooltip(new Tooltip("Pauses current iteration.")); + //Calibrate Cameras button creation Button calibrateCamera = buttonBuilder("Calibrate Cameras",false); calibrateCamera.setOnAction( (event) -> @@ -221,10 +356,13 @@ public class GuiView extends Application GuiController.calibrateCameras(); STAGE.setScene(CAMERA_MENU); }); + + //Test Movement button creation Button testMovement = buttonBuilder("Test Movement",false); testMovement.setOnAction( (event) -> GuiController.testMotions() ); + //Close button creation Button cancel = buttonBuilder("Close",false); cancel.setOnAction( (event) -> { @@ -233,6 +371,7 @@ public class GuiView extends Application }); + //Put the above buttons into the HBox topButtons.getChildren().addAll(START, STOP, calibrateCamera, @@ -241,11 +380,18 @@ public class GuiView extends Application return topButtons; } + /** + * Builder function for the bottom half of the main menu. + * + * Creates an HBox, with however many cameras exist, and their associated {@link #camera(String)} views. + * + * @return Hbox described above + */ private static HBox bottomHalf() { HBox output = new HBox(); output.setAlignment(Pos.CENTER); - output.setSpacing(5.0); + output.setSpacing(INTERNAL_SPACING); int index = 0; for(String camera : GuiModel.getCameras()) @@ -256,11 +402,23 @@ public class GuiView extends Application return output; } + /** + * Builder for a camera view for the main menu. + * + * Creates a VBox, containing: + * - {@link #cameraHeader(String)} + * - HBox with a Label and TextField for the user to set a DUT's serial number. + * - {@link #cameraView(String)} + * + * @param cameraName The name of the camera to be attached to. + * + * @return VBox described above + */ private static VBox camera(String cameraName) { VBox output = new VBox(); output.setAlignment(Pos.CENTER_LEFT); - output.setSpacing(5.0); + output.setSpacing(INTERNAL_SPACING); HBox serialNumber = userTextField("DUT Serial Number:","","Enter the serial number for the device under test."); @@ -284,33 +442,48 @@ public class GuiView extends Application return output; } + /** + * Builder for the camera header, for the main menu. + * + * Creates an HBox, containing a Label with the camera's name, and a checkbox to mark whether it is active. + * As of Gui 0.2.0, the checkbox does not work properly. + * + * @param cameraName The name of the camera being accessed. + * + * @return HBox described above. + */ private static HBox cameraHeader(String cameraName) { HBox output = new HBox(); + output.setSpacing(INTERNAL_SPACING); output.setAlignment(Pos.CENTER); - output.setSpacing(5.0); - output.getChildren().addAll(cameraCheckbox("Camera: " + cameraName)); - return output; - } - - private static HBox cameraCheckbox(String prompt) - { - HBox output = new HBox(); - output.setSpacing(5.0); - output.setAlignment(Pos.CENTER); - output.setAlignment(Pos.CENTER); - Label label = new Label(prompt); + Label label = new Label("Camera: " + cameraName); CheckBox checkBox = new CheckBox("Active"); - checkBox.setId(prompt.toLowerCase()); + checkBox.setSelected(true); + checkBox.setOnAction( (event) -> {/*implement*/}); + checkBox.setId(cameraName.toLowerCase()); output.getChildren().addAll(label, checkBox); return output; } + /** + * Builder for the camera view, used in the main menu. + * + * Creates an HBox, containing: + * - A Label for defining what the following label means (OCR Read:) + * - A Label for showing what the OCR reading is. + * - As of Gui 0.2.0, this has not been implemented + * - An ImageView object for showing the final image + * - As of Gui 0.2.0, this has not been implemented + * @param cameraName Name of the camera being accessed + * + * @return HBox described above. + */ private static HBox cameraView(String cameraName) { HBox output = new HBox(); - output.setSpacing(5.0); + output.setSpacing(INTERNAL_SPACING); output.setAlignment(Pos.CENTER_LEFT); Label label = new Label("OCR Read:"); @@ -323,10 +496,23 @@ public class GuiView extends Application return output; } + /** + * Builder function for a single section in the camera config section. + * + * Creates a VBox, containing: + * - A Label (used for a section header) + * - A series of CheckBoxes used to define whether to crop and/or threshold the image + * - An HBox of inputs, used to define cropping values. (Defined by {@link #cropInputs(String)}) + * - An HBox of inputs, used to define the threshold value, and how many images to compose together (Defined by {@link #miscInputs(String)}) + * + * @param cameraName The name of the camera being modified + * + * @return The VBox described above + */ private static VBox cameraSetup(String cameraName) { VBox output = new VBox(); - output.setSpacing(5.0); + output.setSpacing(INTERNAL_SPACING); output.setAlignment(Pos.CENTER_LEFT); Label sectionHeader = new Label("Camera: " + cameraName); @@ -337,12 +523,25 @@ public class GuiView extends Application return output; } + /** + * Builder for the processing section of the {@link #cameraSetup(String)}, used in the Camera Config section. + * + * Creates an HBox containing: + * - A Button for creating a temporary preview + * - A CheckBox to toggle the cropping of the image + * - A CheckBox to toggle the thresholding of the image + * + * @param cameraName The name of the camera being modified + * + * @return HBox, as described above + */ private static HBox processingInputs(String cameraName) { HBox output = new HBox(); - output.setSpacing(5.0); + output.setSpacing(INTERNAL_SPACING); output.setAlignment(Pos.CENTER_LEFT); + //Preview button generation Button preview = buttonBuilder("Preview"); preview.setId("previewButton-" + cameraName); preview.setOnAction( (event) -> @@ -352,15 +551,22 @@ public class GuiView extends Application GuiController.showImage(cameraName); }); + //Crop image toggle checkbox creation CheckBox cropPreview = new CheckBox("Crop preview"); + cropPreview.setSelected(true); cropPreview.setId("cropToggle-" + cameraName); cropPreview.selectedProperty().addListener((obeservableValue, oldValue, newValue) -> GuiController.toggleCrop(cameraName)); + cropPreview.setOnAction( (event) -> GuiController.toggleCrop(cameraName) ); + //Threshold image toggle switch creation CheckBox thresholdPreview = new CheckBox("Threshold preview"); + thresholdPreview.setSelected(true); thresholdPreview.selectedProperty().addListener((obeservableValue, oldValue, newValue) -> GuiController.toggleThreshold(cameraName)); - cropPreview.setId("thresholdToggle-" + cameraName); + thresholdPreview.setId("thresholdToggle-" + cameraName); + thresholdPreview.setOnAction( (event) -> GuiController.toggleThreshold(cameraName) ); + output.getChildren().addAll(preview, cropPreview, @@ -368,31 +574,45 @@ public class GuiView extends Application return output; } + /** + * Builder function for the crop values, stored within a {@link #cameraSetup(String)} in the Camera Config menu. + * + * Creates an HBox, containing: + * - A Label and TextField for each of the following: + * - Crop X + * - Crop Y + * - Crop Width + * - Crop Height + * + * @param cameraName The name of the camera being modified + * + * @return HBox, as defined above + */ private static HBox cropInputs(String cameraName) { HBox output = new HBox(); - output.setSpacing(5.0); + output.setSpacing(INTERNAL_SPACING); output.setAlignment(Pos.CENTER_LEFT); HBox cropX = userTextField("X:", GuiController.getConfigValue(cameraName,ConfigProperties.CROP_X), "X-value of the top left corner of the newly cropped image. Only accepts whole numbers."); - textFieldSetup(cropX,ConfigProperties.CROP_X,cameraName,"X"); + textFieldSetup(cropX,ConfigProperties.CROP_X,cameraName); HBox cropY = userTextField("Y:", GuiController.getConfigValue(cameraName,ConfigProperties.CROP_Y), "Y-value of the top left corner of the newly cropped image. Only accepts whole numbers."); - textFieldSetup(cropY,ConfigProperties.CROP_Y,cameraName,"Y"); + textFieldSetup(cropY,ConfigProperties.CROP_Y,cameraName); HBox cropW = userTextField("Width:", GuiController.getConfigValue(cameraName,ConfigProperties.CROP_W), "Width, in pixels, of the newly cropped image. Only accepts whole numbers."); - textFieldSetup(cropW,ConfigProperties.CROP_W,cameraName,"Width"); + textFieldSetup(cropW,ConfigProperties.CROP_W,cameraName); HBox cropH = userTextField("Height:", GuiController.getConfigValue(cameraName,ConfigProperties.CROP_H), "Height, in pixels, of the newly cropped image. Only accepts whole numbers."); - textFieldSetup(cropH, ConfigProperties.CROP_H, cameraName, "Height"); + textFieldSetup(cropH, ConfigProperties.CROP_H, cameraName); output.getChildren().addAll(cropX, cropY, @@ -401,28 +621,50 @@ public class GuiView extends Application return output; } + /** + * Builder function for the other modifiable values for the {@link #cameraSetup(String)} portion of the camera config menu. + * + * Creates an HBox, containing a Label and TextField for: + * - threshold value + * - number of composite frames + * + * @param cameraName The name of the camera being configured + * + * @return HBox, defined above + */ private static HBox miscInputs(String cameraName) { HBox output = new HBox(); - output.setSpacing(5.0); + output.setSpacing(INTERNAL_SPACING); output.setAlignment(Pos.CENTER_LEFT); HBox thresholdValue = userTextField("Threshold Value:", GuiController.getConfigValue(cameraName,ConfigProperties.THRESHOLD), "This value can be set from 0 to 255. Higher values mean more black in "+ "the thresholded image. For more information, see the documentation."); - textFieldSetup(thresholdValue,ConfigProperties.THRESHOLD_VALUE,cameraName,"Threshold Value"); + textFieldSetup(thresholdValue,ConfigProperties.THRESHOLD_VALUE,cameraName); HBox compositeFrames = userTextField("Composite Frames:", GuiController.getConfigValue(cameraName,ConfigProperties.COMPOSITE_FRAMES), "Number of frames to bitwise-and together."); - textFieldSetup(compositeFrames,ConfigProperties.COMPOSITE_FRAMES,cameraName,"Composite Frames"); + textFieldSetup(compositeFrames,ConfigProperties.COMPOSITE_FRAMES,cameraName); output.getChildren().addAll(thresholdValue, compositeFrames); return output; } + /** + * Builder function for the final buttons in the Camera Config menu. + * + * Creates an HBox, containing: + * - Save Defaults button + * - Save Current button + * - Save and Close button + * - Close without Saving button + * + * @return HBox, as described above + */ private static HBox cameraMenuButtons() { HBox output = new HBox(); @@ -464,7 +706,17 @@ public class GuiView extends Application return output; } - private static void textFieldSetup(HBox hbox, ConfigProperties property, String cameraName, String oldId) + /** + * Modifying function for a text field. + * + * Brings in an HBox, stores it in the Map with the correct {@link ConfigProperties} value. + * Also sets the action of the text box to be correct. + * + * @param hbox The HBox containing the TextField to be modified and remembered + * @param property The property to be associated with the TextField + * @param cameraName The name of the camera to be associated with the TextField + */ + private static void textFieldSetup(HBox hbox, ConfigProperties property, String cameraName) { TextField field = null; for(Node child : hbox.getChildren()) @@ -501,6 +753,16 @@ public class GuiView extends Application }); } + /** + * Builder function for a button. + * + * Creates a button with a set ID, name, and disabled/enables status. + * + * @param name The name of the new button + * @param disabled Whether or not the button should be disabled on startup + * + * @return Button , with a preset ID, name, and optionally disabled. + */ private static Button buttonBuilder(String name,boolean disabled) { String[] id = name.strip().substring(0, name.length() - 1).toLowerCase().strip().split(" "); @@ -510,13 +772,32 @@ public class GuiView extends Application return button; } + /** + * Builder function for an enabled button. + * + * Creates a button with a set ID and name. + * + * @param name The name of the new button + * @return Button , with a preset ID and name. + */ private static Button buttonBuilder(String name) { return buttonBuilder(name,false); } + /** + * Builder function for a user-interactable TextField, with built-in label. + * + * Creates an HBox, with a Label for the TextField, along with the TextField itself. + * + * @param prompt The name used for the Label + * @param baseValue The default value used in the TextField + * @param description The Tooltip of the TextField/Label + * + * @return Hbox, described above + */ private static HBox userTextField(String prompt, String baseValue, String description) { HBox output = new HBox(); - output.setSpacing(5.0); + output.setSpacing(INTERNAL_SPACING); output.setAlignment(Pos.CENTER_LEFT); Label label = new Label(prompt); TextField field = new TextField(); @@ -531,15 +812,38 @@ public class GuiView extends Application return output; } + /** + * Getter for a given TextField, associated with a camera and property. + * + * @param cameraName The name of the camera the TextField is associated with + * @param property The name of the property the TextField is associated with + * + * @return TextField + */ public static TextField getField(String cameraName, ConfigProperties property) { return uiFields.get(cameraName).get(property); } + /** + * Getter for the Start button. + * + * @return Button used for starting the tests. + */ public static Button getStart() { return startButton; } + /** + * Getter for the user feedback Text object. + * + * @return Text object used for communicating statuses to the user. + */ public static Text getFeedbackText() { return userFeedback; } + /** + * Getter for the TextField used by the user to set the number of iterations. + * + * @return TextField + */ public static TextField getIterationField() { return iterationField; } } -- 2.47.2 From fcd830e6ed381ba074fa982ffe9e2dc61e3d15b5 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Tue, 7 Feb 2023 08:26:37 -0500 Subject: [PATCH 04/13] Continue updating GUI Also added a run script, to be copied with the JAR file to the Pi being imaged. (Stored in main git folder so that it can be version controlled with the rest of the repo, and not get deleted by Maven on `mvn clean package`) --- dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- runScript.sh | 2 + .../org/baxter/disco/ocr/GuiController.java | 45 ++++++++-- .../java/org/baxter/disco/ocr/GuiModel.java | 25 +++++- .../java/org/baxter/disco/ocr/GuiView.java | 89 ++++++++++++++++--- .../org/baxter/disco/ocr/MovementFacade.java | 19 ++-- .../org/baxter/disco/ocr/OpenCVFacade.java | 19 +++- .../org/baxter/disco/ocr/TesseractFacade.java | 4 +- 9 files changed, 169 insertions(+), 38 deletions(-) create mode 100644 runScript.sh diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 15f3151..8ed036b 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -62,7 +62,7 @@ - org.baxter.disco.ocr.Cli + org.baxter.disco.ocr.GuiStarter diff --git a/pom.xml b/pom.xml index 37c2bd0..ae6df4d 100644 --- a/pom.xml +++ b/pom.xml @@ -155,7 +155,7 @@ - org.baxter.disco.ocr.Cli + org.baxter.disco.ocr.GuiStarter diff --git a/runScript.sh b/runScript.sh new file mode 100644 index 0000000..b562275 --- /dev/null +++ b/runScript.sh @@ -0,0 +1,2 @@ +#! /usr/bin/env sh +sudo java -jar discoTesting-4.0.0-rc3.jar diff --git a/src/main/java/org/baxter/disco/ocr/GuiController.java b/src/main/java/org/baxter/disco/ocr/GuiController.java index 0a5ffc0..27acefe 100644 --- a/src/main/java/org/baxter/disco/ocr/GuiController.java +++ b/src/main/java/org/baxter/disco/ocr/GuiController.java @@ -4,6 +4,7 @@ import java.util.List; import javafx.scene.control.TextField; import javafx.scene.control.Tooltip; +import javafx.scene.image.Image; /** * Controller portion of MVC for Accuracy over Life test fixture. @@ -12,7 +13,7 @@ import javafx.scene.control.Tooltip; * {@link GuiView}, GuiController, and {@link GuiModel} versions are tied together, and are referred to collectively as Gui. * * @author Blizzard Finnegan - * @version 0.2.0, 01 Feb, 2023 + * @version 0.2.0, 06 Feb, 2023 */ public class GuiController { @@ -29,8 +30,8 @@ public class GuiController * * @param cameraName The camera whose image should be shown. */ - public static void showImage(String cameraName) - { GuiModel.showImage(cameraName); } + public static String showImage(String cameraName) + { return GuiModel.showImage(cameraName); } /** * Wrapper function to toggle cropping for a given camera @@ -74,8 +75,19 @@ public class GuiController * * @return String of the value of the current object */ - public static String getConfigValue(String cameraName, ConfigProperties property) - { return GuiModel.getConfigVal(cameraName,property); } + public static String getConfigString(String cameraName, ConfigProperties property) + { return GuiModel.getConfigString(cameraName,property); } + + /** + * Wrapper function to get a config value, for a given camera + * + * @param cameraName The name of the camera being inspected + * @param property The config property to be returned + * + * @return String of the value of the current object + */ + public static double getConfigValue(String cameraName, ConfigProperties property) + { return GuiModel.getConfigValue(cameraName,property); } /** * Wrapper function to set a config value for a given camera. @@ -85,7 +97,13 @@ public class GuiController * @param value The new value to set the property to */ public static void setConfigValue(String cameraName, ConfigProperties property, double value) - { GuiModel.setConfigVal(cameraName,property,value); } + { + GuiModel.setConfigVal(cameraName,property,value); + if(property == ConfigProperties.CROP_W) + GuiView.updateImageViewWidth(cameraName); + if(property == ConfigProperties.CROP_H) + GuiView.updateImageViewHeight(cameraName); + } /** * Setter for the number of iterations @@ -150,8 +168,8 @@ public class GuiController public static void updateConfigValue(String cameraName, ConfigProperties property) { TextField field = GuiView.getField(cameraName,property); - field.setText(GuiModel.getConfigVal(cameraName,property)); - field.setPromptText(GuiModel.getConfigVal(cameraName,property)); + field.setText(GuiModel.getConfigString(cameraName,property)); + field.setPromptText(GuiModel.getConfigString(cameraName,property)); } /** @@ -229,4 +247,15 @@ public class GuiController * Close function for the Model; used to end the program */ public static void closeModel() { GuiModel.close(); } + + /** + * Function used to update the ImageView of the GUID + * + * @param cameraName Name of the camera the image is from + * @param fileURL The URL of the file to be shown + */ + public static void updateImage(String cameraName, String fileURL) + { + GuiView.getViewMap().get(cameraName).setImage(new Image(fileURL)); + } } diff --git a/src/main/java/org/baxter/disco/ocr/GuiModel.java b/src/main/java/org/baxter/disco/ocr/GuiModel.java index c9a8d61..83a5331 100644 --- a/src/main/java/org/baxter/disco/ocr/GuiModel.java +++ b/src/main/java/org/baxter/disco/ocr/GuiModel.java @@ -97,9 +97,13 @@ public class GuiModel { return new ArrayList<>(OpenCVFacade.getCameraNames()); } /** - * Wrapper function for showing an image. + * Function that calls the OpenCVFacade image generation function. + * @return String url of the location of the new image */ - public static void showImage(String cameraName) { OpenCVFacade.showImage(cameraName); } + public static String showImage(String cameraName) + { + return OpenCVFacade.showImage(cameraName, new Object()); + } /** * Setter for a given camera's config value @@ -115,16 +119,27 @@ public class GuiModel } /** - * Getter for a given camera's config value + * Getter for a given camera's config value, in String format * * @param cameraName Name of the camera to get the config value from * @param property Property to get the value of * * @return String of the current value in the config */ - public static String getConfigVal(String cameraName, ConfigProperties property) + public static String getConfigString(String cameraName, ConfigProperties property) { return Double.toString(ConfigFacade.getValue(cameraName,property)); } + /** + * Getter for a given camera's config value + * + * @param cameraName Name of the camera to get the config value from + * @param property Property to get the value of + * + * @return double of the current value in the config + */ + public static double getConfigValue(String cameraName, ConfigProperties property) + { return ConfigFacade.getValue(cameraName,property); } + /** * Wrapper function around the MovementFacade's pressButton function. */ @@ -228,6 +243,7 @@ public class GuiModel { while(!LOCK.tryLock()) {} File file = OpenCVFacade.completeProcess(cameraName); + GuiController.updateImage(cameraName,file.getPath()); LOCK.unlock(); while(!LOCK.tryLock()) {} cameraToFile.replace(cameraName,file); @@ -292,4 +308,5 @@ public class GuiModel */ public static void calibrateCameras() { fixture.goDown(); } + } diff --git a/src/main/java/org/baxter/disco/ocr/GuiView.java b/src/main/java/org/baxter/disco/ocr/GuiView.java index 412305b..601510c 100644 --- a/src/main/java/org/baxter/disco/ocr/GuiView.java +++ b/src/main/java/org/baxter/disco/ocr/GuiView.java @@ -8,6 +8,7 @@ import javafx.application.Application; import javafx.geometry.*; import javafx.scene.*; import javafx.scene.control.*; +import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.*; import javafx.scene.text.*; @@ -97,6 +98,11 @@ public class GuiView extends Application */ private static double EXTERNAL_SPACING = 10.0; + /** + * Map used to store ImageViews + */ + private static Map viewMap = new HashMap<>(); + /** * The wrapper function to spawn a new JavaFX Stage. */ @@ -136,9 +142,18 @@ public class GuiView extends Application CAMERA_ANCHOR.getChildren().add(CAMERA_PANE); CAMERA_MENU = new Scene(CAMERA_ANCHOR); - //Initialise the camera fields map + //Initialise the camera fields map and imageview map for(String camera : GuiModel.getCameras()) + { uiFields.put(camera, new HashMap<>()); + ImageView view = new ImageView(); + view.setId(camera + "-view"); + view.setFitWidth(GuiController.getConfigValue(camera,ConfigProperties.CROP_W)); + view.setFitHeight(GuiController.getConfigValue(camera,ConfigProperties.CROP_H)); + viewMap.put(camera, view); + } + + STAGE.setOnCloseRequest( (event) -> GuiController.closeModel() ); } @Override @@ -169,7 +184,7 @@ public class GuiView extends Application for(String cameraName : GuiModel.getCameras()) { if(index != 0) layout.getChildren().add(new Separator(Orientation.HORIZONTAL)); - layout.getChildren().add(cameraSetup(cameraName)); + layout.getChildren().add(cameraSection(cameraName)); index++; } layout.getChildren().add(cameraMenuButtons()); @@ -398,6 +413,7 @@ public class GuiView extends Application { if(index != 0) output.getChildren().add(new Separator(Orientation.VERTICAL)); output.getChildren().add(camera(camera)); + index++; } return output; } @@ -489,7 +505,8 @@ public class GuiView extends Application Label label = new Label("OCR Read:"); Label ocrRead = new Label("[ ]"); ocrRead.setId("cameraOCR-" + cameraName); - ImageView imageView = new ImageView(); + ImageView imageView = viewMap.get(cameraName); + //imageView.setImage(new Image(GuiController.showImage(cameraName))); output.getChildren().addAll(label, ocrRead, imageView); @@ -497,7 +514,7 @@ public class GuiView extends Application } /** - * Builder function for a single section in the camera config section. + * Builder function for the user-editable section in the camera config menu. * * Creates a VBox, containing: * - A Label (used for a section header) @@ -523,6 +540,27 @@ public class GuiView extends Application return output; } + /** + * Builder function for a complete section in the camera config menu. + * + * Creates an HBox, containing: + * - A VBox, created by {@link #cameraSetup(String)} + * - An ImageView, which will be used to show the image to the user + */ + private static HBox cameraSection(String cameraName) + { + HBox output = new HBox(); + output.setSpacing(INTERNAL_SPACING); + output.setAlignment(Pos.CENTER_LEFT); + + output.getChildren().add(cameraSetup(cameraName)); + + ImageView imageView = viewMap.get(cameraName); + output.getChildren().add(imageView); + + return output; + } + /** * Builder for the processing section of the {@link #cameraSetup(String)}, used in the Camera Config section. * @@ -548,7 +586,8 @@ public class GuiView extends Application { GuiController.pressButton(); try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); } - GuiController.showImage(cameraName); + String imageURL = GuiController.showImage(cameraName); + viewMap.get(cameraName).setImage(new Image(imageURL)); }); //Crop image toggle checkbox creation @@ -595,22 +634,22 @@ public class GuiView extends Application output.setAlignment(Pos.CENTER_LEFT); HBox cropX = userTextField("X:", - GuiController.getConfigValue(cameraName,ConfigProperties.CROP_X), + GuiController.getConfigString(cameraName,ConfigProperties.CROP_X), "X-value of the top left corner of the newly cropped image. Only accepts whole numbers."); textFieldSetup(cropX,ConfigProperties.CROP_X,cameraName); HBox cropY = userTextField("Y:", - GuiController.getConfigValue(cameraName,ConfigProperties.CROP_Y), + GuiController.getConfigString(cameraName,ConfigProperties.CROP_Y), "Y-value of the top left corner of the newly cropped image. Only accepts whole numbers."); textFieldSetup(cropY,ConfigProperties.CROP_Y,cameraName); HBox cropW = userTextField("Width:", - GuiController.getConfigValue(cameraName,ConfigProperties.CROP_W), + GuiController.getConfigString(cameraName,ConfigProperties.CROP_W), "Width, in pixels, of the newly cropped image. Only accepts whole numbers."); textFieldSetup(cropW,ConfigProperties.CROP_W,cameraName); HBox cropH = userTextField("Height:", - GuiController.getConfigValue(cameraName,ConfigProperties.CROP_H), + GuiController.getConfigString(cameraName,ConfigProperties.CROP_H), "Height, in pixels, of the newly cropped image. Only accepts whole numbers."); textFieldSetup(cropH, ConfigProperties.CROP_H, cameraName); @@ -639,13 +678,13 @@ public class GuiView extends Application output.setAlignment(Pos.CENTER_LEFT); HBox thresholdValue = userTextField("Threshold Value:", - GuiController.getConfigValue(cameraName,ConfigProperties.THRESHOLD), + GuiController.getConfigString(cameraName,ConfigProperties.THRESHOLD), "This value can be set from 0 to 255. Higher values mean more black in "+ "the thresholded image. For more information, see the documentation."); textFieldSetup(thresholdValue,ConfigProperties.THRESHOLD_VALUE,cameraName); HBox compositeFrames = userTextField("Composite Frames:", - GuiController.getConfigValue(cameraName,ConfigProperties.COMPOSITE_FRAMES), + GuiController.getConfigString(cameraName,ConfigProperties.COMPOSITE_FRAMES), "Number of frames to bitwise-and together."); textFieldSetup(compositeFrames,ConfigProperties.COMPOSITE_FRAMES,cameraName); @@ -846,4 +885,32 @@ public class GuiView extends Application */ public static TextField getIterationField() { return iterationField; } + + /** + * Getter for the ImageView Map. + * + * @return Map with keys of the names of cameras, and the value of the corresponding imageview + */ + public static Map getViewMap() + { return viewMap; } + + /** + * Updater for a given camera's ImageView width + * + * @param cameraName Name of the camera being updated + */ + public static void updateImageViewWidth(String cameraName) + { + viewMap.get(cameraName).setFitWidth(GuiController.getConfigValue(cameraName,ConfigProperties.CROP_W)); + } + + /** + * Updater for a given camera's ImageView height + * + * @param cameraName Name of the camera being updated + */ + public static void updateImageViewHeight(String cameraName) + { + viewMap.get(cameraName).setFitHeight(GuiController.getConfigValue(cameraName,ConfigProperties.CROP_H)); + } } diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index 675cba8..d2f5654 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -21,10 +21,11 @@ import com.pi4j.io.pwm.PwmType; * Currently missing Run switch compatibility. * * @author Blizzard Finnegan - * @version 2.1.0, 03 Feb. 2023 + * @version 2.2.0, 06 Feb. 2023 */ public class MovementFacade { + private static boolean exit = false; /** * Constructor for MovementFacade. * @@ -37,7 +38,7 @@ public class MovementFacade runSwitchThread = new Thread(() -> { boolean unlock = false; - while(true) + while(!exit) { if(runSwitch.isOn()) { @@ -470,7 +471,8 @@ public class MovementFacade goUp(); if(runSwitchThread.isAlive()) { - runSwitchThread.interrupt(); + exit = true; + try{ Thread.sleep(100); } catch(Exception e){} } pi4j.shutdown(); } @@ -492,6 +494,11 @@ public class MovementFacade return output; } + /** + * Function to move the fixture once for an iteration. + * + * @param prime Whether or not to wake up the DUT + */ public void iterationMovement(boolean prime) { goUp(); @@ -499,10 +506,4 @@ public class MovementFacade goDown(); pressButton(); } - - public void main(String[] args) - { - testMotions(); - closeGPIO(); - } } diff --git a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java index ddec0dc..06cf9d4 100644 --- a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java +++ b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java @@ -22,7 +22,7 @@ import java.util.List; * Performs image capture, as well as image manipulation. * * @author Blizzard Finnegan - * @version 1.2.0, 03 Feb. 2023 + * @version 1.3.0, 06 Feb. 2023 */ public class OpenCVFacade { @@ -170,7 +170,7 @@ public class OpenCVFacade } /** - * Show current processed image to user. + * Show current processed image to the CLI user. * * @param cameraName The name of the camera to be previewed * @@ -191,6 +191,21 @@ public class OpenCVFacade return canvas; } + /** + * Show current processed image to the GUI user. + * + * @param cameraName The name of the camera to be previewed + * + * @return The {@link CanvasFrame} that is being opened. This is returned so it can be closed by the program. + */ + public static String showImage(String cameraName, Object object) + { + //ErrorLogging.logError("DEBUG: Showing image from camera: " + cameraName); + //ErrorLogging.logError("DEBUG: camera location: " + cameraMap.get(cameraName).toString()); + File imageLocation = completeProcess(cameraName,ConfigFacade.getImgSaveLocation() + "/config"); + return imageLocation.getPath(); + } + /** * Take multiple pictures in quick succession. * diff --git a/src/main/java/org/baxter/disco/ocr/TesseractFacade.java b/src/main/java/org/baxter/disco/ocr/TesseractFacade.java index 4865d10..d525517 100644 --- a/src/main/java/org/baxter/disco/ocr/TesseractFacade.java +++ b/src/main/java/org/baxter/disco/ocr/TesseractFacade.java @@ -71,10 +71,10 @@ public class TesseractFacade output = sc.nextDouble(); if(output >= 200) { - ErrorLogging.logError("OCR ERROR!!! - OCR output is too high for DUT, attempting to adjust..."); + ErrorLogging.logError("OCR WARNING - OCR output is too high for DUT, attempting to adjust..."); output = output / 10; if(output >= 200) - ErrorLogging.logError("OCR ERROR!!! - OCR output is too high for DUT, potential misread."); + ErrorLogging.logError("OCR WARNING - OCR output is too high for DUT, potential misread."); } if(output <= -10) ErrorLogging.logError("OCR ERROR!!! - OCR output is too low for DUT, potential misread."); -- 2.47.2 From d30edcdceba487c945aa75931fbf9fd09fb12137 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Tue, 7 Feb 2023 09:55:41 -0500 Subject: [PATCH 05/13] Update README with necessary instructions Also, update pom.xml for easier switching between GUI and CLI compilations. --- 83-webcam.rules | 2 + README.md | 104 +++++++++++++++++++++++++++++++++++-- dependency-reduced-pom.xml | 3 +- pom.xml | 3 +- 4 files changed, 105 insertions(+), 7 deletions(-) create mode 100755 83-webcam.rules diff --git a/83-webcam.rules b/83-webcam.rules new file mode 100755 index 0000000..36a2421 --- /dev/null +++ b/83-webcam.rules @@ -0,0 +1,2 @@ +##EXAMPLE: ACTION=="add", KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ENV{ID_PATH}=="platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0", SYMLINK+="video-cam1" +ACTION=="add", KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ENV{ID_PATH}=="fillerText", SYMLINK+="video-cam1" diff --git a/README.md b/README.md index 35cbf88..0b9bd15 100644 --- a/README.md +++ b/README.md @@ -27,29 +27,117 @@ This is a personal/professional project, which makes use of JavaCV, OpenCV, Tess - As of CLI build 1.2.0, the external run switch bricks the system. - Currently, all images are parsed on a per-iteration level. This means that we cannot guarantee the output columns are correct. As a temporary solution, image location, serial number, and parsed reading are placed in groups. Intention is to ultimately have them be column-wise written. - As of CLI build 1.3.0, the first reading row must be discarded, due to one side of the fixture producing a consistent failed reading. This has yet to be debugged, or fully understood. + ## Dependencies -To install this project, and use it fully, you must have the following: -- a Raspberry Pi 4 or 400 (other Pis may work properly, but has not been tested) + +To install this project, you must have the following: +- a Raspberry Pi 4 or 400 (other Pis may work properly, but has not been tested), with the following installed: - OpenJDK11 -- A separate development computer (preferrably x86-64 based) + +To further develop this software, or to compile it from source, the following is also recommended: +- A separate development computer (preferrably x86-64 based), with the following installed: - A Java-compatible IDE - Maven - OpenJDK11 -There are several required dependencies for this project. Maven handles these dependencies for you. Note that your first build of the project will take some time, as all dependencies will need to be downloaded before compiling. +OpenJDK11 is explicitly required, as it is the only currently available Java development platform compatible with [Pi4J](https://pi4j.com/getting-started/). According to the [documentation for Pi4J](https://pi4j.com/getting-started/developing-on-a-remote-pc/), development on a Raspberry Pi is possible, but given this project's build time (as of 4.0.0-rc3, 2-5 minutes on a Baxter-distributed device, before documentation generation), it is recommended to build on x86-64, and copy to the compiled JAR to the Pi. As such, this repository has been designed with this development model in mind. If you are intending on compiling on a Pi, please see the above-linked documentation to see what should be modified in your local `pom.xml` file. + +There are several secondary dependencies that are required for this project. However, Maven handles these dependencies for you. Note that because of this, your first build of the project will take some time, and will require internet access, as all dependencies will need to be downloaded before compiling. + +## First-Time Setup + +If you are working with a newly-installed Raspberry Pi, there are several steps you will need to also take before running this program. This will require some use of the terminal. + +1. You will need to kill and disable the `pigpio` daemon. This is done by running the following commands: +``` +sudo killall pigpiod +sudo systemctl disable pigpiod +``` +The first command stops all currently running `pigpio` daemon processes. The second command disables the daemon, so that it will not start again if you reboot. + +2. You will need to create a new `udev` rule. This creates a symlink for a given camera, plugged into a specific USB port, and allows the Java code to consistently communicate with the camera. An example `udev` rule is given in this repo (`83-webcam.rules`), but will need to be modified to your specific device. + 1. Copy the example `udev` rule file to your Raspberry Pi, and put it in `/etc/udev/rules.d/` (if this directory does not exist, create it). + 2. Open the copied file in the text editor of your choice, and open a terminal window as well. + 3. Run the following command in your terminal window: +``` +sudo udevadm monitor -p | grep ID_PATH= +``` +This will show all `udev` activity as it happens. + 4. Unplug *ONE* camera, and plug it back in to the same port. This will generate several lines of text in your terminal window. + 5. Copy one of the lines, starting with `platform`, and, *crucially*, ending `.0`. + 6. Paste this into your `udev` rule file, replacing the `fillerText` portion, but leaving the quotes. The first line of the file distributed in this repo contains a commented-out example line, with the correct syntax. + 7. Repeat steps 4-6 for all cameras in the fixture. If there are no new lines available, copy and paste the line into a new line to create a new rule, *ensuring to increment the number at the end of the line in the `SYMLINK` section*. + 8. Reboot the Raspberry Pi to load the new `udev` rule. + 9. Open a terminal, and check that the new symlinks were created successfully. This can be done by running the below command. If the symlinks have not been created successfully, restart from step 3 until all symlinks are created properly. +``` +ls /dev/video-* +``` ## Installation The project is then built from source, and the output final binary (located in `target/discoTesting.jar`) is copied to the Raspberry Pi for use. +This project requires use of `udev` rules to ensure that cameras are in the proper location, by default. This can be modified in this project (camera initialisation is done in the initialisation of `OpenCVFacade`). + +## Usage + +To use this program, it *must* be run on a Raspberry Pi, with available GPIO. + +1. Copy both the generated JAR file (the largest file in the `target` directory), the `tessdata` folder (Provided by Baxter, currently ommited from this repository due to licensing conflicts), and the `runScript.sh` to a flash drive. +2. Eject the flash drive, then plug it into your Raspberry Pi (which should be connected to the fixture). +3. Copy the files from step 1 to the desktop of the Pi, then either: + - Easy: Double-click the `runScript.sh` file. This should show a warning, asking if you would like to Execute, Execute in Terminal, Open, or Cancel. Click "Execute in Terminal". + - Open a terminal, `cd` onto the desktop, then run the following command: (The `sudo` is necessary to access GPIO, it should not prompt for password.) + +``` +sudo java -jar [name of JAR file, including extension] +``` +4. What will happen next depends on your current version: + - Versions `4.0.0-rc1` and `4.0.0-rc2` will create a terminal window. From there, use the numbers shown in the menu to control the fixture, and run tests as necessary. + - Versions `4.0.0-rc3` and newer will create a terminal window, which load things for a moment before also creating a GUI. This GUI can be used to control the fixture, and run tests as necessary. + + +### Potential Errors + +If the terminal almost-immediately exits, or something seems wrong, check the log file (which is named with the current date and time; ex. `2023-02-07_09.15.22-log.txt`). This will show potential errors. +- If the file contains the phrase `PI_INIT_FAILED`, the default GPIO daemon is currently active, and needs to be deactivated. To do so, run the following line in a terminal, then try to run the program again: +``` +sudo killall pigpiod +``` +- If the file contains a `CAMERA INIT ERROR`, this means that the associated camera was not picked up properly. This can be due to several reasons. Below are some debugging steps to take. Note that this will cause cascading errors, if attempting to import a camera's config from a pre-existing config file, so config-related errors can be ignored. + 1. Ensure that both cameras are plugged in properly. + 2. Unplug, and then plug back in, the erroring camera. + 3. Ensure that the `/dev/video-cam1` and `/dev/video-cam2` files are both created. If they are not, then you will need to update your `udev` rules, as in the Installation section. + 4. Reboot the Raspberry Pi. (This can be done by opening the terminal, and typing `reboot`, then hitting enter.) Camera drivers occasionally fail to load on boot, and will unload after a long time with no use. Rebooting generally solves this issue (although it may take multiple reboots.) + ## Building from source -Clone the repository onto your computer, then run the following to compile the project into a runnable JAR file: +For your first time compiling the project, clone the repository onto your computer, then run the following in a terminal with Maven to compile the project into a runnable JAR file: ``` mvn clean package ``` +Maven can also be interacted with in a GUI environment in Visual Studio Code, but at time of writing, this process is unknown to me. + +This will create a new `target` folder, download all dependencies, and compile the code into a final JAR file. Subsequent project builds can be alled using either the above command (which will delete the previous `target` folder before recreating it, copying the required libraries, and compiling the code), or to save time, the following can also be run instead: + +``` +mvn package +``` + +As the next section describes, you can also build documentation at the same time, by running the following in your terminal: + +``` +mvn package; mvn site +``` + +or + +``` +mvn clean package; mvn site +``` + ## Documentation This project was built with Javadoc in mind, as it is a good way to explore a project in an interactive manner. To generate Javadocs, run the following: @@ -59,3 +147,9 @@ mvn site ``` The documentation site can then be found in `target/site/index.html`. + +Note that because this documentation is generated in the same folder as the final project file, running `mvn clean package` will delete the documentation in its current state. As such, it is recommended to run a new documentation generation call on every clean build, like so: + +``` +mvn clean package; mvn site +``` diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 8ed036b..45f1162 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -62,7 +62,7 @@ - org.baxter.disco.ocr.GuiStarter + org.baxter.disco.ocr.${uitype} @@ -102,6 +102,7 @@ 11 2.1.0 raspberry + GuiStarter 11 1.5.6 5.2.3 diff --git a/pom.xml b/pom.xml index ae6df4d..aac3210 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,7 @@ 5.9.1 4.1.1 1.9.4 + GuiStarter @@ -155,7 +156,7 @@ - org.baxter.disco.ocr.GuiStarter + org.baxter.disco.ocr.${uitype} -- 2.47.2 From db5b8c4b867f2e466fe9cfa868ee93abe952b07d Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Tue, 7 Feb 2023 09:59:51 -0500 Subject: [PATCH 06/13] Finish* updating README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 0b9bd15..183bf9e 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,10 @@ sudo killall pigpiod ## Building from source +Before building this project, decide whether you want a TUI (Terminal User Interface), or a GUI (Graphical User Interface). As of `4.0.0-rc3`, the GUI has only been partially implemented, so should only be compiled for improvement purposes. +- If you wish to build the TUI, change the `uitype` field in your `pom.xml` to be `Cli`. +- If you wish to build the GUI, change the `uitype` field in your `pom.xml` to be `GuiStarter`. + For your first time compiling the project, clone the repository onto your computer, then run the following in a terminal with Maven to compile the project into a runnable JAR file: ``` -- 2.47.2 From ddfbf7e6c7da54d94ff86face3069aed2bbe5258 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Tue, 7 Feb 2023 10:11:38 -0500 Subject: [PATCH 07/13] Update Readme to be in line with stable --- README.md | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 183bf9e..13c90e1 100644 --- a/README.md +++ b/README.md @@ -10,23 +10,15 @@ This is a personal/professional project, which makes use of JavaCV, OpenCV, Tess - [x] Takes in inputs - [x] Sets config values - [x] Saves config values - - [x] Shows camera preview* - - currently broken, for unknown reasons. In process of parsing... + - [x] Shows camera preview - [x] GPIO test interactions - Test suite - [x] OpenCV image capture - [x] OpenCV image processing - [x] Tesseract OCR processing - - [x] Data storage in defined XLSX file* + - [x] Data storage in defined XLSX file - [x] modify number of iterations for test suite -- [x] JavaFX GUI (Designed, implemented) - -## Known Bugs - -- Closing the program throws a fatal error. This is due to the current implementation of monitoring of the Run switch, and as of now, to my knowledge, cannot be resolved. - - As of CLI build 1.2.0, the external run switch bricks the system. -- Currently, all images are parsed on a per-iteration level. This means that we cannot guarantee the output columns are correct. As a temporary solution, image location, serial number, and parsed reading are placed in groups. Intention is to ultimately have them be column-wise written. -- As of CLI build 1.3.0, the first reading row must be discarded, due to one side of the fixture producing a consistent failed reading. This has yet to be debugged, or fully understood. +- [ ] JavaFX GUI (Designed, partially implemented) ## Dependencies -- 2.47.2 From f008ebfcc50e68309be4d81b5bb3b975036c9e90 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Tue, 7 Feb 2023 10:12:42 -0500 Subject: [PATCH 08/13] Update formatting in first-time install section --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 13c90e1..6b92025 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,12 @@ The first command stops all currently running `pigpio` daemon processes. The sec 1. Copy the example `udev` rule file to your Raspberry Pi, and put it in `/etc/udev/rules.d/` (if this directory does not exist, create it). 2. Open the copied file in the text editor of your choice, and open a terminal window as well. 3. Run the following command in your terminal window: + ``` sudo udevadm monitor -p | grep ID_PATH= ``` -This will show all `udev` activity as it happens. + + This will show all `udev` activity as it happens. 4. Unplug *ONE* camera, and plug it back in to the same port. This will generate several lines of text in your terminal window. 5. Copy one of the lines, starting with `platform`, and, *crucially*, ending `.0`. 6. Paste this into your `udev` rule file, replacing the `fillerText` portion, but leaving the quotes. The first line of the file distributed in this repo contains a commented-out example line, with the correct syntax. -- 2.47.2 From 2b1ba465e1bc2f43c8f72716cdeeb08db04781f8 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Tue, 7 Feb 2023 10:13:54 -0500 Subject: [PATCH 09/13] Continue updating formatting of Readme --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 6b92025..bfccfc5 100644 --- a/README.md +++ b/README.md @@ -50,13 +50,11 @@ The first command stops all currently running `pigpio` daemon processes. The sec 2. You will need to create a new `udev` rule. This creates a symlink for a given camera, plugged into a specific USB port, and allows the Java code to consistently communicate with the camera. An example `udev` rule is given in this repo (`83-webcam.rules`), but will need to be modified to your specific device. 1. Copy the example `udev` rule file to your Raspberry Pi, and put it in `/etc/udev/rules.d/` (if this directory does not exist, create it). 2. Open the copied file in the text editor of your choice, and open a terminal window as well. - 3. Run the following command in your terminal window: + 3. Run the following command in your terminal window. This will show all `udev` activity as it happens. ``` sudo udevadm monitor -p | grep ID_PATH= ``` - - This will show all `udev` activity as it happens. 4. Unplug *ONE* camera, and plug it back in to the same port. This will generate several lines of text in your terminal window. 5. Copy one of the lines, starting with `platform`, and, *crucially*, ending `.0`. 6. Paste this into your `udev` rule file, replacing the `fillerText` portion, but leaving the quotes. The first line of the file distributed in this repo contains a commented-out example line, with the correct syntax. -- 2.47.2 From 2b5acc0c5f01b0b2227da288f6a84f7a84789744 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan <49597944+blizzardfinnegan@users.noreply.github.com> Date: Tue, 7 Feb 2023 10:16:25 -0500 Subject: [PATCH 10/13] Attempt 3 at fixing formatting --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index bfccfc5..b7a3fba 100644 --- a/README.md +++ b/README.md @@ -50,11 +50,7 @@ The first command stops all currently running `pigpio` daemon processes. The sec 2. You will need to create a new `udev` rule. This creates a symlink for a given camera, plugged into a specific USB port, and allows the Java code to consistently communicate with the camera. An example `udev` rule is given in this repo (`83-webcam.rules`), but will need to be modified to your specific device. 1. Copy the example `udev` rule file to your Raspberry Pi, and put it in `/etc/udev/rules.d/` (if this directory does not exist, create it). 2. Open the copied file in the text editor of your choice, and open a terminal window as well. - 3. Run the following command in your terminal window. This will show all `udev` activity as it happens. - -``` -sudo udevadm monitor -p | grep ID_PATH= -``` + 3. Run the following command in your terminal window. `sudo udevadm monitor -p | grep ID_PATH=` This will show all `udev` activity as it happens. 4. Unplug *ONE* camera, and plug it back in to the same port. This will generate several lines of text in your terminal window. 5. Copy one of the lines, starting with `platform`, and, *crucially*, ending `.0`. 6. Paste this into your `udev` rule file, replacing the `fillerText` portion, but leaving the quotes. The first line of the file distributed in this repo contains a commented-out example line, with the correct syntax. -- 2.47.2 From 28f0786569d12c78bf1086e4fe2aee6f2c310631 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Tue, 7 Feb 2023 10:20:06 -0500 Subject: [PATCH 11/13] Attempt to single-line command --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b7a3fba..3784db8 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,13 @@ The first command stops all currently running `pigpio` daemon processes. The sec 2. You will need to create a new `udev` rule. This creates a symlink for a given camera, plugged into a specific USB port, and allows the Java code to consistently communicate with the camera. An example `udev` rule is given in this repo (`83-webcam.rules`), but will need to be modified to your specific device. 1. Copy the example `udev` rule file to your Raspberry Pi, and put it in `/etc/udev/rules.d/` (if this directory does not exist, create it). 2. Open the copied file in the text editor of your choice, and open a terminal window as well. - 3. Run the following command in your terminal window. `sudo udevadm monitor -p | grep ID_PATH=` This will show all `udev` activity as it happens. + 3. Run the following command in your terminal window. + + ``` + sudo udevadm monitor -p | grep ID_PATH= + ``` + + This will show all `udev` activity as it happens. 4. Unplug *ONE* camera, and plug it back in to the same port. This will generate several lines of text in your terminal window. 5. Copy one of the lines, starting with `platform`, and, *crucially*, ending `.0`. 6. Paste this into your `udev` rule file, replacing the `fillerText` portion, but leaving the quotes. The first line of the file distributed in this repo contains a commented-out example line, with the correct syntax. -- 2.47.2 From 4549804ad49e5eb28b47fa3489d88559668e51e0 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan <49597944+blizzardfinnegan@users.noreply.github.com> Date: Tue, 7 Feb 2023 10:22:05 -0500 Subject: [PATCH 12/13] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3784db8..40e08cd 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ The first command stops all currently running `pigpio` daemon processes. The sec ``` This will show all `udev` activity as it happens. + 4. Unplug *ONE* camera, and plug it back in to the same port. This will generate several lines of text in your terminal window. 5. Copy one of the lines, starting with `platform`, and, *crucially*, ending `.0`. 6. Paste this into your `udev` rule file, replacing the `fillerText` portion, but leaving the quotes. The first line of the file distributed in this repo contains a commented-out example line, with the correct syntax. -- 2.47.2 From ae79c0f95fa59fad6fdee8f132caead16d568025 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Wed, 8 Feb 2023 08:49:58 -0500 Subject: [PATCH 13/13] Remove GUI from current build for 4.0.0 ship --- README.md | 13 +- dependency-reduced-pom.xml | 3 +- pom.xml | 7 +- src/main/java/module-info.java | 6 +- src/main/java/org/baxter/disco/ocr/Cli.java | 211 ++-- .../org/baxter/disco/ocr/ConfigFacade.java | 23 +- .../baxter/disco/ocr/ConfigProperties.java | 8 +- .../org/baxter/disco/ocr/GuiController.java | 261 ----- .../java/org/baxter/disco/ocr/GuiModel.java | 312 ------ .../java/org/baxter/disco/ocr/GuiStarter.java | 16 - .../java/org/baxter/disco/ocr/GuiView.java | 916 ------------------ .../org/baxter/disco/ocr/MovementFacade.java | 4 +- .../org/baxter/disco/ocr/OpenCVFacade.java | 6 +- toDoList.md | 58 +- 14 files changed, 206 insertions(+), 1638 deletions(-) delete mode 100644 src/main/java/org/baxter/disco/ocr/GuiController.java delete mode 100644 src/main/java/org/baxter/disco/ocr/GuiModel.java delete mode 100644 src/main/java/org/baxter/disco/ocr/GuiStarter.java delete mode 100644 src/main/java/org/baxter/disco/ocr/GuiView.java diff --git a/README.md b/README.md index 40e08cd..889f580 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This is a personal/professional project, which makes use of JavaCV, OpenCV, Tess - [x] Tesseract OCR processing - [x] Data storage in defined XLSX file - [x] modify number of iterations for test suite -- [ ] JavaFX GUI (Designed, partially implemented) +- [ ] JavaFX GUI (Designed, partially implemented, ommited for smaller build size) ## Dependencies @@ -88,8 +88,9 @@ To use this program, it *must* be run on a Raspberry Pi, with available GPIO. sudo java -jar [name of JAR file, including extension] ``` 4. What will happen next depends on your current version: - - Versions `4.0.0-rc1` and `4.0.0-rc2` will create a terminal window. From there, use the numbers shown in the menu to control the fixture, and run tests as necessary. - - Versions `4.0.0-rc3` and newer will create a terminal window, which load things for a moment before also creating a GUI. This GUI can be used to control the fixture, and run tests as necessary. + - Versions `4.0.0-rc1`,`4.0.0-rc2`, and `4.0.0` will create a terminal window. From there, use the numbers shown in the menu to control the fixture, and run tests as necessary. + - An upcoming version will create a terminal window, which load things for a moment before also creating a GUI. This GUI can be used to control the fixture, and run tests as necessary. + - GUI development is currently limited to the `gui` branch. ### Potential Errors @@ -107,9 +108,9 @@ sudo killall pigpiod ## Building from source -Before building this project, decide whether you want a TUI (Terminal User Interface), or a GUI (Graphical User Interface). As of `4.0.0-rc3`, the GUI has only been partially implemented, so should only be compiled for improvement purposes. -- If you wish to build the TUI, change the `uitype` field in your `pom.xml` to be `Cli`. -- If you wish to build the GUI, change the `uitype` field in your `pom.xml` to be `GuiStarter`. +Before building this project, decide whether you want a TUI (Terminal User Interface), or a GUI (Graphical User Interface). GUI development has been moved to its own separate branch, for ease of project management. +- If you wish to build the TUI, ensure the `uitype` field in your `pom.xml` is `Cli`. +- If you wish to build the GUI, ensure you are in the `gui` branch. For your first time compiling the project, clone the repository onto your computer, then run the following in a terminal with Maven to compile the project into a runnable JAR file: diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 45f1162..22b38d0 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -101,13 +101,12 @@ 2.8.0 11 2.1.0 + Cli raspberry - GuiStarter 11 1.5.6 5.2.3 true - 4.1.1 UTF-8 19 5.9.1 diff --git a/pom.xml b/pom.xml index aac3210..77289ad 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ + Cli UTF-8 11 11 @@ -29,9 +30,7 @@ 2.8.0 1.5.6 5.9.1 - 4.1.1 1.9.4 - GuiStarter @@ -88,11 +87,11 @@ - + diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 80e8d13..856007c 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -3,15 +3,15 @@ module org.baxter.disco.ocr { requires com.pi4j.plugin.raspberrypi; requires com.pi4j.plugin.pigpio; requires com.pi4j.library.pigpio; - requires javafx.fxml; - requires javafx.controls; + //requires javafx.fxml; + //requires javafx.controls; requires org.apache.poi.poi; requires org.apache.commons.configuration2; requires org.apache.xmlbeans; requires org.bytedeco.tesseract; requires org.bytedeco.opencv; requires org.bytedeco.javacpp; - requires javafx.graphics; + //requires javafx.graphics; requires org.apache.poi.ooxml; requires org.apache.poi.ooxml.schemas; requires org.apache.commons.io; diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index 30ad3b4..c32b463 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -49,7 +49,7 @@ public class Cli /** * Number of options currently available in the main menu. */ - private static final int mainMenuOptionCount = 7; + private static final int mainMenuOptionCount = 8; /** * Number of options currently available in the movement sub-menu. @@ -59,7 +59,7 @@ public class Cli /** * Number of options currently available in the camera configuration sub-menu. */ - private static final int cameraMenuOptionCount = 9; + private static final int cameraMenuOptionCount = 10; /** * Lock object, used for temporary interruption of {@link #runTests()} @@ -71,6 +71,8 @@ public class Cli */ private static MovementFacade fixture; + //private static Thread safeThread; + static { ErrorLogging.logError("DEBUG: START OF PROGRAM"); @@ -108,12 +110,15 @@ public class Cli break; case 3: setDUTSerials(); - serialsSet = true; + //serialsSet = true; break; case 4: setIterationCount(); break; case 5: + setActiveCameras(); + break; + case 6: if(!camerasConfigured) { prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): "); @@ -131,8 +136,18 @@ public class Cli ErrorLogging.logError("WARNING! - Potential for error: Un-initialised cameras."); } } - if(!serialsSet) + for(String cameraName : OpenCVFacade.getCameraNames()) { + if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0 && + ConfigFacade.getSerial(cameraName) == null ) + { + serialsSet = false; + break; + } + else serialsSet = true; + } + if(!serialsSet) + { prompt("You have not set the serial numbers for your DUTs yet! Are you sure you would like to continue? (y/N): "); String input = inputScanner.nextLine().toLowerCase(); if( input.isBlank()) @@ -150,10 +165,10 @@ public class Cli } runTests(); break; - case 6: + case 7: printHelp(); break; - case 7: + case 8: break; default: //Input handling already done by inputFiltering() @@ -228,13 +243,16 @@ public class Cli "\n\trun the tests of the device(s)"+ "\n\tunder test."); println("----------------------------------------"); - println("5. Run tests: Run tests, with defined"+ + println("5. Toggle active cameras: Change which cameras" + + "\n\twill be used during Run Tests."); + println("----------------------------------------"); + println("6. Run tests: Run tests, with defined"+ "\n\tnumber of iterations. Uses"+ "\n\tvalues defined in config file."); println("----------------------------------------"); - println("6. Help: Show this help page."); + println("7. Help: Show this help page."); println("----------------------------------------"); - println("7. Exit: Close the program."); + println("8. Exit: Close the program."); println("========================================"); println("Press Enter to continue..."); inputScanner.nextLine(); @@ -255,9 +273,10 @@ public class Cli println("2. Configure camera"); println("3. Set serial numbers"); println("4. Change test iteration count"); - println("5. Run tests"); - println("6. Help"); - println("7. Exit"); + println("5. Toggle active cameras"); + println("6. Run tests"); + println("7. Help"); + println("8. Exit"); println("======================================"); } @@ -319,6 +338,25 @@ public class Cli println("------------------------------------"); } + /** + * Pre-defined method for printing all available cameras and the associated serials in a menu + */ + private static void printActiveToggleMenu(List cameraList) + { + println("Available cameras to toggle:"); + println("------------------------------------"); + for(int index = 0; index < cameraList.size(); index++) + { + int humanIndex = index+1; + String cameraName = (String)cameraList.get(index); + print(humanIndex + " - " + cameraName + " : "); + String activity = (ConfigFacade.getValue(cameraName, ConfigProperties.ACTIVE) != 0 ? "active" : "disabled"); + println(activity); + } + println( (cameraList.size() + 1) + " - Exit to Main Menu"); + println("------------------------------------"); + } + /** * Pre-defined menu for printing camera configuration options */ @@ -341,10 +379,14 @@ public class Cli println("************************************"); println("Current composite frame count: " + ConfigFacade.getValue(cameraName,ConfigProperties.COMPOSITE_FRAMES)); + println("Current threshold value: " + + ConfigFacade.getValue(cameraName,ConfigProperties.THRESHOLD_VALUE)); String cropValue = ((ConfigFacade.getValue(cameraName,ConfigProperties.CROP) != 0) ? "yes" : "no"); println("Will the image be cropped? " + cropValue); String thresholdImage = ((ConfigFacade.getValue(cameraName,ConfigProperties.THRESHOLD) != 0) ? "yes" : "no"); println("Will the image be thresholded? " + thresholdImage); + String cameraActive = ((ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0) ? "yes" : "no"); + println("Will the camera be used when running tests? " + cameraActive); println("------------------------------------"); println("1. Change Crop X"); println("2. Change Crop Y"); @@ -354,7 +396,8 @@ public class Cli println("6. Change Threshold Value"); println("7. Toggle crop"); println("8. Toggle threshold"); - println("9. Exit"); + println("9. Toggle camera"); + println("10. Exit"); println("===================================="); } @@ -459,12 +502,11 @@ public class Cli prompt("Enter a camera number to configure: "); userInput = inputFiltering(inputScanner.nextLine()); userInput--; - } while (cameraList.size() < userInput); + } while (cameraList.size() < userInput && userInput < 0); //Leave do-while loop if the user asks to if(userInput == (cameraList.size())) break; - else if(userInput < 0) - {} + else if(userInput < 0) continue; else cameraName = cameraList.get((userInput)); do @@ -510,13 +552,19 @@ public class Cli modifiedProperty = ConfigProperties.THRESHOLD; break; case 9: + modifiedProperty = ConfigProperties.ACTIVE; + break; + case 0: + case 10: modifiedProperty = ConfigProperties.PRIME; break; default: } } while(modifiedProperty == null); - if(modifiedProperty == ConfigProperties.THRESHOLD || modifiedProperty == ConfigProperties.CROP) + if(modifiedProperty == ConfigProperties.THRESHOLD || + modifiedProperty == ConfigProperties.CROP || + modifiedProperty == ConfigProperties.ACTIVE) { double newValue = ConfigFacade.getValue(cameraName,modifiedProperty); newValue = Math.abs(newValue - 1); @@ -585,73 +633,110 @@ public class Cli iterationCount = input; } + /** + * Function to modify the currently active cameras + */ + private static void setActiveCameras() + { + List cameraList = new ArrayList<>(OpenCVFacade.getCameraNames()); + do + { + //Main menu + printActiveToggleMenu(cameraList); + + //Pick a camera to configure + int userInput; + + String cameraName = ""; + do + { + prompt("Enter the camera you wish to toggle: "); + userInput = inputFiltering(inputScanner.nextLine()); + userInput--; + } while (cameraList.size() < userInput || userInput < 0); + + //Leave do-while loop if the user asks to + if(userInput == (cameraList.size())) break; + else cameraName = cameraList.get((userInput)); + + double newValue = ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE); + newValue = Math.abs(newValue - 1); + ConfigFacade.setValue(cameraName,ConfigProperties.ACTIVE,newValue); + + } while(true); + } + /** * Starts running tests */ private static void runTests() { + ErrorLogging.logError("Initialising tests..."); final int localIterations = iterationCount; //testingThread = new Thread(() -> //{ - DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation(),OpenCVFacade.getCameraNames().size()); - boolean prime = false; - List cameraList = new ArrayList<>(); - for(String cameraName : OpenCVFacade.getCameraNames()) + boolean prime = false; + List cameraList = new ArrayList<>(); + for(String cameraName : OpenCVFacade.getCameraNames()) + { + //if(cameraName != null) { /*println(cameraName);*/ } + //else ErrorLogging.logError("Null camera!"); + if(ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0) + { + prime = true; + } + if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0) { - //if(cameraName != null) { /*println(cameraName);*/ } - //else ErrorLogging.logError("Null camera!"); - if(ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0) - { - prime = true; - } cameraList.add(cameraName); } - ErrorLogging.logError("DEBUG: Waking devices."); + } + DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation(),cameraList.size()); + ErrorLogging.logError("DEBUG: Waking devices..."); + fixture.iterationMovement(prime); + fixture.pressButton(); + fixture.iterationMovement(prime); + ErrorLogging.logError("DEBUG: Starting tests..."); + Map resultMap = new HashMap<>(); + Map cameraToFile = new HashMap<>(); + for(String cameraName : cameraList) + { + cameraToFile.put(cameraName,new File("/dev/null")); + } + for(int i = 0; i < localIterations; i++) + { + while(!LOCK.tryLock()) {} fixture.iterationMovement(prime); - fixture.pressButton(); - fixture.iterationMovement(prime); - ErrorLogging.logError("DEBUG: Starting tests..."); - Map resultMap = new HashMap<>(); - Map cameraToFile = new HashMap<>(); + LOCK.unlock(); for(String cameraName : cameraList) { - cameraToFile.put(cameraName,new File("/dev/null")); + while(!LOCK.tryLock()) {} + File file = OpenCVFacade.completeProcess(cameraName); + LOCK.unlock(); + while(!LOCK.tryLock()) {} + cameraToFile.replace(cameraName,file); + LOCK.unlock(); } - for(int i = 0; i < localIterations; i++) + for(String cameraName : cameraList) { while(!LOCK.tryLock()) {} - fixture.iterationMovement(prime); + File file = cameraToFile.get(cameraName); LOCK.unlock(); - for(String cameraName : cameraList) - { - while(!LOCK.tryLock()) {} - File file = OpenCVFacade.completeProcess(cameraName); - LOCK.unlock(); - while(!LOCK.tryLock()) {} - cameraToFile.replace(cameraName,file); - LOCK.unlock(); - } - for(String cameraName : cameraList) - { - while(!LOCK.tryLock()) {} - File file = cameraToFile.get(cameraName); - LOCK.unlock(); - while(!LOCK.tryLock()) {} - //ErrorLogging.logError("DEBUG: File passed to Tesseract: " + file.getAbsolutePath()); - Double result = TesseractFacade.imageToDouble(file); - LOCK.unlock(); - while(!LOCK.tryLock()) {} - resultMap.put(file,result); - ErrorLogging.logError("DEBUG: Tesseract final output: " + result); - LOCK.unlock(); - } while(!LOCK.tryLock()) {} - DataSaving.writeValues(i,resultMap,cameraToFile); + //ErrorLogging.logError("DEBUG: File passed to Tesseract: " + file.getAbsolutePath()); + Double result = TesseractFacade.imageToDouble(file); + LOCK.unlock(); + while(!LOCK.tryLock()) {} + resultMap.put(file,result); + ErrorLogging.logError("DEBUG: Tesseract final output: " + result); LOCK.unlock(); - resultMap.clear(); } - println("======================================="); - println("Testing complete!"); + while(!LOCK.tryLock()) {} + DataSaving.writeValues(i,resultMap,cameraToFile); + LOCK.unlock(); + resultMap.clear(); + } + println("======================================="); + println("Testing complete!"); //}); //testingThread.start(); } diff --git a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java index c037d3d..9659353 100644 --- a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java +++ b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java @@ -86,12 +86,24 @@ public class ConfigFacade .configure(new Parameters().fileBased().setFile(configFile)); ErrorLogging.logError("Attempting to import config..."); - try - { - CONFIG_STORE = CONFIG_BUILDER.getConfiguration(); + if(newConfig) + { + try + { + CONFIG_STORE = CONFIG_BUILDER.getConfiguration(); + } + catch(Exception e) { ErrorLogging.logError(e); } + finally + { + if(CONFIG_STORE == null) + ErrorLogging.logError("CONFIG INIT ERROR!!! - Unsuccessful config initialisation. Camera commands will fail!"); + } + } + else + { + ErrorLogging.logError("Unable to import config. Loading defaults..."); + saveDefaultConfig(); } - catch(Exception e) { ErrorLogging.logError(e); } - finally { if(CONFIG_STORE == null) ErrorLogging.logError("CONFIG INIT ERROR!!! - Unsuccessful config initialisation. Camera commands will fail!"); } ErrorLogging.logError("Creating image storage directories..."); File imageLocation = new File(imageSaveLocation); @@ -138,6 +150,7 @@ public class ConfigFacade if(!configMap.keySet().contains(cameraName)) { ErrorLogging.logError("CONFIG ERROR!!! - Invalid camera name: " + cameraName); + ErrorLogging.logError("\tKey set: " + configMap.keySet().toString()); ErrorLogging.logError("\tProperty: " + property.getConfig()); ErrorLogging.logError("\tconfigMap keys: " + configMap.keySet().toString()); return output; diff --git a/src/main/java/org/baxter/disco/ocr/ConfigProperties.java b/src/main/java/org/baxter/disco/ocr/ConfigProperties.java index baf4f52..a92f0c0 100644 --- a/src/main/java/org/baxter/disco/ocr/ConfigProperties.java +++ b/src/main/java/org/baxter/disco/ocr/ConfigProperties.java @@ -40,10 +40,16 @@ public enum ConfigProperties * Whether or not to press the button on the device twice, when under test. */ PRIME("Prime device?","prime",0.0), + /** * Where the threshold point should land. */ - THRESHOLD_VALUE("Threshold value","thresholdValue",50.0); + THRESHOLD_VALUE("Threshold value","thresholdValue",45.0), + + /** + * Whether the camera should be active. + */ + ACTIVE("Camera active?","active",1.0); /** * Internal storage of human-readable name/meaning diff --git a/src/main/java/org/baxter/disco/ocr/GuiController.java b/src/main/java/org/baxter/disco/ocr/GuiController.java deleted file mode 100644 index 27acefe..0000000 --- a/src/main/java/org/baxter/disco/ocr/GuiController.java +++ /dev/null @@ -1,261 +0,0 @@ -package org.baxter.disco.ocr; - -import java.util.List; - -import javafx.scene.control.TextField; -import javafx.scene.control.Tooltip; -import javafx.scene.image.Image; - -/** - * Controller portion of MVC for Accuracy over Life test fixture. - * Mostly wrapper interface between View and Model. - * - * {@link GuiView}, GuiController, and {@link GuiModel} versions are tied together, and are referred to collectively as Gui. - * - * @author Blizzard Finnegan - * @version 0.2.0, 06 Feb, 2023 - */ -public class GuiController -{ - /** - * Wrapper function to get available cameras. - * - * @return List[String] of the names of cameras - */ - public static List getCameras() - { return GuiModel.getCameras(); } - - /** - * Wrapper function used to show an image in a separate window - * - * @param cameraName The camera whose image should be shown. - */ - public static String showImage(String cameraName) - { return GuiModel.showImage(cameraName); } - - /** - * Wrapper function to toggle cropping for a given camera - * - * @param cameraName The camera whose image should be shown. - */ - public static void toggleCrop(String cameraName) - { GuiModel.toggleCrop(cameraName); } - - /** - * Wrapper function to toggle threshold for a given camera - * - * @param cameraName The camera whose image should be shown. - */ - public static void toggleThreshold(String cameraName) - { GuiModel.toggleThreshold(cameraName); } - - /** - * Wrapper function to save the default config - */ - public static void saveDefaults() - { GuiModel.saveDefaults(); } - - /** - * Wrapper function to save the current config - */ - public static void save() - { GuiModel.save(); } - - /** - * Wrapper function to save the current config, and re-enable image processing if necessary. - */ - public static void saveClose() - { GuiModel.save(); GuiModel.enableProcessing(); } - - /** - * Wrapper function to get a config value, for a given camera - * - * @param cameraName The name of the camera being inspected - * @param property The config property to be returned - * - * @return String of the value of the current object - */ - public static String getConfigString(String cameraName, ConfigProperties property) - { return GuiModel.getConfigString(cameraName,property); } - - /** - * Wrapper function to get a config value, for a given camera - * - * @param cameraName The name of the camera being inspected - * @param property The config property to be returned - * - * @return String of the value of the current object - */ - public static double getConfigValue(String cameraName, ConfigProperties property) - { return GuiModel.getConfigValue(cameraName,property); } - - /** - * Wrapper function to set a config value for a given camera. - * - * @param cameraName The name of the camera being modified - * @param property The property to be modified - * @param value The new value to set the property to - */ - public static void setConfigValue(String cameraName, ConfigProperties property, double value) - { - GuiModel.setConfigVal(cameraName,property,value); - if(property == ConfigProperties.CROP_W) - GuiView.updateImageViewWidth(cameraName); - if(property == ConfigProperties.CROP_H) - GuiView.updateImageViewHeight(cameraName); - } - - /** - * Setter for the number of iterations - * - * @param iterationCount The new iteration count to be saved - */ - public static void setIterationCount(int iterationCount) - { GuiModel.setIterations(iterationCount); } - - /** - * Wrapper function to interrupt testing. - */ - public static void interruptTests() - { GuiModel.interruptTesting(); } - - /** - * Wrapper function to run tests. - */ - public static void runTests() - { GuiModel.runTests(); } - - /** - * Wrapper function to test the movement of the fixture. - */ - public static void testMotions() - { - testingMotions(); - GuiModel.testMovement(); - } - - /** - * If the Model is ready, set the view to allow the Start button to be pressed. - */ - public static void updateStart() - { - boolean ready = GuiModel.isReady(); - GuiView.getStart().setDisable(ready); - if(ready) GuiView.getStart().setTooltip(new Tooltip("Start running automated testing.")); - } - - /** - * Wrapper function to write to the user feedback Label, stating that the test is starting. - */ - public static void startTests() - { userUpdate("Starting tests..."); } - - /** - * Updates the View's state wit hthe current iteration count - */ - public static void updateIterations() - { - String newIterations = Integer.toString(GuiModel.getIterations()); - GuiView.getIterationField().setText(newIterations); - } - - /** - * Update a given config value, given a camera - * - * @param cameraName The name of the camera being updated - * @param property The property being updated - */ - public static void updateConfigValue(String cameraName, ConfigProperties property) - { - TextField field = GuiView.getField(cameraName,property); - field.setText(GuiModel.getConfigString(cameraName,property)); - field.setPromptText(GuiModel.getConfigString(cameraName,property)); - } - - /** - * Wrapper function to write the current iteration ot the user feedback Label. - * - * @param index The current iteration number - */ - public static void runningUpdate(int index) - { userUpdate("Running iteration " + index + "..."); } - - /** - * Wrapper function used to set a custom message to the user. - * - * @param output What should be sent to the user. - */ - public static void userUpdate(String output) - { GuiView.getFeedbackText().setText(output); } - - /** - * Wrapper function to tell the user that fixture movement testing has begun. - */ - public static void testingMotions() - { userUpdate("Testing fixture movement..."); } - - /** - * Wrapper function to tell the user that fixture movement testing was successful. - */ - public static void testingMotionSuccessful() - { userUpdate("Fixture movement test successful!"); } - - /** - * Wrapper function to tell the user that fixture movement testing failed, and where it failed. - * - * @param failurePoint Where the movement failed. - */ - public static void testingMotionUnsuccessful(String failurePoint) - { userUpdate("Fixture movement unsuccessful! Fail point: " + failurePoint);} - - /** - * Wrapper function for the Model's pressButton function. - */ - public static void pressButton() - { GuiModel.pressButton(); } - - /** - * Wrapper function used to update whether or not the DUTs should be primed before testing. - */ - public static void updatePrime() - { GuiModel.updatePrime(); } - - /** - * Wrapper function to set the Serial for a given camera. - * - * @param cameraName The name of the camera to modify - * @param serial The serial of the DUT under the given camera - */ - public static void setSerial(String cameraName, String serial) - { GuiModel.setSerial(cameraName,serial); } - - /** - * Getter for the current iteration count - * - * @return String of the current iteration count. - */ - public static String getIterationCount() - { return Integer.toString(GuiModel.getIterations()); } - - /** - * Wrapper function around the GuiModel's calibrateCameras function - */ - public static void calibrateCameras() - { GuiModel.calibrateCameras(); } - - /** - * Close function for the Model; used to end the program - */ - public static void closeModel() { GuiModel.close(); } - - /** - * Function used to update the ImageView of the GUID - * - * @param cameraName Name of the camera the image is from - * @param fileURL The URL of the file to be shown - */ - public static void updateImage(String cameraName, String fileURL) - { - GuiView.getViewMap().get(cameraName).setImage(new Image(fileURL)); - } -} diff --git a/src/main/java/org/baxter/disco/ocr/GuiModel.java b/src/main/java/org/baxter/disco/ocr/GuiModel.java deleted file mode 100644 index 83a5331..0000000 --- a/src/main/java/org/baxter/disco/ocr/GuiModel.java +++ /dev/null @@ -1,312 +0,0 @@ -package org.baxter.disco.ocr; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * Model portion of MVC for the Accuracy Over Life test fixture. - * Primarily a wrapper around other classes, but does store some information. - * - * {@link GuiView}, {@link GuiController}, and GuiModel versions are tied together, and are referred to collectively as Gui. - * - * @author Blizzard Finnegan - * @version 0.2.0, 06 Feb, 2023 - */ -public class GuiModel -{ - /** - * Whether or not the backend is prepared to start running. - */ - private static boolean readyToRun = false; - - /** - * The number of iterations. - */ - private static int iterationCount = 3; - - /** - * The Lock object, used for multithreading of the testing function - */ - public static final Lock LOCK = new ReentrantLock(); - - /** - * The testing thread object - */ - private static Thread testingThread = new Thread(); - - /** - * The Movement Facade instance - */ - private static final MovementFacade fixture = new MovementFacade(LOCK); - - /** - * The function called to define the GUI as ready to start testing. - */ - public static void ready() { readyToRun = true; GuiController.updateStart(); } - - /** - * Getter for {@link #readyToRun} - * - * @return boolean of whether or not testing can be started - */ - public static boolean isReady() { return readyToRun; } - - /** - * Setter for the number of iterations - * - * @param iterations The number of times to run the tests - */ - public static void setIterations(int iterations) - { - iterationCount = iterations; - GuiController.userUpdate("Iterations set to: " + iterationCount); - GuiController.updateIterations(); - } - - /** - * Getter for the number of iterations - * - * @return int of the number of iterations to be perfomed. - */ - public static int getIterations() { return iterationCount; } - - /** - * Wrapper around the MovementFacade's testMotions function. - * - * Updates the GUI with whether the testing was successful. - */ - public static void testMovement() - { - GuiController.testingMotions(); - boolean success = fixture.testMotions(); - if(success) GuiController.testingMotionSuccessful(); - else GuiController.testingMotionUnsuccessful("Unknown"); - } - - /** - * Getter for the list of cameras. - * - * @return List[String] of camera names. - */ - public static List getCameras() - { return new ArrayList<>(OpenCVFacade.getCameraNames()); } - - /** - * Function that calls the OpenCVFacade image generation function. - * @return String url of the location of the new image - */ - public static String showImage(String cameraName) - { - return OpenCVFacade.showImage(cameraName, new Object()); - } - - /** - * Setter for a given camera's config value - * - * @param cameraName Name of the camera to be configured - * @param property Property to be changed - * @param value New value for the given property - */ - public static void setConfigVal(String cameraName, ConfigProperties property, double value) - { - ConfigFacade.setValue(cameraName,property,value); - GuiController.updateConfigValue(cameraName,property); - } - - /** - * Getter for a given camera's config value, in String format - * - * @param cameraName Name of the camera to get the config value from - * @param property Property to get the value of - * - * @return String of the current value in the config - */ - public static String getConfigString(String cameraName, ConfigProperties property) - { return Double.toString(ConfigFacade.getValue(cameraName,property)); } - - /** - * Getter for a given camera's config value - * - * @param cameraName Name of the camera to get the config value from - * @param property Property to get the value of - * - * @return double of the current value in the config - */ - public static double getConfigValue(String cameraName, ConfigProperties property) - { return ConfigFacade.getValue(cameraName,property); } - - /** - * Wrapper function around the MovementFacade's pressButton function. - */ - public static void pressButton() - { fixture.pressButton(); } - - /** - * Function used to update whether or not cameras should be primed. - */ - public static void updatePrime() - { - for(String cameraName : OpenCVFacade.getCameraNames()) - { - boolean old = (ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) == 0.0 ); - ConfigFacade.setValue(cameraName,ConfigProperties.PRIME,(old ? 1 : 0)); - } - } - - /** - * Wrapper function to enable all image processing. - */ - public static void enableProcessing() - { - for(String camera : getCameras()) - { - ConfigFacade.setValue(camera,ConfigProperties.CROP, 1.0); - ConfigFacade.setValue(camera,ConfigProperties.THRESHOLD, 1.0); - } - } - - /** - * Wrapper function to save the default config values. - */ - public static void saveDefaults() - { ConfigFacade.saveDefaultConfig(); } - - /** - * Save the current config, and ensure it is loaded properly. - */ - public static void save() - { ConfigFacade.saveCurrentConfig(); ConfigFacade.loadConfig(); } - - /** - * Toggles the threshold processing for the given camera. - * - * @param cameraName The name of the camera to be modified - */ - public static void toggleThreshold(String cameraName) - { - boolean old = (ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) == 0.0 ); - ConfigFacade.setValue(cameraName,ConfigProperties.THRESHOLD,(old ? 1 : 0)); - } - - /** - * Toggles the cropping of the image for the given camera. - * - * @param cameraName The name of the camera to be modified - */ - public static void toggleCrop(String cameraName) - { - boolean old = (ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) == 0.0 ); - ConfigFacade.setValue(cameraName,ConfigProperties.CROP,(old ? 1 : 0)); - } - - /** - * Function used to run all tests. - * - * Currently not working. Will need to rewrite. - */ - public static void runTests() - { - //testingThread = new Thread(() -> - //{ - GuiController.startTests(); - DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation(),OpenCVFacade.getCameraNames().size()); - boolean prime = false; - List cameraList = new ArrayList<>(); - for(String cameraName : OpenCVFacade.getCameraNames()) - { - if(ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0) - { - prime = true; - } - cameraList.add(cameraName); - } - fixture.iterationMovement(prime); - fixture.pressButton(); - fixture.iterationMovement(prime); - Map resultMap = new HashMap<>(); - Map cameraToFile = new HashMap<>(); - for(String cameraName : cameraList) - { - cameraToFile.put(cameraName,new File("/dev/null")); - } - for(int i = 0; i < iterationCount; i++) - { - while(!LOCK.tryLock()) {} - fixture.iterationMovement(prime); - LOCK.unlock(); - for(String cameraName : cameraList) - { - while(!LOCK.tryLock()) {} - File file = OpenCVFacade.completeProcess(cameraName); - GuiController.updateImage(cameraName,file.getPath()); - LOCK.unlock(); - while(!LOCK.tryLock()) {} - cameraToFile.replace(cameraName,file); - LOCK.unlock(); - } - LOCK.unlock(); - for(String cameraName : cameraList) - { - while(!LOCK.tryLock()) {} - File file = cameraToFile.get(cameraName); - LOCK.unlock(); - while(!LOCK.tryLock()) {} - Double result = TesseractFacade.imageToDouble(file); - LOCK.unlock(); - while(!LOCK.tryLock()) {} - resultMap.put(file,result); - LOCK.unlock(); - while(!LOCK.tryLock()) {} - ErrorLogging.logError("DEBUG: Tesseract final output: " + result); - LOCK.unlock(); - } - while(!LOCK.tryLock()) {} - DataSaving.writeValues(i,resultMap,cameraToFile); - LOCK.unlock(); - GuiController.runningUpdate(i); - } - //println("======================================="); - ErrorLogging.logError("Testing complete!"); - //}); - //testingThread.run(); - } - - /** - * Wrapper function to close everything. - */ - public static void close() - { - ErrorLogging.logError("DEBUG: PROGRAM CLOSING."); - fixture.closeGPIO(); - ErrorLogging.logError("DEBUG: END OF PROGRAM."); - ErrorLogging.closeLogs(); - } - - /** - * Function used to interrupt the testing thread. - * - * As of Gui 0.2.0, this does not work properly. - */ - public static void interruptTesting() { testingThread.interrupt(); } - - /** - * Function to set the serial number for a given camera - * - * @param cameraName name of the camera to be modified - * @param serial serial number to be set - */ - public static void setSerial(String cameraName, String serial) - { ConfigFacade.setSerial(cameraName,serial); } - - /** - * Function to force fixture down before starting to calibrate the cameras. - */ - public static void calibrateCameras() - { fixture.goDown(); } - -} diff --git a/src/main/java/org/baxter/disco/ocr/GuiStarter.java b/src/main/java/org/baxter/disco/ocr/GuiStarter.java deleted file mode 100644 index 2309f9e..0000000 --- a/src/main/java/org/baxter/disco/ocr/GuiStarter.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.baxter.disco.ocr; - -/** - * Wrapper class around GuiView. - * - * Maven will not build the {@link GuiView} properly, since it inherits from {@link javafx.application.Application}. - * This will start the Gui's main function, with no other functionality. - * - * @author Blizzard Finnegan - * @version 1.0.1, 01 Feb. 2023 - */ -public class GuiStarter -{ - public static void main(String[] args) - { GuiView.main(args); } -} diff --git a/src/main/java/org/baxter/disco/ocr/GuiView.java b/src/main/java/org/baxter/disco/ocr/GuiView.java deleted file mode 100644 index 601510c..0000000 --- a/src/main/java/org/baxter/disco/ocr/GuiView.java +++ /dev/null @@ -1,916 +0,0 @@ -package org.baxter.disco.ocr; - -import java.util.HashMap; -import java.util.Map; -import java.util.Scanner; - -import javafx.application.Application; -import javafx.geometry.*; -import javafx.scene.*; -import javafx.scene.control.*; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.layout.*; -import javafx.scene.text.*; -import javafx.stage.Stage; - -/** - * View portion of MVC for the Accuracy Over Life test fixture. - * - * GuiView, {@link GuiController}, and {@link GuiModel} versions are tied together, and are referred to collectively as Gui. - * - * @author Blizzard Finnegan - * @version 0.2.0, 06 Feb, 2023 - */ -public class GuiView extends Application -{ - /** - * Scene used for the Main Menu. - */ - public static final Scene MAIN_MENU; - - /** - * The base Node object for the Main menu; used to define window borders. - */ - private static final AnchorPane MAIN_ANCHOR; - - /** - * The Node object within the {@link #MAIN_ANCHOR}, where all portions of the main menu are stored. - */ - private static final Pane MAIN_PANE; - - - /** - * Scene used for the camera configuration menu - */ - public static final Scene CAMERA_MENU; - - /** - * The base Node object for the camera config menu; used to define window borders. - */ - private static final AnchorPane CAMERA_ANCHOR; - - /** - * The node object within the {@link #CAMERA_ANCHOR}, where all portions of the camera config menu are stored. - */ - private static final Pane CAMERA_PANE; - - /** - * An easily-accessible map of text fields used in the Camera config menu. - * The outer map's keys are the respective cameras, with the inner map being config properties and the associated text field. - */ - private static final Map> uiFields = new HashMap<>(); - - /** - * An easily-accessible location for the user-feedback Text object. - */ - private static Text userFeedback; - - /** - * An easily accessible location for the TextField the user uses to set the current amount of iterations. - */ - private static TextField iterationField; - - /** - * Easily-accessible Button object for the start button. - */ - private static Button startButton; - - /* - * Easily-accessible Button object for the stop button. - */ - //private static Button stopButton; - - /** - * The main Stage object, used in the GUI. - * - * The Stage object is analogous to the window generated. - */ - private static Stage STAGE; - - /** - * Value used for spacing within VBoxes and HBoxes - */ - private static double INTERNAL_SPACING = 5.0; - - /** - * Value used for window border spacing - */ - private static double EXTERNAL_SPACING = 10.0; - - /** - * Map used to store ImageViews - */ - private static Map viewMap = new HashMap<>(); - - /** - * The wrapper function to spawn a new JavaFX Stage. - */ - public static void main(String[] args) { launch(args); } - - /** - * Initialiser for the static objects. - */ - static - { - ErrorLogging.logError("START OF PROGRAM"); - ErrorLogging.logError("Setting up main menu..."); - MAIN_ANCHOR = new AnchorPane(); - MAIN_ANCHOR.setMinWidth(Double.NEGATIVE_INFINITY); - MAIN_ANCHOR.setMinHeight(Double.NEGATIVE_INFINITY); - MAIN_PANE = new Pane(); - - //Set the window border - AnchorPane.setTopAnchor(MAIN_PANE,EXTERNAL_SPACING); - AnchorPane.setLeftAnchor(MAIN_PANE,EXTERNAL_SPACING); - AnchorPane.setRightAnchor(MAIN_PANE,EXTERNAL_SPACING); - AnchorPane.setBottomAnchor(MAIN_PANE,EXTERNAL_SPACING); - MAIN_ANCHOR.getChildren().add(MAIN_PANE); - MAIN_MENU = new Scene(MAIN_ANCHOR); - - ErrorLogging.logError("Setting up camera config menu..."); - CAMERA_ANCHOR = new AnchorPane(); - CAMERA_ANCHOR.setMinWidth(Double.NEGATIVE_INFINITY); - CAMERA_ANCHOR.setMinHeight(Double.NEGATIVE_INFINITY); - CAMERA_PANE = new Pane(); - - //Set the window border - AnchorPane.setTopAnchor(CAMERA_PANE,EXTERNAL_SPACING); - AnchorPane.setLeftAnchor(CAMERA_PANE,EXTERNAL_SPACING); - AnchorPane.setRightAnchor(CAMERA_PANE,EXTERNAL_SPACING); - AnchorPane.setBottomAnchor(CAMERA_PANE,EXTERNAL_SPACING); - CAMERA_ANCHOR.getChildren().add(CAMERA_PANE); - CAMERA_MENU = new Scene(CAMERA_ANCHOR); - - //Initialise the camera fields map and imageview map - for(String camera : GuiModel.getCameras()) - { - uiFields.put(camera, new HashMap<>()); - ImageView view = new ImageView(); - view.setId(camera + "-view"); - view.setFitWidth(GuiController.getConfigValue(camera,ConfigProperties.CROP_W)); - view.setFitHeight(GuiController.getConfigValue(camera,ConfigProperties.CROP_H)); - viewMap.put(camera, view); - } - - STAGE.setOnCloseRequest( (event) -> GuiController.closeModel() ); - } - - @Override - public void start(Stage stage) throws Exception - { - ErrorLogging.logError("Finalising GUI..."); - STAGE = stage; - mainMenuBuilder(); - cameraMenuBuilder(); - STAGE.setScene(MAIN_MENU); - STAGE.show(); - ErrorLogging.logError("Gui loading complete."); - } - - /** - * Camera Configuration Menu builder function. - * - * Creates a {@link VBox}, creates a {@link #cameraSetup(String)} object, and adds a separator between each camera. - * Finally, sets the created VBox to be the child of the {@link #CAMERA_PANE}, so it can be shown. - */ - private static void cameraMenuBuilder() - { - VBox layout = new VBox(); - layout.setSpacing(INTERNAL_SPACING); - layout.setAlignment(Pos.CENTER_LEFT); - - int index = 0; - for(String cameraName : GuiModel.getCameras()) - { - if(index != 0) layout.getChildren().add(new Separator(Orientation.HORIZONTAL)); - layout.getChildren().add(cameraSection(cameraName)); - index++; - } - layout.getChildren().add(cameraMenuButtons()); - CAMERA_PANE.getChildren().add(layout); - } - - /** - * Main Menu builder function. - * - * Creates a VBox, fills it with the {@link #topHalf()}, a {@link Separator}, and the {@link #bottomHalf()} - * Finally, sets the created VBox to be the child of the {@link #MAIN_PANE}, so it can be shown. - */ - private static void mainMenuBuilder() - { - VBox layout = new VBox(); - layout.getChildren().addAll(topHalf(), - new Separator(Orientation.HORIZONTAL), - bottomHalf()); - MAIN_PANE.getChildren().add(layout); - } - - - /** - * Builder for the top half of the main menu. - * - * Creates a VBox, fills it with the {@link #topButtons()}, a {@link Separator}, the {@link #setupSection()}, - * the {@link #primeCheckbox()}, and the {@link #testFeedback()}. - * - * @return VBox described above - */ - private static VBox topHalf() - { - VBox output = new VBox(); - output.setSpacing(INTERNAL_SPACING); - output.getChildren().addAll(topButtons(), - new Separator(Orientation.HORIZONTAL), - setupSection(), - primeCheckbox(), - testFeedback()); - return output; - } - - /** - * Builder for the priming section of the main menu. - * - * Builds a pre-defined checkbox for setting whether the DUTs should be primed. - * - * @return CheckBox with a preset Tooltip, Id, and Listener. - */ - private static CheckBox primeCheckbox() - { - CheckBox output = new CheckBox("Prime devices"); - output.setTooltip(new Tooltip("This presses the button on the device under test twice for every iteration.")); - output.setSelected(true); - output.setId("primeCheckbox"); - output.selectedProperty().addListener( - (obeservableValue, oldValue, newValue) -> - { - GuiController.updatePrime(); - }); - return output; - } - - /** - * Builder for the user feedback section of the main menu. - * - * Creates an HBox, fills it with a {@link Label} and a {@link Text} used for communicating - * program status. - * - * @return HBox defined above - */ - private static HBox testFeedback() - { - HBox output = new HBox(); - output.setSpacing(INTERNAL_SPACING); - Label textboxLabel = new Label("Test feedback: "); - Text textbox = new Text("Awaiting input..."); - userFeedback = textbox; - textbox.setId("testOutputToUser"); - - output.getChildren().addAll(textboxLabel,textbox); - return output; - } - - /** - * Builder function for the iteration count user input. - * - * Creates an HBox, filled with a Label and a TextField for user input. - * This TextField is used for setting the number of iterations to complete. - * - * @return HBox defined above - */ - private static HBox setupSection() - { - HBox output = userTextField("Cycles:",GuiController.getIterationCount(), "Enter the number of times to test the devices in the fixture."); - TextField field = null; - for(Node child : output.getChildren()) - { - if(child instanceof TextField) - { - field = (TextField)child; - break; - } - } - if(field == null) - { - ErrorLogging.logError("GUI INIT ERROR!!! - Failed text field setup."); - GuiController.closeModel(); - } - - iterationField = field; - //TextField textField = (TextField)(output.lookup("#cycles")); - field.textProperty().addListener( - (observable, oldValue, newValue) -> - { - try(Scanner sc = new Scanner(newValue);) - { GuiController.setIterationCount(sc.nextInt()); } - catch(Exception e) - { - ErrorLogging.logError("USER INPUT ERROR: Illegal input in cycles count."); - newValue = oldValue; - } - }); - - return output; - } - - /** - * Builder function for the top buttons of the Main Menu. - * - * Creates an HBox for the top buttons, then fills it with a - * - start Button - * - Runs the tests. As of Gui 0.2.0, this only partially runs the first portion of the test. - * - stop Button - * - Intended to stop the test. As of Gui 0.2.0, this has not yet been properly implemented. - * - calibrate cameras Button - * - Changes Scene to {@link #CAMERA_MENU}, allowing for camera setup. - * - test movement Button - * - Tests the movement of the fixture, informs the user of the test's success/failure - * - close Button - * - Closes the window, and the program. Note that as of Gui 0.2.0, this errors out the JVM - * - * @return HBox containing the above-listed buttons. - */ - private static HBox topButtons() - { - //Initial HBox creation - HBox topButtons = new HBox(); - topButtons.setSpacing(INTERNAL_SPACING); - topButtons.setAlignment(Pos.CENTER); - topButtons.setMinWidth(Region.USE_COMPUTED_SIZE); - topButtons.setMinHeight(Region.USE_COMPUTED_SIZE); - - //Start button creation - final Button START = buttonBuilder("Start",true); - startButton = START; - - //Stop button created early, as it is affected by Start, and must be passed in - final Button STOP = buttonBuilder("Stop",true); - - //Start button action and tooltip setting. - START.setOnAction( (event) -> - { - START.setDisable(true); - STOP.setDisable(false); - GuiController.runTests(); - }); - START.setTooltip(new Tooltip("Configure cameras to start the program.")); - - //Stop button action and tooltip setting. - STOP.setOnAction( (event) -> - { - GuiController.interruptTests(); - START.setDisable(false); - STOP.setDisable(true); - }); - STOP.setTooltip(new Tooltip("Pauses current iteration.")); - - //Calibrate Cameras button creation - Button calibrateCamera = buttonBuilder("Calibrate Cameras",false); - calibrateCamera.setOnAction( - (event) -> - { - GuiController.calibrateCameras(); - STAGE.setScene(CAMERA_MENU); - }); - - - //Test Movement button creation - Button testMovement = buttonBuilder("Test Movement",false); - testMovement.setOnAction( (event) -> GuiController.testMotions() ); - - //Close button creation - Button cancel = buttonBuilder("Close",false); - cancel.setOnAction( (event) -> - { - GuiController.closeModel(); - STAGE.close(); - }); - - - //Put the above buttons into the HBox - topButtons.getChildren().addAll(START, - STOP, - calibrateCamera, - testMovement, - cancel); - return topButtons; - } - - /** - * Builder function for the bottom half of the main menu. - * - * Creates an HBox, with however many cameras exist, and their associated {@link #camera(String)} views. - * - * @return Hbox described above - */ - private static HBox bottomHalf() - { - HBox output = new HBox(); - output.setAlignment(Pos.CENTER); - output.setSpacing(INTERNAL_SPACING); - - int index = 0; - for(String camera : GuiModel.getCameras()) - { - if(index != 0) output.getChildren().add(new Separator(Orientation.VERTICAL)); - output.getChildren().add(camera(camera)); - index++; - } - return output; - } - - /** - * Builder for a camera view for the main menu. - * - * Creates a VBox, containing: - * - {@link #cameraHeader(String)} - * - HBox with a Label and TextField for the user to set a DUT's serial number. - * - {@link #cameraView(String)} - * - * @param cameraName The name of the camera to be attached to. - * - * @return VBox described above - */ - private static VBox camera(String cameraName) - { - VBox output = new VBox(); - output.setAlignment(Pos.CENTER_LEFT); - output.setSpacing(INTERNAL_SPACING); - - HBox serialNumber = userTextField("DUT Serial Number:","","Enter the serial number for the device under test."); - - TextField field = null; - for(Node child : serialNumber.getChildren()) - { - if(child instanceof TextField) - { - field = (TextField)child; - break; - } - } - - field.setId("serial" + cameraName); - field.textProperty().addListener( - (observable, oldValue, newValue) -> GuiController.setSerial(cameraName, newValue)); - - output.getChildren().addAll(cameraHeader(cameraName), - serialNumber, - cameraView(cameraName)); - return output; - } - - /** - * Builder for the camera header, for the main menu. - * - * Creates an HBox, containing a Label with the camera's name, and a checkbox to mark whether it is active. - * As of Gui 0.2.0, the checkbox does not work properly. - * - * @param cameraName The name of the camera being accessed. - * - * @return HBox described above. - */ - private static HBox cameraHeader(String cameraName) - { - HBox output = new HBox(); - output.setSpacing(INTERNAL_SPACING); - output.setAlignment(Pos.CENTER); - Label label = new Label("Camera: " + cameraName); - CheckBox checkBox = new CheckBox("Active"); - checkBox.setSelected(true); - checkBox.setOnAction( (event) -> {/*implement*/}); - checkBox.setId(cameraName.toLowerCase()); - output.getChildren().addAll(label, - checkBox); - return output; - } - - /** - * Builder for the camera view, used in the main menu. - * - * Creates an HBox, containing: - * - A Label for defining what the following label means (OCR Read:) - * - A Label for showing what the OCR reading is. - * - As of Gui 0.2.0, this has not been implemented - * - An ImageView object for showing the final image - * - As of Gui 0.2.0, this has not been implemented - * @param cameraName Name of the camera being accessed - * - * @return HBox described above. - */ - private static HBox cameraView(String cameraName) - { - HBox output = new HBox(); - output.setSpacing(INTERNAL_SPACING); - output.setAlignment(Pos.CENTER_LEFT); - - Label label = new Label("OCR Read:"); - Label ocrRead = new Label("[ ]"); - ocrRead.setId("cameraOCR-" + cameraName); - ImageView imageView = viewMap.get(cameraName); - //imageView.setImage(new Image(GuiController.showImage(cameraName))); - output.getChildren().addAll(label, - ocrRead, - imageView); - return output; - } - - /** - * Builder function for the user-editable section in the camera config menu. - * - * Creates a VBox, containing: - * - A Label (used for a section header) - * - A series of CheckBoxes used to define whether to crop and/or threshold the image - * - An HBox of inputs, used to define cropping values. (Defined by {@link #cropInputs(String)}) - * - An HBox of inputs, used to define the threshold value, and how many images to compose together (Defined by {@link #miscInputs(String)}) - * - * @param cameraName The name of the camera being modified - * - * @return The VBox described above - */ - private static VBox cameraSetup(String cameraName) - { - VBox output = new VBox(); - output.setSpacing(INTERNAL_SPACING); - output.setAlignment(Pos.CENTER_LEFT); - - Label sectionHeader = new Label("Camera: " + cameraName); - output.getChildren().addAll(sectionHeader, - processingInputs(cameraName), - cropInputs(cameraName), - miscInputs(cameraName)); - return output; - } - - /** - * Builder function for a complete section in the camera config menu. - * - * Creates an HBox, containing: - * - A VBox, created by {@link #cameraSetup(String)} - * - An ImageView, which will be used to show the image to the user - */ - private static HBox cameraSection(String cameraName) - { - HBox output = new HBox(); - output.setSpacing(INTERNAL_SPACING); - output.setAlignment(Pos.CENTER_LEFT); - - output.getChildren().add(cameraSetup(cameraName)); - - ImageView imageView = viewMap.get(cameraName); - output.getChildren().add(imageView); - - return output; - } - - /** - * Builder for the processing section of the {@link #cameraSetup(String)}, used in the Camera Config section. - * - * Creates an HBox containing: - * - A Button for creating a temporary preview - * - A CheckBox to toggle the cropping of the image - * - A CheckBox to toggle the thresholding of the image - * - * @param cameraName The name of the camera being modified - * - * @return HBox, as described above - */ - private static HBox processingInputs(String cameraName) - { - HBox output = new HBox(); - output.setSpacing(INTERNAL_SPACING); - output.setAlignment(Pos.CENTER_LEFT); - - //Preview button generation - Button preview = buttonBuilder("Preview"); - preview.setId("previewButton-" + cameraName); - preview.setOnAction( (event) -> - { - GuiController.pressButton(); - try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); } - String imageURL = GuiController.showImage(cameraName); - viewMap.get(cameraName).setImage(new Image(imageURL)); - }); - - //Crop image toggle checkbox creation - CheckBox cropPreview = new CheckBox("Crop preview"); - cropPreview.setSelected(true); - cropPreview.setId("cropToggle-" + cameraName); - cropPreview.selectedProperty().addListener((obeservableValue, oldValue, newValue) -> - GuiController.toggleCrop(cameraName)); - cropPreview.setOnAction( (event) -> GuiController.toggleCrop(cameraName) ); - - //Threshold image toggle switch creation - CheckBox thresholdPreview = new CheckBox("Threshold preview"); - thresholdPreview.setSelected(true); - thresholdPreview.selectedProperty().addListener((obeservableValue, oldValue, newValue) -> - GuiController.toggleThreshold(cameraName)); - thresholdPreview.setId("thresholdToggle-" + cameraName); - thresholdPreview.setOnAction( (event) -> GuiController.toggleThreshold(cameraName) ); - - - output.getChildren().addAll(preview, - cropPreview, - thresholdPreview); - return output; - } - - /** - * Builder function for the crop values, stored within a {@link #cameraSetup(String)} in the Camera Config menu. - * - * Creates an HBox, containing: - * - A Label and TextField for each of the following: - * - Crop X - * - Crop Y - * - Crop Width - * - Crop Height - * - * @param cameraName The name of the camera being modified - * - * @return HBox, as defined above - */ - private static HBox cropInputs(String cameraName) - { - HBox output = new HBox(); - output.setSpacing(INTERNAL_SPACING); - output.setAlignment(Pos.CENTER_LEFT); - - HBox cropX = userTextField("X:", - GuiController.getConfigString(cameraName,ConfigProperties.CROP_X), - "X-value of the top left corner of the newly cropped image. Only accepts whole numbers."); - textFieldSetup(cropX,ConfigProperties.CROP_X,cameraName); - - HBox cropY = userTextField("Y:", - GuiController.getConfigString(cameraName,ConfigProperties.CROP_Y), - "Y-value of the top left corner of the newly cropped image. Only accepts whole numbers."); - textFieldSetup(cropY,ConfigProperties.CROP_Y,cameraName); - - HBox cropW = userTextField("Width:", - GuiController.getConfigString(cameraName,ConfigProperties.CROP_W), - "Width, in pixels, of the newly cropped image. Only accepts whole numbers."); - textFieldSetup(cropW,ConfigProperties.CROP_W,cameraName); - - HBox cropH = userTextField("Height:", - GuiController.getConfigString(cameraName,ConfigProperties.CROP_H), - "Height, in pixels, of the newly cropped image. Only accepts whole numbers."); - textFieldSetup(cropH, ConfigProperties.CROP_H, cameraName); - - output.getChildren().addAll(cropX, - cropY, - cropW, - cropH); - return output; - } - - /** - * Builder function for the other modifiable values for the {@link #cameraSetup(String)} portion of the camera config menu. - * - * Creates an HBox, containing a Label and TextField for: - * - threshold value - * - number of composite frames - * - * @param cameraName The name of the camera being configured - * - * @return HBox, defined above - */ - private static HBox miscInputs(String cameraName) - { - HBox output = new HBox(); - output.setSpacing(INTERNAL_SPACING); - output.setAlignment(Pos.CENTER_LEFT); - - HBox thresholdValue = userTextField("Threshold Value:", - GuiController.getConfigString(cameraName,ConfigProperties.THRESHOLD), - "This value can be set from 0 to 255. Higher values mean more black in "+ - "the thresholded image. For more information, see the documentation."); - textFieldSetup(thresholdValue,ConfigProperties.THRESHOLD_VALUE,cameraName); - - HBox compositeFrames = userTextField("Composite Frames:", - GuiController.getConfigString(cameraName,ConfigProperties.COMPOSITE_FRAMES), - "Number of frames to bitwise-and together."); - textFieldSetup(compositeFrames,ConfigProperties.COMPOSITE_FRAMES,cameraName); - - output.getChildren().addAll(thresholdValue, - compositeFrames); - return output; - } - - /** - * Builder function for the final buttons in the Camera Config menu. - * - * Creates an HBox, containing: - * - Save Defaults button - * - Save Current button - * - Save and Close button - * - Close without Saving button - * - * @return HBox, as described above - */ - private static HBox cameraMenuButtons() - { - HBox output = new HBox(); - output.setAlignment(Pos.CENTER); - output.setSpacing(10.0); - - Button defaults = buttonBuilder("Save Defaults"); - defaults.setOnAction( (event) -> - { - GuiController.saveDefaults(); - GuiController.updateStart(); - }); - - Button save = buttonBuilder("Save"); - save.setOnAction( (event) -> - { - GuiController.save(); - GuiController.updateStart(); - }); - - Button saveClose = buttonBuilder("Save and Close"); - saveClose.setOnAction( (event) -> - { - GuiController.saveClose(); - GuiController.updateStart(); - STAGE.setScene(MAIN_MENU); - }); - - Button close = buttonBuilder("Close without Saving"); - close.setOnAction( (event) -> - { - STAGE.setScene(MAIN_MENU); - }); - - output.getChildren().addAll(defaults, - save, - saveClose, - close); - return output; - } - - /** - * Modifying function for a text field. - * - * Brings in an HBox, stores it in the Map with the correct {@link ConfigProperties} value. - * Also sets the action of the text box to be correct. - * - * @param hbox The HBox containing the TextField to be modified and remembered - * @param property The property to be associated with the TextField - * @param cameraName The name of the camera to be associated with the TextField - */ - private static void textFieldSetup(HBox hbox, ConfigProperties property, String cameraName) - { - TextField field = null; - for(Node child : hbox.getChildren()) - { - if(child instanceof TextField) - { - field = (TextField)child; - break; - } - } - if(field == null) - { - ErrorLogging.logError("GUI INIT ERROR!!! - Failed text field setup."); - GuiController.closeModel(); - } - - //GuiController.addToMap(cameraName,property,field); - Map cameraFields = uiFields.get(cameraName); - if(cameraFields.containsKey(property)) - { ErrorLogging.logError("GUI Setup Error!!! - Duplicate field: " + cameraName + " " + property.getConfig()); } - cameraFields.put(property,field); - uiFields.replace(cameraName,cameraFields); - field.setId(property.getConfig() + cameraName); - field.textProperty().addListener( - (observable, oldValue, newValue) -> - { - try(Scanner sc = new Scanner(newValue);) - { GuiController.setConfigValue(cameraName,property,sc.nextInt()); } - catch(Exception e) - { - ErrorLogging.logError("USER INPUT ERROR: Illegal input in " + property.getConfig() + " for " + cameraName + "."); - newValue = oldValue; - } - }); - } - - /** - * Builder function for a button. - * - * Creates a button with a set ID, name, and disabled/enables status. - * - * @param name The name of the new button - * @param disabled Whether or not the button should be disabled on startup - * - * @return Button , with a preset ID, name, and optionally disabled. - */ - private static Button buttonBuilder(String name,boolean disabled) - { - String[] id = name.strip().substring(0, name.length() - 1).toLowerCase().strip().split(" "); - Button button = new Button(name); - button.setId(id[0]); - button.setDisable(disabled); - return button; - } - - /** - * Builder function for an enabled button. - * - * Creates a button with a set ID and name. - * - * @param name The name of the new button - * @return Button , with a preset ID and name. - */ - private static Button buttonBuilder(String name) - { return buttonBuilder(name,false); } - - /** - * Builder function for a user-interactable TextField, with built-in label. - * - * Creates an HBox, with a Label for the TextField, along with the TextField itself. - * - * @param prompt The name used for the Label - * @param baseValue The default value used in the TextField - * @param description The Tooltip of the TextField/Label - * - * @return Hbox, described above - */ - private static HBox userTextField(String prompt, String baseValue, String description) - { - HBox output = new HBox(); - output.setSpacing(INTERNAL_SPACING); - output.setAlignment(Pos.CENTER_LEFT); - Label label = new Label(prompt); - TextField field = new TextField(); - String[] id = prompt.strip().substring(0, prompt.length() - 1).toLowerCase().strip().split(" "); - field.setId(id[0]); - output.setId(id[0] + "-box"); - field.setPromptText(baseValue); - Tooltip tooltip = new Tooltip(description); - field.setTooltip(tooltip); - label.setTooltip(tooltip); - output.getChildren().addAll(label,field); - return output; - } - - /** - * Getter for a given TextField, associated with a camera and property. - * - * @param cameraName The name of the camera the TextField is associated with - * @param property The name of the property the TextField is associated with - * - * @return TextField - */ - public static TextField getField(String cameraName, ConfigProperties property) - { return uiFields.get(cameraName).get(property); } - - /** - * Getter for the Start button. - * - * @return Button used for starting the tests. - */ - public static Button getStart() - { return startButton; } - - /** - * Getter for the user feedback Text object. - * - * @return Text object used for communicating statuses to the user. - */ - public static Text getFeedbackText() - { return userFeedback; } - - /** - * Getter for the TextField used by the user to set the number of iterations. - * - * @return TextField - */ - public static TextField getIterationField() - { return iterationField; } - - /** - * Getter for the ImageView Map. - * - * @return Map with keys of the names of cameras, and the value of the corresponding imageview - */ - public static Map getViewMap() - { return viewMap; } - - /** - * Updater for a given camera's ImageView width - * - * @param cameraName Name of the camera being updated - */ - public static void updateImageViewWidth(String cameraName) - { - viewMap.get(cameraName).setFitWidth(GuiController.getConfigValue(cameraName,ConfigProperties.CROP_W)); - } - - /** - * Updater for a given camera's ImageView height - * - * @param cameraName Name of the camera being updated - */ - public static void updateImageViewHeight(String cameraName) - { - viewMap.get(cameraName).setFitHeight(GuiController.getConfigValue(cameraName,ConfigProperties.CROP_H)); - } -} diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index d2f5654..5eea773 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -66,7 +66,7 @@ public class MovementFacade /** * PWM Frequency */ - private static int FREQUENCY = 60000; + private static int FREQUENCY = 70000; /** * PWM Duty Cycle @@ -76,7 +76,7 @@ public class MovementFacade /** * Number of seconds to wait before timing out a fixture movement. */ - private static int TIME_OUT = 10; + private static int TIME_OUT = 3; //PWM Addresses //All addresses are in BCM format. diff --git a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java index 06cf9d4..8af4337 100644 --- a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java +++ b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java @@ -421,9 +421,11 @@ public class OpenCVFacade return output; } int compositeFrames = (int)ConfigFacade.getValue(cameraName,ConfigProperties.COMPOSITE_FRAMES); - boolean threshold = (ConfigFacade.getValue(cameraName,ConfigProperties.THRESHOLD) != 0.0); + boolean threshold = false; + //boolean threshold = (ConfigFacade.getValue(cameraName,ConfigProperties.THRESHOLD) != 0.0); //ErrorLogging.logError("DEBUG: Threshold config value: " + threshold); - boolean crop = (ConfigFacade.getValue(cameraName,ConfigProperties.CROP) != 0.0); + boolean crop = false; + //boolean crop = (ConfigFacade.getValue(cameraName,ConfigProperties.CROP) != 0.0); //ErrorLogging.logError("DEBUG: Crop config value: " + crop); output = completeProcess(cameraName,crop,threshold,compositeFrames,saveLocation); if(output == null) diff --git a/toDoList.md b/toDoList.md index 5c763bc..0e00336 100644 --- a/toDoList.md +++ b/toDoList.md @@ -2,56 +2,27 @@ ## High-priority fixes -### CLI +## Cli(?) -- [ x ] complete implementation (see comments in file) - - [ x ] primary functionality - - [ ] connect to other classes - - partially complete - - [ x ] Javadoc documentation -- [ ] Debug so CLI is usable (HIGHEST PRIORITY) -- [ ] Find way to kill CanvasFrame protection Thread on exit - -### DataSaving - -- [ ] complete implementation - - [ x ] need to store location of excel file - - [ x ] need to have a function to write values to an excel file, parsing along the way for anomalies (Currently not backwards compatible) - - requires looking back at `logging_tools.py` - - [ x ] Javadoc documentation - - [ ] Test if it works - -### ConfigFacade - -- [ x ] refactor static block to load defaults -- [ ] refactor `saveCurrentConfig(String filename)` -- [ x ] set default for `imageSaveLocation` -- [ x ] properly fill the list of active cameras - - Is this still a necessary object? Appears to be similar to the `Set` of `configMap`; consider replacing with a wrapper around `configMap.keySet()`. This is not necessary, and has been removed. - -### ErrorLogging - -- [ x ] refactor static block create a new file at runtime +- [ ] enable camera toggling + - may require more classes to be modified ### MovementFacade -- [ ] Implement multithreading for physical Run switch - - requires much documentation reading +- [x] Implement multithreading for physical Run switch\* + - Currently questionably implemented, consider looking into more ### OpenCVFacade -- [ ] Overload takeBurst with a default framecount from config -- [ x ] Overload crop with default values from config +- [x] Overload takeBurst with a default framecount from config +- [x] Overload crop with default values from config - [ ] completeProcess should have more robust file output checking ### Gui -- [ ] rebuild menus with current feature set -- [ ] Complete implementation; waiting on: - - [ ] read documentation - - [ ] implement Cli successfully - -Notes: JavaFXML is the View. Model should be wrapper around all classes, similar to Cli. Controller should connect JavaFXML interface to Model. +- [x] rebuild menus with current feature set +- [x] Complete implementation + - currently questionably implemented; debugging... ## Low-priority improvements @@ -61,16 +32,13 @@ Notes: JavaFXML is the View. Model should be wrapper around all classes, similar - [ ] reduce Javadoc linking to one link per reference per class - [ ] Use generated Javadocs to improve Javadocs (perpetual) -### OpenCVFacade +### Cli -- [ x ] convert all uses of Frame to Mat +- [ ] Find way to kill CanvasFrame protection Thread on exit ### TesseractFacade - [ ] parse text-based input? - requires further communication with Pete to determine if necessary. - -### ConfigProperties - -- [ x ] More complete documentation + - requires retraining Tesseract -- 2.47.2