cameraConfig = new HashMap<>();
for(ConfigProperties property : ConfigProperties.values())
@@ -470,7 +495,7 @@ public class ConfigFacade
cameraConfig.put(property,propertyValue);
CONFIG_STORE.setProperty(propertyName,propertyValue);
}
- configMap.put(sectionName,cameraConfig);
+ CONFIG_MAP.put(sectionName,cameraConfig);
try
{
CONFIG_BUILDER.save();
diff --git a/src/main/java/org/baxter/disco/ocr/ConfigProperties.java b/src/main/java/org/baxter/disco/ocr/ConfigProperties.java
index 8d8c282..9eea39f 100644
--- a/src/main/java/org/baxter/disco/ocr/ConfigProperties.java
+++ b/src/main/java/org/baxter/disco/ocr/ConfigProperties.java
@@ -9,45 +9,105 @@ package org.baxter.disco.ocr;
public enum ConfigProperties
{
/**
- *X coordinate of the top-left coordinate for the newly cropped image.
+ *
+ * X coordinate of the top-left coordinate for the newly cropped image.
+ *
+ * Human readable name: "Crop X"
+ * Config name: "cropX"
+ * Default value: "275.0"
+ *
*/
CROP_X("Crop X","cropX",275.0),
/**
- *Y coordinate of the top-left coordinate for the newly cropped image.
+ *
+ * Y coordinate of the top-left coordinate for the newly cropped image.
+ *
+ * Human readable name: "Crop Y"
+ * Config name: "cropY"
+ * Default value: "205.0"
+ *
*/
CROP_Y("Crop Y","cropY",205.0),
/**
- *Width of the newly cropped image.
+ *
+ * Width of the newly cropped image.
+ *
+ * Human readable name: "Crop Width"
+ * Config name: "cropW"
+ * Default value: "80.0"
+ *
*/
CROP_W("Crop Width","cropW",80.0),
/**
- *Height of the newly cropped image.
+ *
+ * Height of the newly cropped image.
+ *
+ * Human readable name: "Crop Height"
+ * Config name: "cropH"
+ * Default value: "50.0"
+ *
*/
CROP_H("Crop Height","cropH",50.0),
/**
+ *
* Whether or not to threshold the image during processing.
+ *
+ * Human readable name: "Toggle Threshold"
+ * Config name: "threshold"
+ * Default value: "1.0"
+ *
*/
THRESHOLD("Toggle threshold","threshold",1.0),
/**
- * Whether or not to threshold the image during processing.
+ *
+ * Whether or not to crop the image during processing.
+ *
+ * Human readable name: "Toggle crop"
+ * Config name: "crop"
+ * Default value: "1.0"
+ *
*/
CROP("Toggle crop","crop",1.0),
/**
+ *
*How many frames to composite together while processing this camera's image.
+ *
+ * Human readable name: "Composite frame count"
+ * Config name: "compositeCount"
+ * Default value: "5.0"
+ *
*/
COMPOSITE_FRAMES("Composite frame count","compositeCount",5.0),
/**
+ *
* Whether or not to press the button on the device twice, when under test.
+ *
+ * Human readable name: "Prime device"
+ * Config name: "prime"
+ * Default value: "0.0"
+ *
*/
PRIME("Prime device?","prime",0.0),
/**
+ *
* Where the threshold point should land.
+ *
+ * Human readable name: "Threshold value"
+ * Config name: "thresholdValue"
+ * Default value: "45.0"
+ *
*/
THRESHOLD_VALUE("Threshold value","thresholdValue",45.0),
/**
+ *
* Whether the camera should be active.
+ *
+ * Human readable name: "Camera active"
+ * Config name: "active"
+ * Default value: "1.0"
+ *
*/
ACTIVE("Camera active?","active",1.0);
diff --git a/src/main/java/org/baxter/disco/ocr/DataSaving.java b/src/main/java/org/baxter/disco/ocr/DataSaving.java
index 6a8fed1..0f2a415 100644
--- a/src/main/java/org/baxter/disco/ocr/DataSaving.java
+++ b/src/main/java/org/baxter/disco/ocr/DataSaving.java
@@ -91,6 +91,7 @@ public class DataSaving
*/
public static boolean initWorkbook(String filename, int camCount, double targetTemp, double failRange)
{
+ ErrorLogging.logError("DEBUG: Initialising workbook...");
DataSaving.targetTemp = targetTemp;
DataSaving.failRange = failRange;
boolean output = false;
@@ -148,20 +149,27 @@ public class DataSaving
passPercentCell.setCellValue("Pass %");
try (FileOutputStream outputStream = new FileOutputStream(outputFile))
- { outputWorkbook.write(outputStream); }
- catch(Exception e) {ErrorLogging.logError(e);}
+ {
+ outputWorkbook.write(outputStream);
+ ErrorLogging.logError("DEBUG: Initialising workbook completed successfully.");
+ }
+ catch(Exception e)
+ {
+ ErrorLogging.logError("DEBUG: Workbook save failed!");
+ 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.
*/
private static void updateFormulas(int cameraCount)
{
+ ErrorLogging.logError("DEBUG: Updating formulas in Excel sheet...");
int rowIndex = 0;
FormulaEvaluator formulaEvaluator = outputWorkbook.getCreationHelper().createFormulaEvaluator();
int lastColumnOfData = outputSheet.getRow(rowIndex).getLastCellNum();
@@ -225,6 +233,7 @@ public class DataSaving
*/
public static boolean writeValues(int cycle, Map inputMap, Map cameraToFile)
{
+ ErrorLogging.logError("DEBUG: Writing values for " + (cycle + 1) + " cycle to worksheet.");
boolean output = false;
int cellnum = 0;
int startingRow = outputSheet.getLastRowNum();
@@ -284,8 +293,16 @@ public class DataSaving
row.createCell(cellnum++);
}
try (FileOutputStream outputStream = new FileOutputStream(outputFile))
- { outputWorkbook.write(outputStream); output = true; }
- catch(Exception e) {ErrorLogging.logError(e);}
+ {
+ outputWorkbook.write(outputStream);
+ output = true;
+ ErrorLogging.logError("DEBUG: Writing values to Excel sheet was successful.");
+ }
+ catch(Exception e)
+ {
+ ErrorLogging.logError("DEBUG: Writing values to Excel sheet failed!");
+ ErrorLogging.logError(e);
+ }
updateFormulas(cameraNames.size());
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 dd78c86..f13949c 100644
--- a/src/main/java/org/baxter/disco/ocr/ErrorLogging.java
+++ b/src/main/java/org/baxter/disco/ocr/ErrorLogging.java
@@ -18,7 +18,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
* as well as stderr.
*
* @author Blizzard Finnegan
- * @version 1.3.2, 07 Mar. 2023
+ * @version 1.4.0, 07 Mar. 2023
*/
public class ErrorLogging
@@ -85,8 +85,7 @@ public class ErrorLogging
* Logs error thrown by runtime.
* Prepends the current date and time to the log line.
*
- * @param error Pass in the appropriate error,
- * for it to be parsed and logged.
+ * @param error Any error being thrown to be logged.
*/
public static void logError(Throwable error)
{
@@ -97,27 +96,22 @@ public class ErrorLogging
}
/**
- * Logs error manually caught by user.
- * Prepends the current date and time to the log line.
- * Particularly useful for catching potential errors that do not
- * eplicitly throw an error.
+ * Logs data manually set by the developer.
*
- * @param error Pass in the necessary error information,
- * as a string.
+ * @param error Any information to save to the log file.
*/
public static void logError(String error)
{
String errorMessage = datetime.format(LocalDateTime.now()) + "\t- " + error;
fileOut.println(errorMessage);
fileOut.flush();
- if(!error.substring(0,5).equals("DEBUG"))
- System.out.println(errorMessage);
+ if(error.length() < 5) System.out.println(error);
+ else if(!error.substring(0,5).equals("DEBUG"))
+ System.out.println(error);
}
/**
* Close all open logs.
- *
- * !!! CALL ONCE, AT END OF PROGRAM !!!
*/
public static void closeLogs()
{
diff --git a/src/main/java/org/baxter/disco/ocr/MovementFacade.java b/src/main/java/org/baxter/disco/ocr/MovementFacade.java
index a1024ae..c1f53d9 100644
--- a/src/main/java/org/baxter/disco/ocr/MovementFacade.java
+++ b/src/main/java/org/baxter/disco/ocr/MovementFacade.java
@@ -17,7 +17,7 @@ import com.pi4j.io.gpio.digital.PullResistance;
* Currently missing Run switch compatibility.
*
* @author Blizzard Finnegan
- * @version 3.0.1, 10 Mar. 2023
+ * @version 3.1.0, 16 Mar. 2023
*/
public class MovementFacade
{
@@ -39,11 +39,23 @@ public class MovementFacade
/**
* Amount of distance to travel.
- * Measured in... seemingly arbitrary units? Not sure on the math here.
+ * Measured in polls (~10ms intervals currently)
* Set in {@link #findDistance()}
*/
private static double TRAVEL_DIST;
+ /**
+ * How many milliseconds to wait before polling the GPIO
+ */
+ private static final int POLL_WAIT = 10;
+
+ /**
+ * How many polls before assuming bad GPIO connection.
+ * The DUTs fall asleep after ~6 seconds,so the travel must be less than 3 seconds.
+ */
+ private static final int TIMEOUT = 300;
+
+
//PWM Addresses
//All addresses are in BCM format.
@@ -77,58 +89,65 @@ public class MovementFacade
*/
private static final int LOWER_LIMIT_ADDR = 24;
- /**
- * How many milliseconds to wait before polling the GPIO
- */
- private static final int POLL_WAIT = 10;
-
//Pi GPIO pin objects
/**
+ *
* Upper limit switch object.
*
* Status: High; Upper limit switch has been reached.
* Status: Low; Upper limit switch has not been reached.
+ *
*/
private static DigitalInput upperLimit;
/**
+ *
* Lower limit switch object.
*
* Status: High; Lower limit switch has been reached.
* Status: Low; Lower limit switch has not been reached.
+ *
*/
private static DigitalInput lowerLimit;
/**
- * Lower limit switch object.
+ *
+ * Run switch object.
*
* Status: High; Test may continue.
* Status: Low; Test must stop immediately.
+ *
*/
private static DigitalInput runSwitch;
/**
+ *
* Motor power object.
*
* Status: High; Motor starts moving, in the direction defined by {@link #motorDirection}.
* Status: Low; Motor stops moving.
+ *
*/
private static DigitalOutput motorEnable;
/**
+ *
* Defines the movement direction for the motor enabled by {@link #motorEnable}.
*
* Status: High; Motor will move upwards.
* Status: Low; Motor will move downwards.
+ *
*/
private static DigitalOutput motorDirection;
/**
+ *
* Piston control pin object.
*
* Status: High; Piston is extended.
* Status: Low; Piston is retracted.
+ *
*/
private static DigitalOutput pistonActivate;
@@ -137,8 +156,32 @@ public class MovementFacade
*/
private static Context pi4j;
- static
+ public static void init() throws Exception
{
+ pi4j = Pi4J.newAutoContext();
+
+ ErrorLogging.logError("DEBUG: Opening input GPIO pins...");
+ upperLimit = inputBuilder(UPPER_LIMIT_ADDR);
+ lowerLimit = inputBuilder(LOWER_LIMIT_ADDR);
+ runSwitch = inputBuilder(RUN_SWITCH_ADDR);
+
+ ErrorLogging.logError("DEBUG: Opening output GPIO pins...");
+ motorEnable = outputBuilder(MOTOR_ENABLE_ADDR);
+ motorDirection = outputBuilder(MOTOR_DIRECTION_ADDR);
+ pistonActivate = outputBuilder(PISTON_ADDR);
+
+ try
+ {
+ findDistance();
+ pressButton();
+ }
+ catch(Exception e)
+ {
+ ErrorLogging.logError(e);
+ pi4j.shutdown();
+ throw new Exception("GPIO init error!!! Check GPIO connections.");
+ }
+
ErrorLogging.logError("DEBUG: Starting lock thread...");
runSwitchThread = new Thread(() ->
{
@@ -163,33 +206,19 @@ public class MovementFacade
}, "Run switch monitor.");
runSwitchThread.start();
- pi4j = Pi4J.newAutoContext();
-
- 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);
-
- motorEnable = outputBuilder("motorEnable" , "Motor Enable" , MOTOR_ENABLE_ADDR);
- motorDirection = outputBuilder("motorDirection", "Motor Direction", MOTOR_DIRECTION_ADDR);
- pistonActivate = outputBuilder("piston" , "Piston Activate", PISTON_ADDR);
-
- findDistance();
}
/**
* Builder function for DigitalInput pins.
*
- * @param id ID of the new {@link DigitalInput} pin.
- * @param name Name of the new {@link DigitalInput} pin.
* @param address BCM address of the {@link DigitalInput} pin.
*
* @return newly created {@link DigitalInput} object.
*/
- private static DigitalInput inputBuilder(String id, String name, int address)
+ private static DigitalInput inputBuilder(int address)
{
DigitalInputConfigBuilder configBuilder = DigitalInput.newConfigBuilder(pi4j)
- .id(id)
.address(address)
.pull(PullResistance.PULL_DOWN)
.debounce(3000L)
@@ -200,16 +229,13 @@ public class MovementFacade
/**
* Builder function for DigitalOutput pins.
*
- * @param id ID of the new {@link DigitalOutput} pin.
- * @param name Name of the new {@link DigitalOutput} pin.
* @param address BCM address of the {@link DigitalOutput} pin.
*
* @return newly created {@link DigitalOutput} object
*/
- private static DigitalOutput outputBuilder(String id, String name, int address)
+ private static DigitalOutput outputBuilder(int address)
{
DigitalOutputConfigBuilder configBuilder = DigitalOutput.newConfigBuilder(pi4j)
- .id(id)
.address(address)
.shutdown(DigitalState.LOW)
.initial(DigitalState.LOW)
@@ -224,27 +250,23 @@ public class MovementFacade
public static int resetArm()
{
ErrorLogging.logError("DEBUG: --------------------------------------");
+ ErrorLogging.logError("DEBUG: Resetting arm...");
int counter;
- ErrorLogging.logError("DEBUG: Setting minimum frequency of PWM...");
if(upperLimit.isHigh())
{
ErrorLogging.logError("DEBUG: Motor at highest point! Lowering to reset.");
- motorDirection.low();
- ErrorLogging.logError("DEBUG: Motor offset on.");
- motorEnable.on();
+ motorDirectionDown();
+ motorOn();
try{ Thread.sleep(500); }
catch (Exception e){ ErrorLogging.logError(e); }
- motorEnable.off();
- ErrorLogging.logError("DEBUG: Motor offset off.");
+ motorOff();
}
ErrorLogging.logError("DEBUG: Moving motor to highest point.");
- motorDirection.high();
+ motorDirectionUp();
- ErrorLogging.logError("DEBUG: Motor return on.");
- motorEnable.on();
+ motorOn();
- ErrorLogging.logError("DEBUG: Is the upper limit switch reached? " + upperLimit.isHigh());
- for(counter = 0; counter < Integer.MAX_VALUE; counter++)
+ for(counter = 0; counter < TIMEOUT; counter++)
{
try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); }
if(upperLimit.isOn())
@@ -253,24 +275,34 @@ public class MovementFacade
if(upperLimit.isOn()) break;
}
}
- motorEnable.off();
- ErrorLogging.logError("DEBUG: Motor returned after " + counter + " polls.");
- ErrorLogging.logError("DEBUG: --------------------------------------");
- return counter;
+ motorOff();
+ if(counter < TIMEOUT)
+ {
+ ErrorLogging.logError("DEBUG: Motor returned after " + counter + " polls.");
+ ErrorLogging.logError("DEBUG: --------------------------------------");
+ return counter;
+ }
+ else
+ {
+ ErrorLogging.logError("DEBUG: No motor return after 3 seconds.");
+ ErrorLogging.logError("DEBUG: --------------------------------------");
+ return -1;
+ }
}
/**
* Used to programmatically find the distance between the upper and lower limit switches.
*/
- private static void findDistance()
+ private static void findDistance() throws Exception
{
- resetArm();
+ ErrorLogging.logError("DEBUG: Measuring travel time to buttons...");
+ int safeGPIO = resetArm();
+ if(safeGPIO < 0) throw new Exception("Failed GPIO initialisation!");
int downTravelCounter = 0;
int upTravelCounter = 0;
- //pwm.on(DUTY_CYCLE, MIN_FREQUENCY);
- motorDirection.low();
- motorEnable.on();
- for(downTravelCounter = 0; downTravelCounter < Integer.MAX_VALUE; downTravelCounter++)
+ motorDirectionDown();
+ motorOn();
+ for(downTravelCounter = 0; downTravelCounter < TIMEOUT; downTravelCounter++)
{
try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); }
if(lowerLimit.isOn())
@@ -279,14 +311,15 @@ public class MovementFacade
if(lowerLimit.isOn()) break;
}
}
- motorEnable.off();
+ motorOff();
if(lowerLimit.isOff()) ErrorLogging.logError("DEBUG: False positive on findDistance down!");
+ if(downTravelCounter == TIMEOUT) throw new Exception("PWM set too slow!");
ErrorLogging.logError("DEBUG: Down travel count: " + downTravelCounter);
- motorDirection.high();
- motorEnable.on();
- for(upTravelCounter = 0; upTravelCounter < Integer.MAX_VALUE; upTravelCounter++)
+ motorDirectionUp();
+ motorOn();
+ for(upTravelCounter = 0; upTravelCounter < TIMEOUT; upTravelCounter++)
{
try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); }
if(upperLimit.isOn())
@@ -295,8 +328,9 @@ public class MovementFacade
if(upperLimit.isOn()) break;
}
}
- motorEnable.off();
+ motorOff();
if(upperLimit.isOff()) ErrorLogging.logError("DEBUG: False positive on findDistance up!");
+ if(upTravelCounter == TIMEOUT) throw new Exception("Failed GPIO initialisation!");
ErrorLogging.logError("DEBUG: Up travel count: " + downTravelCounter);
@@ -310,7 +344,10 @@ public class MovementFacade
* Detects if the limit switch is active before activating motor.
*
* @param moveUp Whether to send the fixture up or down. (True = up, False = down)
- * @return true if movement was successful; otherwise false
+ * @return {@link FinalState} of the final status of the motor.
+ * FAILED if travel fails. (Theoretically possible, no return state though)
+ * UNSAFE if travel is stopped by the limit switch.
+ * SAFE if travel is stopped by timeout, or if fixture is already at limit switch before movement.
*/
private static FinalState gotoLimit(boolean moveUp)
{
@@ -318,18 +355,16 @@ public class MovementFacade
DigitalInput limitSense;
if(moveUp)
{
- motorDirection.high();
+ motorDirectionUp();
limitSense = upperLimit;
- ErrorLogging.logError("DEBUG: Sending fixture up...");
}
else
{
- motorDirection.low();
+ motorDirectionDown();
limitSense = lowerLimit;
- ErrorLogging.logError("DEBUG: Sending fixture down...");
}
- if(limitSense.isHigh()) return FinalState.SAFE;
+ if(limitSense.isOn()) return FinalState.SAFE;
int totalPollCount = (int)(TRAVEL_DIST);
int highSpeedPolls = (int)(totalPollCount * SLOW_POLL_FACTOR);
@@ -337,17 +372,21 @@ public class MovementFacade
ErrorLogging.logError("DEBUG: Travel time: " + totalPollCount);
ErrorLogging.logError("DEBUG: High speed poll count: " + highSpeedPolls);
ErrorLogging.logError("DEBUG: =============================");
- motorEnable.on();
+ motorOn();
for(int i = 0; i < highSpeedPolls; i++)
{
try{ Thread.sleep(POLL_WAIT); } catch(Exception e){ ErrorLogging.logError(e); }
if(limitSense.isOn())
{
- motorEnable.off();
- break;
+ try{ Thread.sleep(5); } catch(Exception e){ErrorLogging.logError(e); }
+ if(limitSense.isOn())
+ {
+ motorOff();
+ break;
+ }
}
}
- motorEnable.off();
+ motorOff();
output = (limitSense.isOn() ? FinalState.UNSAFE : FinalState.SAFE);
@@ -408,6 +447,33 @@ public class MovementFacade
pressButton();
}
+ private static void motorOn()
+ {
+ ErrorLogging.logError("DEBUG: Motor on.");
+ motorEnable.on();
+ }
+
+ private static void motorOff()
+ {
+ ErrorLogging.logError("DEBUG: Motor off.");
+ motorEnable.off();
+ }
+
+ private static void motorDirectionUp()
+ {
+ ErrorLogging.logError("DEBUG: Motor travelling up.");
+ motorDirection.high();
+ }
+
+ private static void motorDirectionDown()
+ {
+ ErrorLogging.logError("DEBUG: Motor travelling down.");
+ motorDirection.low();
+ }
+
+ /**
+ * Possible states for motor control movement.
+ */
public enum FinalState
{ UNSAFE, SAFE, FAILED; }
}
diff --git a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java
index 13b5a91..d946869 100644
--- a/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java
+++ b/src/main/java/org/baxter/disco/ocr/OpenCVFacade.java
@@ -27,6 +27,7 @@ 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;
@@ -76,13 +77,13 @@ public class OpenCVFacade
//Initial Camera creation
static
{
+ ErrorLogging.logError("Initialising cameras...");
File devDirectory = new File("/dev");
for(File cameraFile : devDirectory.listFiles(
(file) -> { return file.getName().contains(CAMERA_FILE_PREFIX); }))
{
String cameraName = cameraFile.getName().
substring(CAMERA_FILE_PREFIX.length());
- ErrorLogging.logError("DEBUG: Camera name: " + cameraName);
newCamera(cameraName, cameraFile.getAbsolutePath());
}
}
@@ -90,29 +91,14 @@ public class OpenCVFacade
/**
* Default camera creator function.
* Creates a camera, and adds it to cameraMap.
- * Uses values in constants, listed previous.
+ * Uses {@link #IMG_WIDTH}, {@link #IMG_HEIGHT}, {@value #IMG_WIDTH}
*
* @param name Name of the new camera
* @param location Location of the new camera
*/
private static void newCamera(String name, String location)
{
- newCamera(name, location, IMG_WIDTH, IMG_HEIGHT);
- }
-
- /**
- * Camera creator function, with custom width and height.
- * Creates a camera, and adds it to cameraMap.
- * Defaults to {@link #CAMERA_CODEC} definition.
- *
- * @param name Name of the new camera
- * @param location Location of the new camera
- * @param width Width of the camera's image, in pixels.
- * @param height height of the camera's image, in pixels.
- */
- private static void newCamera(String name, String location, int width, int height)
- {
- newCamera(name, location, width, height, CAMERA_CODEC);
+ newCamera(name, location, IMG_WIDTH, IMG_HEIGHT, CAMERA_CODEC);
}
/**
@@ -166,10 +152,11 @@ public class OpenCVFacade
* @param cameraName Name of the camera to take a picture with.
*
* @return null if camera doesn't exist, or if capture fails;
- * otherwise, Frame of the taken image
+ * otherwise, Mat of the taken image
*/
private static Mat takePicture(String cameraName)
{
+ ErrorLogging.logError("DEBUG: Taking picture...");
Mat output = null;
Frame temp = null;
@@ -177,12 +164,16 @@ public class OpenCVFacade
{
try{ temp = cameraMap.get(cameraName).grab(); }
catch(Exception e) { ErrorLogging.logError(e); }
- }
- //Convert to grayscale
- Mat in = MAT_CONVERTER.convertToMat(temp);
- output = MAT_CONVERTER.convertToMat(temp);
- cvtColor(in,output,CV_BGR2GRAY);
+ //Convert to grayscale
+ Mat in = MAT_CONVERTER.convertToMat(temp);
+ output = MAT_CONVERTER.convertToMat(temp);
+ cvtColor(in,output,CV_BGR2GRAY);
+ }
+ else
+ {
+ ErrorLogging.logError("DEBUG: Invalid camera!");
+ }
return output;
}
@@ -196,6 +187,7 @@ public class OpenCVFacade
*/
public static File showImage(String cameraName)
{
+ ErrorLogging.logError("DEBUG: Showing preview...");
File imageLocation = completeProcess(cameraName,ConfigFacade.getImgSaveLocation() + "/config");
if(imageLocation == null) return null;
Frame outputImage = MAT_CONVERTER.convert(imread(imageLocation.getAbsolutePath()));
@@ -224,10 +216,11 @@ public class OpenCVFacade
* @param cameraName Name of the camera to take a picture with.
* @param frameCount The number of images to take.
*
- * @return List of Frames taken from the camera. List is in order
+ * @return List of {@link Mat} taken from the camera. List is in order
*/
private static List takeBurst(String cameraName, int frameCount)
{
+ ErrorLogging.logError("DEBUG: Taking burst of " + frameCount + " images...");
List output = null;
if(getCameraNames().contains(cameraName))
{
@@ -237,6 +230,10 @@ public class OpenCVFacade
output.add(takePicture(cameraName));
}
}
+ else
+ {
+ ErrorLogging.logError("DEBUG: Invalid camera!");
+ }
return output;
}
@@ -248,27 +245,31 @@ public class OpenCVFacade
public static void setCrop(String cameraName)
{
Mat uncroppedImage = takePicture(cameraName);
- Rect roi = selectROI("Pick Crop Location", uncroppedImage);
- if(roi.x() == 0 && roi.y() == 0 && roi.width() == 0 && roi.height() == 0)
+ if(uncroppedImage != null)
{
- ErrorLogging.logError("Crop error! - Invalid crop selection.");
- ErrorLogging.logError("If the crop region did not have a box indicating is location, please restart the program.");
- ConfigFacade.setValue(cameraName,ConfigProperties.CROP_X,ConfigProperties.CROP_X.getDefaultValue());
- ConfigFacade.setValue(cameraName,ConfigProperties.CROP_Y,ConfigProperties.CROP_Y.getDefaultValue());
- ConfigFacade.setValue(cameraName,ConfigProperties.CROP_W,ConfigProperties.CROP_W.getDefaultValue());
- ConfigFacade.setValue(cameraName,ConfigProperties.CROP_H,ConfigProperties.CROP_H.getDefaultValue());
- return;
+ ErrorLogging.logError("DEBUG: Allowing user to modify crop.");
+ Rect roi = selectROI("Pick Crop Location", uncroppedImage);
+ if(roi.x() == 0 && roi.y() == 0 && roi.width() == 0 && roi.height() == 0)
+ {
+ ErrorLogging.logError("Crop error! - Invalid crop selection.");
+ ErrorLogging.logError("If the crop region did not have a box indicating is location, please restart the program.");
+ ConfigFacade.setValue(cameraName,ConfigProperties.CROP_X,ConfigProperties.CROP_X.getDefaultValue());
+ ConfigFacade.setValue(cameraName,ConfigProperties.CROP_Y,ConfigProperties.CROP_Y.getDefaultValue());
+ ConfigFacade.setValue(cameraName,ConfigProperties.CROP_W,ConfigProperties.CROP_W.getDefaultValue());
+ ConfigFacade.setValue(cameraName,ConfigProperties.CROP_H,ConfigProperties.CROP_H.getDefaultValue());
+ return;
+ }
+ ConfigFacade.setValue(cameraName,ConfigProperties.CROP_X, roi.x());
+ ConfigFacade.setValue(cameraName,ConfigProperties.CROP_Y, roi.y());
+ ConfigFacade.setValue(cameraName,ConfigProperties.CROP_W, roi.width());
+ ConfigFacade.setValue(cameraName,ConfigProperties.CROP_H, roi.height());
}
- ConfigFacade.setValue(cameraName,ConfigProperties.CROP_X, roi.x());
- ConfigFacade.setValue(cameraName,ConfigProperties.CROP_Y, roi.y());
- ConfigFacade.setValue(cameraName,ConfigProperties.CROP_W, roi.width());
- ConfigFacade.setValue(cameraName,ConfigProperties.CROP_H, roi.height());
}
/**
* Crop a given image, based on dimensions in the configuration.
*
- * @param image Frame taken from the camera
+ * @param image Mat taken from the camera
* @param cameraName Name of the camera the frame is from
*/
private static Mat crop(Mat image, String cameraName)
@@ -278,18 +279,18 @@ public class OpenCVFacade
int width = (int)ConfigFacade.getValue(cameraName,ConfigProperties.CROP_W);
int height = (int)ConfigFacade.getValue(cameraName,ConfigProperties.CROP_H);
Rect roi = new Rect(x,y,width,height);
- return crop(image, roi,cameraName);
+ return crop(image, roi);
}
/**
* Crop the given image, based on dimensions defined in a {@link Rect}
*
- * @param image Frame taken from the camera
+ * @param image Mat taken from the camera
* @param roi The region of interest to crop the image to
*
- * @return Frame of the cropped image
+ * @return Mat of the cropped image
*/
- private static Mat crop(Mat image, Rect roi, String cameraName)
+ private static Mat crop(Mat image, Rect roi)
{
Mat output = image.apply(roi).clone();
return output;
@@ -300,29 +301,44 @@ public class OpenCVFacade
* Put the given image through a binary threshold.
* This reduces the image from greyscale to only pure white and black pixels.
*
- * @param image Frame taken from the camera.
+ * @param image Mat taken from the camera.
*
- * @return Frame of the thresholded image
+ * @return Mat of the thresholded image
*/
private static Mat thresholdImage(Mat image,String cameraName)
+ {
+ double thresholdValue = ConfigFacade.getValue(cameraName,ConfigProperties.THRESHOLD_VALUE);
+ return thresholdImage(image, thresholdValue);
+ }
+
+ /**
+ * Put the given image through a binary threshold.
+ * This reduces the image from greyscale to only pure white and black pixels.
+ *
+ * @param image Mat taken from the camera.
+ *
+ * @return Mat of the thresholded image
+ */
+ private static Mat thresholdImage(Mat image, double thresholdValue)
{
Mat output = image;
Mat in = image;
- double thresholdValue = ConfigFacade.getValue(cameraName,ConfigProperties.THRESHOLD_VALUE);
threshold(in,output,thresholdValue,255,THRESH_BINARY);
return output;
}
/**
- * Save input Frame at the location given.
+ * Save input image at the location given.
*
* @param image Image to be saved.
* @param fileLocation Where to save the image.
+ * @param cameraName Name of the camera the image came from.
*
* @return File if save was successful, otherwise null
*/
private static File saveImage(Mat image, String fileLocation, String cameraName)
{
+ ErrorLogging.logError("DEBUG: Saving image from " + cameraName + " to " + fileLocation);
File output = null;
IplImage temp = MAT_CONVERTER.convertToIplImage(MAT_CONVERTER.convert(image));
fileLocation = fileLocation + "/" + ErrorLogging.fileDatetime.format(LocalDateTime.now()) + "-" + cameraName + ".png";
@@ -338,29 +354,21 @@ public class OpenCVFacade
* from {@link ConfigFacade}.
*
* @param images List of images to be composed
- * @param threshold Whether to put the image through a binary threshold
- * @param crop Whether to crop the image
- *
* @return A single image, found by boolean AND-ing together all parsed images.
*/
- private static Mat compose(List images, boolean threshold,
- boolean crop, String cameraName)
+ private static Mat compose(List images)
{
ErrorLogging.logError("DEBUG: Attempting to compose " + images.size() + " images...");
Mat output = null;
- int iterationCount = 1;
for(Mat image : images)
{
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(output == null) output = processedImage.clone();
- bitwise_and((iterationCount == 1 ? processedImage : output),processedImage, output);
+ bitwise_and(output,processedImage, output);
- iterationCount++;
}
if(output != null) ErrorLogging.logError("DEBUG: Compositing successful!");
@@ -391,8 +399,32 @@ public class OpenCVFacade
return output;
}
List imageList = takeBurst(cameraName, compositeFrames);
+ List processedImageList = new ArrayList<>();
- Mat finalImage = compose(imageList, threshold, crop, cameraName);
+ Mat finalImage = null;
+ if(crop) ErrorLogging.logError("DEBUG: Cropping is enabled.");
+ if(threshold) ErrorLogging.logError("DEBUG: Thresholding is enabled.");
+ for(Mat image : imageList)
+ {
+ Mat processedImage = image.clone();
+ image.copyTo(processedImage);
+
+ if(crop)
+ processedImage = crop(processedImage,cameraName);
+
+ if(threshold)
+ processedImage = thresholdImage(processedImage,cameraName);
+
+ if(finalImage == null)
+ finalImage = processedImage.clone();
+
+ processedImageList.add(processedImage);
+ }
+
+ if(compositeFrames > 1)
+ finalImage = compose(processedImageList);
+
+ //Mat finalImage = compose(imageList, threshold, crop, cameraName);
output = saveImage(finalImage, saveLocation,cameraName);
return output;
}
diff --git a/src/main/java/org/baxter/disco/ocr/TesseractFacade.java b/src/main/java/org/baxter/disco/ocr/TesseractFacade.java
index 00b7e76..79682ef 100644
--- a/src/main/java/org/baxter/disco/ocr/TesseractFacade.java
+++ b/src/main/java/org/baxter/disco/ocr/TesseractFacade.java
@@ -31,6 +31,7 @@ public class TesseractFacade
private static TessBaseAPI api;
/**
+ *
* OCR engine mode.
*
* From https://ai-facets.org/tesseract-ocr-best-practices/:
@@ -40,6 +41,7 @@ public class TesseractFacade
* 3: Default, based on what is available
*
* As I didn't write the training data, and don't actually know what kind of network the training set requires, this value is set to default.
+ *
*/
private static final int OCR_ENGINE_MODE = 3;
@@ -99,6 +101,7 @@ public class TesseractFacade
else ErrorLogging.logError("OCR ERROR!!! - OCR output is not a Double.");
}
}
+ else { ErrorLogging.logError("OCR Failed to retrieve information!"); }
return output;
}
}
diff --git a/toDoList.md b/toDoList.md
index 9ecbeeb..829a597 100644
--- a/toDoList.md
+++ b/toDoList.md
@@ -4,13 +4,13 @@
### OpenCVFacade
-- [ ] completeProcess should have more robust file output checking
+- [x] Break out compose into a separate function
+- [ ] saveImage should have more robust file output checking
## Low-priority improvements
### All
-- [x] denote overrided functions in first sentence of function
- [ ] reduce Javadoc linking to one link per reference per class
- [ ] Use generated Javadocs to improve Javadocs (perpetual)
@@ -18,8 +18,3 @@
- [ ] Debug and ensure GUI is written properly
-### TesseractFacade
-
-- [x] parse text-based input?
- - Determined to be unnecessary, as further data filtering is all that is required.
-
diff --git a/umlAndStateFlow.xmi b/umlAndStateFlow.xmi
index a1edaa8..75e0aa6 100644
--- a/umlAndStateFlow.xmi
+++ b/umlAndStateFlow.xmi
@@ -1,84 +1,1010 @@
-
+
- umbrello uml modeller 2.32.3 http://umbrello.kde.org
- 1.7.3
+ umbrello uml modeller 2.32.2 http://umbrello.kde.org
+ 1.6.19
UnicodeUTF8
-
+
-
+
-
-
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -86,55 +1012,238 @@
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+