diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml
index f91bfaf..c88a90e 100644
--- a/dependency-reduced-pom.xml
+++ b/dependency-reduced-pom.xml
@@ -4,7 +4,7 @@
org.baxter.discoocrDisco OCR Accuracy Over Life Testing
- 4.1.0
+ 4.2.0Testing Discos for long-term accuracy, using automated optical character recognition.Baxter International
@@ -104,10 +104,11 @@
Cliraspberry11
- 1.5.6
+ 1.5.85.2.3trueUTF-8
+ 195.9.11.9.4
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/pom.xml b/pom.xml
index a0c178a..71dc1f0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0org.baxter.discoocr
- 4.1.0
+ 4.3.0jarDisco OCR Accuracy Over Life TestingTesting Discos for long-term accuracy, using automated optical character recognition.
@@ -25,10 +25,10 @@
2.1.0
-
+ 195.2.32.8.0
- 1.5.6
+ 1.5.85.9.11.9.4
@@ -47,6 +47,12 @@
javacv-platform${opencv.version}
+
+
+ org.openjfx
+ javafx-swing
+ ${javafx.version}
+
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/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 6b13da1..4d157e8 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.3.0";
- private static final String version = "4.1.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,15 +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();
}
@@ -875,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 939fdd4..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.*;
-import org.apache.commons.configuration2.builder.*;
-import org.apache.commons.configuration2.builder.fluent.*;
-
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 92b9634..dfe08c6 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,27 +8,37 @@ 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;
+
+//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.HSSFSheet;
+import static org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined;
/**
* Facade for saving data out to a file.
*
* @author Blizzard Finnegan
- * @version 3.0.0, 06 Feb. 2023
+ * @version 4.0.1, 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,40 +46,166 @@ 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
+ 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 XSSFWorkbook();
- outputSheet = outputWorkbook.createSheet();
- int startingRow = outputSheet.getLastRowNum();
- Row row = outputSheet.createRow(++startingRow);
- int cellnum = 0;
- Cell cell = row.createCell(cellnum++);
- cell.setCellValue("Iteration");
- for(int i = 0; i < camCount; i++)
- {
- cell = row.createCell(cellnum++);
- cell.setCellValue("Serial");
- cell = row.createCell(cellnum++);
- cell.setCellValue("Image Location");
- cell = row.createCell(cellnum++);
- cell.setCellValue("Read Value");
- cell = row.createCell(cellnum++);
- cell.setCellValue("");
- }
- FileOutputStream outputStream = new FileOutputStream(outputFile);
- outputWorkbook.write(outputStream);
- output = true;
- outputStream.close();
+ 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));
+
+ //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);
+ }
+
+ //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);}
+ }
+
/**
* Writes line to XLSX file.
*
@@ -80,14 +217,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