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 64a1281..889f580 100644 --- a/README.md +++ b/README.md @@ -16,40 +16,128 @@ This is a personal/professional project, which makes use of JavaCV, OpenCV, Tess - [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, ommited for smaller build size) ## 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`,`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 + +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: +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: ``` 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 c3c9cc5..22b38d0 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.${uitype} @@ -101,12 +101,12 @@ 2.8.0 11 2.1.0 + Cli raspberry 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 614b1e1..77289ad 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. @@ -14,6 +14,7 @@ + Cli UTF-8 11 11 @@ -29,7 +30,6 @@ 2.8.0 1.5.6 5.9.1 - 4.1.1 1.9.4 @@ -87,11 +87,11 @@ - + @@ -155,7 +155,7 @@ - org.baxter.disco.ocr.Cli + org.baxter.disco.ocr.${uitype} 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/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 5e9baf5..c32b463 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; @@ -50,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. @@ -60,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()} @@ -72,6 +71,8 @@ public class Cli */ private static MovementFacade fixture; + //private static Thread safeThread; + static { ErrorLogging.logError("DEBUG: START OF PROGRAM"); @@ -109,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): "); @@ -132,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()) @@ -151,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() @@ -229,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(); @@ -256,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("======================================"); } @@ -320,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 */ @@ -342,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"); @@ -355,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("===================================="); } @@ -460,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 @@ -511,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); @@ -586,56 +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; - 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) { - if(cameraName != null) { /*println(cameraName);*/ } - else ErrorLogging.logError("Null camera!"); - if(ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0) - { - prime = true; - } + prime = true; } - 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++) + if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0) + { + cameraList.add(cameraName); + } + } + 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); + LOCK.unlock(); + for(String cameraName : cameraList) { - Map resultMap = new HashMap<>(); while(!LOCK.tryLock()) {} - fixture.iterationMovement(prime); + File file = OpenCVFacade.completeProcess(cameraName); 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); + cameraToFile.replace(cameraName,file); LOCK.unlock(); } - println("======================================="); - println("Testing complete!"); + 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); + 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/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 deleted file mode 100644 index 73c5f64..0000000 --- a/src/main/java/org/baxter/disco/ocr/GuiController.java +++ /dev/null @@ -1,102 +0,0 @@ -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 deleted file mode 100644 index 6d88604..0000000 --- a/src/main/java/org/baxter/disco/ocr/GuiModel.java +++ /dev/null @@ -1,160 +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. - * - * @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 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 418dcf1..0000000 --- a/src/main/java/org/baxter/disco/ocr/GuiView.java +++ /dev/null @@ -1,519 +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.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 675cba8..5eea773 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()) { @@ -65,7 +66,7 @@ public class MovementFacade /** * PWM Frequency */ - private static int FREQUENCY = 60000; + private static int FREQUENCY = 70000; /** * PWM Duty Cycle @@ -75,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. @@ -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 19c1036..8af4337 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 * @@ -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()); @@ -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. * @@ -406,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) @@ -424,9 +441,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..d525517 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,17 +63,18 @@ 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()) { 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."); 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