diff --git a/README.md b/README.md index 2a2df83..35cbf88 100644 --- a/README.md +++ b/README.md @@ -5,22 +5,28 @@ This is a personal/professional project, which makes use of JavaCV, OpenCV, Tess ## Currently Working Features - CLI Interface (All instances have user input checking) - - [ x ] Main Menu + - [x] Main Menu - Camera config menu - - [ x ] Takes in inputs - - [ x ] Sets config values - - [ x ] Saves config values - - [ x ] Shows camera preview* + - [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 ] GPIO test interactions + - [x] GPIO test interactions - Test suite - - [ x ] OpenCV image capture - - [ x ] OpenCV image processing - - [ x ] Tesseract OCR processing - - [ ] Data storage in defined XLSX file (implemented, untested) - - [ x ] modify number of iterations for test suite -- [ ] JavaFX GUI (Designed, yet to be implemented) + - [x] OpenCV image capture + - [x] OpenCV image processing + - [x] Tesseract OCR processing + - [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. ## 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) diff --git a/antrun/build.xml b/antrun/build.xml deleted file mode 100644 index 970415b..0000000 --- a/antrun/build.xml +++ /dev/null @@ -1,215 +0,0 @@ - - - - - - - - - - - - - - Remote platform name must be set. - - - 'target.platform.host' property is missing in ${target.platform.filename}. - 'target.platform.port' property is missing in ${target.platform.filename}. - 'target.platform.username' property is missing in ${target.platform.filename}. - - - - - - - - - - - - - - - - - - - - - - - Either 'target.platform.password' or 'target.platform.privatekey' + 'target.platform.passphrase' properties must be set in ${target.platform.filename}. - 'target.remote.jre' property is missing in ${target.platform.filename}. - 'target.remote.home' property is missing in ${target.platform.filename}. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 5152179..c3c9cc5 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-rc1 + 4.0.0-rc2 Testing Discos for long-term accuracy, using automated optical character recognition. Baxter International diff --git a/pom.xml b/pom.xml index b2f6297..614b1e1 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.baxter.disco ocr - 4.0.0-rc1 + 4.0.0-rc2 jar Disco OCR Accuracy Over Life Testing Testing Discos for long-term accuracy, using automated optical character recognition. @@ -86,7 +86,7 @@ ${pi4j.version} - + org.openjfx javafx-fxml @@ -178,7 +178,7 @@ - + --> diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index ef88b29..80e8d13 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -4,6 +4,7 @@ module org.baxter.disco.ocr { requires com.pi4j.plugin.pigpio; requires com.pi4j.library.pigpio; requires javafx.fxml; + requires javafx.controls; requires org.apache.poi.poi; requires org.apache.commons.configuration2; requires org.apache.xmlbeans; diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index 610909b..5e9baf5 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -1,11 +1,14 @@ 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; import java.util.Map; import java.util.Scanner; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import org.bytedeco.javacv.CanvasFrame; @@ -13,17 +16,18 @@ import org.bytedeco.javacv.CanvasFrame; * CLI for the Fixture. * * Creates a terminal-based user interface for the other - * classes in this package (with the exception of {@link Gui} [for now]). + * classes in this package (with the exception of Gui-related + * classes). * * @author Blizzard Finnegan - * @version 1.0.0, 27 Jan. 2023 + * @version 1.3.0, 03 Feb. 2023 */ public class Cli { /** * Currently saved iteration count. */ - private static int iterationCount = 3; + private static int iterationCount = 10; /** * Scanner used for monitoring user input. @@ -33,10 +37,20 @@ public class Cli */ private static Scanner inputScanner; + /** + * Whether the user has set the serial numbers yet. + */ + private static boolean serialsSet = false; + + /** + * Whether the user has successfully configured the cameras. + */ + private static boolean camerasConfigured = false; + /** * Number of options currently available in the main menu. */ - private static final int mainMenuOptionCount = 6; + private static final int mainMenuOptionCount = 7; /** * Number of options currently available in the movement sub-menu. @@ -48,11 +62,33 @@ public class Cli */ private static final int cameraMenuOptionCount = 9; + /** + * Lock object, used for temporary interruption of {@link #runTests()} + */ + private static Lock LOCK = new ReentrantLock(); + + /** + * Instance of {@link MovementFacade} for controlling the fixture. + */ + private static MovementFacade fixture; + + static + { + ErrorLogging.logError("DEBUG: START OF PROGRAM"); + } + public static void main(String[] args) { try{ inputScanner = new Scanner(System.in); - ErrorLogging.logError("DEBUG: START OF PROGRAM"); + + //ErrorLogging.logError("DEBUG: Setting up multithreading..."); + fixture = new MovementFacade(LOCK); + //ErrorLogging.logError("DEBUG: Multithreading complete!"); + + //ErrorLogging.logError("DEBUG: Importing config..."); + ConfigFacade.init(); + //ErrorLogging.logError("DEBUG: Config imported!"); int userInput = 0; @@ -69,24 +105,62 @@ public class Cli println("Setting up cameras..."); println("This may take a moment..."); configureCameras(); + camerasConfigured = true; break; case 3: - setIterationCount(); + setDUTSerials(); + serialsSet = true; break; case 4: - runTests(); - println("Test complete!"); + setIterationCount(); break; case 5: - printHelp(); + if(!camerasConfigured) + { + prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): "); + String input = inputScanner.nextLine().toLowerCase(); + if( input.isBlank()) + { + break; + } + else if (input.charAt(0) != 'y' ) + { + break; + } + else + { + ErrorLogging.logError("WARNING! - Potential for error: Un-initialised cameras."); + } + } + 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()) + { + break; + } + else if (input.charAt(0) != 'y' ) + { + break; + } + else + { + ErrorLogging.logError("WARNING! - Potential for error: Un-initialised DUT Serial numbers."); + } + } + runTests(); break; case 6: + printHelp(); + break; + case 7: break; default: //Input handling already done by inputFiltering() } - } while (userInput != 6); + } while (userInput != mainMenuOptionCount); } catch(Exception e) @@ -96,11 +170,7 @@ public class Cli } finally { - ErrorLogging.logError("DEBUG: PROGRAM CLOSING."); - inputScanner.close(); - MovementFacade.closeGPIO(); - ErrorLogging.logError("DEBUG: END OF PROGRAM."); - ErrorLogging.closeLogs(); + close(); } } @@ -150,18 +220,22 @@ public class Cli "\n\tAvailable variables to change:"+ "\n\t\tCrop dimensions"); println("----------------------------------------"); - println("3. Change test iteration count:"+ + println("3. Set serial numbers: Set the serial " + + "\n\tnumber for the device under test." + + "\n\tThis is used in final data saving."); + println("----------------------------------------"); + println("4. Change test iteration count:"+ "\n\tChange the number of times to"+ "\n\trun the tests of the device(s)"+ "\n\tunder test."); println("----------------------------------------"); - println("4. Run tests: Run tests, with defined"+ + println("5. Run tests: Run tests, with defined"+ "\n\tnumber of iterations. Uses"+ "\n\tvalues defined in config file."); println("----------------------------------------"); - println("5. Help: Show this help page."); + println("6. Help: Show this help page."); println("----------------------------------------"); - println("6. Exit: Close the program."); + println("7. Exit: Close the program."); println("========================================"); println("Press Enter to continue..."); inputScanner.nextLine(); @@ -180,22 +254,26 @@ public class Cli println("--------------------------------------"); println("1. Test and configure fixture movement"); println("2. Configure camera"); - println("3. Change test iteration count"); - println("4. Run tests"); - println("5. Help"); - println("6. Exit"); + println("3. Set serial numbers"); + println("4. Change test iteration count"); + println("5. Run tests"); + println("6. Help"); + println("7. Exit"); println("======================================"); } + /** + * Predefined print statements for the movement submenu. + */ private static void printMovementMenu() { println("\n\n"); println("===================================="); println("Movement Menu:"); println("------------------------------------"); - println("Current Duty Cycle: " + MovementFacade.getDutyCycle()); - println("Current Frequency: " + MovementFacade.getFrequency()); - println("Current Motor Time-out: " + MovementFacade.getTimeout()); + println("Current Duty Cycle: " + fixture.getDutyCycle()); + println("Current Frequency: " + fixture.getFrequency()); + println("Current Motor Time-out: " + fixture.getTimeout()); println("------------------------------------"); println("1. Change Duty Cycle"); println("2. Change Frequency"); @@ -204,6 +282,9 @@ public class Cli println("===================================="); } + /** + * Pre-defined method for printing all available cameras in a menu + */ private static void printCameraMenu(List cameraList) { println("Available cameras to configure:"); @@ -218,6 +299,30 @@ public class Cli println("------------------------------------"); } + /** + * Pre-defined method for printing all available cameras and the associated serials in a menu + */ + private static void printSerialMenu(List cameraList) + { + println("Available serial numbers to set:"); + println("------------------------------------"); + for(int index = 0; index < cameraList.size(); index++) + { + int humanIndex = index+1; + String cameraName = (String)cameraList.get(index); + print(humanIndex + " - " + cameraName + " : "); + if(ConfigFacade.getSerial(cameraName) != null) + println(ConfigFacade.getSerial(cameraName)); + else + println(""); + } + println( (cameraList.size() + 1) + " - Exit to Main Menu"); + println("------------------------------------"); + } + + /** + * Pre-defined menu for printing camera configuration options + */ private static void printCameraConfigMenu(String cameraName) { println("\n\n"); @@ -247,20 +352,24 @@ public class Cli println("3. Change Crop Width"); println("4. Change Crop Height"); println("5. Change Composite Frame Count"); - println("6. Toggle crop"); - println("7. Toggle threshold"); - println("8. Exit"); + println("6. Change Threshold Value"); + println("7. Toggle crop"); + println("8. Toggle threshold"); + println("9. Exit"); println("===================================="); } + /** + * Function for testing movement, and modifying hardware values + */ private static void testMovement() { int userInput = -1; do { println("Testing movement..."); - MovementFacade.testMotions(); + fixture.testMotions(); printMovementMenu(); userInput = inputFiltering(inputScanner.nextLine()); switch (userInput) @@ -277,7 +386,7 @@ public class Cli int newDutyCycle = inputFiltering(inputScanner.nextLine()); if (newDutyCycle != -1) { - MovementFacade.setDutyCycle(newDutyCycle); + fixture.setDutyCycle(newDutyCycle); break; } case 2: @@ -285,7 +394,7 @@ public class Cli int newFrequency = inputFiltering(inputScanner.nextLine()); if (newFrequency != -1) { - MovementFacade.setFrequency(newFrequency); + fixture.setFrequency(newFrequency); break; } case 3: @@ -293,7 +402,7 @@ public class Cli int newTimeout = inputFiltering(inputScanner.nextLine()); if (newTimeout != -1) { - MovementFacade.setTimeout(newTimeout); + fixture.setTimeout(newTimeout); break; } case 4: @@ -313,28 +422,29 @@ public class Cli List cameraList = new ArrayList<>(OpenCVFacade.getCameraNames()); //println(cameraList.toString()); - //Open a single new thread, so the canvas - //used further down to display the temporary - //image doesn't accidentally kill the program. - //Created at beginning of function call to reduce - //thread spawn count. - //See also: https://docs.oracle.com/javase/8/docs/api/java/awt/doc-files/AWTThreadIssues.html#Autoshutdown - Runnable r = new Runnable() { - public void run() { - Object o = new Object(); - try { - synchronized (o) { - o.wait(); - } - } catch (InterruptedException ie) { - } - } - }; - Thread t = new Thread(r); - t.setDaemon(false); - t.start(); + // The below code should be unnecessary now. Leaving in for now to ensure things work properly. + ////Open a single new thread, so the canvas + ////used further down to display the temporary + ////image doesn't accidentally kill the program. + ////Created at beginning of function call to reduce + ////thread spawn count. + ////See also: https://docs.oracle.com/javase/8/docs/api/java/awt/doc-files/AWTThreadIssues.html#Autoshutdown + //Runnable r = new Runnable() { + // public void run() { + // Object o = new Object(); + // try { + // synchronized (o) { + // o.wait(); + // } + // } catch (InterruptedException ie) { + // } + // } + //}; + //Thread t = new Thread(r); + //t.setDaemon(false); + //t.start(); - MovementFacade.iterationMovement(true); + fixture.iterationMovement(true); do { @@ -354,11 +464,13 @@ public class Cli //Leave do-while loop if the user asks to if(userInput == (cameraList.size())) break; + else if(userInput < 0) + {} else cameraName = cameraList.get((userInput)); do { - MovementFacade.pressButton(); + fixture.pressButton(); try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); } //Show image CanvasFrame canvas = OpenCVFacade.showImage(cameraName); @@ -390,12 +502,15 @@ public class Cli modifiedProperty = ConfigProperties.COMPOSITE_FRAMES; break; case 6: - modifiedProperty = ConfigProperties.CROP; + modifiedProperty = ConfigProperties.THRESHOLD_VALUE; break; case 7: - modifiedProperty = ConfigProperties.THRESHOLD; + modifiedProperty = ConfigProperties.CROP; break; case 8: + modifiedProperty = ConfigProperties.THRESHOLD; + break; + case 9: modifiedProperty = ConfigProperties.PRIME; break; default: @@ -420,12 +535,45 @@ public class Cli } while(true); + ConfigFacade.saveCurrentConfig(); println("Configuration complete!"); } + /** + * Sub-function used for defining the serial numbers of the devices under test + */ + private static void setDUTSerials() + { + List cameraList = new ArrayList<>(OpenCVFacade.getCameraNames()); + do + { + //Main menu + printSerialMenu(cameraList); + + //Pick a camera to configure + int userInput; + + String cameraName = ""; + do + { + prompt("Enter the camera you wish to set the serial of: "); + 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)); + + prompt("Enter the serial number you wish to use for this camera: "); + ConfigFacade.setSerial(cameraName,inputScanner.nextLine()); + + } while(true); + } + /** - * Setter for {@link #iterationCount} + * CLI-level setter for {@link #iterationCount} */ private static void setIterationCount() { @@ -443,31 +591,72 @@ public class Cli */ private static void runTests() { - DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation()); - boolean prime = false; - for(String cameraName : OpenCVFacade.getCameraNames()) - { - if(ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0) + final int localIterations = iterationCount; + //testingThread = new Thread(() -> + //{ + DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation(),OpenCVFacade.getCameraNames().size()); + boolean prime = false; + for(String cameraName : OpenCVFacade.getCameraNames()) { - prime = true; + if(cameraName != null) { /*println(cameraName);*/ } + else ErrorLogging.logError("Null camera!"); + if(ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0) + { + prime = true; + } } - } - for(int i = 0; i < iterationCount; i++) - { - Map resultMap = new HashMap<>(); - MovementFacade.iterationMovement(prime); - List iteration = OpenCVFacade.singleIteration(); - for(File file : iteration) + ErrorLogging.logError("DEBUG: Waking devices."); + fixture.iterationMovement(prime); + fixture.pressButton(); + fixture.iterationMovement(prime); + ErrorLogging.logError("DEBUG: Starting tests..."); + for(int i = 0; i < localIterations; i++) { - Double result = TesseractFacade.imageToDouble(file); - String fileLocation = file.getAbsolutePath(); - resultMap.put(fileLocation,result); - ErrorLogging.logError("DEBUG: Tesseract final output: " + result); + 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) + { + 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); + ErrorLogging.logError("DEBUG: Tesseract final output: " + result); + LOCK.unlock(); + } + while(!LOCK.tryLock()) {} + DataSaving.writeValues(i,resultMap); + LOCK.unlock(); } - DataSaving.writeValues(i,resultMap); - } - println("======================================="); - println("Tests complete!"); + println("======================================="); + println("Testing complete!"); + //}); + //testingThread.start(); + } + + + /** + * Function used if a config file was successfully imported. + */ + public static void configImported() + { camerasConfigured = true; } + + /** + * Function that closes GPIO and logging. + */ + private static void close() + { + ErrorLogging.logError("DEBUG: PROGRAM CLOSING."); + if(inputScanner != null) inputScanner.close(); + fixture.closeGPIO(); + ErrorLogging.logError("DEBUG: END OF PROGRAM."); + ErrorLogging.closeLogs(); } /** @@ -482,7 +671,7 @@ public class Cli * Parse the user's input, and check it for errors. * * @param input The unparsed user input, directly from the {@link Scanner} - * @param mainMenu Whether or not the parsed input is a main menu value + * @param menu Which menu is being parsed * * @return The parsed value from the user. Returns -1 upon any error. */ @@ -578,7 +767,7 @@ public class Cli */ private static void invalidInput() { - invalidInput(""); + invalidInput("Please input a valid number."); } /** @@ -596,5 +785,8 @@ public class Cli println(""); } + /** + * Enum of possible menus available + */ private enum Menus { MAIN,MOVEMENT,CAMERA,OTHER; } } diff --git a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java index f0dde06..c037d3d 100644 --- a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java +++ b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java @@ -13,6 +13,8 @@ import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; /** @@ -22,7 +24,7 @@ import java.util.ArrayList; * Can write to file when requested, reads from file on initial start. * * @author Blizzard Finnegan - * @version 1.0.0, 27 Jan. 2023 + * @version 1.2.0, 03 Feb. 2023 */ public class ConfigFacade { @@ -41,19 +43,20 @@ public class ConfigFacade /** * Location to save output XLSX file to. */ - private static String outputSaveLocation = "outputData.xlsx"; + public static String outputSaveLocation = "outputData-" + + (LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)) + ".xlsx"; - //For values that are ultimately ints, truncate. - //For values that are ultimately booleans, anything that isn't 0 should be considered True. /** * Map of all config values relating to the camera. + * For values that are ultimately ints, truncate. + * For values that are ultimately booleans, anything that isn't 0 should be considered True. */ private static final Map> configMap = new HashMap<>(); /** - * Base class used for interacting with Configuration files. + * Temporary storage for the DUT's serial number. */ - private static final Configurations CONFIGURATIONS = new Configurations(); + private static final Map DUT_SERIALS = new HashMap<>(); /** * Builder for the main Configuration object. @@ -71,26 +74,34 @@ public class ConfigFacade static { + ErrorLogging.logError("Starting configuration setup..."); CONFIG_STORE = null; File configFile = new File(configFileLocation); boolean newConfig = true; try{ newConfig = configFile.createNewFile(); } catch(Exception e){ ErrorLogging.logError(e); } + + ErrorLogging.logError("Creating config file interface..."); CONFIG_BUILDER = new FileBasedConfigurationBuilder<>(INIConfiguration.class) .configure(new Parameters().fileBased().setFile(configFile)); + + ErrorLogging.logError("Attempting to import config..."); 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!"); } - CONFIGURATIONS.iniBuilder(configFileLocation); + + ErrorLogging.logError("Creating image storage directories..."); File imageLocation = new File(imageSaveLocation); imageLocation.mkdir(); File debugImageLocation = new File(imageSaveLocation + "/debug"); debugImageLocation.mkdir(); File configImageLocation = new File(imageSaveLocation + "/config"); configImageLocation.mkdir(); + + ErrorLogging.logError("Creating output file...."); File outputFile = new File(outputSaveLocation); try{ outputFile.createNewFile(); } catch(Exception e){ ErrorLogging.logError(e); } @@ -98,8 +109,13 @@ public class ConfigFacade { boolean saveSuccessful = saveDefaultConfig(); if(!saveSuccessful) ErrorLogging.logError("Save config failed!!!"); + else ErrorLogging.logError("Configuration settings set up!"); + } + else + { + loadConfig(); + ErrorLogging.logError("Configuration settings loaded!"); } - else { loadConfig(); } CONFIG_BUILDER.setAutoSave(true); } /** @@ -108,7 +124,7 @@ public class ConfigFacade * Ints should be truncated. * Any boolean that should be false should be stored as 0. * - * @param cameraName Name of the camera (as defined in {@link #activeCameras}) + * @param cameraName Name of the camera (defined in {@link OpenCVFacade}) * @param property name of the property ({@link ConfigProperties}) * * @return double of config value. Returns 0 if invalid input. @@ -119,8 +135,13 @@ public class ConfigFacade public static double getValue(String cameraName, ConfigProperties property) { double output = 0.0; - List activeCameras = new ArrayList<>(OpenCVFacade.getCameraNames()); - if(!activeCameras.contains(cameraName)) return output; + if(!configMap.keySet().contains(cameraName)) + { + ErrorLogging.logError("CONFIG ERROR!!! - Invalid camera name: " + cameraName); + ErrorLogging.logError("\tProperty: " + property.getConfig()); + ErrorLogging.logError("\tconfigMap keys: " + configMap.keySet().toString()); + return output; + } Map cameraConfig = configMap.get(cameraName); output = cameraConfig.get(property); //Debug logger. @@ -129,15 +150,21 @@ public class ConfigFacade return output; } + /** + * Initialises local list of available cameras + */ + public static void init() + { + //ErrorLogging.logError("Starting import..."); + } + /** * Getter for the location of the output XLSX file. * * @return Absolute path of the image save location. */ public static String getOutputSaveLocation() - { - return outputSaveLocation; - } + { return outputSaveLocation; } /** * Setter for the location of the output XLSX file. @@ -161,9 +188,7 @@ public class ConfigFacade * @return Absolute path of the image save location. */ public static String getImgSaveLocation() - { - return imageSaveLocation; - } + { return imageSaveLocation; } /** * Setter for the saved image location. @@ -203,6 +228,31 @@ public class ConfigFacade return output; } + /** + * Setter for a Device Under Test's serial number. + * + * @param cameraName The camera observing the given serial number + * @param serial The serial of the DUT + */ + public static void setSerial(String cameraName, String serial) + { DUT_SERIALS.put(cameraName,serial); } + + /** + * Getter for a Device Under Test's serial number. + * + * @param cameraName The camera observing the given serial number + * + * @return The DUT's serial + */ + public static String getSerial(String cameraName) + { + if(!DUT_SERIALS.keySet().contains(cameraName)) return null; + return DUT_SERIALS.get(cameraName); + } + + public static Map getSerials() + { return DUT_SERIALS; } + //********************************************** //SAVE AND LOAD SETTINGS //********************************************** @@ -228,7 +278,7 @@ public class ConfigFacade Map cameraConfig = new HashMap<>(); for(ConfigProperties property : ConfigProperties.values()) { - String propertyName = camera + "/" + property.getConfig(); + String propertyName = camera + "." + property.getConfig(); double propertyValue = property.getDefaultValue(); cameraConfig.put(property,propertyValue); //ErrorLogging.logError("DEBUG: Attempting to save to config: "); @@ -271,7 +321,7 @@ public class ConfigFacade { for(ConfigProperties property : ConfigProperties.values()) { - String propertyName = camera + "/" + property.toString(); + String propertyName = camera + "." + property.toString(); String propertyValue =configMap.get(camera).get(property).toString(); CONFIG_STORE.addProperty(propertyName,propertyValue); } @@ -304,17 +354,18 @@ public class ConfigFacade */ public static boolean loadConfig(String filename) { + boolean emptyMap = configMap.keySet().size() == 0; boolean output = false; File file = new File(filename); try { if(file.createNewFile()) - saveDefaultConfig(); + return saveDefaultConfig(); } catch(Exception e) { ErrorLogging.logError(e); - saveDefaultConfig(); + return saveDefaultConfig(); } List cameraNames = new ArrayList<>(OpenCVFacade.getCameraNames()); if(Files.isRegularFile(Path.of(file.toURI()))) @@ -322,18 +373,17 @@ public class ConfigFacade try { CONFIG_BUILDER = new FileBasedConfigurationBuilder<>(INIConfiguration.class).configure(new Parameters().fileBased().setFile(file)); - CONFIG_STORE = CONFIGURATIONS.ini(filename); + CONFIG_STORE = CONFIG_BUILDER.getConfiguration(); } catch(Exception e){ ErrorLogging.logError(e); } Set configSections = CONFIG_STORE.getSections(); - ErrorLogging.logError("DEBUG: imported sections - " + configSections.toString()); - ErrorLogging.logError("DEBUG: imported section size - " + configSections.size()); + //ErrorLogging.logError("DEBUG: imported sections - " + configSections.toString()); + //ErrorLogging.logError("DEBUG: empty map? : " + (configMap.keySet().size() == 0)); + //ErrorLogging.logError("DEBUG: imported section size - " + configSections.size()); for(String sectionName : configSections) { Map savedSection = new HashMap<>(); - SubnodeConfiguration subSection = CONFIG_STORE.getSection(sectionName); - String subSectionPrefix = ""; for(String cameraName : cameraNames) { @@ -362,27 +412,26 @@ public class ConfigFacade for(ConfigProperties configState : ConfigProperties.values()) { - String subSectionValueName = subSectionPrefix + "/" + configState.getConfig(); - if(!subSection.containsKey(subSectionValueName)) - { - ErrorLogging.logError("CONFIG LOAD ERROR!!! - Invalid config file."); - return saveDefaultConfig(file.getAbsolutePath()); - } - else - { - Double configValue = subSection.getDouble(configState.toString()); - savedSection.put(configState,configValue); - } + Double configValue = CONFIG_STORE.getDouble(sectionName + "." + configState.getConfig()); + //ErrorLogging.logError("DEBUG: Imported config value: " + Double.toString(configValue)); + savedSection.put(configState,configValue); } - if(configMap.containsKey(sectionName)) + if(emptyMap) configMap.put(sectionName,savedSection); + else { - configMap.put(sectionName,savedSection); + for(String key : configMap.keySet()) + { + //ErrorLogging.logError("DEBUG: configMap Key: " + key + " ?= " + sectionName); + if( key.equals(sectionName)) + { configMap.put(key,savedSection); } + } } } output = true; } if(!output) ErrorLogging.logError("CONFIG LOAD ERROR!!! - Invalid path."); + else Cli.configImported(); 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 282938b..baf4f52 100644 --- a/src/main/java/org/baxter/disco/ocr/ConfigProperties.java +++ b/src/main/java/org/baxter/disco/ocr/ConfigProperties.java @@ -39,7 +39,11 @@ public enum ConfigProperties /** * Whether or not to press the button on the device twice, when under test. */ - PRIME("Prime device?","prime",0.0); + PRIME("Prime device?","prime",0.0), + /** + * Where the threshold point should land. + */ + THRESHOLD_VALUE("Threshold value","thresholdValue",50.0); /** * Internal storage of human-readable name/meaning diff --git a/src/main/java/org/baxter/disco/ocr/DataSaving.java b/src/main/java/org/baxter/disco/ocr/DataSaving.java index b4059fd..ddfb8ff 100644 --- a/src/main/java/org/baxter/disco/ocr/DataSaving.java +++ b/src/main/java/org/baxter/disco/ocr/DataSaving.java @@ -2,10 +2,10 @@ package org.baxter.disco.ocr; import java.io.File; import java.io.FileOutputStream; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Set; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; @@ -16,7 +16,7 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook; * Facade for saving data out to a file. * * @author Blizzard Finnegan - * @version 1.0.0, 27 Jan. 2023 + * @version 2.0.0, 02 Feb. 2023 */ public class DataSaving { @@ -34,10 +34,12 @@ public class DataSaving */ private static File outputFile; + private static Map serials; + /** * Prepares writer to write to XLSX file. */ - public static boolean initWorkbook(String filename) + public static boolean initWorkbook(String filename, int camCount) { boolean output = false; outputFile = new File(filename); @@ -45,6 +47,27 @@ public class DataSaving { outputWorkbook = new XSSFWorkbook(); outputSheet = outputWorkbook.createSheet(); + int startingRow = outputSheet.getLastRowNum(); + Row row = outputSheet.createRow(++startingRow); + int cellnum = 0; + Cell cell = row.createCell(cellnum++); + cell.setCellValue("Iteration"); + for(int i = 0; i < camCount; i++) + { + cell = row.createCell(cellnum++); + cell.setCellValue("Serial"); + cell = row.createCell(cellnum++); + cell.setCellValue("Image Location"); + cell = row.createCell(cellnum++); + cell.setCellValue("Read Value"); + cell = row.createCell(cellnum++); + cell.setCellValue(""); + } + FileOutputStream outputStream = new FileOutputStream(outputFile); + outputWorkbook.write(outputStream); + output = true; + outputStream.close(); + serials = ConfigFacade.getSerials(); } catch(Exception e) { ErrorLogging.logError(e); } return output; @@ -62,13 +85,39 @@ public class DataSaving boolean output = false; int startingRow = outputSheet.getLastRowNum(); Row row = outputSheet.createRow(++startingRow); - Set imageLocations = inputMap.keySet(); + List imageLocations = new ArrayList<>(inputMap.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) { + 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); + + //ErrorLogging.logError("DEBUG: " + cameraName); + + String serialNumber = ConfigFacade.getSerial(cameraName); + objectArray.add(serialNumber); objectArray.add(imageLocation); objectArray.add(inputMap.get(imageLocation)); + objectArray.add(" "); } int cellnum = 0; for(Object cellObject : objectArray) @@ -76,7 +125,11 @@ public class DataSaving Cell cell = row.createCell(cellnum++); if(cellObject instanceof Double) cell.setCellValue((Double) cellObject); else if(cellObject instanceof String) cell.setCellValue((String) cellObject); - else { ErrorLogging.logError("XLSX Write Error!!! - Invalid input."); } + else + { + ErrorLogging.logError("XLSX Write Error!!! - Invalid input."); + ErrorLogging.logError("\t" + cellObject.toString()); + } } try @@ -84,6 +137,7 @@ public class DataSaving FileOutputStream outputStream = new FileOutputStream(outputFile); outputWorkbook.write(outputStream); output = true; + outputStream.close(); } catch(Exception e) {ErrorLogging.logError(e);} return output; diff --git a/src/main/java/org/baxter/disco/ocr/ErrorLogging.java b/src/main/java/org/baxter/disco/ocr/ErrorLogging.java index d637552..8d70d7f 100644 --- a/src/main/java/org/baxter/disco/ocr/ErrorLogging.java +++ b/src/main/java/org/baxter/disco/ocr/ErrorLogging.java @@ -2,8 +2,10 @@ package org.baxter.disco.ocr; import java.io.BufferedWriter; import java.io.File; +import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; +import java.io.PrintStream; import java.io.PrintWriter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -15,7 +17,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils; * as well as stderr. * * @author Blizzard Finnegan - * @version 1.2.0, 27 Jan. 2023 + * @version 1.3.0, 03 Feb. 2023 */ public class ErrorLogging @@ -55,7 +57,7 @@ public class ErrorLogging static { fileDatetime = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); - datetime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + datetime = DateTimeFormatter.ISO_LOCAL_DATE_TIME; logFile = fileDatetime.format(LocalDateTime.now()) + "-log.txt"; File outFile = new File(logFile); try @@ -64,6 +66,7 @@ public class ErrorLogging fw = new FileWriter(logFile, true); bw = new BufferedWriter(fw); fileOut = new PrintWriter(bw); + System.setErr(new PrintStream(new FileOutputStream(logFile,true))); } catch (IOException e) { @@ -100,7 +103,7 @@ public class ErrorLogging String errorMessage = datetime.format(LocalDateTime.now()) + " - " + error; fileOut.println(errorMessage); fileOut.flush(); - System.err.println(errorMessage); + System.out.println(errorMessage); } /** diff --git a/src/main/java/org/baxter/disco/ocr/Gui.java b/src/main/java/org/baxter/disco/ocr/Gui.java deleted file mode 100644 index 049e3b9..0000000 --- a/src/main/java/org/baxter/disco/ocr/Gui.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.baxter.disco.ocr; - -import javafx.event.*; -import javafx.fxml.*; -import javafx.scene.*; -import javafx.scene.text.Text; - -/** - * To be implemented. - */ -public class Gui -{ -} diff --git a/src/main/java/org/baxter/disco/ocr/GuiController.java b/src/main/java/org/baxter/disco/ocr/GuiController.java new file mode 100644 index 0000000..73c5f64 --- /dev/null +++ b/src/main/java/org/baxter/disco/ocr/GuiController.java @@ -0,0 +1,102 @@ +package org.baxter.disco.ocr; + +import java.util.List; + +import javafx.scene.control.TextField; +import javafx.scene.control.Tooltip; + +/** + * Controller portion of MVC for Accuracy over Life test fixture. + * Mostly wrapper interface between View and Model. + * + * @author Blizzard Finnegan + * @version 0.0.1, 01 Feb, 2023 + */ +public class GuiController +{ + public static List getCameras() + { return GuiModel.getCameras(); } + + public static void showImage(String cameraName) + { GuiModel.showImage(cameraName); } + + public static void toggleCrop(String cameraName) + { GuiModel.toggleCrop(cameraName); } + + public static void toggleThreshold(String cameraName) + { GuiModel.toggleThreshold(cameraName); } + + public static void saveDefaults() + { GuiModel.saveDefaults(); } + + public static void save() + { GuiModel.save(); } + + public static void saveClose() + { GuiModel.save(); GuiModel.enableProcessing(); } + + public static String getConfigValue(String cameraName, ConfigProperties property) + { return GuiModel.getConfigVal(cameraName,property); } + + public static void setConfigValue(String cameraName, ConfigProperties property, double value) + { GuiModel.setConfigVal(cameraName,property,value); } + + public static void setIterationCount(int iterationCount) + { GuiModel.setIterations(iterationCount); } + + public static void interruptTests() + { GuiModel.interruptTesting(); } + + public static void runTests() + { GuiModel.runTests(); } + + public static void testMotions() + { GuiModel.testMovement(); } + + public static void updateStart() + { + boolean ready = GuiModel.isReady(); + GuiView.getStart().setDisable(ready); + if(ready) GuiView.getStart().setTooltip(new Tooltip("Start running automated testing.")); + } + + public static void updateIterations() + { + String newIterations = Integer.toString(GuiModel.getIterations()); + GuiView.getIterationField().setPromptText(newIterations); + GuiView.getIterationField().setText(newIterations); + } + + 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)); + } + + public static void runningUpdate(int index) + { userUpdate("Running iteration " + index + "..."); } + + public static void userUpdate(String output) + { GuiView.getFeedbackText().setText(output); } + + public static void testingMotions() + { userUpdate("Testing fixture movement..."); } + + public static void testingMotionSuccessful() + { userUpdate("Fixture movement test successful!"); } + + public static void testingMotionUnsuccessful(String failurePoint) + { userUpdate("Fixture movement unsuccessful! Fail point: " + failurePoint);} + + public static void pressButton() + { GuiModel.pressButton(); } + + public static void updatePrime() + { GuiModel.updatePrime(); } + + public static String getIterationCount() + { return Integer.toString(GuiModel.getIterations()); } + + 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 new file mode 100644 index 0000000..6d88604 --- /dev/null +++ b/src/main/java/org/baxter/disco/ocr/GuiModel.java @@ -0,0 +1,160 @@ +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. + * + * @author Blizzard Finnegan + * @version 0.0.2, 03 Feb, 2023 + */ +public class GuiModel +{ + private static boolean readyToRun = false; + + private static int iterationCount = 3; + + public static final Lock LOCK = new ReentrantLock(); + + private static Thread testingThread = new Thread(); + + private static final MovementFacade fixture = new MovementFacade(LOCK); + + public static void ready() { readyToRun = true; GuiController.updateStart(); } + + public static boolean isReady() { return readyToRun; } + + public static void setIterations(int iterations) + { + iterationCount = iterations; + GuiController.updateIterations(); + } + + public static int getIterations() { return iterationCount; } + + public static void testMovement() + { + GuiController.testingMotions(); + boolean success = fixture.testMotions(); + if(success) GuiController.testingMotionSuccessful(); + else GuiController.testingMotionUnsuccessful("Unknown"); + } + + public static List getCameras() + { return new ArrayList<>(OpenCVFacade.getCameraNames()); } + + public static void showImage(String cameraName) { OpenCVFacade.showImage(cameraName); } + + public static void setConfigVal(String cameraName, ConfigProperties property, double value) + { + ConfigFacade.setValue(cameraName,property,value); + GuiController.updateConfigValue(cameraName,property); + } + + public static String getConfigVal(String cameraName, ConfigProperties property) + { return Double.toString(ConfigFacade.getValue(cameraName,property)); } + + public static void pressButton() + { fixture.pressButton(); } + + 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)); + } + } + + public static void enableProcessing() + { + for(String camera : getCameras()) + { + ConfigFacade.setValue(camera,ConfigProperties.CROP, 1.0); + ConfigFacade.setValue(camera,ConfigProperties.THRESHOLD, 1.0); + } + } + + public static void saveDefaults() + { ConfigFacade.saveDefaultConfig(); } + + public static void save() + { ConfigFacade.saveCurrentConfig(); ConfigFacade.loadConfig(); } + + public static void toggleThreshold(String cameraName) + { + boolean old = (ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) == 0.0 ); + ConfigFacade.setValue(cameraName,ConfigProperties.THRESHOLD,(old ? 1 : 0)); + } + + public static void toggleCrop(String cameraName) + { + boolean old = (ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) == 0.0 ); + ConfigFacade.setValue(cameraName,ConfigProperties.CROP,(old ? 1 : 0)); + } + + public static void runTests() + { + testingThread = new Thread(() -> + { + DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation(),OpenCVFacade.getCameraNames().size()); + boolean prime = false; + for(String cameraName : OpenCVFacade.getCameraNames()) + { + if(ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0) + { + prime = true; + } + } + for(int i = 0; i < iterationCount; i++) + { + Map resultMap = new HashMap<>(); + LOCK.lock(); + fixture.iterationMovement(prime); + LOCK.unlock(); + LOCK.lock(); + List iteration = OpenCVFacade.singleIteration(); + LOCK.unlock(); + for(File file : iteration) + { + LOCK.lock(); + Double result = TesseractFacade.imageToDouble(file); + LOCK.unlock(); + LOCK.lock(); + String fileLocation = file.getAbsolutePath(); + LOCK.unlock(); + LOCK.lock(); + resultMap.put(fileLocation,result); + LOCK.unlock(); + LOCK.lock(); + ErrorLogging.logError("DEBUG: Tesseract final output: " + result); + LOCK.unlock(); + } + LOCK.lock(); + DataSaving.writeValues(i,resultMap); + LOCK.unlock(); + GuiController.runningUpdate(i); + } + //println("======================================="); + ErrorLogging.logError("Testing complete!"); + }); + testingThread.run(); + } + + public static void close() + { + ErrorLogging.logError("DEBUG: PROGRAM CLOSING."); + fixture.closeGPIO(); + ErrorLogging.logError("DEBUG: END OF PROGRAM."); + ErrorLogging.closeLogs(); + } + + public static void interruptTesting() { testingThread.interrupt(); } +} diff --git a/src/main/java/org/baxter/disco/ocr/GuiStarter.java b/src/main/java/org/baxter/disco/ocr/GuiStarter.java new file mode 100644 index 0000000..2309f9e --- /dev/null +++ b/src/main/java/org/baxter/disco/ocr/GuiStarter.java @@ -0,0 +1,16 @@ +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 new file mode 100644 index 0000000..418dcf1 --- /dev/null +++ b/src/main/java/org/baxter/disco/ocr/GuiView.java @@ -0,0 +1,519 @@ +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.ImageView; +import javafx.scene.layout.*; +import javafx.scene.text.*; +import javafx.stage.Stage; + +/** + * View portion of MVC for the Accuracy Over Life test fixture. + * + * @author Blizzard Finnegan + * @version 0.0.1, 01 Feb, 2023 + */ +public class GuiView extends Application +{ + public static final Scene MAIN_MENU; + private static final AnchorPane MAIN_ANCHOR; + private static final Pane MAIN_PANE; + + public static final Scene CAMERA_MENU; + private static final AnchorPane CAMERA_ANCHOR; + private static final Pane CAMERA_PANE; + + private static final Map> uiFields = new HashMap<>(); + + private static Text userFeedback; + + private static TextField iterationField; + + private static Button startButton; + + private static Stage STAGE; + + public static void main(String[] args) { launch(args); } + + 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(); + AnchorPane.setTopAnchor(MAIN_PANE,10.0); + AnchorPane.setLeftAnchor(MAIN_PANE,10.0); + AnchorPane.setRightAnchor(MAIN_PANE,10.0); + AnchorPane.setBottomAnchor(MAIN_PANE,10.0); + 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(); + AnchorPane.setTopAnchor(CAMERA_PANE,10.0); + AnchorPane.setLeftAnchor(CAMERA_PANE,10.0); + AnchorPane.setRightAnchor(CAMERA_PANE,10.0); + AnchorPane.setBottomAnchor(CAMERA_PANE,10.0); + CAMERA_ANCHOR.getChildren().add(CAMERA_PANE); + CAMERA_MENU = new Scene(CAMERA_ANCHOR); + + for(String camera : GuiModel.getCameras()) + uiFields.put(camera, new HashMap<>()); + } + + @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."); + } + + private static void cameraMenuBuilder() + { + VBox layout = new VBox(); + layout.setSpacing(5.0); + 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(cameraSetup(cameraName)); + index++; + } + layout.getChildren().add(cameraMenuButtons()); + CAMERA_PANE.getChildren().add(layout); + } + + private static void mainMenuBuilder() + { + VBox layout = new VBox(); + layout.getChildren().addAll(topHalf(), + new Separator(Orientation.HORIZONTAL), + bottomHalf()); + MAIN_PANE.getChildren().add(layout); + } + + + private static VBox topHalf() + { + VBox output = new VBox(); + output.setSpacing(5.0); + output.getChildren().addAll(topButtons(), + new Separator(Orientation.HORIZONTAL), + setupSection(), + primeCheckbox(), + testFeedback()); + return output; + } + + 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.setId("primeCheckbox"); + output.selectedProperty().addListener( + (obeservableValue, oldValue, newValue) -> + { + GuiController.updatePrime(); + }); + return output; + } + + private static HBox testFeedback() + { + HBox output = new HBox(); + output.setSpacing(5.0); + Label textboxLabel = new Label("Test feedback: "); + Text textbox = new Text("Awaiting input..."); + userFeedback = textbox; + textbox.setId("testOutputToUser"); + + output.getChildren().addAll(textboxLabel,textbox); + return output; + } + + 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; + } + + private static HBox topButtons() + { + HBox topButtons = new HBox(); + topButtons.setSpacing(5.0); + topButtons.setAlignment(Pos.CENTER); + topButtons.setMinWidth(Region.USE_COMPUTED_SIZE); + topButtons.setMinHeight(Region.USE_COMPUTED_SIZE); + + final Button START = buttonBuilder("Start",true); + startButton = START; + final Button STOP = buttonBuilder("Stop",true); + START.setOnAction( (event) -> + { + START.setDisable(true); + STOP.setDisable(false); + GuiController.runTests(); + }); + START.setTooltip(new Tooltip("Configure cameras to start the program.")); + + STOP.setOnAction( (event) -> + { + GuiController.interruptTests(); + START.setDisable(false); + STOP.setDisable(true); + }); + + STOP.setTooltip(new Tooltip("Pauses current iteration.")); + + Button calibrateCamera = buttonBuilder("Calibrate Cameras",false); + calibrateCamera.setOnAction( (event) -> STAGE.setScene(CAMERA_MENU) ); + + Button testMovement = buttonBuilder("Test Movement",false); + testMovement.setOnAction( (event) -> GuiController.testMotions() ); + + Button cancel = buttonBuilder("Close",false); + cancel.setOnAction( (event) -> + { + GuiController.closeModel(); + STAGE.close(); + }); + + + topButtons.getChildren().addAll(START, + STOP, + calibrateCamera, + testMovement, + cancel); + return topButtons; + } + + private static HBox bottomHalf() + { + HBox output = new HBox(); + output.setAlignment(Pos.CENTER); + output.setSpacing(5.0); + + int index = 0; + for(String camera : GuiModel.getCameras()) + { + if(index != 0) output.getChildren().add(new Separator(Orientation.VERTICAL)); + output.getChildren().add(camera(camera)); + } + return output; + } + + private static VBox camera(String cameraName) + { + 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."); + output.getChildren().addAll(cameraHeader(cameraName), + //serialNumber, + cameraView(cameraName)); + return output; + } + + private static HBox cameraHeader(String cameraName) + { + HBox output = new HBox(); + 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); + CheckBox checkBox = new CheckBox("Active"); + checkBox.setId(prompt.toLowerCase()); + output.getChildren().addAll(label, + checkBox); + return output; + } + + private static HBox cameraView(String cameraName) + { + HBox output = new HBox(); + output.setSpacing(5.0); + output.setAlignment(Pos.CENTER_LEFT); + + Label label = new Label("OCR Read:"); + Label ocrRead = new Label("[ ]"); + ocrRead.setId("cameraOCR-" + cameraName); + ImageView imageView = new ImageView(); + output.getChildren().addAll(label, + ocrRead, + imageView); + return output; + } + + private static VBox cameraSetup(String cameraName) + { + VBox output = new VBox(); + output.setSpacing(5.0); + output.setAlignment(Pos.CENTER_LEFT); + + Label sectionHeader = new Label("Camera: " + cameraName); + output.getChildren().addAll(sectionHeader, + processingInputs(cameraName), + cropInputs(cameraName), + miscInputs(cameraName)); + return output; + } + + private static HBox processingInputs(String cameraName) + { + HBox output = new HBox(); + output.setSpacing(5.0); + output.setAlignment(Pos.CENTER_LEFT); + + Button preview = buttonBuilder("Preview"); + preview.setId("previewButton-" + cameraName); + preview.setOnAction( (event) -> + { + GuiController.pressButton(); + try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); } + GuiController.showImage(cameraName); + }); + + CheckBox cropPreview = new CheckBox("Crop preview"); + cropPreview.setId("cropToggle-" + cameraName); + cropPreview.selectedProperty().addListener((obeservableValue, oldValue, newValue) -> + GuiController.toggleCrop(cameraName)); + + CheckBox thresholdPreview = new CheckBox("Threshold preview"); + thresholdPreview.selectedProperty().addListener((obeservableValue, oldValue, newValue) -> + GuiController.toggleThreshold(cameraName)); + cropPreview.setId("thresholdToggle-" + cameraName); + + output.getChildren().addAll(preview, + cropPreview, + thresholdPreview); + return output; + } + + private static HBox cropInputs(String cameraName) + { + HBox output = new HBox(); + output.setSpacing(5.0); + 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"); + + 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"); + + 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"); + + 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"); + + output.getChildren().addAll(cropX, + cropY, + cropW, + cropH); + return output; + } + + private static HBox miscInputs(String cameraName) + { + HBox output = new HBox(); + output.setSpacing(5.0); + 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"); + + 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"); + + output.getChildren().addAll(thresholdValue, + compositeFrames); + return output; + } + + 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(); + }); + + Button save = buttonBuilder("Save"); + save.setOnAction( (event) -> + { + GuiController.save(); + }); + + Button saveClose = buttonBuilder("Save and Close"); + saveClose.setOnAction( (event) -> + { + GuiController.saveClose(); + 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; + } + + private static void textFieldSetup(HBox hbox, ConfigProperties property, String cameraName, String oldId) + { + 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; + } + }); + } + + 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; + } + + private static Button buttonBuilder(String name) + { return buttonBuilder(name,false); } + + private static HBox userTextField(String prompt, String baseValue, String description) + { + HBox output = new HBox(); + output.setSpacing(5.0); + 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; + } + + public static TextField getField(String cameraName, ConfigProperties property) + { return uiFields.get(cameraName).get(property); } + + public static Button getStart() + { return startButton; } + + public static Text getFeedbackText() + { return userFeedback; } + + public static TextField getIterationField() + { return iterationField; } +} diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index 1ba0cec..675cba8 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -1,5 +1,7 @@ package org.baxter.disco.ocr; +import java.util.concurrent.locks.Lock; + import com.pi4j.Pi4J; import com.pi4j.context.Context; import com.pi4j.io.gpio.digital.DigitalInput; @@ -19,10 +21,46 @@ import com.pi4j.io.pwm.PwmType; * Currently missing Run switch compatibility. * * @author Blizzard Finnegan - * @version 1.1.0, 27 Jan. 2023 + * @version 2.1.0, 03 Feb. 2023 */ public class MovementFacade { + /** + * Constructor for MovementFacade. + * + * @param LOCK A Lock object, used for interactions with + * the physical lock switch on the fixture. + */ + public MovementFacade(Lock LOCK) + { + //ErrorLogging.logError("DEBUG: Starting lock thread..."); + runSwitchThread = new Thread(() -> + { + boolean unlock = false; + while(true) + { + if(runSwitch.isOn()) + { + ErrorLogging.logError("Run switch turned off!"); + while(!LOCK.tryLock()) + {} + unlock = true; + } + else + { + //ErrorLogging.logError("Run switch on!"); + if(unlock) + { LOCK.unlock(); unlock = false; } + } + //try{ Thread.sleep(100); } catch(Exception e) { ErrorLogging.logError(e); } + } + }, "Run switch monitor."); + runSwitchThread.start(); + //ErrorLogging.logError("DEBUG: Lock thread started!"); + } + + private static Thread runSwitchThread; + //Externally Available Variables /** * PWM Frequency @@ -37,7 +75,7 @@ public class MovementFacade /** * Number of seconds to wait before timing out a fixture movement. */ - private static int TIME_OUT = 20; + private static int TIME_OUT = 10; //PWM Addresses //All addresses are in BCM format. @@ -95,7 +133,7 @@ public class MovementFacade */ private static DigitalInput lowerLimit; - /**TODO: Multithreading; + /** * Lower limit switch object. * * Status: High; Test may continue. @@ -157,6 +195,7 @@ public class MovementFacade //as the PWM signal is simply a clock for the motor. pwm = pwmBuilder("pwm","PWM Pin",PWM_PIN_ADDR); pwm.on(DUTY_CYCLE, FREQUENCY); + } /** @@ -173,7 +212,6 @@ public class MovementFacade PwmConfigBuilder configBuilder; switch (address) { - //TODO: Set PWM duty cycle //The following pins allow for hardware PWM support. case 12: case 13: @@ -261,7 +299,7 @@ public class MovementFacade * * @return True if the value was set successfully; otherwise false. */ - public static boolean setDutyCycle(int newDutyCycle) + public boolean setDutyCycle(int newDutyCycle) { boolean output = false; if(newDutyCycle < 0) @@ -271,7 +309,6 @@ public class MovementFacade else { DUTY_CYCLE = newDutyCycle; - pwmBuilder("pwm","PWM Pin",PWM_PIN_ADDR); pwm.on(DUTY_CYCLE, FREQUENCY); output = true; } @@ -283,7 +320,7 @@ public class MovementFacade * * @return The current DutyCycle. */ - public static int getDutyCycle() { return DUTY_CYCLE; } + public int getDutyCycle() { return DUTY_CYCLE; } /** * Setter for the fixture's time to give up on a movement. @@ -292,7 +329,7 @@ public class MovementFacade * * @return True if the value was set successfully; otherwise false. */ - public static boolean setTimeout(int newTimeout) + public boolean setTimeout(int newTimeout) { boolean output = false; if(newTimeout < 0) @@ -312,7 +349,7 @@ public class MovementFacade * * @return The current timeout. */ - public static int getTimeout() { return TIME_OUT; } + public int getTimeout() { return TIME_OUT; } /** * Setter for the fixture's PWM frequency. @@ -321,7 +358,7 @@ public class MovementFacade * * @return True if the value was set successfully; otherwise false. */ - public static boolean setFrequency(int newFrequency) + public boolean setFrequency(int newFrequency) { boolean output = false; if(newFrequency < 0) @@ -331,7 +368,6 @@ public class MovementFacade else { FREQUENCY = newFrequency; - pwmBuilder("pwm","PWM Pin",PWM_PIN_ADDR); pwm.on(DUTY_CYCLE, FREQUENCY); output = true; } @@ -343,7 +379,7 @@ public class MovementFacade * * @return The current PWM frequency. */ - public static int getFrequency() { return FREQUENCY; } + public int getFrequency() { return FREQUENCY; } /** * Internal function to send the fixture to one limit switch or another. @@ -352,7 +388,7 @@ public class MovementFacade * @param timeout How long (in seconds) to wait before timing out. * @return true if movement was successful; otherwise false */ - private static boolean gotoLimit(boolean moveUp, int timeout) + private boolean gotoLimit(boolean moveUp, int timeout) { boolean output = false; DigitalInput limitSense; @@ -388,7 +424,7 @@ public class MovementFacade * @param timeout How long (in seconds) to wait before timing out. * @return true if movement was successful; otherwise false */ - public static boolean goDown(int timeout) { return gotoLimit(false, timeout); } + public boolean goDown(int timeout) { return gotoLimit(false, timeout); } /** * Send the fixture to the upper limit switch. @@ -396,7 +432,7 @@ public class MovementFacade * @param timeout How long (in seconds) to wait before timing out. * @return true if movement was successful; otherwise false */ - public static boolean goUp(int timeout) { return gotoLimit(true, timeout); } + public boolean goUp(int timeout) { return gotoLimit(true, timeout); } /** * Send the fixture to the lower limit switch. @@ -404,7 +440,7 @@ public class MovementFacade * * @return true if movement was successful; otherwise false */ - public static boolean goDown() { return goDown(TIME_OUT); } + public boolean goDown() { return goDown(TIME_OUT); } /** * Send the fixture to the upper limit switch. @@ -412,12 +448,12 @@ public class MovementFacade * * @return true if movement was successful; otherwise false */ - public static boolean goUp() { return goUp(TIME_OUT); } + public boolean goUp() { return goUp(TIME_OUT); } /** * Extends the piston for 1 second, pushing the button on the DUT. */ - public static void pressButton() + public void pressButton() { ErrorLogging.logError("DEBUG: Pressing button..."); pistonActivate.on(); @@ -429,9 +465,13 @@ public class MovementFacade /** * Closes connections to all GPIO pins. */ - public static void closeGPIO() + public void closeGPIO() { goUp(); + if(runSwitchThread.isAlive()) + { + runSwitchThread.interrupt(); + } pi4j.shutdown(); } @@ -440,7 +480,7 @@ public class MovementFacade * * @return True if all movements worked properly; otherwise False */ - public static boolean testMotions() + public boolean testMotions() { boolean output = goUp(); if(!output) return output; @@ -452,7 +492,7 @@ public class MovementFacade return output; } - public static void iterationMovement(boolean prime) + public void iterationMovement(boolean prime) { goUp(); if(prime) pressButton(); @@ -460,17 +500,9 @@ public class MovementFacade pressButton(); } - public static void main(String[] args) + public void main(String[] args) { testMotions(); closeGPIO(); } - - //TODO: Multithreading, to allow for RunSwitch Interrupts. -// protected class RunSwitchInterrupt implements Runnable -// { -// public void run() -// { -// } -// } } diff --git a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java index 6019c67..19c1036 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.0.0, 27 Jan. 2023 + * @version 1.2.0, 03 Feb. 2023 */ public class OpenCVFacade { @@ -73,9 +73,6 @@ public class OpenCVFacade * * @param name Name of the new camera * @param location Location of the new camera - * - * @return false if camera already exists, or if camera does not exist. - * Otherwise, returns true. */ private static void newCamera(String name, String location) { @@ -91,9 +88,6 @@ public class OpenCVFacade * @param location Location of the new camera * @param width Width of the camera's image, in pixels. * @param height height of the camera's image, in pixels. - * - * @return false if camera already exists, or if camera does not exist. - * Otherwise, returns true. */ private static void newCamera(String name, String location, int width, int height) { @@ -109,9 +103,6 @@ public class OpenCVFacade * @param width Width of the camera's image, in pixels. * @param height height of the camera's image, in pixels. * @param codec Codec to use for the new camera. - * - * @return false if camera already exists, or if camera does not exist. - * Otherwise, returns true. */ private static void newCamera(String name, String location, int width, int height, String codec) { @@ -270,11 +261,12 @@ public class OpenCVFacade * * @return Frame of the thresholded image */ - public static Mat thresholdImage(Mat image) + public static Mat thresholdImage(Mat image,String cameraName) { Mat output = image; Mat in = image; - threshold(in,output,50,255,THRESH_BINARY); + double thresholdValue = ConfigFacade.getValue(cameraName,ConfigProperties.THRESHOLD_VALUE); + threshold(in,output,thresholdValue,255,THRESH_BINARY); return output; } @@ -299,15 +291,12 @@ public class OpenCVFacade /** * Compose several images together. * This will also perform thresholding, and cropping, - * based on boolean toggles. + * based on boolean toggles. Crop information is collected + * from {@link ConfigFacade}. * * @param images List of images to be composed * @param threshold Whether to put the image through a binary threshold * @param crop Whether to crop the image - * @param x X-coordinate of the top-left of the cropped portion of the image. - * @param y y-coordinate of the top-left of the cropped portion of the image. - * @param width width of the the cropped portion of the image. - * @param height height of the the cropped portion of the image. * * @return A single image, found by boolean AND-ing together all parsed images. */ @@ -323,7 +312,7 @@ public class OpenCVFacade image.copyTo(processedImage); if(crop) { - ErrorLogging.logError("DEBUG: Cropping image " + iterationCount + "..."); + //ErrorLogging.logError("DEBUG: Cropping image " + iterationCount + "..."); processedImage = crop(processedImage,cameraName); //String fileLocation = ConfigFacade.getImgSaveLocation() + "/debug/" // + ErrorLogging.fileDatetime.format(LocalDateTime.now()) + @@ -333,8 +322,8 @@ public class OpenCVFacade } if(threshold) { - ErrorLogging.logError("DEBUG: Thresholding image " + iterationCount + "..."); - processedImage = thresholdImage(processedImage); + //ErrorLogging.logError("DEBUG: Thresholding image " + iterationCount + "..."); + processedImage = thresholdImage(processedImage,cameraName); //String fileLocation = ConfigFacade.getImgSaveLocation() + "/debug/" // + ErrorLogging.fileDatetime.format(LocalDateTime.now()) + // "." + iterationCount + "-post-threshold.jpg"; @@ -355,7 +344,7 @@ public class OpenCVFacade { output = processedImage.clone(); } - ErrorLogging.logError("DEBUG: Thresholding image " + iterationCount + "..."); + //ErrorLogging.logError("DEBUG: Thresholding image " + iterationCount + "..."); bitwise_and((iterationCount == 1 ? processedImage : output),processedImage, output); iterationCount++; @@ -451,6 +440,7 @@ public class OpenCVFacade for(String cameraName : getCameraNames()) { output.add(completeProcess(cameraName, ConfigFacade.getImgSaveLocation())); + ErrorLogging.logError("DEBUG: ---------------------------------"); } return output; } diff --git a/src/main/java/org/baxter/disco/ocr/cameraTest.fxml b/src/main/java/org/baxter/disco/ocr/cameraTest.fxml deleted file mode 100644 index 138b7c3..0000000 --- a/src/main/java/org/baxter/disco/ocr/cameraTest.fxml +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -