diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index bb421ef..0211333 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.3.5 + 4.3.6 Testing Discos for long-term accuracy, using automated optical character recognition. Baxter International @@ -91,9 +91,6 @@ maven-javadoc-plugin 3.4.1 - - private - diff --git a/pom.xml b/pom.xml index af94a17..9ba0377 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.baxter.disco ocr - 4.3.5 + 4.3.6 jar Disco OCR Accuracy Over Life Testing Testing Discos for long-term accuracy, using automated optical character recognition. @@ -47,7 +47,6 @@ javacv-platform ${opencv.version} - org.openjfx javafx-swing @@ -91,13 +90,6 @@ pi4j-plugin-pigpio ${pi4j.version} - - - @@ -184,30 +176,6 @@ - @@ -216,9 +184,9 @@ org.apache.maven.plugins maven-javadoc-plugin 3.4.1 - + diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index 8012638..45c5181 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -18,14 +18,14 @@ import java.util.concurrent.locks.ReentrantLock; * classes). * * @author Blizzard Finnegan - * @version 1.7.0, 06 Mar. 2023 + * @version 1.7.1, 10 Mar. 2023 */ public class Cli { /** * Complete build version number */ - private static final String version = "4.3.5"; + private static final String version = "4.3.6"; /** * Currently saved iteration count. @@ -65,12 +65,10 @@ public class Cli */ public static final Lock LOCK = new ReentrantLock(); - //private static Thread safeThread; - //Start of program message; always runs first static { - ErrorLogging.logError("START OF PROGRAM"); + ErrorLogging.logError("DEBUG: START OF PROGRAM"); } public static void main(String[] args) @@ -354,13 +352,10 @@ public class Cli MovementFacade.iterationMovement(true); double tesseractValue = 0.0; - //Main camera config loop do { - //Show the menu printCameraMenu(cameraList); - //Pick a camera to configure int userInput; String cameraName = ""; do @@ -370,12 +365,10 @@ public class Cli 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) continue; else cameraName = cameraList.get((userInput)); - //Single camera config loop do { //Press button twice, to make sure the DUT is awake @@ -384,17 +377,12 @@ public class Cli MovementFacade.pressButton(); try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); } - //Show image File image = OpenCVFacade.showImage(cameraName); - - //Parse the image with Tesseract, to show user what the excel output will be tesseractValue = TesseractFacade.imageToDouble(image); - //User input parsing ConfigProperties modifiedProperty = null; do { - //list configurable settings printCameraConfigMenu(cameraName,tesseractValue); userInput = (int)inputFiltering(inputScanner.nextLine(),Menus.CAMERA); @@ -422,7 +410,6 @@ public class Cli } } while(modifiedProperty == null); - //Toggle threshold/crop if(modifiedProperty == ConfigProperties.THRESHOLD || modifiedProperty == ConfigProperties.CROP) { @@ -431,11 +418,9 @@ public class Cli ConfigFacade.setValue(cameraName,modifiedProperty,newValue); } - //Redefine crop points else if(modifiedProperty == ConfigProperties.CROP_X) { OpenCVFacade.setCrop(cameraName); } - //Modify number of composite frames, or threshold value else if(modifiedProperty == ConfigProperties.COMPOSITE_FRAMES || modifiedProperty == ConfigProperties.THRESHOLD_VALUE) { @@ -447,7 +432,6 @@ public class Cli ConfigFacade.setValue(cameraName,modifiedProperty,userInput); } - //Exit loop else { ConfigFacade.saveCurrentConfig(); @@ -457,7 +441,6 @@ public class Cli } while(true); - //Save the current config to the config file ConfigFacade.saveCurrentConfig(); println("Configuration complete!"); } @@ -467,15 +450,11 @@ public class Cli */ private static void setDUTSerials() { - //Get a list of available cameras List cameraList = new ArrayList<>(OpenCVFacade.getCameraNames()); - //Main serial setting loop do { - //Main menu printSerialMenu(cameraList); - //Pick a camera to configure int userInput; String cameraName = ""; do @@ -486,12 +465,9 @@ public class Cli 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)); - //Save the serial number. - //No parsing is ever done on this serial number. prompt("Enter the serial number you wish to use for this camera: "); ConfigFacade.setSerial(cameraName,inputScanner.nextLine()); @@ -518,16 +494,12 @@ public class Cli */ private static void setActiveCameras() { - //Get available cameras List cameraList = new ArrayList<>(OpenCVFacade.getCameraNames()); - //Main loop do { - //Print menu printActiveToggleMenu(cameraList); - //Pick a camera to configure int userInput; String cameraName = ""; do @@ -537,11 +509,9 @@ public class Cli 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)); - //Toggle whether the camera is active, at the config level double newValue = ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE); newValue = Math.abs(newValue - 1); ConfigFacade.setValue(cameraName,ConfigProperties.ACTIVE,newValue); @@ -561,22 +531,18 @@ public class Cli //useful for multithreading, which isn't necessary in CLI final int localIterations = iterationCount; - //Save whether to prime the devices; defaults to false + //Hide legacy functionality boolean prime = false; - //Create a List of *active* cameras. List cameraList = new ArrayList<>(); for(String cameraName : OpenCVFacade.getCameraNames()) { prime = (ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0) || prime; if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0) - { cameraList.add(cameraName); - } } - //Initialise the workbook, with the number of cameras and the final output location DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation(),cameraList.size()); //Wake the device, then wait to ensure they're awake before continuing @@ -584,7 +550,6 @@ public class Cli MovementFacade.pressButton(); try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); } - //Create final maps for result images, result values, and camera names Map resultMap = new HashMap<>(); Map cameraToFile = new HashMap<>(); @@ -595,7 +560,7 @@ public class Cli } ErrorLogging.logError("DEBUG: Starting tests..."); - //Start actually running tests + //All portions of the test check with the GPIO Run/Pause switch before //continuing, using the Lock object. for(int i = 0; i < localIterations; i++) @@ -611,7 +576,6 @@ public class Cli do { fail = false; - //Move the fixture for one iteration, with whether or not the DUTs need to be primed while(!LOCK.tryLock()) {} MovementFacade.iterationMovement(prime); LOCK.unlock(); @@ -619,9 +583,6 @@ public class Cli //Wait for the DUT to display an image try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); } - //For all available cameras: - // take an image, process it, and save it to a file - // put that file into the camera name file Map for(String cameraName : cameraList) { while(!LOCK.tryLock()) {} @@ -633,9 +594,6 @@ public class Cli LOCK.unlock(); } - //ONCE ALL IMAGES ARE CREATED - //Re-iterate over list of cameras, parse the images with Tesseract, then add - //the parsed value to the map for(String cameraName : cameraList) { while(!LOCK.tryLock()) {} @@ -666,16 +624,13 @@ public class Cli } while(fail); - //Write all given values to the Excel file while(!LOCK.tryLock()) {} DataSaving.writeValues(i,resultMap,cameraToFile); LOCK.unlock(); - //Clear the result map //DO NOT CLEAR camera to file Map. This will change the order of the objects within it resultMap.clear(); } - //Alert the user to testing being complete println("======================================="); println("Testing complete!"); } diff --git a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java index 2a79741..5f2804d 100644 --- a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java +++ b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java @@ -24,7 +24,7 @@ import org.apache.commons.configuration2.builder.fluent.Parameters; * Can write to file when requested, reads from file on initial start. * * @author Blizzard Finnegan - * @version 1.3.1, 09 Feb. 2023 + * @version 1.3.2, 10 Mar. 2023 */ public class ConfigFacade { @@ -44,7 +44,7 @@ public class ConfigFacade /** * Location to save output XLSX file to. */ - public static String outputSaveLocation = "outputData/" + + private static String outputSaveLocation = "outputData/" + (LocalDateTime.now().format(ErrorLogging.fileDatetime)) + ".xlsx"; /** @@ -73,16 +73,14 @@ public class ConfigFacade */ private static INIConfiguration CONFIG_STORE; - //This block will ALWAYS run first. static { - //Get config values ErrorLogging.logError("Starting configuration setup..."); - //Give CONFIG_STORE an intentionally bad value CONFIG_STORE = null; - //See if a config file already exists + File configFile = new File(configFileLocation); boolean newConfig = true; + try{ newConfig = configFile.createNewFile(); } catch(Exception e){ ErrorLogging.logError(e); } @@ -90,7 +88,6 @@ public class ConfigFacade CONFIG_BUILDER = new FileBasedConfigurationBuilder<>(INIConfiguration.class) .configure(new Parameters().fileBased().setFile(configFile)); - //Try to import the config ErrorLogging.logError("Attempting to import config..."); if(!newConfig) { @@ -111,8 +108,6 @@ public class ConfigFacade } } - //If there isn't a config file yet (or rather, if we just made a new one) - //save the default values to it else { ErrorLogging.logError("Unable to import config. Loading defaults..."); @@ -121,24 +116,19 @@ public class ConfigFacade else ErrorLogging.logError("Configuration settings set up!"); } - //Make necessary directories, if not already available 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(); + File setupImageLocation = new File(imageSaveLocation + "/config"); + setupImageLocation.mkdir(); File outputFileDirectory = new File("outputData"); outputFileDirectory.mkdir(); - //Create a new output file ErrorLogging.logError("Creating output file...."); File outputFile = new File(outputSaveLocation); try{ outputFile.createNewFile(); } catch(Exception e){ ErrorLogging.logError(e); } - //Autosave the config CONFIG_BUILDER.setAutoSave(true); } @@ -161,7 +151,6 @@ public class ConfigFacade double output = 0.0; if(!configMap.keySet().contains(cameraName)) { - //Log failure information ErrorLogging.logError("CONFIG ERROR!!! - Invalid camera name: " + cameraName); ErrorLogging.logError("\tKey set: " + configMap.keySet().toString()); ErrorLogging.logError("\tProperty: " + property.getConfig()); @@ -170,9 +159,6 @@ public class ConfigFacade } Map cameraConfig = configMap.get(cameraName); output = cameraConfig.get(property); - //Debug logger. - //ErrorLogging.logError("DEBUG: getValue - return value: " + cameraName - // + "/" + property.getConfig() + " = " + output); return output; } @@ -240,12 +226,16 @@ public class ConfigFacade public static boolean setValue(String cameraName, ConfigProperties property, double propertyValue) { boolean output = false; + List activeCameras = new ArrayList<>(OpenCVFacade.getCameraNames()); if(!activeCameras.contains(cameraName)) return output; + Map cameraConfig = configMap.get(cameraName); if(cameraConfig == null) return output; + Double oldValue = cameraConfig.get(property); output = cameraConfig.replace(property,oldValue,propertyValue); + saveCurrentConfig(); return output; } @@ -288,21 +278,15 @@ public class ConfigFacade */ public static boolean saveDefaultConfig(String filename) { - //Get set of camera names boolean output = false; Set cameraNames = OpenCVFacade.getCameraNames(); - //Set the config builder to a file-based, INI configuration, - //with the given filename CONFIG_BUILDER = new FileBasedConfigurationBuilder<>(INIConfiguration.class) .configure(new Parameters().fileBased() .setFile(new File(filename))); - //Set CONFIG_STORE to the Configuration created by the Builder try { CONFIG_STORE = CONFIG_BUILDER.getConfiguration(); } catch(Exception e) { ErrorLogging.logError(e); } - - //If the save default fails, warn the user that something is wrong finally { if(CONFIG_STORE == null) @@ -312,10 +296,6 @@ public class ConfigFacade } } - //Iterate over every camera - // Create a map of the default values - // Save the default values to the CONFIG_STORE - // save the map to the main config map for(String camera : cameraNames) { Map cameraConfig = new HashMap<>(); @@ -324,14 +304,11 @@ public class ConfigFacade String propertyName = camera + "." + property.getConfig(); double propertyValue = property.getDefaultValue(); cameraConfig.put(property,propertyValue); - //ErrorLogging.logError("DEBUG: Attempting to save to config: "); - //ErrorLogging.logError("DEBUG: " + propertyName + ", " + propertyValue); CONFIG_STORE.setProperty(propertyName,propertyValue); } configMap.put(camera,cameraConfig); } - //Save out to the file try { CONFIG_BUILDER.save(); @@ -442,7 +419,6 @@ public class ConfigFacade for(ConfigProperties configState : ConfigProperties.values()) { Double configValue = CONFIG_STORE.getDouble(sectionName + "." + configState.getConfig()); - //ErrorLogging.logError("DEBUG: Imported config value: " + Double.toString(configValue)); savedSection.put(configState,configValue); } } @@ -456,7 +432,6 @@ public class ConfigFacade { for(String key : configMap.keySet()) { - //ErrorLogging.logError("DEBUG: configMap Key: " + key + " ?= " + sectionName); if( key.equals(sectionName)) { configMap.put(key,savedSection); } } @@ -493,8 +468,6 @@ public class ConfigFacade String propertyName = sectionName + "." + property.getConfig(); double propertyValue = property.getDefaultValue(); cameraConfig.put(property,propertyValue); - //ErrorLogging.logError("DEBUG: Attempting to save to config: "); - //ErrorLogging.logError("DEBUG: " + propertyName + ", " + propertyValue); CONFIG_STORE.setProperty(propertyName,propertyValue); } configMap.put(sectionName,cameraConfig); diff --git a/src/main/java/org/baxter/disco/ocr/DataSaving.java b/src/main/java/org/baxter/disco/ocr/DataSaving.java index ba47b76..6a8fed1 100644 --- a/src/main/java/org/baxter/disco/ocr/DataSaving.java +++ b/src/main/java/org/baxter/disco/ocr/DataSaving.java @@ -1,10 +1,10 @@ package org.baxter.disco.ocr; -import java.io.DataInputStream; //Standard imports import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.DataInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -32,7 +32,7 @@ import static org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined; * Facade for saving data out to a file. * * @author Blizzard Finnegan - * @version 5.0.0, 07 Mar. 2023 + * @version 5.0.1, 10 Mar. 2023 */ public class DataSaving { @@ -99,16 +99,13 @@ public class DataSaving //Create workbook, Sheet, and DataFormat object //HSSF objects are used, as these are compatible with Microsoft Excel - //XSSF objects were initially used, but caused issues. outputWorkbook = new HSSFWorkbook(); outputSheet = outputWorkbook.createSheet(); format = outputWorkbook.createDataFormat(); - //Create a default style for values. defaultStyle = outputWorkbook.createCellStyle(); defaultStyle.setDataFormat(format.getFormat("0.0")); - //Create a style for the final percentage values finalValuesStyle = outputWorkbook.createCellStyle(); finalValuesStyle.setDataFormat(format.getFormat("0.000%")); @@ -124,19 +121,16 @@ public class DataSaving failStyle.setDataFormat(format.getFormat("0.0")); failStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); - //Create a style for error-ed, but not out-of-range, values errorStyle = outputWorkbook.createCellStyle(); errorStyle.setFillForegroundColor(HSSFColorPredefined.YELLOW.getIndex()); errorStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); - //Create the header int startingRow = outputSheet.getLastRowNum(); HSSFRow row = outputSheet.createRow(++startingRow); int cellnum = 0; HSSFCell cell = row.createCell(cellnum++); cell.setCellValue("Iteration"); - //Create a section for every camera for(int i = 0; i < camCount; i++) { cell = row.createCell(cellnum++); @@ -153,7 +147,6 @@ public class DataSaving HSSFCell passPercentCell = row.createCell(cellnum++); passPercentCell.setCellValue("Pass %"); - //Save to file try (FileOutputStream outputStream = new FileOutputStream(outputFile)) { outputWorkbook.write(outputStream); } catch(Exception e) {ErrorLogging.logError(e);} @@ -175,10 +168,8 @@ public class DataSaving int serialColumn = lastColumnOfData - 2; int percentColumn = lastColumnOfData - 1; - //Get the last row, add another row below it, and name the first cell "Totals:" int lastRowOfData = outputSheet.getLastRowNum(); - //For each camera, create a unique total line int column = 1; for(int i = 0; i < cameraCount; i++) { @@ -219,7 +210,6 @@ public class DataSaving column += 4; } - //Once all totals have been created, write to the file try (FileOutputStream outputStream = new FileOutputStream(outputFile)) { outputWorkbook.write(outputStream); } catch(Exception e) {ErrorLogging.logError(e);} @@ -238,10 +228,8 @@ public class DataSaving boolean output = false; int cellnum = 0; int startingRow = outputSheet.getLastRowNum(); - HSSFRow row = (startingRow == 1) ? outputSheet.getRow(++startingRow) : outputSheet.createRow(++startingRow); + HSSFRow row = (cycle == 1) ? outputSheet.getRow(startingRow) : outputSheet.createRow(++startingRow); List cameraNames = new ArrayList<>(cameraToFile.keySet()); - //ErrorLogging.logError("DEBUG: image locations: " + imageLocations.toString()); - //List objectArray = new LinkedList<>(); cycle++; @@ -249,12 +237,10 @@ public class DataSaving indexCell.setCellValue(cycle); for(String cameraName : cameraNames) { - //put serial number into sheet String serialNumber = ConfigFacade.getSerial(cameraName); HSSFCell serialCell = row.createCell(cellnum++); serialCell.setCellValue(serialNumber); - //Put the generated image into the spreadsheet File file = cameraToFile.get(cameraName); HSSFCell imageCell = row.createCell(cellnum++); try diff --git a/src/main/java/org/baxter/disco/ocr/ErrorLogging.java b/src/main/java/org/baxter/disco/ocr/ErrorLogging.java index 0384064..dd78c86 100644 --- a/src/main/java/org/baxter/disco/ocr/ErrorLogging.java +++ b/src/main/java/org/baxter/disco/ocr/ErrorLogging.java @@ -61,10 +61,8 @@ public class ErrorLogging //Make sure the filename formatter is compatible with Windows and Linux fileDatetime = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss"); - //Local formatter for logs datetime = DateTimeFormatter.ISO_LOCAL_DATE_TIME; - //Create a new log file on every run; put them all in a common folder logFile = "logs/" + fileDatetime.format(LocalDateTime.now()) + "-log.txt"; File logDirectory = new File("logs"); File outFile = new File(logFile); diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java index 2d2e141..05166da 100644 --- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java +++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java @@ -20,7 +20,7 @@ import com.pi4j.io.pwm.PwmType; * Currently missing Run switch compatibility. * * @author Blizzard Finnegan - * @version 3.0.0, 06 Mar. 2023 + * @version 3.0.1, 10 Mar. 2023 */ public class MovementFacade { @@ -44,7 +44,7 @@ public class MovementFacade /** * Amount of buffer between the found absolute speed, and used speed. */ - private static final int SPEED_BUFFER = 5000; + private static final int SPEED_BUFFER = 7500; /** * Minimum allowed speed of the fixture arm; also used for reset travels. @@ -190,7 +190,7 @@ public class MovementFacade static { - //ErrorLogging.logError("DEBUG: Starting lock thread..."); + ErrorLogging.logError("DEBUG: Starting lock thread..."); runSwitchThread = new Thread(() -> { boolean unlock = false; @@ -198,7 +198,7 @@ public class MovementFacade { if(runSwitch.isOn()) { - ErrorLogging.logError("Run switch turned off!"); + ErrorLogging.logError("DEBUG: Run switch turned off!"); while(!Cli.LOCK.tryLock()) {} unlock = true; @@ -214,24 +214,19 @@ public class MovementFacade }, "Run switch monitor."); runSwitchThread.start(); - //Initialise Pi4J pi4j = Pi4J.newAutoContext(); - //Initialise input pins upperLimit = inputBuilder("upperLimit", "Upper Limit Switch", UPPER_LIMIT_ADDR); lowerLimit = inputBuilder("lowerLimit", "Lower Limit Switch", LOWER_LIMIT_ADDR); runSwitch = inputBuilder("runSwitch" , "Run Switch" , RUN_SWITCH_ADDR); - //Initialise output pins motorEnable = outputBuilder("motorEnable" , "Motor Enable" , MOTOR_ENABLE_ADDR); motorDirection = outputBuilder("motorDirection", "Motor Direction", MOTOR_DIRECTION_ADDR); pistonActivate = outputBuilder("piston" , "Piston Activate", PISTON_ADDR); - //Initialise PWM object. pwm = pwmBuilder("pwm","PWM Pin",PWM_PIN_ADDR); pwm.on(DUTY_CYCLE, FREQUENCY); - //Find Distance and max speeds calibrate(); } @@ -267,7 +262,6 @@ public class MovementFacade .frequency(FREQUENCY) .provider("pigpio-pwm") .initial(1) - //On program close, turn off PWM. .shutdown(0); break; //Any pin not listed above must be software PWM controlled. @@ -280,7 +274,6 @@ public class MovementFacade .frequency(FREQUENCY) .provider("pigpio-pwm") .initial(1) - //On program close, turn off PWM. .shutdown(0); } return pi4j.create(configBuilder); @@ -299,7 +292,6 @@ public class MovementFacade { DigitalInputConfigBuilder configBuilder = DigitalInput.newConfigBuilder(pi4j) .id(id) - //.name(name) .address(address) .pull(PullResistance.PULL_DOWN) .debounce(3000L) @@ -320,7 +312,6 @@ public class MovementFacade { DigitalOutputConfigBuilder configBuilder = DigitalOutput.newConfigBuilder(pi4j) .id(id) - .name(name) .address(address) .shutdown(DigitalState.LOW) .initial(DigitalState.LOW) @@ -382,8 +373,6 @@ public class MovementFacade resetArm(); ErrorLogging.logError("Calibrating..."); FREQUENCY = calib(MIN_FREQUENCY, MAX_FREQUENCY, 10000); - //ErrorLogging.logError("Fine calibrating..."); - //FREQUENCY = calib(FREQUENCY,(FREQUENCY+10000),1000); ErrorLogging.logError("Calibration complete!"); ErrorLogging.logError("DEBUG: Speed set to " + (FREQUENCY - SPEED_BUFFER)); setFrequency(FREQUENCY - SPEED_BUFFER); @@ -454,7 +443,6 @@ public class MovementFacade */ private static int calib(int start, int max, int iterate) { - //start -= iterate; for(int i = start; i < max; i+=iterate) { if(!setFrequency(i)) diff --git a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java index 80ce246..fe20f69 100644 --- a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java +++ b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java @@ -27,7 +27,6 @@ import java.util.Map; import java.util.Set; import java.io.File; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -37,11 +36,10 @@ import java.util.List; * Performs image capture, as well as image manipulation. * * @author Blizzard Finnegan - * @version 2.1.0, 06 Mar. 2023 + * @version 3.0.0, 10 Mar. 2023 */ public class OpenCVFacade { - //Local variable instantiation /** * Storage of all cameras as Map. * To get available camera names, getKeys. @@ -51,7 +49,7 @@ public class OpenCVFacade /** * Object used to convert between Mats and Frames */ - public static final OpenCVFrameConverter.ToMat MAT_CONVERTER = new OpenCVFrameConverter.ToMat(); + private static final OpenCVFrameConverter.ToMat MAT_CONVERTER = new OpenCVFrameConverter.ToMat(); /** * Width of the image created by the camera. @@ -129,7 +127,6 @@ public class OpenCVFacade */ private static void newCamera(String name, String location, int width, int height, String codec) { - //ErrorLogging.logError("DEBUG: Attempting to create new camera " + name + " from location " + location + "..."); ErrorLogging.logError("Initialising camera : " + name + "..."); File cameraLocation = new File(location); if (cameraLocation.exists()) @@ -171,7 +168,7 @@ public class OpenCVFacade * @return null if camera doesn't exist, or if capture fails; * otherwise, Frame of the taken image */ - public static Mat takePicture(String cameraName) + private static Mat takePicture(String cameraName) { Mat output = null; Frame temp = null; @@ -199,12 +196,8 @@ public class OpenCVFacade */ public static File showImage(String cameraName) { - //ErrorLogging.logError("DEBUG: Showing image from camera: " + cameraName); - //ErrorLogging.logError("DEBUG: camera location: " + cameraMap.get(cameraName).toString()); 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()); Frame outputImage = MAT_CONVERTER.convert(imread(imageLocation.getAbsolutePath())); String canvasTitle = "Camera " + cameraName + " Preview"; final CanvasFrame canvas = new CanvasFrame(canvasTitle); @@ -212,20 +205,18 @@ public class OpenCVFacade return imageLocation; } - /** - * 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(); - } + ///** + // * 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. + // */ + //private static String showImage(String cameraName, Object object) + //{ + // File imageLocation = completeProcess(cameraName,ConfigFacade.getImgSaveLocation() + "/config"); + // return imageLocation.getPath(); + //} /** * Take multiple pictures in quick succession. @@ -235,11 +226,9 @@ public class OpenCVFacade * * @return List of Frames taken from the camera. List is in order */ - public static List takeBurst(String cameraName, int frameCount) + private static List takeBurst(String cameraName, int frameCount) { List output = null; - //ErrorLogging.logError("DEBUG: takeBurst - Camera Name: " + cameraName); - //ErrorLogging.logError("DEBUG: takeBurst - Possible camera names: " + getCameraNames().toString()); if(getCameraNames().contains(cameraName)) { output = new LinkedList<>(); @@ -272,7 +261,7 @@ public class OpenCVFacade * @param image Frame taken from the camera * @param cameraName Name of the camera the frame is from */ - public static Mat crop(Mat image, String cameraName) + private static Mat crop(Mat image, String cameraName) { int x = (int)ConfigFacade.getValue(cameraName,ConfigProperties.CROP_X); int y = (int)ConfigFacade.getValue(cameraName,ConfigProperties.CROP_Y); @@ -290,15 +279,9 @@ public class OpenCVFacade * * @return Frame of the cropped image */ - public static Mat crop(Mat image, Rect roi, String cameraName) + private static Mat crop(Mat image, Rect roi, String cameraName) { Mat output = image.apply(roi).clone(); - //IplImage croppedImage = MAT_CONVERTER.convertToIplImage(MAT_CONVERTER.convert(output)); - //String fileLocation = ConfigFacade.getImgSaveLocation() + "/debug/" - // + ErrorLogging.fileDatetime.format(LocalDateTime.now()) + - // "." + cameraName + "-preProcess.jpg"; - //cvSaveImage(fileLocation,croppedImage); - return output; } @@ -311,7 +294,7 @@ public class OpenCVFacade * * @return Frame of the thresholded image */ - public static Mat thresholdImage(Mat image,String cameraName) + private static Mat thresholdImage(Mat image,String cameraName) { Mat output = image; Mat in = image; @@ -328,7 +311,7 @@ public class OpenCVFacade * * @return File if save was successful, otherwise null */ - public static File saveImage(Mat image, String fileLocation, String cameraName) + private static File saveImage(Mat image, String fileLocation, String cameraName) { File output = null; IplImage temp = MAT_CONVERTER.convertToIplImage(MAT_CONVERTER.convert(image)); @@ -350,64 +333,32 @@ public class OpenCVFacade * * @return A single image, found by boolean AND-ing together all parsed images. */ - public static Mat compose(List images, boolean threshold, + private static Mat compose(List images, boolean threshold, boolean crop, String cameraName) { ErrorLogging.logError("DEBUG: Attempting to compose " + images.size() + " images..."); Mat output = null; int iterationCount = 1; for(Mat image : images) - { //crop and threshold, based on booleans - Mat processedImage = image.clone(); - image.copyTo(processedImage); - if(crop) - { - //ErrorLogging.logError("DEBUG: Cropping image " + iterationCount + "..."); - processedImage = crop(processedImage,cameraName); - //String fileLocation = ConfigFacade.getImgSaveLocation() + "/debug/" - // + ErrorLogging.fileDatetime.format(LocalDateTime.now()) + - // "." + iterationCount + "-post-crop.jpg"; - //cvSaveImage(fileLocation,MAT_CONVERTER.convertToIplImage( - // MAT_CONVERTER.convert(processedImage))); - } - if(threshold) - { - //ErrorLogging.logError("DEBUG: Thresholding image " + iterationCount + "..."); - processedImage = thresholdImage(processedImage,cameraName); - //String fileLocation = ConfigFacade.getImgSaveLocation() + "/debug/" - // + ErrorLogging.fileDatetime.format(LocalDateTime.now()) + - // "." + iterationCount + "-post-threshold.jpg"; - //cvSaveImage(fileLocation,MAT_CONVERTER.convertToIplImage( - // MAT_CONVERTER.convert(processedImage))); - } - //String fileLocation = ConfigFacade.getImgSaveLocation() + "/debug/" - // + ErrorLogging.fileDatetime.format(LocalDateTime.now()) + - // "." + iterationCount + "-pre.compose.jpg"; - //cvSaveImage(fileLocation,MAT_CONVERTER.convertToIplImage( - // MAT_CONVERTER.convert(processedImage))); - //ErrorLogging.logError("DEBUG: Post-threshold/crop image: " + fileLocation); - //ErrorLogging.logError("DEBUG: Image " + iterationCount + " complete!"); - //ErrorLogging.logError("DEBUG: -----------------"); - //ErrorLogging.logError("DEBUG: Post-threshold/crop width: " + processedImage.cols()); + { + Mat processedImage = image.clone(); + image.copyTo(processedImage); + if(crop) processedImage = crop(processedImage,cameraName); + if(threshold) processedImage = thresholdImage(processedImage,cameraName); + + if(iterationCount == 1) output = processedImage.clone(); - if(iterationCount == 1) - { - output = processedImage.clone(); - } - //ErrorLogging.logError("DEBUG: Thresholding image " + iterationCount + "..."); bitwise_and((iterationCount == 1 ? processedImage : output),processedImage, output); iterationCount++; } - if(output != null) - ErrorLogging.logError("DEBUG: Compositing successful!"); - else - ErrorLogging.logError("ERROR: Final output image is null!"); + if(output != null) ErrorLogging.logError("DEBUG: Compositing successful!"); + else ErrorLogging.logError("ERROR: Final output image is null!"); return output; } - /**TODO: More robust file output checking; + /** * Processes image from defined camera, using the config defaults. * * @@ -429,18 +380,8 @@ public class OpenCVFacade ErrorLogging.logError("OPENCV ERROR!!! - Invalid camera name."); return output; } - //ErrorLogging.logError("DEBUG: Camera to take picture from: " + cameraName); - //ErrorLogging.logError("DEBUG: Composite frame count: " + compositeFrames); List imageList = takeBurst(cameraName, compositeFrames); - //Debug save of pre-processing image - //String fileLocation = ConfigFacade.getImgSaveLocation() + "/debug/" - // + ErrorLogging.fileDatetime.format(LocalDateTime.now()) + - // "." + cameraName + "-preProcess.jpg"; - //cvSaveImage(fileLocation,MAT_CONVERTER.convertToIplImage( - // MAT_CONVERTER.convert(imageList.get(0)))); - - ErrorLogging.logError("DEBUG: Size of output image list: " + imageList.size()); Mat finalImage = compose(imageList, threshold, crop, cameraName); output = saveImage(finalImage, saveLocation,cameraName); return output; @@ -458,21 +399,21 @@ public class OpenCVFacade public static File completeProcess(String cameraName, String saveLocation) { File output = null; + if(!getCameraNames().contains(cameraName)) { ErrorLogging.logError("OPENCV ERROR!!! - Invalid camera name."); return output; } + int compositeFrames = (int)ConfigFacade.getValue(cameraName,ConfigProperties.COMPOSITE_FRAMES); - //boolean threshold = false; - boolean threshold = (ConfigFacade.getValue(cameraName,ConfigProperties.THRESHOLD) != 0.0); - //ErrorLogging.logError("DEBUG: Threshold config value: " + threshold); - //boolean crop = false; - boolean crop = (ConfigFacade.getValue(cameraName,ConfigProperties.CROP) != 0.0); - //ErrorLogging.logError("DEBUG: Crop config value: " + crop); + + boolean threshold = (ConfigFacade.getValue(cameraName,ConfigProperties.THRESHOLD) != 0.0); + boolean crop = (ConfigFacade.getValue(cameraName,ConfigProperties.CROP) != 0.0); + output = completeProcess(cameraName,crop,threshold,compositeFrames,saveLocation); - if(output == null) - ErrorLogging.logError("OPENCV ERROR!!!: Final processed image is null!"); + + if(output == null) ErrorLogging.logError("OPENCV ERROR!!!: Final processed image is null!"); return output; } @@ -485,23 +426,5 @@ public class OpenCVFacade * @return null if any error occurs; otherwise File of output image */ public static File completeProcess(String cameraName) - { - return completeProcess(cameraName,ConfigFacade.getImgSaveLocation()); - } - - /** - * Collect images from all cameras and save them, using the config defaults. - * - * @return List of Files, as defined by {@code #completeProcess(String, String)} - */ - public static List singleIteration() - { - List output = new ArrayList<>(); - for(String cameraName : getCameraNames()) - { - output.add(completeProcess(cameraName, ConfigFacade.getImgSaveLocation())); - ErrorLogging.logError("DEBUG: ---------------------------------"); - } - return output; - } + { 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 a4b382d..00b7e76 100644 --- a/src/main/java/org/baxter/disco/ocr/TesseractFacade.java +++ b/src/main/java/org/baxter/disco/ocr/TesseractFacade.java @@ -69,23 +69,16 @@ public class TesseractFacade */ public static double imageToDouble(File file) { - //Set default output double output = Double.NEGATIVE_INFINITY; - //Import image, parse image PIX importedImage = pixRead(file.getAbsolutePath()); api.SetImage(importedImage); String stringOutput = api.GetUTF8Text().getString(); - //Determine whether the OCR output is actually a double if(!stringOutput.isEmpty()) { - try(Scanner sc = new Scanner(stringOutput.trim());) + try( Scanner sc = new Scanner(stringOutput.trim()); ) { - /* - * Discos have error messages (LO, HI, POS, ?). Consider parsing as well. - * Update on above note: Requires retraining Tesseract - */ if(sc.hasNextDouble()) { output = sc.nextDouble(); @@ -98,19 +91,14 @@ public class TesseractFacade output = output / 10; ErrorLogging.logError("OCR output saved, as value appears to be real. Value needs to be verified."); } - else if(output >= 200) - ErrorLogging.logError("OCR WARNING - OCR output is too high for DUT, potential misread."); - else - ErrorLogging.logError("OCR output successfully adjusted. Disregard warning."); + else if(output >= 200) ErrorLogging.logError("OCR WARNING - OCR output is too high for DUT, potential misread."); + else ErrorLogging.logError("OCR output successfully adjusted. Disregard warning."); } - if(output <= -10) - ErrorLogging.logError("OCR ERROR!!! - OCR output is too low for DUT, potential misread."); + if(output <= -10) ErrorLogging.logError("OCR ERROR!!! - OCR output is too low for DUT, potential misread."); } else ErrorLogging.logError("OCR ERROR!!! - OCR output is not a Double."); } } - - //Return output return output; } }