From 0d789f7813ee0d93bb7010f8715539ba92abe7d6 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Fri, 10 Feb 2023 15:43:01 -0500 Subject: [PATCH 1/4] Start creating UML Diagram Also, made import statements more explicit --- finalisedJavaUML.xmi | 195 ++++++++++++++++++ .../org/baxter/disco/ocr/ConfigFacade.java | 6 +- .../org/baxter/disco/ocr/ErrorLogging.java | 5 +- .../org/baxter/disco/ocr/OpenCVFacade.java | 21 +- .../org/baxter/disco/ocr/TesseractFacade.java | 4 +- 5 files changed, 217 insertions(+), 14 deletions(-) create mode 100644 finalisedJavaUML.xmi diff --git a/finalisedJavaUML.xmi b/finalisedJavaUML.xmi new file mode 100644 index 0000000..27b719c --- /dev/null +++ b/finalisedJavaUML.xmi @@ -0,0 +1,195 @@ + + + + + umbrello uml modeller 2.32.3 http://umbrello.kde.org + 1.7.3 + UnicodeUTF8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java index 939fdd4..a39c397 100644 --- a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java +++ b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java @@ -4,9 +4,9 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import org.apache.commons.configuration2.*; -import org.apache.commons.configuration2.builder.*; -import org.apache.commons.configuration2.builder.fluent.*; +import org.apache.commons.configuration2.INIConfiguration; +import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder; +import org.apache.commons.configuration2.builder.fluent.Parameters; import java.util.List; import java.io.File; diff --git a/src/main/java/org/baxter/disco/ocr/ErrorLogging.java b/src/main/java/org/baxter/disco/ocr/ErrorLogging.java index fb1c540..60d11fc 100644 --- a/src/main/java/org/baxter/disco/ocr/ErrorLogging.java +++ b/src/main/java/org/baxter/disco/ocr/ErrorLogging.java @@ -4,7 +4,6 @@ import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; -import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; import java.time.LocalDateTime; @@ -70,7 +69,7 @@ public class ErrorLogging fileOut = new PrintWriter(bw); System.setErr(new PrintStream(new FileOutputStream(logFile,true))); } - catch (IOException e) + catch (Exception e) { System.err.println(e); } @@ -125,7 +124,7 @@ public class ErrorLogging if(bw != null) bw.close(); if(fw != null) fw.close(); } - catch(IOException e) + catch(Exception e) { /* This is being run because the program is closing. Errors here don't matter. */} } } diff --git a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java index 1d6bb88..68dd94f 100644 --- a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java +++ b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java @@ -3,13 +3,22 @@ package org.baxter.disco.ocr; import java.util.Map; import java.util.Set; -import static org.bytedeco.opencv.global.opencv_imgproc.*; -import static org.bytedeco.opencv.global.opencv_imgcodecs.*; -import static org.bytedeco.opencv.global.opencv_highgui.*; -import static org.bytedeco.opencv.global.opencv_core.*; +import static org.bytedeco.opencv.global.opencv_imgproc.CV_BGR2GRAY; +import static org.bytedeco.opencv.global.opencv_imgproc.THRESH_BINARY; +import static org.bytedeco.opencv.global.opencv_imgproc.cvtColor; +import static org.bytedeco.opencv.global.opencv_imgcodecs.imread; +import static org.bytedeco.opencv.global.opencv_imgcodecs.cvSaveImage; +import static org.bytedeco.opencv.global.opencv_highgui.selectROI; +import static org.bytedeco.opencv.global.opencv_core.bitwise_and; -import org.bytedeco.javacv.*; -import org.bytedeco.opencv.opencv_core.*; +import org.bytedeco.javacv.Frame; +import org.bytedeco.javacv.CanvasFrame; +import org.bytedeco.javacv.FrameGrabber; +import org.bytedeco.javacv.OpenCVFrameGrabber; +import org.bytedeco.javacv.OpenCVFrameConverter; +import org.bytedeco.opencv.opencv_core.Mat; +import org.bytedeco.opencv.opencv_core.IplImage; +import org.bytedeco.opencv.opencv_core.Rect; import java.io.File; import java.time.LocalDateTime; diff --git a/src/main/java/org/baxter/disco/ocr/TesseractFacade.java b/src/main/java/org/baxter/disco/ocr/TesseractFacade.java index d525517..2355103 100644 --- a/src/main/java/org/baxter/disco/ocr/TesseractFacade.java +++ b/src/main/java/org/baxter/disco/ocr/TesseractFacade.java @@ -3,8 +3,8 @@ package org.baxter.disco.ocr; import java.io.File; import java.util.Scanner; -import org.bytedeco.leptonica.*; -import org.bytedeco.leptonica.global.*; +import org.bytedeco.leptonica.PIX; +import org.bytedeco.leptonica.global.lept; import org.bytedeco.tesseract.TessBaseAPI; /** -- 2.47.2 From d13bce152195d43503c680622b1fb36dde7a6a8d Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Mon, 13 Feb 2023 15:03:30 -0500 Subject: [PATCH 2/4] Correct data-saving methodology Edit data-saving methodology to include color-matching, and a final line formula. Also, minor version bump --- dependency-reduced-pom.xml | 2 +- pom.xml | 2 +- runScript.sh | 2 +- src/main/java/org/baxter/disco/ocr/Cli.java | 3 +- .../java/org/baxter/disco/ocr/DataSaving.java | 159 ++++++++++++++++-- .../org/baxter/disco/ocr/OpenCVFacade.java | 1 + 6 files changed, 149 insertions(+), 20 deletions(-) diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index f91bfaf..7e6208b 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.1.0 + 4.2.0 Testing Discos for long-term accuracy, using automated optical character recognition. Baxter International diff --git a/pom.xml b/pom.xml index a0c178a..ab7ce5b 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.baxter.disco ocr - 4.1.0 + 4.2.0 jar Disco OCR Accuracy Over Life Testing Testing Discos for long-term accuracy, using automated optical character recognition. diff --git a/runScript.sh b/runScript.sh index ada5cb0..bf30fd0 100644 --- a/runScript.sh +++ b/runScript.sh @@ -1,2 +1,2 @@ #! /usr/bin/env sh -sudo java -jar discoTesting-4.1.0.jar 2>/dev/null +sudo java -jar discoTesting-4.2.0.jar 2>/dev/null diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index 6b13da1..f4eab46 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -25,7 +25,7 @@ public class Cli * Complete build version number */ - private static final String version = "4.1.0"; + private static final String version = "4.2.0"; /** * Currently saved iteration count. */ @@ -731,6 +731,7 @@ public class Cli LOCK.unlock(); resultMap.clear(); } + DataSaving.closeWorkbook(cameraList.size()); println("======================================="); println("Testing complete!"); //}); diff --git a/src/main/java/org/baxter/disco/ocr/DataSaving.java b/src/main/java/org/baxter/disco/ocr/DataSaving.java index 92b9634..cfe3f5e 100644 --- a/src/main/java/org/baxter/disco/ocr/DataSaving.java +++ b/src/main/java/org/baxter/disco/ocr/DataSaving.java @@ -7,27 +7,35 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.xssf.usermodel.XSSFSheet; -import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import static org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined; + +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.util.CellReference; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFCellStyle; +import org.apache.poi.hssf.usermodel.HSSFRow; +//import org.apache.poi.hssf.usermodel.HSSF +import org.apache.poi.hssf.usermodel.HSSFSheet; /** * Facade for saving data out to a file. * * @author Blizzard Finnegan - * @version 3.0.0, 06 Feb. 2023 + * @version 4.0.0, 13 Feb. 2023 */ public class DataSaving { /** * Workbook object; used for writing to the final XLSX file. */ - private static XSSFWorkbook outputWorkbook; + private static HSSFWorkbook outputWorkbook; /** * Object defining what sheet within the workbook we are working in. */ - private static XSSFSheet outputSheet; + private static HSSFSheet outputSheet; /** * File representing the location of the final output file. @@ -35,20 +43,82 @@ public class DataSaving private static File outputFile; /** - * Prepares writer to write to XLSX file. + * Default target temperature + */ + private static double targetTemp = 36.0; + + /** + * Default range for a measurement to be considered a fail + */ + private static double failRange = 0.2; + + /** + * Style of cell if the measurement falls outside the fail range + */ + private static HSSFCellStyle failStyle; + + /** + * Style of cell if Tesseract can't read the image + */ + private static HSSFCellStyle errorStyle; + + /** + * Style of a default cell + */ + private static HSSFCellStyle defaultStyle; + + /** + * Style of the total cells (sets typing to %) + */ + private static HSSFCellStyle finalValuesStyle; + + /** + * Prepares writer to write to XLSX file, with default fail values. */ public static boolean initWorkbook(String filename, int camCount) + { return initWorkbook(filename, camCount, targetTemp, failRange); } + + /** + * Prepares writer to write to XLSX file, with custom fail values. + */ + public static boolean initWorkbook(String filename, int camCount, double targetTemp, double failRange) { + DataSaving.targetTemp = targetTemp; + DataSaving.failRange = failRange; boolean output = false; outputFile = new File(filename); try { - outputWorkbook = new XSSFWorkbook(); + outputWorkbook = new HSSFWorkbook(); outputSheet = outputWorkbook.createSheet(); + DataFormat format = outputWorkbook.createDataFormat(); + + defaultStyle = outputWorkbook.createCellStyle(); + defaultStyle.setDataFormat(format.getFormat("0.0")); + + finalValuesStyle = outputWorkbook.createCellStyle(); + finalValuesStyle.setDataFormat(format.getFormat("0.000%")); + + //Note on backgrounds: + //Excel cells have a foreground and a background, allowing + //for various patterned backgrounds. + //To set a solid background, and NOT modify the font, + //as below is shown, we need to set the foreground color. + //As of POI 5.2.3, there is no defined fill type + //SOLID_BACKGROUND or similar + failStyle = outputWorkbook.createCellStyle(); + failStyle.setFillForegroundColor(HSSFColorPredefined.RED.getIndex()); + failStyle.setDataFormat(format.getFormat("0.0")); + failStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + + errorStyle = outputWorkbook.createCellStyle(); + errorStyle.setFillForegroundColor(HSSFColorPredefined.YELLOW.getIndex()); + errorStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); + int startingRow = outputSheet.getLastRowNum(); - Row row = outputSheet.createRow(++startingRow); + HSSFRow row = outputSheet.createRow(++startingRow); int cellnum = 0; - Cell cell = row.createCell(cellnum++); + HSSFCell cell = row.createCell(cellnum++); cell.setCellValue("Iteration"); for(int i = 0; i < camCount; i++) { @@ -67,8 +137,50 @@ public class DataSaving outputStream.close(); } catch(Exception e) { ErrorLogging.logError(e); } + return output; } + + /** + * + */ + public static void closeWorkbook(int cameraCount) + { + int lastRowOfData = outputSheet.getLastRowNum(); + HSSFRow finalRow = outputSheet.createRow(++lastRowOfData); + HSSFCell titleCell = finalRow.createCell(0); + titleCell.setCellValue("Totals:"); + + ErrorLogging.logError("DEBUG: 3 ?= " + (cameraCount*3)); + for(int column = 3; column <= (cameraCount*3); column+=3) + { + HSSFCell cell = finalRow.createCell(column); + FormulaEvaluator formulaEvaluator = outputWorkbook.getCreationHelper().createFormulaEvaluator(); + String columnName = CellReference.convertNumToColString(column); + String verticalArray = String.format("$%s$2:$%s$%s",columnName,columnName,lastRowOfData); + ErrorLogging.logError("DEBUG: Vertical Array: " + verticalArray); + String formula = String.format( + "(COUNT(%s)-COUNTIF(%s,{\"<%s\",\"%s\"}))/(COUNT(%s))", + verticalArray, + verticalArray, (targetTemp - failRange), (targetTemp + failRange), + verticalArray); + cell.setCellFormula(formula); + cell.setCellStyle(finalValuesStyle); + + formulaEvaluator.evaluate(cell); + + ErrorLogging.logError("DEBUG: Formula: " + formula); + } + + try + { + FileOutputStream outputStream = new FileOutputStream(outputFile); + outputWorkbook.write(outputStream); + outputStream.close(); + } + catch(Exception e) {ErrorLogging.logError(e);} + } + /** * Writes line to XLSX file. * @@ -80,14 +192,17 @@ public class DataSaving public static boolean writeValues(int cycle, Map inputMap, Map cameraToFile) { boolean output = false; + int cellnum = 0; int startingRow = outputSheet.getLastRowNum(); - Row row = outputSheet.createRow(++startingRow); + HSSFRow row = outputSheet.createRow(++startingRow); List cameraNames = new ArrayList<>(cameraToFile.keySet()); //ErrorLogging.logError("DEBUG: image locations: " + imageLocations.toString()); List objectArray = new LinkedList<>(); cycle++; - objectArray.add((double)cycle); + + HSSFCell indexCell = row.createCell(cellnum++); + indexCell.setCellValue(cycle); for(String cameraName : cameraNames) { File file = cameraToFile.get(cameraName); @@ -99,16 +214,28 @@ public class DataSaving objectArray.add(inputMap.get(file)); objectArray.add(" "); } - int cellnum = 0; for(Object cellObject : objectArray) { - Cell cell = row.createCell(cellnum++); + HSSFCell cell = row.createCell(cellnum++); if(cellObject instanceof Double) { Double cellValue = (Double)cellObject; + ErrorLogging.logError("DEBUG: " + cellValue + " ?= " + targetTemp + " +- " + failRange); if(cellValue.equals(Double.NEGATIVE_INFINITY)) + { cell.setCellValue("ERROR!"); - else cell.setCellValue(cellValue); + cell.setCellStyle(errorStyle); + } + else + { + cell.setCellValue(cellValue); + if( cellValue.doubleValue() > (targetTemp + failRange) || + cellValue.doubleValue() < (targetTemp - failRange) ) + { + ErrorLogging.logError("DEBUG: Cell value " + cellValue.doubleValue() + " is outside the allowed range! (" + (targetTemp -failRange) + "-" + (targetTemp + failRange) + "). Setting cell to fail colouring."); + cell.setCellStyle(failStyle); + } + } } else if(cellObject instanceof String) cell.setCellValue((String) cellObject); else diff --git a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java index 68dd94f..cf2a267 100644 --- a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java +++ b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java @@ -6,6 +6,7 @@ import java.util.Set; import static org.bytedeco.opencv.global.opencv_imgproc.CV_BGR2GRAY; import static org.bytedeco.opencv.global.opencv_imgproc.THRESH_BINARY; import static org.bytedeco.opencv.global.opencv_imgproc.cvtColor; +import static org.bytedeco.opencv.global.opencv_imgproc.threshold; import static org.bytedeco.opencv.global.opencv_imgcodecs.imread; import static org.bytedeco.opencv.global.opencv_imgcodecs.cvSaveImage; import static org.bytedeco.opencv.global.opencv_highgui.selectROI; -- 2.47.2 From b460e572a7d803d9d057616824d53cd28c58a12a Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Mon, 13 Feb 2023 16:16:03 -0500 Subject: [PATCH 3/4] Start code documentation --- src/main/java/org/baxter/disco/ocr/Cli.java | 140 +++++++++++----- .../org/baxter/disco/ocr/ConfigFacade.java | 157 ++++++++++-------- .../java/org/baxter/disco/ocr/DataSaving.java | 150 +++++++++-------- .../org/baxter/disco/ocr/ErrorLogging.java | 8 + 4 files changed, 280 insertions(+), 175 deletions(-) diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index f4eab46..b680b2d 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -1,5 +1,6 @@ package org.baxter.disco.ocr; +//Standard imports import java.io.File; import java.util.ArrayList; import java.util.HashMap; @@ -24,8 +25,8 @@ public class Cli /** * Complete build version number */ - private static final String version = "4.2.0"; + /** * Currently saved iteration count. */ @@ -76,6 +77,7 @@ public class Cli //private static Thread safeThread; + //Start of program message; always runs first static { ErrorLogging.logError("START OF PROGRAM"); @@ -83,49 +85,61 @@ public class Cli public static void main(String[] args) { + //Beginning message to user ErrorLogging.logError("========================"); ErrorLogging.logError("Accuracy Over Life Test"); ErrorLogging.logError("Version: " + version); ErrorLogging.logError("========================"); try{ + //Create scanner for user input from the console inputScanner = new Scanner(System.in); - //ErrorLogging.logError("DEBUG: Setting up multithreading..."); + //Initialise the fixture, start monitor thread fixture = new MovementFacade(LOCK); - //ErrorLogging.logError("DEBUG: Multithreading complete!"); - //ErrorLogging.logError("DEBUG: Importing config..."); + //Initialise the config ConfigFacade.init(); - //ErrorLogging.logError("DEBUG: Config imported!"); + //Create the user input value int userInput = 0; + //Main menu loop do { + //Show the main menu, wait for user input printMainMenu(); userInput = inputFiltering(inputScanner.nextLine()); + + //Perform action based on user input switch (userInput) { case 1: + //Test fixture movement, modify fixture values as necessary testMovement(); break; case 2: + //Warn user that starting cameras will take a moment. println("Setting up cameras..."); println("This may take a moment..."); configureCameras(); + //Set that cameras are successfully configured, to mute runTests warning camerasConfigured = true; break; case 3: + //Set serials of the DUTs setDUTSerials(); - //serialsSet = true; break; case 4: + //Change the number of iterations to run the tests setIterationCount(); break; case 5: + //Set cameras to use in testing setActiveCameras(); break; case 6: + //Warn user that cameras haven't been set up, if they haven't been set up + //Won't warn user if config was imported successfully if(!camerasConfigured) { prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): "); @@ -138,11 +152,14 @@ public class Cli { break; } + //Save in the logs that cameras may not have been configured. else { ErrorLogging.logError("WARNING! - Potential for error: Un-initialised cameras."); } } + + //If there's an unset camera serial, prompt the user to go back and set that up for(String cameraName : OpenCVFacade.getCameraNames()) { if(ConfigFacade.getValue(cameraName,ConfigProperties.ACTIVE) != 0 && @@ -170,12 +187,16 @@ public class Cli ErrorLogging.logError("WARNING! - Potential for error: Un-initialised DUT Serial numbers."); } } + + //Run tests for the given number of iterations runTests(); break; case 7: + //Show help menu printHelp(); break; case 8: + //Leave the menu break; default: //Input handling already done by inputFiltering() @@ -184,11 +205,13 @@ public class Cli } while (userInput != mainMenuOptionCount); } + //If anything ever goes wrong, catch the error and exit catch(Exception e) { ErrorLogging.logError(e); ErrorLogging.logError("ERROR CAUGHT - CLOSING PROGRAM."); } + //Always return the fixture back to the upper limit switch, close all open connections safely finally { close(); @@ -401,6 +424,7 @@ public class Cli private static void testMovement() { int userInput = -1; + //Loop to allow multiple changes to device GPIO settings do { println("Testing movement..."); @@ -455,41 +479,19 @@ public class Cli private static void configureCameras() { List cameraList = new ArrayList<>(OpenCVFacade.getCameraNames()); - //println(cameraList.toString()); - - // The below code should be unnecessary now. Leaving in for now to ensure things work properly. - ////Open a single new thread, so the canvas - ////used further down to display the temporary - ////image doesn't accidentally kill the program. - ////Created at beginning of function call to reduce - ////thread spawn count. - ////See also: https://docs.oracle.com/javase/8/docs/api/java/awt/doc-files/AWTThreadIssues.html#Autoshutdown - //Runnable r = new Runnable() { - // public void run() { - // Object o = new Object(); - // try { - // synchronized (o) { - // o.wait(); - // } - // } catch (InterruptedException ie) { - // } - // } - //}; - //Thread t = new Thread(r); - //t.setDaemon(false); - //t.start(); + //Always wake the camera, to ensure that the image is useful fixture.iterationMovement(true); double tesseractValue = 0.0; + //Main camera config loop do { - //Main menu + //Show the menu printCameraMenu(cameraList); //Pick a camera to configure int userInput; - String cameraName = ""; do { @@ -503,13 +505,19 @@ public class Cli 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 fixture.pressButton(); try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); } + fixture.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 @@ -557,14 +565,16 @@ public class Cli else if(modifiedProperty == ConfigProperties.CROP_X) { OpenCVFacade.setCrop(cameraName); } - //Modify config values - else if(modifiedProperty != ConfigProperties.PRIME) + //Modify number of composite frames, or threshold value + else if(modifiedProperty == ConfigProperties.COMPOSITE_FRAMES || + modifiedProperty == ConfigProperties.THRESHOLD_VALUE) { - prompt("Enter new value for this property (" + modifiedProperty.toString() + ", currently : " + + prompt("Enter new value for this property (" + modifiedProperty.toString() + ": " + + //Prompt is in int, as the ultimate values are cast + //to int anyways, a decimal would be confusing (int)ConfigFacade.getValue(cameraName,modifiedProperty) + "): "); userInput = inputFiltering(inputScanner.nextLine()); ConfigFacade.setValue(cameraName,modifiedProperty,userInput); - //if(canvas != null) canvas.dispose(); } //Exit loop @@ -573,6 +583,7 @@ public class Cli } while(true); + //Save the current config to the config file ConfigFacade.saveCurrentConfig(); println("Configuration complete!"); } @@ -582,7 +593,9 @@ 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 @@ -590,12 +603,12 @@ public class Cli //Pick a camera to configure int userInput; - String cameraName = ""; do { prompt("Enter the camera you wish to set the serial of: "); userInput = inputFiltering(inputScanner.nextLine()); + //Compensate for off-by-one errors userInput--; } while (cameraList.size() < userInput || userInput < 0); @@ -603,6 +616,8 @@ public class Cli 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()); @@ -629,15 +644,17 @@ public class Cli */ private static void setActiveCameras() { + //Get available cameras List cameraList = new ArrayList<>(OpenCVFacade.getCameraNames()); + + //Main loop do { - //Main menu + //Print menu printActiveToggleMenu(cameraList); //Pick a camera to configure int userInput; - String cameraName = ""; do { @@ -650,6 +667,7 @@ public class Cli 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); @@ -664,10 +682,15 @@ public class Cli { println("===================================="); ErrorLogging.logError("Initialising tests..."); + + //Bring the iteration count into the function as a final variable + //useful for multithreading, which isn't necessary in CLI final int localIterations = iterationCount; - //testingThread = new Thread(() -> - //{ + + //TODO: Hard-coded value that needs fixing boolean prime = false; + + //Create a List of *active* cameras. List cameraList = new ArrayList<>(); for(String cameraName : OpenCVFacade.getCameraNames()) { @@ -682,43 +705,67 @@ public class Cli cameraList.add(cameraName); } } + + //Initialise the workbook, with the number of cameras and the final output location DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation(),cameraList.size()); + + //Do 2 dummy passes, to make completely sure that the devices are awake ErrorLogging.logError("DEBUG: Waking devices..."); fixture.iterationMovement(prime); fixture.pressButton(); fixture.iterationMovement(prime); - ErrorLogging.logError("DEBUG: Starting tests..."); + + //Create final maps for result images, result values, and camera names Map resultMap = new HashMap<>(); Map cameraToFile = new HashMap<>(); + + //Initialise cameraToFile, so keys don't shuffle. for(String cameraName : cameraList) { cameraToFile.put(cameraName,new File("/dev/null")); } + + 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++) { println(""); ErrorLogging.logError("===================================="); ErrorLogging.logError("Starting iteration " + (i+1) + " of " + localIterations + "..."); + + //Move the fixture for one iteration, with whether or not the DUTs need to be primed while(!LOCK.tryLock()) {} fixture.iterationMovement(prime); LOCK.unlock(); + + //Wait for the DUT to display an image try{ Thread.sleep(1500); } 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()) {} File file = OpenCVFacade.completeProcess(cameraName); LOCK.unlock(); + while(!LOCK.tryLock()) {} cameraToFile.replace(cameraName,file); 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()) {} 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()) {} @@ -726,16 +773,19 @@ public class Cli ErrorLogging.logError("Tesseract final output: " + result); LOCK.unlock(); } + //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(); } + //Close the Excel workbook DataSaving.closeWorkbook(cameraList.size()); + //Alert the user to testing being complete println("======================================="); println("Testing complete!"); - //}); - //testingThread.start(); } @@ -876,7 +926,7 @@ public class Cli */ private static void invalidInput(String input) { - ErrorLogging.logError("Invalid User Input!!! - Message to user: '" + input + "'"); + ErrorLogging.logError("DEBUG: Invalid User Input!!! - Message to user: '" + input + "'"); println(""); println("================================================="); println("Invalid input! - " + input); diff --git a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java index a39c397..dc0a79c 100644 --- a/src/main/java/org/baxter/disco/ocr/ConfigFacade.java +++ b/src/main/java/org/baxter/disco/ocr/ConfigFacade.java @@ -1,13 +1,9 @@ package org.baxter.disco.ocr; +//Standard imports import java.util.HashMap; import java.util.Map; import java.util.Set; - -import org.apache.commons.configuration2.INIConfiguration; -import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder; -import org.apache.commons.configuration2.builder.fluent.Parameters; - import java.util.List; import java.io.File; import java.nio.file.Files; @@ -16,6 +12,11 @@ import java.nio.file.Paths; import java.time.LocalDateTime; import java.util.ArrayList; +//Apache Commons Configuration imports +import org.apache.commons.configuration2.INIConfiguration; +import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder; +import org.apache.commons.configuration2.builder.fluent.Parameters; + /** * Facade for working with config files, using the Apache Commons * Configuration library. @@ -72,10 +73,13 @@ public class ConfigFacade */ private static INIConfiguration CONFIG_STORE; - static + //This block will ALWAYS run first. + static { 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(); } @@ -85,6 +89,7 @@ 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) { @@ -93,17 +98,29 @@ public class ConfigFacade finally { if(CONFIG_STORE == null) - ErrorLogging.logError("CONFIG INIT ERROR!!! - Unsuccessful config initialisation. Camera commands will fail!"); + ErrorLogging.logError("CONFIG INIT ERROR!!! - Unsuccessful "+ + "config initialisation. Please delete the current config "+ + "file, then restart this program!"); else + { ErrorLogging.logError("Config successfully imported!"); + loadConfig(); + ErrorLogging.logError("Configuration settings loaded!"); + } } } + + //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..."); - saveDefaultConfig(); + boolean saveSuccessful = saveDefaultConfig(); + if(!saveSuccessful) ErrorLogging.logError("Save config failed!!!"); + 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(); @@ -114,21 +131,13 @@ public class ConfigFacade 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); } - if(newConfig) - { - boolean saveSuccessful = saveDefaultConfig(); - if(!saveSuccessful) ErrorLogging.logError("Save config failed!!!"); - else ErrorLogging.logError("Configuration settings set up!"); - } - else - { - loadConfig(); - ErrorLogging.logError("Configuration settings loaded!"); - } + + //Autosave the config CONFIG_BUILDER.setAutoSave(true); } /** @@ -150,6 +159,7 @@ 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()); @@ -160,17 +170,15 @@ public class ConfigFacade output = cameraConfig.get(property); //Debug logger. //NOTE THAT THIS BREAKS TUI MENUS, AS OF ErrorLogging 1.1.0 - //ErrorLogging.logError("DEBUG: getValue - return value: " + cameraName + "/" + property.getConfig() + " = " + output); + //ErrorLogging.logError("DEBUG: getValue - return value: " + cameraName + // + "/" + property.getConfig() + " = " + output); return output; } /** - * Initialises local list of available cameras + * Called to force early calling of the static block */ - public static void init() - { - //ErrorLogging.logError("Starting import..."); - } + public static void init() {} /** * Getter for the location of the output XLSX file. @@ -222,7 +230,6 @@ public class ConfigFacade /** * Set a given config value. - * DOES NOT SAVE VALUE TO FILE. * * @param cameraName Name of the camera * @param property name of the property @@ -270,6 +277,7 @@ public class ConfigFacade //********************************************** //SAVE AND LOAD SETTINGS //********************************************** + /** * Save current config to a user-defined file location. * @@ -278,15 +286,34 @@ public class ConfigFacade */ public static boolean saveDefaultConfig(String filename) { + //Get set of camera names boolean output = false; Set cameraNames = OpenCVFacade.getCameraNames(); - CONFIG_BUILDER = new FileBasedConfigurationBuilder<>(INIConfiguration.class).configure(new Parameters().fileBased().setFile(new File(filename))); - try - { - CONFIG_STORE = CONFIG_BUILDER.getConfiguration(); - } + + //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); } - finally { if(CONFIG_STORE == null) ErrorLogging.logError("CONFIG INIT ERROR!!! - Unsuccessful config initialisation. Camera commands will fail!"); } + + //If the save default fails, warn the user that something is wrong + finally + { + if(CONFIG_STORE == null) + { + ErrorLogging.logError("CONFIG INIT ERROR!!! - Unsuccessful config initialisation. Camera commands will fail!"); + ErrorLogging.logError("CONFIG INIT ERROR!!! - Attempted file save point: "+ filename); + } + } + + //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<>(); @@ -301,15 +328,14 @@ public class ConfigFacade } configMap.put(camera,cameraConfig); } + + //Save out to the file try { CONFIG_BUILDER.save(); output = true; } - catch(Exception e) - { - ErrorLogging.logError(e); - } + catch(Exception e){ ErrorLogging.logError(e); } return output; } @@ -319,7 +345,8 @@ public class ConfigFacade * * @return true if saved successfully, otherwise false */ - public static boolean saveDefaultConfig() { return saveDefaultConfig(configFileLocation); } + public static boolean saveDefaultConfig() + { return saveDefaultConfig(configFileLocation); } /** * Save current config to a user-defined file location. @@ -330,7 +357,12 @@ public class ConfigFacade public static boolean saveCurrentConfig(String filename) { boolean output = false; + + //Get a list of all cameras List activeCameras = new ArrayList<>(OpenCVFacade.getCameraNames()); + + //For every available camera + // get every current property value, save it to the CONFIG_STORE for(String camera : activeCameras) { for(ConfigProperties property : ConfigProperties.values()) @@ -340,15 +372,14 @@ public class ConfigFacade CONFIG_STORE.setProperty(propertyName,propertyValue); } } + + //Save to the file try { CONFIG_BUILDER.save(); output = true; } - catch(Exception e) - { - ErrorLogging.logError(e); - } + catch(Exception e) { ErrorLogging.logError(e); } return output; } @@ -358,7 +389,8 @@ public class ConfigFacade * * @return true if saved successfully, otherwise false */ - public static boolean saveCurrentConfig() { return saveCurrentConfig(configFileLocation); } + public static boolean saveCurrentConfig() + { return saveCurrentConfig(configFileLocation); } /** * Load config from a user-defined file location. @@ -368,60 +400,55 @@ public class ConfigFacade */ public static boolean loadConfig(String filename) { + //Check if the current configMap is empty boolean emptyMap = configMap.keySet().size() == 0; boolean output = false; + + //If the config file we're trying to load from doesn't exist, failover to saving + //the default values to a new file with that name File file = new File(filename); - try - { - if(file.createNewFile()) - return saveDefaultConfig(); - } + try{ if(file.createNewFile()) return saveDefaultConfig(); } catch(Exception e) { ErrorLogging.logError(e); - return saveDefaultConfig(); + return saveDefaultConfig(filename); } + + //At this point, the file should exist + //Get a list of camera names List cameraNames = new ArrayList<>(OpenCVFacade.getCameraNames()); if(Files.isRegularFile(Path.of(file.toURI()))) { + //Import the config file into a Java object try { - CONFIG_BUILDER = new FileBasedConfigurationBuilder<>(INIConfiguration.class).configure(new Parameters().fileBased().setFile(file)); + CONFIG_BUILDER = new FileBasedConfigurationBuilder<>(INIConfiguration.class) + .configure(new Parameters().fileBased().setFile(file)); CONFIG_STORE = CONFIG_BUILDER.getConfiguration(); } catch(Exception e){ ErrorLogging.logError(e); } + //Iterate over the imported object, saving the file's config values to the map Set configSections = CONFIG_STORE.getSections(); - //ErrorLogging.logError("DEBUG: imported sections - " + configSections.toString()); - //ErrorLogging.logError("DEBUG: empty map? : " + (configMap.keySet().size() == 0)); - //ErrorLogging.logError("DEBUG: imported section size - " + configSections.size()); for(String sectionName : configSections) { Map savedSection = new HashMap<>(); String subSectionPrefix = ""; for(String cameraName : cameraNames) { - if(cameraName == null) - { - ErrorLogging.logError("CONFIG LOAD ERROR!!! - Empty camera name."); - continue; - } - else if(sectionName == null) - { - ErrorLogging.logError("CONFIG LOAD ERROR!!! - Invalid imported section name."); - continue; - } - else if(sectionName.equals(cameraName)) + if(sectionName.equals(cameraName)) { subSectionPrefix = cameraName; break; } } + //If an imported section fails, fallback to saving the default values to + //the given location if(subSectionPrefix.equals("")) { ErrorLogging.logError("CONFIG LOAD ERROR!!! - Failed import from file. Setting default config."); - return saveDefaultConfig(); + return saveDefaultConfig(filename); } for(ConfigProperties configState : ConfigProperties.values()) @@ -444,8 +471,8 @@ public class ConfigFacade output = true; } + //If something broke, complain if(!output) ErrorLogging.logError("CONFIG LOAD ERROR!!! - Invalid path."); - else Cli.configImported(); return output; } diff --git a/src/main/java/org/baxter/disco/ocr/DataSaving.java b/src/main/java/org/baxter/disco/ocr/DataSaving.java index cfe3f5e..a189a0c 100644 --- a/src/main/java/org/baxter/disco/ocr/DataSaving.java +++ b/src/main/java/org/baxter/disco/ocr/DataSaving.java @@ -1,5 +1,6 @@ package org.baxter.disco.ocr; +//Standard imports import java.io.File; import java.io.FileOutputStream; import java.util.ArrayList; @@ -7,18 +8,20 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import static org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; +//Generic spreadsheet imports import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.usermodel.DataFormat; import org.apache.poi.ss.usermodel.FillPatternType; import org.apache.poi.ss.usermodel.FormulaEvaluator; + +//Excel-specific imports import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFCellStyle; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.usermodel.HSSFRow; -//import org.apache.poi.hssf.usermodel.HSSF import org.apache.poi.hssf.usermodel.HSSFSheet; +import static org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined; /** * Facade for saving data out to a file. @@ -87,97 +90,119 @@ public class DataSaving DataSaving.failRange = failRange; boolean output = false; outputFile = new File(filename); - try + DataFormat format = null; + + //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%")); + + //Note on backgrounds: + //Excel cells have a foreground and a background, allowing + //for various patterned backgrounds. + //To set a solid background, and NOT modify the font, + //as below is shown, we need to set the foreground color. + //As of POI 5.2.3, there is no defined fill type + //SOLID_BACKGROUND or similar + failStyle = outputWorkbook.createCellStyle(); + failStyle.setFillForegroundColor(HSSFColorPredefined.RED.getIndex()); + 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++) { - outputWorkbook = new HSSFWorkbook(); - outputSheet = outputWorkbook.createSheet(); - DataFormat format = outputWorkbook.createDataFormat(); - - defaultStyle = outputWorkbook.createCellStyle(); - defaultStyle.setDataFormat(format.getFormat("0.0")); - - finalValuesStyle = outputWorkbook.createCellStyle(); - finalValuesStyle.setDataFormat(format.getFormat("0.000%")); - - //Note on backgrounds: - //Excel cells have a foreground and a background, allowing - //for various patterned backgrounds. - //To set a solid background, and NOT modify the font, - //as below is shown, we need to set the foreground color. - //As of POI 5.2.3, there is no defined fill type - //SOLID_BACKGROUND or similar - failStyle = outputWorkbook.createCellStyle(); - failStyle.setFillForegroundColor(HSSFColorPredefined.RED.getIndex()); - failStyle.setDataFormat(format.getFormat("0.0")); - failStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); - - errorStyle = outputWorkbook.createCellStyle(); - errorStyle.setFillForegroundColor(HSSFColorPredefined.YELLOW.getIndex()); - errorStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); - - int startingRow = outputSheet.getLastRowNum(); - HSSFRow row = outputSheet.createRow(++startingRow); - int cellnum = 0; - HSSFCell cell = row.createCell(cellnum++); - cell.setCellValue("Iteration"); - for(int i = 0; i < camCount; i++) - { - cell = row.createCell(cellnum++); - cell.setCellValue("Serial"); - cell = row.createCell(cellnum++); - cell.setCellValue("Image Location"); - cell = row.createCell(cellnum++); - cell.setCellValue("Read Value"); - cell = row.createCell(cellnum++); - cell.setCellValue(""); - } - FileOutputStream outputStream = new FileOutputStream(outputFile); - outputWorkbook.write(outputStream); - output = true; - outputStream.close(); + cell = row.createCell(cellnum++); + cell.setCellValue("Serial"); + cell = row.createCell(cellnum++); + cell.setCellValue("Image Location"); + cell = row.createCell(cellnum++); + cell.setCellValue("Read Value"); + cell = row.createCell(cellnum++); + cell.setCellValue(""); } - catch(Exception e) { ErrorLogging.logError(e); } + + //Save to file + try (FileOutputStream outputStream = new FileOutputStream(outputFile)) + { outputWorkbook.write(outputStream); } + catch(Exception e) {ErrorLogging.logError(e);} return output; } /** + * Add final totals to the excel document. + * Run at the end of testing. * + * @param cameraCount The number of cameras that were used. */ public static void closeWorkbook(int cameraCount) { + //Get the last row, add another row below it, and name the first cell "Totals:" int lastRowOfData = outputSheet.getLastRowNum(); HSSFRow finalRow = outputSheet.createRow(++lastRowOfData); HSSFCell titleCell = finalRow.createCell(0); titleCell.setCellValue("Totals:"); - ErrorLogging.logError("DEBUG: 3 ?= " + (cameraCount*3)); + //ErrorLogging.logError("DEBUG: 3 ?= " + (cameraCount*3)); + + //For each camera, create a unique total line for(int column = 3; column <= (cameraCount*3); column+=3) { + //Create a cell in the right column HSSFCell cell = finalRow.createCell(column); FormulaEvaluator formulaEvaluator = outputWorkbook.getCreationHelper().createFormulaEvaluator(); String columnName = CellReference.convertNumToColString(column); + + //Intermediate variable to store the array of possible values + //Dollar signs in use here indicate that if the cell is copied and pasted, + //the same cells will be referenced String verticalArray = String.format("$%s$2:$%s$%s",columnName,columnName,lastRowOfData); ErrorLogging.logError("DEBUG: Vertical Array: " + verticalArray); + + //Create the error formula, then set it as the cell's formula. String formula = String.format( "(COUNT(%s)-COUNTIF(%s,{\"<%s\",\"%s\"}))/(COUNT(%s))", verticalArray, verticalArray, (targetTemp - failRange), (targetTemp + failRange), verticalArray); cell.setCellFormula(formula); + + //Make the percentage human-readable cell.setCellStyle(finalValuesStyle); + //To make the cell be a readable value, you need to + //evaluate the formula within the cell. formulaEvaluator.evaluate(cell); ErrorLogging.logError("DEBUG: Formula: " + formula); } - try - { - FileOutputStream outputStream = new FileOutputStream(outputFile); - outputWorkbook.write(outputStream); - outputStream.close(); - } + //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);} } @@ -245,13 +270,8 @@ public class DataSaving } } - try - { - FileOutputStream outputStream = new FileOutputStream(outputFile); - outputWorkbook.write(outputStream); - output = true; - outputStream.close(); - } + try (FileOutputStream outputStream = new FileOutputStream(outputFile)) + { outputWorkbook.write(outputStream); output = true; } catch(Exception e) {ErrorLogging.logError(e);} return output; } diff --git a/src/main/java/org/baxter/disco/ocr/ErrorLogging.java b/src/main/java/org/baxter/disco/ocr/ErrorLogging.java index 60d11fc..37bcf16 100644 --- a/src/main/java/org/baxter/disco/ocr/ErrorLogging.java +++ b/src/main/java/org/baxter/disco/ocr/ErrorLogging.java @@ -1,5 +1,6 @@ package org.baxter.disco.ocr; +//Standard imports import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; @@ -9,6 +10,7 @@ import java.io.PrintWriter; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +//Error parsing import import org.apache.commons.lang3.exception.ExceptionUtils; /** @@ -53,10 +55,16 @@ public class ErrorLogging */ public static final DateTimeFormatter fileDatetime; + //This will always run first, before anything else in this file static { + //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); -- 2.47.2 From 0f2bcdfc9001c164aae6de37c0697f57f2088865 Mon Sep 17 00:00:00 2001 From: Blizzard Finnegan Date: Wed, 15 Feb 2023 09:41:43 -0500 Subject: [PATCH 4/4] Update OpenCV to 4.6.0, misc fixes - Update OpenCV to 4.6.0 (Bytedeco API v1.5.8) - Remove extra debug image saves to minimise storage space used - minor version bump - update final formula in DataSaving to be correct --- dependency-reduced-pom.xml | 3 ++- pom.xml | 12 ++++++++--- src/main/java/module-info.java | 2 ++ src/main/java/org/baxter/disco/ocr/Cli.java | 2 +- .../java/org/baxter/disco/ocr/DataSaving.java | 4 ++-- .../org/baxter/disco/ocr/OpenCVFacade.java | 21 ++++++++++--------- .../org/baxter/disco/ocr/TesseractFacade.java | 6 +++--- 7 files changed, 30 insertions(+), 20 deletions(-) diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index 7e6208b..c88a90e 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -104,10 +104,11 @@ Cli raspberry 11 - 1.5.6 + 1.5.8 5.2.3 true UTF-8 + 19 5.9.1 1.9.4 diff --git a/pom.xml b/pom.xml index ab7ce5b..71dc1f0 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.baxter.disco ocr - 4.2.0 + 4.3.0 jar Disco OCR Accuracy Over Life Testing Testing Discos for long-term accuracy, using automated optical character recognition. @@ -25,10 +25,10 @@ 2.1.0 - + 19 5.2.3 2.8.0 - 1.5.6 + 1.5.8 5.9.1 1.9.4 @@ -47,6 +47,12 @@ javacv-platform ${opencv.version} + + + org.openjfx + javafx-swing + ${javafx.version} + diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index fdb76d2..1d6dfd4 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -5,10 +5,12 @@ module org.baxter.disco.ocr { requires com.pi4j.library.pigpio; //requires javafx.fxml; //requires javafx.controls; + requires javafx.swing; requires org.apache.poi.poi; requires org.apache.commons.configuration2; requires org.apache.xmlbeans; requires org.bytedeco.tesseract; + requires org.bytedeco.leptonica; requires org.bytedeco.opencv; requires org.bytedeco.javacpp; //requires javafx.graphics; diff --git a/src/main/java/org/baxter/disco/ocr/Cli.java b/src/main/java/org/baxter/disco/ocr/Cli.java index b680b2d..4d157e8 100644 --- a/src/main/java/org/baxter/disco/ocr/Cli.java +++ b/src/main/java/org/baxter/disco/ocr/Cli.java @@ -25,7 +25,7 @@ public class Cli /** * Complete build version number */ - private static final String version = "4.2.0"; + private static final String version = "4.3.0"; /** * Currently saved iteration count. diff --git a/src/main/java/org/baxter/disco/ocr/DataSaving.java b/src/main/java/org/baxter/disco/ocr/DataSaving.java index a189a0c..dfe08c6 100644 --- a/src/main/java/org/baxter/disco/ocr/DataSaving.java +++ b/src/main/java/org/baxter/disco/ocr/DataSaving.java @@ -27,7 +27,7 @@ import static org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined; * Facade for saving data out to a file. * * @author Blizzard Finnegan - * @version 4.0.0, 13 Feb. 2023 + * @version 4.0.1, 13 Feb. 2023 */ public class DataSaving { @@ -184,7 +184,7 @@ public class DataSaving //Create the error formula, then set it as the cell's formula. String formula = String.format( - "(COUNT(%s)-COUNTIF(%s,{\"<%s\",\"%s\"}))/(COUNT(%s))", + "(COUNT(%s)-COUNTIF(%s,{\"<%s\",\">%s\"}))/(COUNT(%s))", verticalArray, verticalArray, (targetTemp - failRange), (targetTemp + failRange), verticalArray); diff --git a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java index cf2a267..38fc092 100644 --- a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java +++ b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java @@ -31,9 +31,9 @@ import java.util.List; /** * Facade for the OpenCV package. * Performs image capture, as well as image manipulation. - * + * * @author Blizzard Finnegan - * @version 1.5.0, 10 Feb. 2023 + * @version 2.0.0, 15 Feb. 2023 */ public class OpenCVFacade { @@ -195,7 +195,7 @@ public class OpenCVFacade //ErrorLogging.logError("DEBUG: Image location: " + imageLocation.getAbsolutePath()); Frame outputImage = MAT_CONVERTER.convert(imread(imageLocation.getAbsolutePath())); String canvasTitle = "Camera " + cameraName + " Preview"; - CanvasFrame canvas = new CanvasFrame(canvasTitle); + final CanvasFrame canvas = new CanvasFrame(canvasTitle); canvas.showImage(outputImage); return imageLocation; } @@ -280,16 +280,17 @@ public class OpenCVFacade */ public static Mat crop(Mat image, Rect roi, String cameraName) { - Mat output = null; - output = image.apply(roi); - String fileLocation = ConfigFacade.getImgSaveLocation() + "/debug/" - + ErrorLogging.fileDatetime.format(LocalDateTime.now()) + - "." + cameraName + "-preProcess.jpg"; - cvSaveImage(fileLocation,MAT_CONVERTER.convertToIplImage( - MAT_CONVERTER.convert(output))); + 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; } + /** * Put the given image through a binary threshold. * This reduces the image from greyscale to only pure white and black pixels. diff --git a/src/main/java/org/baxter/disco/ocr/TesseractFacade.java b/src/main/java/org/baxter/disco/ocr/TesseractFacade.java index 2355103..8e678d6 100644 --- a/src/main/java/org/baxter/disco/ocr/TesseractFacade.java +++ b/src/main/java/org/baxter/disco/ocr/TesseractFacade.java @@ -4,7 +4,7 @@ import java.io.File; import java.util.Scanner; import org.bytedeco.leptonica.PIX; -import org.bytedeco.leptonica.global.lept; +import static org.bytedeco.leptonica.global.leptonica.pixRead; import org.bytedeco.tesseract.TessBaseAPI; /** @@ -14,7 +14,7 @@ import org.bytedeco.tesseract.TessBaseAPI; * information for this specific testing aparatus. * * @author Blizzard Finnegan - * @version 2.0.0, 06 Feb. 2023 + * @version 2.1.0, 06 Feb. 2023 */ public class TesseractFacade { @@ -53,7 +53,7 @@ public class TesseractFacade double output = Double.NEGATIVE_INFINITY; //Import image, parse image - PIX importedImage = lept.pixRead(file.getAbsolutePath()); + PIX importedImage = pixRead(file.getAbsolutePath()); api.SetImage(importedImage); String stringOutput = api.GetUTF8Text().getString(); -- 2.47.2