v4.3.8 push #17

Merged
blizzardfinnegan merged 12 commits from devel into stable 2023-03-16 09:56:18 -04:00
18 changed files with 1685 additions and 343 deletions

View file

@ -1,34 +0,0 @@
# This workflow will build a package using Maven and then publish it to GitHub packages when a release is created
# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#apache-maven-with-a-settings-path
name: Maven Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Publish to GitHub Packages Apache Maven
run: mvn deploy -s $GITHUB_WORKSPACE/settings.xml
env:
GITHUB_TOKEN: ${{ github.token }}

View file

@ -1,2 +1,2 @@
##EXAMPLE: ACTION=="add", KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ENV{ID_PATH}=="platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0", SYMLINK+="video-cam1"
ACTION=="add", KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ENV{ID_PATH}=="fillerText", SYMLINK+="video-cam1"
##EXAMPLE: ACTION=="add", KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ENV{ID_PATH}=="platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1.4:1.0", SYMLINK+="video-cam-left"
ACTION=="add", KERNEL=="video[0-9]*", SUBSYSTEM=="video4linux", SUBSYSTEMS=="usb", ENV{ID_PATH}=="fillerText", SYMLINK+="video-cam-left"

View file

@ -35,6 +35,8 @@ To further develop this software, or to compile it from source, the following is
- A Java-compatible IDE
- Maven
- OpenJDK11
- .NET 6.0
- (Optional, for complete dependency checking)
OpenJDK11 is explicitly required, as it is the only currently available Java development platform compatible with [Pi4J](https://pi4j.com/getting-started/). According to the [documentation for Pi4J](https://pi4j.com/getting-started/developing-on-a-remote-pc/), development on a Raspberry Pi is possible, but given this project's build time (as of 4.0.0-rc3, 2-5 minutes on a Baxter-distributed device, before documentation generation), it is recommended to build on x86-64, and copy to the compiled JAR to the Pi. As such, this repository has been designed with this development model in mind. If you are intending on compiling on a Pi, please see the above-linked documentation to see what should be modified in your local `pom.xml` file.
@ -94,7 +96,7 @@ ls /dev/video-*
## Installation
The project is then built from source, and the output final binary (located in `target/discoTesting.jar`) is copied to the Raspberry Pi for use.
The project is then built from source, and the output final binary (located in `target/discoTesting-[version].jar`) is copied to the Raspberry Pi for use.
This project requires use of `udev` rules to ensure that cameras are in the proper location, by default. This can be modified in this project (camera initialisation is done in the initialisation of `OpenCVFacade`).
@ -140,27 +142,31 @@ Before building this project, decide whether you want a TUI (Terminal User Inter
For your first time compiling the project, clone the repository onto your computer, then run the following in a terminal with Maven to compile the project into a runnable JAR file:
```
mvn clean package
mvn -Djavacpp.platform=linux-armhf -Djavacpp.platform=linux-arm64 clean package
```
The `-Djavacpp.platform=linux-armhf -Djavacpp.platform=linux-arm64` flags are *crucial*, as without them, the final compiled JAR file will be ~1GB, rather than ~100MB. The first flag tells the compiler to only include the dependencies necessary for the `linux-armhf` platform, while the second flag additionally says to include dependencies for `linux-arm64`. Without this flag, the compiler includes dependencies for every platform, from `android-arm` to `windows-x86_64`, which are not necessary and will result in slower compile times.
Note that, if you are aware of which architecture your particular Raspberry Pi's operating system is built for, you can remove the other flag from the above (and below) command(s), to improve efficiency, compile time, and storage requirements. This can be found by booting your pi, and running the command `uname -m`. If the response is `arm7l`, then it requires the `linux-armhf` flag. If the response is `aarch64`, then it requires the `linux-arm64` flag.
Maven can also be interacted with in a GUI environment in Visual Studio Code, but at time of writing, this process is unknown to me.
This will create a new `target` folder, download all dependencies, and compile the code into a final JAR file. Subsequent project builds can be alled using either the above command (which will delete the previous `target` folder before recreating it, copying the required libraries, and compiling the code), or to save time, the following can also be run instead:
```
mvn package
mvn -Djavacpp.platform=linux-armhf -Djavacpp.platform=linux-arm64 package
```
As the next section describes, you can also build documentation at the same time, by running the following in your terminal:
```
mvn package; mvn site
mvn -Djavacpp.platform=linux-armhf -Djavacpp.platform=linux-arm64 package; mvn site
```
or
```
mvn clean package; mvn site
mvn -Djavacpp.platform=linux-armhf -Djavacpp.platform=linux-arm64 clean package; mvn site
```
## Documentation
@ -176,5 +182,15 @@ The documentation site can then be found in `target/site/index.html`.
Note that because this documentation is generated in the same folder as the final project file, running `mvn clean package` will delete the documentation in its current state. As such, it is recommended to run a new documentation generation call on every clean build, like so:
```
mvn clean package; mvn site
mvn -Djavacpp.platform=linux-armhf -Djavacpp.platform=linux-arm64 clean package; mvn site
```
## Dependency Analysis
This project has added the ability to check if its dependencies have any published vulnerabilities. This can be done by cloning the repository, then running the following command:
```
mvn dependency-check:check
```
This will generated a file in the target directory entitled `dependency-check-report.html`. This file can be opened in any browser. This contains a list of dependencies, and their associated vulnerabilities.
At the time of writing, this report primarily contains vulnerabilities in the Windows DLLs packaged as part of OpenCV. These are only referenced at compile time, and are not contained in the package themselves.

BIN
activity diagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
class diagram.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

View file

@ -4,7 +4,7 @@
<groupId>org.baxter.disco</groupId>
<artifactId>ocr</artifactId>
<name>Disco OCR Accuracy Over Life Testing</name>
<version>4.3.7</version>
<version>4.3.8</version>
<description>Testing Discos for long-term accuracy, using automated optical character recognition.</description>
<organization>
<name>Baxter International</name>
@ -18,10 +18,14 @@
<version>3.7.1</version>
<configuration />
</plugin>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>8.1.2</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
<configuration />
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
@ -91,6 +95,9 @@
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.1</version>
<configuration>
<show>private</show>
</configuration>
</plugin>
</plugins>
</reporting>

18
pom.xml
View file

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.baxter.disco</groupId>
<artifactId>ocr</artifactId>
<version>4.3.7</version>
<version>4.3.8</version>
<packaging>jar</packaging>
<name>Disco OCR Accuracy Over Life Testing</name>
<description>Testing Discos for long-term accuracy, using automated optical character recognition.</description>
@ -102,12 +102,20 @@
<configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>8.1.2</version>
<!--<configuration>
<suppressionFiles>
<suppressionFile>dependency-check-suppressions.xml</suppressionFile>
</suppressionFiles>
</configuration>-->
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
<configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -184,9 +192,9 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.1</version>
<!--<configuration>
<configuration>
<show>private</show>
</configuration>-->
</configuration>
</plugin>
</plugins>
</reporting>

View file

@ -1,2 +1,2 @@
#! /usr/bin/env sh
sudo java -jar discoTesting-4.3.7.jar 2>/dev/null
sudo java -jar discoTesting-4.3.8.jar 2>/dev/null

View file

@ -18,14 +18,14 @@ import java.util.concurrent.locks.ReentrantLock;
* classes).
*
* @author Blizzard Finnegan
* @version 1.7.1, 10 Mar. 2023
* @version 1.8.0, 16 Mar. 2023
*/
public class Cli
{
/**
* Complete build version number
*/
private static final String version = "4.3.7";
private static final String version = "4.3.8";
/**
* Currently saved iteration count.
@ -50,6 +50,11 @@ public class Cli
*/
private static boolean camerasConfigured = false;
/**
* Whether GPIO is safe to use
*/
private static boolean safeGPIO = false;
/**
* Number of options currently available in the main menu.
*/
@ -68,16 +73,14 @@ public class Cli
static
{
ErrorLogging.logError("DEBUG: START OF PROGRAM");
}
public static void main(String[] args)
{
//Beginning message to user
ErrorLogging.logError("========================");
ErrorLogging.logError("Accuracy Over Life Test");
ErrorLogging.logError("Version: " + version);
ErrorLogging.logError("========================");
}
public static void main(String[] args)
{
try{
inputScanner = new Scanner(System.in);
@ -87,7 +90,33 @@ public class Cli
ErrorLogging.logError("Calibrating motor movement. ");
ErrorLogging.logError("The piston will fire momentarily when the motor calibration is complete.");
MovementFacade.pressButton();
boolean testedGPIO = false;
do
{
try
{
MovementFacade.init();
ErrorLogging.logError("Motor movement successfully calibrated!");
testedGPIO = true;
safeGPIO = true;
}
catch(Exception e)
{
ErrorLogging.logError(e);
ErrorLogging.logError("GPIO initialisation error! Please check GPIO connections,");
ErrorLogging.logError("and ensure that the PWM is set fast enough to the lower limit switch");
ErrorLogging.logError("in less than 3 seconds.");
println("");
prompt("Press enter to try again.");
String userInputString = inputScanner.nextLine();
if(userInputString.length() > 0)
{
testedGPIO = userInputString.contains("override");
ErrorLogging.logError("DEBUG: WARNING! - User override of GPIO check.");
}
}
}while(!testedGPIO);
do
{
@ -97,8 +126,6 @@ public class Cli
switch (userInput)
{
case 1:
println("Setting up cameras...");
println("This may take a moment...");
configureCameras();
camerasConfigured = true;
break;
@ -143,6 +170,7 @@ public class Cli
printHelp();
break;
case 8:
ErrorLogging.logError("DEBUG: User requested manual exit of program. Cleanly exiting...");
break;
default:
//Input handling already done by inputFiltering()
@ -191,6 +219,7 @@ public class Cli
*/
private static void printHelp()
{
ErrorLogging.logError("DEBUG: User asked for help at main menu.");
println("\n\n");
println("========================================");
println("Explanations:");
@ -351,6 +380,7 @@ public class Cli
*/
private static void printCameraConfigHelpMenu()
{
ErrorLogging.logError("DEBUG: User asked for help at camera config menu.");
println("\n\n");
println("============================================================");
println("Camera Config Menu options:");
@ -392,10 +422,11 @@ public class Cli
*/
private static void configureCameras()
{
ErrorLogging.logError("DEBUG: Configuing cameras...");
List<String> cameraList = new ArrayList<>(OpenCVFacade.getCameraNames());
//Always wake the camera, to ensure that the image is useful
MovementFacade.iterationMovement(true);
if(safeGPIO) MovementFacade.iterationMovement(true);
double tesseractValue = 0.0;
do
@ -417,11 +448,14 @@ public class Cli
do
{
//Press button twice, to make sure the DUT is awake
MovementFacade.pressButton();
try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); }
MovementFacade.pressButton();
try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); }
if(safeGPIO)
{
//Press button twice, to make sure the DUT is awake
MovementFacade.pressButton();
try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); }
MovementFacade.pressButton();
try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); }
}
File image = OpenCVFacade.showImage(cameraName);
tesseractValue = TesseractFacade.imageToDouble(image);
@ -500,6 +534,7 @@ public class Cli
*/
private static void setDUTSerials()
{
ErrorLogging.logError("DEBUG: Setting DUT serials...");
List<String> cameraList = new ArrayList<>(OpenCVFacade.getCameraNames());
do
{
@ -537,6 +572,7 @@ public class Cli
input = (int)inputFiltering(inputScanner.nextLine());
} while(input == -1);
iterationCount = input;
ErrorLogging.logError("DEBUG: Iteration count modified! Iteration count is now: " + iterationCount);
}
/**
@ -544,6 +580,7 @@ public class Cli
*/
private static void setActiveCameras()
{
ErrorLogging.logError("DEBUG: User modified active cameras.");
List<String> cameraList = new ArrayList<>(OpenCVFacade.getCameraNames());
do
@ -567,6 +604,13 @@ public class Cli
ConfigFacade.setValue(cameraName,ConfigProperties.ACTIVE,newValue);
} while(true);
ErrorLogging.logError("DEBUG: Currently active cameras:");
for(String camera : OpenCVFacade.getCameraNames())
{
if(ConfigFacade.getValue(camera,ConfigProperties.ACTIVE) != 0)
ErrorLogging.logError("DEBUG: - " + camera);
}
ErrorLogging.logError("DEBUG:");
}
/**
@ -597,8 +641,11 @@ public class Cli
//Wake the device, then wait to ensure they're awake before continuing
ErrorLogging.logError("DEBUG: Waking devices...");
MovementFacade.pressButton();
try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); }
if(safeGPIO)
{
MovementFacade.pressButton();
try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); }
}
Map<File,Double> resultMap = new HashMap<>();
Map<String,File> cameraToFile = new HashMap<>();
@ -626,12 +673,15 @@ public class Cli
do
{
fail = false;
while(!LOCK.tryLock()) {}
MovementFacade.iterationMovement(prime);
LOCK.unlock();
if(safeGPIO)
{
while(!LOCK.tryLock()) {}
MovementFacade.iterationMovement(prime);
LOCK.unlock();
//Wait for the DUT to display an image
try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); }
//Wait for the DUT to display an image
try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); }
}
for(String cameraName : cameraList)
{
@ -654,7 +704,7 @@ public class Cli
LOCK.unlock();
while(!LOCK.tryLock()) {}
resultMap.put(file,result);
ErrorLogging.logError("Tesseract final output: " + result);
ErrorLogging.logError("Parsed value from camera " + cameraName +": " + result);
LOCK.unlock();
if(result <= 10 ||
result >= 100 ||
@ -686,12 +736,6 @@ public class Cli
}
/**
* Function used if a config file was successfully imported.
*/
public static void configImported()
{ camerasConfigured = true; }
/**
* Function that closes GPIO and logging.
*/
@ -701,7 +745,7 @@ public class Cli
ErrorLogging.logError("DEBUG: PROGRAM CLOSING.");
ErrorLogging.logError("DEBUG: =================");
if(inputScanner != null) inputScanner.close();
MovementFacade.closeGPIO();
if(safeGPIO) MovementFacade.closeGPIO();
ErrorLogging.logError("DEBUG: END OF PROGRAM.");
ErrorLogging.closeLogs();
println("The program has exited successfully. Please press Ctrl-c to return to the terminal prompt.");

View file

@ -52,7 +52,7 @@ public class ConfigFacade
* For values that are ultimately ints, truncate.
* For values that are ultimately booleans, anything that isn't 0 should be considered True.
*/
private static final Map<String, Map<ConfigProperties, Double>> configMap = new HashMap<>();
private static final Map<String, Map<ConfigProperties, Double>> CONFIG_MAP = new HashMap<>();
/**
* Temporary storage for the DUT's serial number.
@ -148,22 +148,23 @@ public class ConfigFacade
*/
public static double getValue(String cameraName, ConfigProperties property)
{
ErrorLogging.logError("DEBUG: Getting " + property.getConfig() + " for " + cameraName + "camera...");
double output = 0.0;
if(!configMap.keySet().contains(cameraName))
if(!CONFIG_MAP.keySet().contains(cameraName))
{
ErrorLogging.logError("CONFIG ERROR!!! - Invalid camera name: " + cameraName);
ErrorLogging.logError("\tKey set: " + configMap.keySet().toString());
ErrorLogging.logError("\tKey set: " + CONFIG_MAP.keySet().toString());
ErrorLogging.logError("\tProperty: " + property.getConfig());
ErrorLogging.logError("\tconfigMap keys: " + configMap.keySet().toString());
ErrorLogging.logError("\tCONFIG_MAP keys: " + CONFIG_MAP.keySet().toString());
return output;
}
Map<ConfigProperties,Double> cameraConfig = configMap.get(cameraName);
Map<ConfigProperties,Double> cameraConfig = CONFIG_MAP.get(cameraName);
output = cameraConfig.get(property);
return output;
}
/**
* Called to force early calling of the static block
* Initialises ConfigFacade and OpenCVFacade values
*/
public static void init() {}
@ -182,12 +183,18 @@ public class ConfigFacade
*/
public static boolean setOutputSaveLocation(String path)
{
ErrorLogging.logError("DEBUG: Output save location modified!");
boolean output = false;
if(Files.exists(Paths.get(path)))
{
outputSaveLocation = path;
ErrorLogging.logError("DEBUG: New output save location: " + outputSaveLocation);
output = true;
}
else
{
ErrorLogging.logError("DEBUG: Output save location modification failed.");
}
return output;
}
@ -206,12 +213,18 @@ public class ConfigFacade
*/
public static boolean setImgSaveLocation(String path)
{
ErrorLogging.logError("DEBUG: Image save location modified!");
boolean output = false;
if(Files.exists(Paths.get(path)))
{
imageSaveLocation = path;
ErrorLogging.logError("DEBUG: New image save location: " + outputSaveLocation);
output = true;
}
else
{
ErrorLogging.logError("DEBUG: Output save location modification failed.");
}
return output;
}
@ -225,12 +238,13 @@ public class ConfigFacade
*/
public static boolean setValue(String cameraName, ConfigProperties property, double propertyValue)
{
ErrorLogging.logError("DEBUG: Setting " + property.getConfig() + " to " + propertyValue + "for " + cameraName);
boolean output = false;
List<String> activeCameras = new ArrayList<>(OpenCVFacade.getCameraNames());
if(!activeCameras.contains(cameraName)) return output;
Map<ConfigProperties,Double> cameraConfig = configMap.get(cameraName);
Map<ConfigProperties,Double> cameraConfig = CONFIG_MAP.get(cameraName);
if(cameraConfig == null) return output;
Double oldValue = cameraConfig.get(property);
@ -278,6 +292,7 @@ public class ConfigFacade
*/
public static boolean saveDefaultConfig(String filename)
{
ErrorLogging.logError("DEBUG: Saving default config to " + filename);
boolean output = false;
Set<String> cameraNames = OpenCVFacade.getCameraNames();
@ -306,7 +321,7 @@ public class ConfigFacade
cameraConfig.put(property,propertyValue);
CONFIG_STORE.setProperty(propertyName,propertyValue);
}
configMap.put(camera,cameraConfig);
CONFIG_MAP.put(camera,cameraConfig);
}
try
@ -335,6 +350,7 @@ public class ConfigFacade
*/
public static boolean saveCurrentConfig(String filename)
{
ErrorLogging.logError("DEBUG: Saving current config to " + filename);
boolean output = false;
//Get a list of all cameras
@ -347,7 +363,7 @@ public class ConfigFacade
for(ConfigProperties property : ConfigProperties.values())
{
String propertyName = camera + "." + property.getConfig();
String propertyValue = configMap.get(camera).get(property).toString();
String propertyValue = CONFIG_MAP.get(camera).get(property).toString();
CONFIG_STORE.setProperty(propertyName,propertyValue);
}
}
@ -379,14 +395,22 @@ public class ConfigFacade
*/
public static boolean loadConfig(String filename)
{
//Check if the current configMap is empty
boolean emptyMap = configMap.keySet().size() == 0;
ErrorLogging.logError("DEBUG: Attempting to load config from " + filename);
//Check if the current CONFIG_MAP is empty
boolean emptyMap = CONFIG_MAP.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())
{
ErrorLogging.logError("DEBUG: Listed file does not exist!");
return saveDefaultConfig();
}
}
catch(Exception e)
{
ErrorLogging.logError(e);
@ -427,13 +451,13 @@ public class ConfigFacade
if(savedSection.size() == 0)
{ saveSingleDefault(sectionName); }
if(emptyMap) configMap.put(sectionName,savedSection);
if(emptyMap) CONFIG_MAP.put(sectionName,savedSection);
else
{
for(String key : configMap.keySet())
for(String key : CONFIG_MAP.keySet())
{
if( key.equals(sectionName))
{ configMap.put(key,savedSection); }
{ CONFIG_MAP.put(key,savedSection); }
}
}
}
@ -461,6 +485,7 @@ public class ConfigFacade
*/
private static boolean saveSingleDefault(String sectionName)
{
ErrorLogging.logError("DEBUG: Section unsuccessfully imported! Saving defaults to " + sectionName);
boolean output = false;
Map<ConfigProperties,Double> 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();

View file

@ -9,45 +9,105 @@ package org.baxter.disco.ocr;
public enum ConfigProperties
{
/**
*X coordinate of the top-left coordinate for the newly cropped image.
* <pre>
* X coordinate of the top-left coordinate for the newly cropped image.
*
* Human readable name: "Crop X"
* Config name: "cropX"
* Default value: "275.0"
* </pre>
*/
CROP_X("Crop X","cropX",275.0),
/**
*Y coordinate of the top-left coordinate for the newly cropped image.
* <pre>
* Y coordinate of the top-left coordinate for the newly cropped image.
*
* Human readable name: "Crop Y"
* Config name: "cropY"
* Default value: "205.0"
* </pre>
*/
CROP_Y("Crop Y","cropY",205.0),
/**
*Width of the newly cropped image.
* <pre>
* Width of the newly cropped image.
*
* Human readable name: "Crop Width"
* Config name: "cropW"
* Default value: "80.0"
* </pre>
*/
CROP_W("Crop Width","cropW",80.0),
/**
*Height of the newly cropped image.
* <pre>
* Height of the newly cropped image.
*
* Human readable name: "Crop Height"
* Config name: "cropH"
* Default value: "50.0"
* </pre>
*/
CROP_H("Crop Height","cropH",50.0),
/**
* <pre>
* Whether or not to threshold the image during processing.
*
* Human readable name: "Toggle Threshold"
* Config name: "threshold"
* Default value: "1.0"
* </pre>
*/
THRESHOLD("Toggle threshold","threshold",1.0),
/**
* Whether or not to threshold the image during processing.
* <pre>
* Whether or not to crop the image during processing.
*
* Human readable name: "Toggle crop"
* Config name: "crop"
* Default value: "1.0"
* </pre>
*/
CROP("Toggle crop","crop",1.0),
/**
* <pre>
*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"
* </pre>
*/
COMPOSITE_FRAMES("Composite frame count","compositeCount",5.0),
/**
* <pre>
* 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"
* </pre>
*/
PRIME("Prime device?","prime",0.0),
/**
* <pre>
* Where the threshold point should land.
*
* Human readable name: "Threshold value"
* Config name: "thresholdValue"
* Default value: "45.0"
* </pre>
*/
THRESHOLD_VALUE("Threshold value","thresholdValue",45.0),
/**
* <pre>
* Whether the camera should be active.
*
* Human readable name: "Camera active"
* Config name: "active"
* Default value: "1.0"
* </pre>
*/
ACTIVE("Camera active?","active",1.0);

View file

@ -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<File,Double> inputMap, Map<String,File> 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;
}

View file

@ -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()
{

View file

@ -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
/**
* <pre>
* Upper limit switch object.
*
* Status: High; Upper limit switch has been reached.
* Status: Low; Upper limit switch has not been reached.
* </pre>
*/
private static DigitalInput upperLimit;
/**
* <pre>
* Lower limit switch object.
*
* Status: High; Lower limit switch has been reached.
* Status: Low; Lower limit switch has not been reached.
* </pre>
*/
private static DigitalInput lowerLimit;
/**
* Lower limit switch object.
* <pre>
* Run switch object.
*
* Status: High; Test may continue.
* Status: Low; Test must stop immediately.
* </pre>
*/
private static DigitalInput runSwitch;
/**
* <pre>
* Motor power object.
*
* Status: High; Motor starts moving, in the direction defined by {@link #motorDirection}.
* Status: Low; Motor stops moving.
* </pre>
*/
private static DigitalOutput motorEnable;
/**
* <pre>
* Defines the movement direction for the motor enabled by {@link #motorEnable}.
*
* Status: High; Motor will move upwards.
* Status: Low; Motor will move downwards.
* </pre>
*/
private static DigitalOutput motorDirection;
/**
* <pre>
* Piston control pin object.
*
* Status: High; Piston is extended.
* Status: Low; Piston is retracted.
* </pre>
*/
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; }
}

View file

@ -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<Mat> takeBurst(String cameraName, int frameCount)
{
ErrorLogging.logError("DEBUG: Taking burst of " + frameCount + " images...");
List<Mat> 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<Mat> images, boolean threshold,
boolean crop, String cameraName)
private static Mat compose(List<Mat> 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<Mat> imageList = takeBurst(cameraName, compositeFrames);
List<Mat> 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;
}

View file

@ -31,6 +31,7 @@ public class TesseractFacade
private static TessBaseAPI api;
/**
* <pre>
* 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.
* </pre>
*/
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;
}
}

View file

@ -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.

File diff suppressed because it is too large Load diff