4.0.0-rc2 #1

Merged
blizzardfinnegan merged 7 commits from devel into stable 2023-02-03 15:57:23 -05:00
22 changed files with 1343 additions and 749 deletions

View file

@ -5,22 +5,28 @@ This is a personal/professional project, which makes use of JavaCV, OpenCV, Tess
## Currently Working Features
- CLI Interface (All instances have user input checking)
- [ x ] Main Menu
- [x] Main Menu
- Camera config menu
- [ x ] Takes in inputs
- [ x ] Sets config values
- [ x ] Saves config values
- [ x ] Shows camera preview*
- [x] Takes in inputs
- [x] Sets config values
- [x] Saves config values
- [x] Shows camera preview*
- currently broken, for unknown reasons. In process of parsing...
- [ x ] GPIO test interactions
- [x] GPIO test interactions
- Test suite
- [ x ] OpenCV image capture
- [ x ] OpenCV image processing
- [ x ] Tesseract OCR processing
- [ ] Data storage in defined XLSX file (implemented, untested)
- [ x ] modify number of iterations for test suite
- [ ] JavaFX GUI (Designed, yet to be implemented)
- [x] OpenCV image capture
- [x] OpenCV image processing
- [x] Tesseract OCR processing
- [x] Data storage in defined XLSX file*
- [x] modify number of iterations for test suite
- [x] JavaFX GUI (Designed, implemented)
## Known Bugs
- Closing the program throws a fatal error. This is due to the current implementation of monitoring of the Run switch, and as of now, to my knowledge, cannot be resolved.
- As of CLI build 1.2.0, the external run switch bricks the system.
- Currently, all images are parsed on a per-iteration level. This means that we cannot guarantee the output columns are correct. As a temporary solution, image location, serial number, and parsed reading are placed in groups. Intention is to ultimately have them be column-wise written.
- As of CLI build 1.3.0, the first reading row must be discarded, due to one side of the fixture producing a consistent failed reading. This has yet to be debugged, or fully understood.
## Dependencies
To install this project, and use it fully, you must have the following:
- a Raspberry Pi 4 or 400 (other Pis may work properly, but has not been tested)

View file

@ -1,215 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="RemotePlatform" default="default" basedir=".." xmlns:remote="http://www.netbeans.org/ns/j2se-project/remote-platform/1" xmlns:j2seproject1="http://www.netbeans.org/ns/j2se-project/1" xmlns:j2seproject3="http://www.netbeans.org/ns/j2se-project/3">
<description></description>
<target name="default">
<echo message="Default target is not set, you must specify which target you want to run."/>
</target>
<target name="-init">
<property name="remote.run.jvmargs" value=""/>
<property name="remote.debug.jvmargs" value=""/>
<property name="remote.debug.suspend" value="y"/>
<property name="remote.application.args" value=""/>
<fail unless="target.platform.name">Remote platform name must be set.</fail>
<property name="target.platform.filename" value="./platform/${target.platform.name}.properties"/>
<property file="${target.platform.filename}"/>
<fail unless="target.platform.host">'target.platform.host' property is missing in ${target.platform.filename}.</fail>
<fail unless="target.platform.port">'target.platform.port' property is missing in ${target.platform.filename}.</fail>
<fail unless="target.platform.username">'target.platform.username' property is missing in ${target.platform.filename}.</fail>
<condition property="remote.platform.auth.passwd">
<isset property="target.platform.password"/>
</condition>
<condition property="remote.platform.auth.key">
<and>
<not>
<isset property="remote.platform.auth.passwd"/>
</not>
<isset property="target.platform.privatekey"/>
<isset property="target.platform.passphrase"/>
</and>
</condition>
<condition property="auth.ok">
<or>
<isset property="remote.platform.auth.passwd"/>
<isset property="remote.platform.auth.key"/>
</or>
</condition>
<condition property="run.mode" value="sudo">
<equals arg1="${target.run.as.root}" arg2="true" casesensitive="" />
</condition>
<property name="run.mode" value=""/>
<fail unless="auth.ok">Either 'target.platform.password' or 'target.platform.privatekey' + 'target.platform.passphrase' properties must be set in ${target.platform.filename}.</fail>
<fail unless="target.remote.jre">'target.remote.jre' property is missing in ${target.platform.filename}.</fail>
<fail unless="target.remote.home">'target.remote.home' property is missing in ${target.platform.filename}.</fail>
<!--basename file="${dist.jar}" property="dist.jar.name"/-->
<basename file="${basedir}" property="remote.project.name"/>
<property name="remote.project.dir" value="${target.remote.home}/${remote.project.name}"/>
<property name="remote.dist.dir" value="${remote.project.dir}/dist"/>
<property name="remote.java.executable" value="${target.remote.jre}/bin/java"/>
<property name="remote.dist.jar" value="${remote.dist.dir}/${dist.jar.name}.${project.packaging}"/>
<property name="local.dist.jar" value="${local.dist.dir}/${dist.jar.name}.${project.packaging}"/>
<property name="remote.dist.fatjar" value="${remote.dist.dir}/${dist.jar.name}-jar-with-dependencies.${project.packaging}"/>
<property name="local.dist.fatjar" value="${local.dist.dir}/${dist.jar.name}-jar-with-dependencies.${project.packaging}"/>
<fail message="The 'target.run.module.class' property must be set in ${target.platform.filename} if 'run.with.java.module'=true in the 'pom.xml'.">
<condition>
<and>
<equals arg1="${target.run.as.module}" arg2="true" casesensitive="" />
<not>
<isset property="target.run.module.class"/>
</not>
</and>
</condition>
</fail>
<condition property="project.run.cmd" value="-p ${remote.dist.dir}/lib -m ${target.run.module.class}" else="-jar ${remote.dist.jar}">
<equals arg1="${target.run.as.module}" arg2="true" casesensitive="" />
</condition>
</target>
<target name="-check-fatjar" depends="-init">
<available file="${local.dist.fatjar}" property="fatjar.exist"/>
</target>
<target name="-rename-fatjar" depends="-check-fatjar" if="fatjar.exist">
<move file="${local.dist.fatjar}" tofile="${local.dist.jar}"/>
</target>
<target name="-declare-macros" depends="-rename-fatjar">
<macrodef name="cleanwithpasswd" uri="http://www.netbeans.org/ns/j2se-project/remote-platform/1">
<sequential>
<sshexec host="${target.platform.host}" port="${target.platform.port}" username="${target.platform.username}"
password="${target.platform.password}" trust="true"
command="cd '${remote.dist.dir}'; rm -rf *.jar lib/*.jar"/>
</sequential>
</macrodef>
<macrodef name="cleanwithkey" uri="http://www.netbeans.org/ns/j2se-project/remote-platform/1">
<sequential>
<sshexec host="${target.platform.host}" port="${target.platform.port}" username="${target.platform.username}"
keyfile="${target.platform.privatekey}" passphrase="${target.platform.passphrase}" trust="true"
command="cd '${remote.dist.dir}'; rm -rf *.jar lib/*.jar"/>
</sequential>
</macrodef>
<macrodef name="copywithpasswd" uri="http://www.netbeans.org/ns/j2se-project/remote-platform/1">
<attribute name="additionaljvmargs" default=""/>
<sequential>
<sshexec host="${target.platform.host}" port="${target.platform.port}" username="${target.platform.username}" password="${target.platform.password}" trust="true" command="mkdir -p '${remote.dist.dir}'"/>
<scp todir="${target.platform.username}@${target.platform.host}:${remote.dist.dir}" port="${target.platform.port}" password="${target.platform.password}" trust="true">
<fileset dir="${local.dist.dir}">
<include name="*.${project.packaging}"/>
<include name="lib/*.${project.packaging}"/>
</fileset>
</scp>
</sequential>
</macrodef>
<macrodef name="copywithkey" uri="http://www.netbeans.org/ns/j2se-project/remote-platform/1">
<attribute name="additionaljvmargs" default=""/>
<sequential>
<sshexec host="${target.platform.host}" port="${target.platform.port}" username="${target.platform.username}" keyfile="${target.platform.privatekey}" passphrase="${target.platform.passphrase}" trust="true" command="mkdir -p '${remote.dist.dir}'"/>
<scp todir="${target.platform.username}@${target.platform.host}:${remote.dist.dir}" port="${target.platform.port}" keyfile="${target.platform.privatekey}" passphrase="${target.platform.passphrase}" trust="true">
<fileset dir="${local.dist.dir}">
<include name="*.${project.packaging}"/>
<include name="lib/*.${project.packaging}"/>
</fileset>
</scp>
</sequential>
</macrodef>
<macrodef name="runwithpasswd" uri="http://www.netbeans.org/ns/j2se-project/remote-platform/1">
<attribute name="additionaljvmargs" default=""/>
<sequential>
<!--antcall target="profile-rp-calibrate-passwd"/-->
<sshexec host="${target.platform.host}" port="${target.platform.port}" username="${target.platform.username}" password="${target.platform.password}" trust="true" usepty="true"
command="cd '${remote.project.dir}'; ${run.mode} '${remote.java.executable}' @{additionaljvmargs} -Dfile.encoding=${remote.runtime.encoding} ${remote.run.jvmargs} ${project.run.cmd} ${remote.application.args}"/>
</sequential>
</macrodef>
<macrodef name="runwithkey" uri="http://www.netbeans.org/ns/j2se-project/remote-platform/1">
<attribute name="additionaljvmargs" default=""/>
<sequential>
<!--antcall target="profile-rp-calibrate-key"/-->
<sshexec host="${target.platform.host}" port="${target.platform.port}" username="${target.platform.username}" keyfile="${target.platform.privatekey}" passphrase="${target.platform.passphrase}" trust="true" usepty="true"
command="cd '${remote.project.dir}'; ${run.mode} '${remote.java.executable}' @{additionaljvmargs} -Dfile.encoding=${remote.runtime.encoding} ${remote.run.jvmargs} ${project.run.cmd} ${remote.application.args}"/>
</sequential>
</macrodef>
</target>
<target name="-ask-password" unless="target.platform.password" if="remote.platform.auth.passwd">
<input message="Password ${target.platform.user}@${target.platform.host}:" addproperty="target.platform.password">
<handler type="secure"/>
</input>
</target>
<target name="-ask-passphrase" unless="target.platform.passphrase" if="remote.platform.auth.key">
<input message="Passphrase ${target.platform.user}@${target.platform.host}:" addproperty="target.platform.passphrase">
<handler type="secure"/>
</input>
</target>
<target name="-check-vm-debug" depends="-init">
<condition property="remote.debug.available" value="true">
<or>
<istrue value="${target.debug.supported}"/>
</or>
</condition>
<fail unless="remote.debug.available" message="The Runtime JVM ${target.platform.host} does not support debugging."/>
</target>
<target name="-copy-remote-passwd" depends="-init, -declare-macros, -ask-password" if="remote.platform.auth.passwd">
<remote:copywithpasswd/>
</target>
<target name="-copy-remote-key" depends="-init, -declare-macros, -ask-passphrase" if="remote.platform.auth.key">
<remote:copywithkey/>
</target>
<target name="-run-remote-passwd" depends="-init, -declare-macros, -ask-password, -copy-remote-passwd" if="remote.platform.auth.passwd">
<remote:runwithpasswd/>
</target>
<target name="-run-remote-key" depends="-init, -declare-macros, -ask-passphrase, -copy-remote-key" if="remote.platform.auth.key">
<remote:runwithkey/>
</target>
<target name="-clean-remote-passwd" depends="-init, -declare-macros, -ask-password" if="remote.platform.auth.passwd">
<remote:cleanwithpasswd/>
</target>
<target name="-clean-remote-key" depends="-init, -declare-macros, -ask-passphrase" if="remote.platform.auth.key">
<remote:cleanwithkey/>
</target>
<target name="-debug-remote-passwd" depends="-init, -declare-macros, -ask-password" if="remote.platform.auth.passwd">
<sshsession host="${target.platform.host}" port="${target.platform.port}" username="${target.platform.username}" password="${target.platform.password}" trust="true">
<remotetunnel lport="${target.debug.port}" lhost="localhost" rport="${target.debug.port}"/>
<sequential>
<remote:runwithpasswd additionaljvmargs="${remote.debug.jvmargs} -agentlib:jdwp=transport=dt_socket,server=y,suspend=${remote.debug.suspend},address=${target.platform.host}:${target.debug.port}"/>
</sequential>
</sshsession>
</target>
<target name="-debug-remote-key" depends="-init, -declare-macros, -ask-passphrase" if="remote.platform.auth.key">
<sshsession host="${remote.platform.host}" port="${remote.platform.port}" username="${remote.platform.username}" keyfile="${remote.platform.keyfile}" passphrase="${remote.platform.passphrase}" trust="true">
<remotetunnel lport="${target.debug.port}" lhost="localhost" rport="${target.debug.port}"/>
<sequential>
<remote:runwithkey additionaljvmargs="${remote.debug.jvmargs} -agentlib:jdwp=transport=dt_socket,server=y,suspend=${remote.debug.suspend},address=${target.platform.host}:${target.debug.port}"/>
</sequential>
</sshsession>
</target>
<target name="build-remote" depends="-copy-remote-passwd, -copy-remote-key">
</target>
<target name="run-remote" depends="-run-remote-passwd, -run-remote-key">
</target>
<target name="clean-remote" depends="-clean-remote-passwd, -clean-remote-key">
</target>
<target name="debug-remote" depends="-debug-remote-passwd, -debug-remote-key">
</target>
</project>

View file

@ -4,7 +4,7 @@
<groupId>org.baxter.disco</groupId>
<artifactId>ocr</artifactId>
<name>Disco OCR Accuracy Over Life Testing</name>
<version>4.0.0-rc1</version>
<version>4.0.0-rc2</version>
<description>Testing Discos for long-term accuracy, using automated optical character recognition.</description>
<organization>
<name>Baxter International</name>

12
pom.xml
View file

@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.baxter.disco</groupId>
<artifactId>ocr</artifactId>
<version>4.0.0-rc1</version>
<version>4.0.0-rc2</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>
@ -86,7 +86,7 @@
<version>${pi4j.version}</version>
</dependency>
<!-- JavaFX Import; Only one is necessary, as Maven grabs dependencies automatically -->
<!-- JavaFX Import -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
@ -178,7 +178,7 @@
</execution>
</executions>
</plugin>
<!--plugin>
<!--<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
@ -191,8 +191,8 @@
<configuration>
<archive>
<manifest>
<addDefaultImplementationEntries/>
<mainClass>it.lb.rasp.rasp01.Rasp01</mainClass>
<addClasspath>true</addClasspath>
<mainClass>org.baxter.disco.ocr.GuiStarter</mainClass>
</manifest>
</archive>
<descriptorRefs>
@ -201,7 +201,7 @@
</configuration>
</execution>
</executions>
</plugin-->
</plugin>-->
</plugins>
</build>
<reporting>

View file

@ -4,6 +4,7 @@ module org.baxter.disco.ocr {
requires com.pi4j.plugin.pigpio;
requires com.pi4j.library.pigpio;
requires javafx.fxml;
requires javafx.controls;
requires org.apache.poi.poi;
requires org.apache.commons.configuration2;
requires org.apache.xmlbeans;

View file

@ -1,11 +1,14 @@
package org.baxter.disco.ocr;
import java.io.File;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.bytedeco.javacv.CanvasFrame;
@ -13,17 +16,18 @@ import org.bytedeco.javacv.CanvasFrame;
* CLI for the Fixture.
*
* Creates a terminal-based user interface for the other
* classes in this package (with the exception of {@link Gui} [for now]).
* classes in this package (with the exception of Gui-related
* classes).
*
* @author Blizzard Finnegan
* @version 1.0.0, 27 Jan. 2023
* @version 1.3.0, 03 Feb. 2023
*/
public class Cli
{
/**
* Currently saved iteration count.
*/
private static int iterationCount = 3;
private static int iterationCount = 10;
/**
* Scanner used for monitoring user input.
@ -33,10 +37,20 @@ public class Cli
*/
private static Scanner inputScanner;
/**
* Whether the user has set the serial numbers yet.
*/
private static boolean serialsSet = false;
/**
* Whether the user has successfully configured the cameras.
*/
private static boolean camerasConfigured = false;
/**
* Number of options currently available in the main menu.
*/
private static final int mainMenuOptionCount = 6;
private static final int mainMenuOptionCount = 7;
/**
* Number of options currently available in the movement sub-menu.
@ -48,11 +62,33 @@ public class Cli
*/
private static final int cameraMenuOptionCount = 9;
/**
* Lock object, used for temporary interruption of {@link #runTests()}
*/
private static Lock LOCK = new ReentrantLock();
/**
* Instance of {@link MovementFacade} for controlling the fixture.
*/
private static MovementFacade fixture;
static
{
ErrorLogging.logError("DEBUG: START OF PROGRAM");
}
public static void main(String[] args)
{
try{
inputScanner = new Scanner(System.in);
ErrorLogging.logError("DEBUG: START OF PROGRAM");
//ErrorLogging.logError("DEBUG: Setting up multithreading...");
fixture = new MovementFacade(LOCK);
//ErrorLogging.logError("DEBUG: Multithreading complete!");
//ErrorLogging.logError("DEBUG: Importing config...");
ConfigFacade.init();
//ErrorLogging.logError("DEBUG: Config imported!");
int userInput = 0;
@ -69,24 +105,62 @@ public class Cli
println("Setting up cameras...");
println("This may take a moment...");
configureCameras();
camerasConfigured = true;
break;
case 3:
setIterationCount();
setDUTSerials();
serialsSet = true;
break;
case 4:
runTests();
println("Test complete!");
setIterationCount();
break;
case 5:
printHelp();
if(!camerasConfigured)
{
prompt("You have not configured the cameras yet! Are you sure you would like to continue? (y/N): ");
String input = inputScanner.nextLine().toLowerCase();
if( input.isBlank())
{
break;
}
else if (input.charAt(0) != 'y' )
{
break;
}
else
{
ErrorLogging.logError("WARNING! - Potential for error: Un-initialised cameras.");
}
}
if(!serialsSet)
{
prompt("You have not set the serial numbers for your DUTs yet! Are you sure you would like to continue? (y/N): ");
String input = inputScanner.nextLine().toLowerCase();
if( input.isBlank())
{
break;
}
else if (input.charAt(0) != 'y' )
{
break;
}
else
{
ErrorLogging.logError("WARNING! - Potential for error: Un-initialised DUT Serial numbers.");
}
}
runTests();
break;
case 6:
printHelp();
break;
case 7:
break;
default:
//Input handling already done by inputFiltering()
}
} while (userInput != 6);
} while (userInput != mainMenuOptionCount);
}
catch(Exception e)
@ -96,11 +170,7 @@ public class Cli
}
finally
{
ErrorLogging.logError("DEBUG: PROGRAM CLOSING.");
inputScanner.close();
MovementFacade.closeGPIO();
ErrorLogging.logError("DEBUG: END OF PROGRAM.");
ErrorLogging.closeLogs();
close();
}
}
@ -150,18 +220,22 @@ public class Cli
"\n\tAvailable variables to change:"+
"\n\t\tCrop dimensions");
println("----------------------------------------");
println("3. Change test iteration count:"+
println("3. Set serial numbers: Set the serial " +
"\n\tnumber for the device under test." +
"\n\tThis is used in final data saving.");
println("----------------------------------------");
println("4. Change test iteration count:"+
"\n\tChange the number of times to"+
"\n\trun the tests of the device(s)"+
"\n\tunder test.");
println("----------------------------------------");
println("4. Run tests: Run tests, with defined"+
println("5. Run tests: Run tests, with defined"+
"\n\tnumber of iterations. Uses"+
"\n\tvalues defined in config file.");
println("----------------------------------------");
println("5. Help: Show this help page.");
println("6. Help: Show this help page.");
println("----------------------------------------");
println("6. Exit: Close the program.");
println("7. Exit: Close the program.");
println("========================================");
println("Press Enter to continue...");
inputScanner.nextLine();
@ -180,22 +254,26 @@ public class Cli
println("--------------------------------------");
println("1. Test and configure fixture movement");
println("2. Configure camera");
println("3. Change test iteration count");
println("4. Run tests");
println("5. Help");
println("6. Exit");
println("3. Set serial numbers");
println("4. Change test iteration count");
println("5. Run tests");
println("6. Help");
println("7. Exit");
println("======================================");
}
/**
* Predefined print statements for the movement submenu.
*/
private static void printMovementMenu()
{
println("\n\n");
println("====================================");
println("Movement Menu:");
println("------------------------------------");
println("Current Duty Cycle: " + MovementFacade.getDutyCycle());
println("Current Frequency: " + MovementFacade.getFrequency());
println("Current Motor Time-out: " + MovementFacade.getTimeout());
println("Current Duty Cycle: " + fixture.getDutyCycle());
println("Current Frequency: " + fixture.getFrequency());
println("Current Motor Time-out: " + fixture.getTimeout());
println("------------------------------------");
println("1. Change Duty Cycle");
println("2. Change Frequency");
@ -204,6 +282,9 @@ public class Cli
println("====================================");
}
/**
* Pre-defined method for printing all available cameras in a menu
*/
private static void printCameraMenu(List<String> cameraList)
{
println("Available cameras to configure:");
@ -218,6 +299,30 @@ public class Cli
println("------------------------------------");
}
/**
* Pre-defined method for printing all available cameras and the associated serials in a menu
*/
private static void printSerialMenu(List<String> cameraList)
{
println("Available serial numbers to set:");
println("------------------------------------");
for(int index = 0; index < cameraList.size(); index++)
{
int humanIndex = index+1;
String cameraName = (String)cameraList.get(index);
print(humanIndex + " - " + cameraName + " : ");
if(ConfigFacade.getSerial(cameraName) != null)
println(ConfigFacade.getSerial(cameraName));
else
println("");
}
println( (cameraList.size() + 1) + " - Exit to Main Menu");
println("------------------------------------");
}
/**
* Pre-defined menu for printing camera configuration options
*/
private static void printCameraConfigMenu(String cameraName)
{
println("\n\n");
@ -247,20 +352,24 @@ public class Cli
println("3. Change Crop Width");
println("4. Change Crop Height");
println("5. Change Composite Frame Count");
println("6. Toggle crop");
println("7. Toggle threshold");
println("8. Exit");
println("6. Change Threshold Value");
println("7. Toggle crop");
println("8. Toggle threshold");
println("9. Exit");
println("====================================");
}
/**
* Function for testing movement, and modifying hardware values
*/
private static void testMovement()
{
int userInput = -1;
do
{
println("Testing movement...");
MovementFacade.testMotions();
fixture.testMotions();
printMovementMenu();
userInput = inputFiltering(inputScanner.nextLine());
switch (userInput)
@ -277,7 +386,7 @@ public class Cli
int newDutyCycle = inputFiltering(inputScanner.nextLine());
if (newDutyCycle != -1)
{
MovementFacade.setDutyCycle(newDutyCycle);
fixture.setDutyCycle(newDutyCycle);
break;
}
case 2:
@ -285,7 +394,7 @@ public class Cli
int newFrequency = inputFiltering(inputScanner.nextLine());
if (newFrequency != -1)
{
MovementFacade.setFrequency(newFrequency);
fixture.setFrequency(newFrequency);
break;
}
case 3:
@ -293,7 +402,7 @@ public class Cli
int newTimeout = inputFiltering(inputScanner.nextLine());
if (newTimeout != -1)
{
MovementFacade.setTimeout(newTimeout);
fixture.setTimeout(newTimeout);
break;
}
case 4:
@ -313,28 +422,29 @@ public class Cli
List<String> cameraList = new ArrayList<>(OpenCVFacade.getCameraNames());
//println(cameraList.toString());
//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();
// 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();
MovementFacade.iterationMovement(true);
fixture.iterationMovement(true);
do
{
@ -354,11 +464,13 @@ public class Cli
//Leave do-while loop if the user asks to
if(userInput == (cameraList.size())) break;
else if(userInput < 0)
{}
else cameraName = cameraList.get((userInput));
do
{
MovementFacade.pressButton();
fixture.pressButton();
try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); }
//Show image
CanvasFrame canvas = OpenCVFacade.showImage(cameraName);
@ -390,12 +502,15 @@ public class Cli
modifiedProperty = ConfigProperties.COMPOSITE_FRAMES;
break;
case 6:
modifiedProperty = ConfigProperties.CROP;
modifiedProperty = ConfigProperties.THRESHOLD_VALUE;
break;
case 7:
modifiedProperty = ConfigProperties.THRESHOLD;
modifiedProperty = ConfigProperties.CROP;
break;
case 8:
modifiedProperty = ConfigProperties.THRESHOLD;
break;
case 9:
modifiedProperty = ConfigProperties.PRIME;
break;
default:
@ -420,12 +535,45 @@ public class Cli
} while(true);
ConfigFacade.saveCurrentConfig();
println("Configuration complete!");
}
/**
* Sub-function used for defining the serial numbers of the devices under test
*/
private static void setDUTSerials()
{
List<String> cameraList = new ArrayList<>(OpenCVFacade.getCameraNames());
do
{
//Main menu
printSerialMenu(cameraList);
//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());
userInput--;
} while (cameraList.size() < userInput || userInput < 0);
//Leave do-while loop if the user asks to
if(userInput == (cameraList.size())) break;
else cameraName = cameraList.get((userInput));
prompt("Enter the serial number you wish to use for this camera: ");
ConfigFacade.setSerial(cameraName,inputScanner.nextLine());
} while(true);
}
/**
* Setter for {@link #iterationCount}
* CLI-level setter for {@link #iterationCount}
*/
private static void setIterationCount()
{
@ -443,31 +591,72 @@ public class Cli
*/
private static void runTests()
{
DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation());
boolean prime = false;
for(String cameraName : OpenCVFacade.getCameraNames())
{
if(ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0)
final int localIterations = iterationCount;
//testingThread = new Thread(() ->
//{
DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation(),OpenCVFacade.getCameraNames().size());
boolean prime = false;
for(String cameraName : OpenCVFacade.getCameraNames())
{
prime = true;
if(cameraName != null) { /*println(cameraName);*/ }
else ErrorLogging.logError("Null camera!");
if(ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0)
{
prime = true;
}
}
}
for(int i = 0; i < iterationCount; i++)
{
Map<String, Double> resultMap = new HashMap<>();
MovementFacade.iterationMovement(prime);
List<File> iteration = OpenCVFacade.singleIteration();
for(File file : iteration)
ErrorLogging.logError("DEBUG: Waking devices.");
fixture.iterationMovement(prime);
fixture.pressButton();
fixture.iterationMovement(prime);
ErrorLogging.logError("DEBUG: Starting tests...");
for(int i = 0; i < localIterations; i++)
{
Double result = TesseractFacade.imageToDouble(file);
String fileLocation = file.getAbsolutePath();
resultMap.put(fileLocation,result);
ErrorLogging.logError("DEBUG: Tesseract final output: " + result);
Map<String, Double> resultMap = new HashMap<>();
while(!LOCK.tryLock()) {}
fixture.iterationMovement(prime);
LOCK.unlock();
while(!LOCK.tryLock()) {}
List<File> iteration = OpenCVFacade.singleIteration();
LOCK.unlock();
for(File file : iteration)
{
while(!LOCK.tryLock()) {}
//ErrorLogging.logError("DEBUG: File passed to Tesseract: " + file.getAbsolutePath());
Double result = TesseractFacade.imageToDouble(file);
LOCK.unlock();
while(!LOCK.tryLock()) {}
resultMap.put(file.getPath(),result);
ErrorLogging.logError("DEBUG: Tesseract final output: " + result);
LOCK.unlock();
}
while(!LOCK.tryLock()) {}
DataSaving.writeValues(i,resultMap);
LOCK.unlock();
}
DataSaving.writeValues(i,resultMap);
}
println("=======================================");
println("Tests complete!");
println("=======================================");
println("Testing complete!");
//});
//testingThread.start();
}
/**
* Function used if a config file was successfully imported.
*/
public static void configImported()
{ camerasConfigured = true; }
/**
* Function that closes GPIO and logging.
*/
private static void close()
{
ErrorLogging.logError("DEBUG: PROGRAM CLOSING.");
if(inputScanner != null) inputScanner.close();
fixture.closeGPIO();
ErrorLogging.logError("DEBUG: END OF PROGRAM.");
ErrorLogging.closeLogs();
}
/**
@ -482,7 +671,7 @@ public class Cli
* Parse the user's input, and check it for errors.
*
* @param input The unparsed user input, directly from the {@link Scanner}
* @param mainMenu Whether or not the parsed input is a main menu value
* @param menu Which menu is being parsed
*
* @return The parsed value from the user. Returns -1 upon any error.
*/
@ -578,7 +767,7 @@ public class Cli
*/
private static void invalidInput()
{
invalidInput("");
invalidInput("Please input a valid number.");
}
/**
@ -596,5 +785,8 @@ public class Cli
println("");
}
/**
* Enum of possible menus available
*/
private enum Menus { MAIN,MOVEMENT,CAMERA,OTHER; }
}

View file

@ -13,6 +13,8 @@ import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
/**
@ -22,7 +24,7 @@ import java.util.ArrayList;
* Can write to file when requested, reads from file on initial start.
*
* @author Blizzard Finnegan
* @version 1.0.0, 27 Jan. 2023
* @version 1.2.0, 03 Feb. 2023
*/
public class ConfigFacade
{
@ -41,19 +43,20 @@ public class ConfigFacade
/**
* Location to save output XLSX file to.
*/
private static String outputSaveLocation = "outputData.xlsx";
public static String outputSaveLocation = "outputData-" +
(LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)) + ".xlsx";
//For values that are ultimately ints, truncate.
//For values that are ultimately booleans, anything that isn't 0 should be considered True.
/**
* Map of all config values relating to the camera.
* 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<>();
/**
* Base class used for interacting with Configuration files.
* Temporary storage for the DUT's serial number.
*/
private static final Configurations CONFIGURATIONS = new Configurations();
private static final Map<String, String> DUT_SERIALS = new HashMap<>();
/**
* Builder for the main Configuration object.
@ -71,26 +74,34 @@ public class ConfigFacade
static
{
ErrorLogging.logError("Starting configuration setup...");
CONFIG_STORE = null;
File configFile = new File(configFileLocation);
boolean newConfig = true;
try{ newConfig = configFile.createNewFile(); }
catch(Exception e){ ErrorLogging.logError(e); }
ErrorLogging.logError("Creating config file interface...");
CONFIG_BUILDER = new FileBasedConfigurationBuilder<>(INIConfiguration.class)
.configure(new Parameters().fileBased().setFile(configFile));
ErrorLogging.logError("Attempting to import config...");
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!"); }
CONFIGURATIONS.iniBuilder(configFileLocation);
ErrorLogging.logError("Creating image storage directories...");
File imageLocation = new File(imageSaveLocation);
imageLocation.mkdir();
File debugImageLocation = new File(imageSaveLocation + "/debug");
debugImageLocation.mkdir();
File configImageLocation = new File(imageSaveLocation + "/config");
configImageLocation.mkdir();
ErrorLogging.logError("Creating output file....");
File outputFile = new File(outputSaveLocation);
try{ outputFile.createNewFile(); }
catch(Exception e){ ErrorLogging.logError(e); }
@ -98,8 +109,13 @@ public class ConfigFacade
{
boolean saveSuccessful = saveDefaultConfig();
if(!saveSuccessful) ErrorLogging.logError("Save config failed!!!");
else ErrorLogging.logError("Configuration settings set up!");
}
else
{
loadConfig();
ErrorLogging.logError("Configuration settings loaded!");
}
else { loadConfig(); }
CONFIG_BUILDER.setAutoSave(true);
}
/**
@ -108,7 +124,7 @@ public class ConfigFacade
* Ints should be truncated.
* Any boolean that should be false should be stored as 0.
*
* @param cameraName Name of the camera (as defined in {@link #activeCameras})
* @param cameraName Name of the camera (defined in {@link OpenCVFacade})
* @param property name of the property ({@link ConfigProperties})
*
* @return double of config value. Returns 0 if invalid input.
@ -119,8 +135,13 @@ public class ConfigFacade
public static double getValue(String cameraName, ConfigProperties property)
{
double output = 0.0;
List<String> activeCameras = new ArrayList<>(OpenCVFacade.getCameraNames());
if(!activeCameras.contains(cameraName)) return output;
if(!configMap.keySet().contains(cameraName))
{
ErrorLogging.logError("CONFIG ERROR!!! - Invalid camera name: " + cameraName);
ErrorLogging.logError("\tProperty: " + property.getConfig());
ErrorLogging.logError("\tconfigMap keys: " + configMap.keySet().toString());
return output;
}
Map<ConfigProperties,Double> cameraConfig = configMap.get(cameraName);
output = cameraConfig.get(property);
//Debug logger.
@ -129,15 +150,21 @@ public class ConfigFacade
return output;
}
/**
* Initialises local list of available cameras
*/
public static void init()
{
//ErrorLogging.logError("Starting import...");
}
/**
* Getter for the location of the output XLSX file.
*
* @return Absolute path of the image save location.
*/
public static String getOutputSaveLocation()
{
return outputSaveLocation;
}
{ return outputSaveLocation; }
/**
* Setter for the location of the output XLSX file.
@ -161,9 +188,7 @@ public class ConfigFacade
* @return Absolute path of the image save location.
*/
public static String getImgSaveLocation()
{
return imageSaveLocation;
}
{ return imageSaveLocation; }
/**
* Setter for the saved image location.
@ -203,6 +228,31 @@ public class ConfigFacade
return output;
}
/**
* Setter for a Device Under Test's serial number.
*
* @param cameraName The camera observing the given serial number
* @param serial The serial of the DUT
*/
public static void setSerial(String cameraName, String serial)
{ DUT_SERIALS.put(cameraName,serial); }
/**
* Getter for a Device Under Test's serial number.
*
* @param cameraName The camera observing the given serial number
*
* @return The DUT's serial
*/
public static String getSerial(String cameraName)
{
if(!DUT_SERIALS.keySet().contains(cameraName)) return null;
return DUT_SERIALS.get(cameraName);
}
public static Map<String,String> getSerials()
{ return DUT_SERIALS; }
//**********************************************
//SAVE AND LOAD SETTINGS
//**********************************************
@ -228,7 +278,7 @@ public class ConfigFacade
Map<ConfigProperties,Double> cameraConfig = new HashMap<>();
for(ConfigProperties property : ConfigProperties.values())
{
String propertyName = camera + "/" + property.getConfig();
String propertyName = camera + "." + property.getConfig();
double propertyValue = property.getDefaultValue();
cameraConfig.put(property,propertyValue);
//ErrorLogging.logError("DEBUG: Attempting to save to config: ");
@ -271,7 +321,7 @@ public class ConfigFacade
{
for(ConfigProperties property : ConfigProperties.values())
{
String propertyName = camera + "/" + property.toString();
String propertyName = camera + "." + property.toString();
String propertyValue =configMap.get(camera).get(property).toString();
CONFIG_STORE.addProperty(propertyName,propertyValue);
}
@ -304,17 +354,18 @@ public class ConfigFacade
*/
public static boolean loadConfig(String filename)
{
boolean emptyMap = configMap.keySet().size() == 0;
boolean output = false;
File file = new File(filename);
try
{
if(file.createNewFile())
saveDefaultConfig();
return saveDefaultConfig();
}
catch(Exception e)
{
ErrorLogging.logError(e);
saveDefaultConfig();
return saveDefaultConfig();
}
List<String> cameraNames = new ArrayList<>(OpenCVFacade.getCameraNames());
if(Files.isRegularFile(Path.of(file.toURI())))
@ -322,18 +373,17 @@ public class ConfigFacade
try
{
CONFIG_BUILDER = new FileBasedConfigurationBuilder<>(INIConfiguration.class).configure(new Parameters().fileBased().setFile(file));
CONFIG_STORE = CONFIGURATIONS.ini(filename);
CONFIG_STORE = CONFIG_BUILDER.getConfiguration();
}
catch(Exception e){ ErrorLogging.logError(e); }
Set<String> configSections = CONFIG_STORE.getSections();
ErrorLogging.logError("DEBUG: imported sections - " + configSections.toString());
ErrorLogging.logError("DEBUG: imported section size - " + configSections.size());
//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<ConfigProperties,Double> savedSection = new HashMap<>();
SubnodeConfiguration subSection = CONFIG_STORE.getSection(sectionName);
String subSectionPrefix = "";
for(String cameraName : cameraNames)
{
@ -362,27 +412,26 @@ public class ConfigFacade
for(ConfigProperties configState : ConfigProperties.values())
{
String subSectionValueName = subSectionPrefix + "/" + configState.getConfig();
if(!subSection.containsKey(subSectionValueName))
{
ErrorLogging.logError("CONFIG LOAD ERROR!!! - Invalid config file.");
return saveDefaultConfig(file.getAbsolutePath());
}
else
{
Double configValue = subSection.getDouble(configState.toString());
savedSection.put(configState,configValue);
}
Double configValue = CONFIG_STORE.getDouble(sectionName + "." + configState.getConfig());
//ErrorLogging.logError("DEBUG: Imported config value: " + Double.toString(configValue));
savedSection.put(configState,configValue);
}
if(configMap.containsKey(sectionName))
if(emptyMap) configMap.put(sectionName,savedSection);
else
{
configMap.put(sectionName,savedSection);
for(String key : configMap.keySet())
{
//ErrorLogging.logError("DEBUG: configMap Key: " + key + " ?= " + sectionName);
if( key.equals(sectionName))
{ configMap.put(key,savedSection); }
}
}
}
output = true;
}
if(!output) ErrorLogging.logError("CONFIG LOAD ERROR!!! - Invalid path.");
else Cli.configImported();
return output;
}

View file

@ -39,7 +39,11 @@ public enum ConfigProperties
/**
* Whether or not to press the button on the device twice, when under test.
*/
PRIME("Prime device?","prime",0.0);
PRIME("Prime device?","prime",0.0),
/**
* Where the threshold point should land.
*/
THRESHOLD_VALUE("Threshold value","thresholdValue",50.0);
/**
* Internal storage of human-readable name/meaning

View file

@ -2,10 +2,10 @@ package org.baxter.disco.ocr;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
@ -16,7 +16,7 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook;
* Facade for saving data out to a file.
*
* @author Blizzard Finnegan
* @version 1.0.0, 27 Jan. 2023
* @version 2.0.0, 02 Feb. 2023
*/
public class DataSaving
{
@ -34,10 +34,12 @@ public class DataSaving
*/
private static File outputFile;
private static Map<String,String> serials;
/**
* Prepares writer to write to XLSX file.
*/
public static boolean initWorkbook(String filename)
public static boolean initWorkbook(String filename, int camCount)
{
boolean output = false;
outputFile = new File(filename);
@ -45,6 +47,27 @@ public class DataSaving
{
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();
serials = ConfigFacade.getSerials();
}
catch(Exception e) { ErrorLogging.logError(e); }
return output;
@ -62,13 +85,39 @@ public class DataSaving
boolean output = false;
int startingRow = outputSheet.getLastRowNum();
Row row = outputSheet.createRow(++startingRow);
Set<String> imageLocations = inputMap.keySet();
List<String> imageLocations = new ArrayList<>(inputMap.keySet());
//ErrorLogging.logError("DEBUG: image locations: " + imageLocations.toString());
List<Object> objectArray = new LinkedList<>();
cycle++;
objectArray.add((double)cycle);
List<String> serialList = new ArrayList<>(serials.keySet());
for(String imageLocation : imageLocations)
{
String[] temp = imageLocation.split("-");
//ErrorLogging.logError("DEBUG: Image location post-split:");
//String println = "";
//for(String val : temp)
//{
// println = println + " " + val;
//}
//ErrorLogging.logError("DEBUG: " + println);
String cameraAndFile = temp[temp.length - 1];
//ErrorLogging.logError("DEBUG: " + cameraAndFile);
//Magic number explanation:
//The 4 used below is defined by the length of the file extension
//The files being saved are known to be JPEGs, so the file extension
//will be ".jpg", which is 4 characters long.
String cameraName = cameraAndFile.substring(0,cameraAndFile.length() - 4);
//ErrorLogging.logError("DEBUG: " + cameraName);
String serialNumber = ConfigFacade.getSerial(cameraName);
objectArray.add(serialNumber);
objectArray.add(imageLocation);
objectArray.add(inputMap.get(imageLocation));
objectArray.add(" ");
}
int cellnum = 0;
for(Object cellObject : objectArray)
@ -76,7 +125,11 @@ public class DataSaving
Cell cell = row.createCell(cellnum++);
if(cellObject instanceof Double) cell.setCellValue((Double) cellObject);
else if(cellObject instanceof String) cell.setCellValue((String) cellObject);
else { ErrorLogging.logError("XLSX Write Error!!! - Invalid input."); }
else
{
ErrorLogging.logError("XLSX Write Error!!! - Invalid input.");
ErrorLogging.logError("\t" + cellObject.toString());
}
}
try
@ -84,6 +137,7 @@ public class DataSaving
FileOutputStream outputStream = new FileOutputStream(outputFile);
outputWorkbook.write(outputStream);
output = true;
outputStream.close();
}
catch(Exception e) {ErrorLogging.logError(e);}
return output;

View file

@ -2,8 +2,10 @@ package org.baxter.disco.ocr;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@ -15,7 +17,7 @@ import org.apache.commons.lang3.exception.ExceptionUtils;
* as well as stderr.
*
* @author Blizzard Finnegan
* @version 1.2.0, 27 Jan. 2023
* @version 1.3.0, 03 Feb. 2023
*/
public class ErrorLogging
@ -55,7 +57,7 @@ public class ErrorLogging
static
{
fileDatetime = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss");
datetime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
datetime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
logFile = fileDatetime.format(LocalDateTime.now()) + "-log.txt";
File outFile = new File(logFile);
try
@ -64,6 +66,7 @@ public class ErrorLogging
fw = new FileWriter(logFile, true);
bw = new BufferedWriter(fw);
fileOut = new PrintWriter(bw);
System.setErr(new PrintStream(new FileOutputStream(logFile,true)));
}
catch (IOException e)
{
@ -100,7 +103,7 @@ public class ErrorLogging
String errorMessage = datetime.format(LocalDateTime.now()) + " - " + error;
fileOut.println(errorMessage);
fileOut.flush();
System.err.println(errorMessage);
System.out.println(errorMessage);
}
/**

View file

@ -1,13 +0,0 @@
package org.baxter.disco.ocr;
import javafx.event.*;
import javafx.fxml.*;
import javafx.scene.*;
import javafx.scene.text.Text;
/**
* To be implemented.
*/
public class Gui
{
}

View file

@ -0,0 +1,102 @@
package org.baxter.disco.ocr;
import java.util.List;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
/**
* Controller portion of MVC for Accuracy over Life test fixture.
* Mostly wrapper interface between View and Model.
*
* @author Blizzard Finnegan
* @version 0.0.1, 01 Feb, 2023
*/
public class GuiController
{
public static List<String> getCameras()
{ return GuiModel.getCameras(); }
public static void showImage(String cameraName)
{ GuiModel.showImage(cameraName); }
public static void toggleCrop(String cameraName)
{ GuiModel.toggleCrop(cameraName); }
public static void toggleThreshold(String cameraName)
{ GuiModel.toggleThreshold(cameraName); }
public static void saveDefaults()
{ GuiModel.saveDefaults(); }
public static void save()
{ GuiModel.save(); }
public static void saveClose()
{ GuiModel.save(); GuiModel.enableProcessing(); }
public static String getConfigValue(String cameraName, ConfigProperties property)
{ return GuiModel.getConfigVal(cameraName,property); }
public static void setConfigValue(String cameraName, ConfigProperties property, double value)
{ GuiModel.setConfigVal(cameraName,property,value); }
public static void setIterationCount(int iterationCount)
{ GuiModel.setIterations(iterationCount); }
public static void interruptTests()
{ GuiModel.interruptTesting(); }
public static void runTests()
{ GuiModel.runTests(); }
public static void testMotions()
{ GuiModel.testMovement(); }
public static void updateStart()
{
boolean ready = GuiModel.isReady();
GuiView.getStart().setDisable(ready);
if(ready) GuiView.getStart().setTooltip(new Tooltip("Start running automated testing."));
}
public static void updateIterations()
{
String newIterations = Integer.toString(GuiModel.getIterations());
GuiView.getIterationField().setPromptText(newIterations);
GuiView.getIterationField().setText(newIterations);
}
public static void updateConfigValue(String cameraName, ConfigProperties property)
{
TextField field = GuiView.getField(cameraName,property);
field.setText(GuiModel.getConfigVal(cameraName,property));
field.setPromptText(GuiModel.getConfigVal(cameraName,property));
}
public static void runningUpdate(int index)
{ userUpdate("Running iteration " + index + "..."); }
public static void userUpdate(String output)
{ GuiView.getFeedbackText().setText(output); }
public static void testingMotions()
{ userUpdate("Testing fixture movement..."); }
public static void testingMotionSuccessful()
{ userUpdate("Fixture movement test successful!"); }
public static void testingMotionUnsuccessful(String failurePoint)
{ userUpdate("Fixture movement unsuccessful! Fail point: " + failurePoint);}
public static void pressButton()
{ GuiModel.pressButton(); }
public static void updatePrime()
{ GuiModel.updatePrime(); }
public static String getIterationCount()
{ return Integer.toString(GuiModel.getIterations()); }
public static void closeModel() { GuiModel.close(); }
}

View file

@ -0,0 +1,160 @@
package org.baxter.disco.ocr;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Model portion of MVC for the Accuracy Over Life test fixture.
* Primarily a wrapper around other classes, but does store some information.
*
* @author Blizzard Finnegan
* @version 0.0.2, 03 Feb, 2023
*/
public class GuiModel
{
private static boolean readyToRun = false;
private static int iterationCount = 3;
public static final Lock LOCK = new ReentrantLock();
private static Thread testingThread = new Thread();
private static final MovementFacade fixture = new MovementFacade(LOCK);
public static void ready() { readyToRun = true; GuiController.updateStart(); }
public static boolean isReady() { return readyToRun; }
public static void setIterations(int iterations)
{
iterationCount = iterations;
GuiController.updateIterations();
}
public static int getIterations() { return iterationCount; }
public static void testMovement()
{
GuiController.testingMotions();
boolean success = fixture.testMotions();
if(success) GuiController.testingMotionSuccessful();
else GuiController.testingMotionUnsuccessful("Unknown");
}
public static List<String> getCameras()
{ return new ArrayList<>(OpenCVFacade.getCameraNames()); }
public static void showImage(String cameraName) { OpenCVFacade.showImage(cameraName); }
public static void setConfigVal(String cameraName, ConfigProperties property, double value)
{
ConfigFacade.setValue(cameraName,property,value);
GuiController.updateConfigValue(cameraName,property);
}
public static String getConfigVal(String cameraName, ConfigProperties property)
{ return Double.toString(ConfigFacade.getValue(cameraName,property)); }
public static void pressButton()
{ fixture.pressButton(); }
public static void updatePrime()
{
for(String cameraName : OpenCVFacade.getCameraNames())
{
boolean old = (ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) == 0.0 );
ConfigFacade.setValue(cameraName,ConfigProperties.PRIME,(old ? 1 : 0));
}
}
public static void enableProcessing()
{
for(String camera : getCameras())
{
ConfigFacade.setValue(camera,ConfigProperties.CROP, 1.0);
ConfigFacade.setValue(camera,ConfigProperties.THRESHOLD, 1.0);
}
}
public static void saveDefaults()
{ ConfigFacade.saveDefaultConfig(); }
public static void save()
{ ConfigFacade.saveCurrentConfig(); ConfigFacade.loadConfig(); }
public static void toggleThreshold(String cameraName)
{
boolean old = (ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) == 0.0 );
ConfigFacade.setValue(cameraName,ConfigProperties.THRESHOLD,(old ? 1 : 0));
}
public static void toggleCrop(String cameraName)
{
boolean old = (ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) == 0.0 );
ConfigFacade.setValue(cameraName,ConfigProperties.CROP,(old ? 1 : 0));
}
public static void runTests()
{
testingThread = new Thread(() ->
{
DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation(),OpenCVFacade.getCameraNames().size());
boolean prime = false;
for(String cameraName : OpenCVFacade.getCameraNames())
{
if(ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0)
{
prime = true;
}
}
for(int i = 0; i < iterationCount; i++)
{
Map<String, Double> resultMap = new HashMap<>();
LOCK.lock();
fixture.iterationMovement(prime);
LOCK.unlock();
LOCK.lock();
List<File> iteration = OpenCVFacade.singleIteration();
LOCK.unlock();
for(File file : iteration)
{
LOCK.lock();
Double result = TesseractFacade.imageToDouble(file);
LOCK.unlock();
LOCK.lock();
String fileLocation = file.getAbsolutePath();
LOCK.unlock();
LOCK.lock();
resultMap.put(fileLocation,result);
LOCK.unlock();
LOCK.lock();
ErrorLogging.logError("DEBUG: Tesseract final output: " + result);
LOCK.unlock();
}
LOCK.lock();
DataSaving.writeValues(i,resultMap);
LOCK.unlock();
GuiController.runningUpdate(i);
}
//println("=======================================");
ErrorLogging.logError("Testing complete!");
});
testingThread.run();
}
public static void close()
{
ErrorLogging.logError("DEBUG: PROGRAM CLOSING.");
fixture.closeGPIO();
ErrorLogging.logError("DEBUG: END OF PROGRAM.");
ErrorLogging.closeLogs();
}
public static void interruptTesting() { testingThread.interrupt(); }
}

View file

@ -0,0 +1,16 @@
package org.baxter.disco.ocr;
/**
* Wrapper class around GuiView.
*
* Maven will not build the {@link GuiView} properly, since it inherits from {@link javafx.application.Application}.
* This will start the Gui's main function, with no other functionality.
*
* @author Blizzard Finnegan
* @version 1.0.1, 01 Feb. 2023
*/
public class GuiStarter
{
public static void main(String[] args)
{ GuiView.main(args); }
}

View file

@ -0,0 +1,519 @@
package org.baxter.disco.ocr;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import javafx.application.Application;
import javafx.geometry.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.scene.text.*;
import javafx.stage.Stage;
/**
* View portion of MVC for the Accuracy Over Life test fixture.
*
* @author Blizzard Finnegan
* @version 0.0.1, 01 Feb, 2023
*/
public class GuiView extends Application
{
public static final Scene MAIN_MENU;
private static final AnchorPane MAIN_ANCHOR;
private static final Pane MAIN_PANE;
public static final Scene CAMERA_MENU;
private static final AnchorPane CAMERA_ANCHOR;
private static final Pane CAMERA_PANE;
private static final Map<String,Map<ConfigProperties,TextField>> uiFields = new HashMap<>();
private static Text userFeedback;
private static TextField iterationField;
private static Button startButton;
private static Stage STAGE;
public static void main(String[] args) { launch(args); }
static
{
ErrorLogging.logError("START OF PROGRAM");
ErrorLogging.logError("Setting up main menu...");
MAIN_ANCHOR = new AnchorPane();
MAIN_ANCHOR.setMinWidth(Double.NEGATIVE_INFINITY);
MAIN_ANCHOR.setMinHeight(Double.NEGATIVE_INFINITY);
MAIN_PANE = new Pane();
AnchorPane.setTopAnchor(MAIN_PANE,10.0);
AnchorPane.setLeftAnchor(MAIN_PANE,10.0);
AnchorPane.setRightAnchor(MAIN_PANE,10.0);
AnchorPane.setBottomAnchor(MAIN_PANE,10.0);
MAIN_ANCHOR.getChildren().add(MAIN_PANE);
MAIN_MENU = new Scene(MAIN_ANCHOR);
ErrorLogging.logError("Setting up camera config menu...");
CAMERA_ANCHOR = new AnchorPane();
CAMERA_ANCHOR.setMinWidth(Double.NEGATIVE_INFINITY);
CAMERA_ANCHOR.setMinHeight(Double.NEGATIVE_INFINITY);
CAMERA_PANE = new Pane();
AnchorPane.setTopAnchor(CAMERA_PANE,10.0);
AnchorPane.setLeftAnchor(CAMERA_PANE,10.0);
AnchorPane.setRightAnchor(CAMERA_PANE,10.0);
AnchorPane.setBottomAnchor(CAMERA_PANE,10.0);
CAMERA_ANCHOR.getChildren().add(CAMERA_PANE);
CAMERA_MENU = new Scene(CAMERA_ANCHOR);
for(String camera : GuiModel.getCameras())
uiFields.put(camera, new HashMap<>());
}
@Override
public void start(Stage stage) throws Exception
{
ErrorLogging.logError("Finalising GUI...");
STAGE = stage;
mainMenuBuilder();
cameraMenuBuilder();
STAGE.setScene(MAIN_MENU);
STAGE.show();
ErrorLogging.logError("Gui loading complete.");
}
private static void cameraMenuBuilder()
{
VBox layout = new VBox();
layout.setSpacing(5.0);
layout.setAlignment(Pos.CENTER_LEFT);
int index = 0;
for(String cameraName : GuiModel.getCameras())
{
if(index != 0) layout.getChildren().add(new Separator(Orientation.HORIZONTAL));
layout.getChildren().add(cameraSetup(cameraName));
index++;
}
layout.getChildren().add(cameraMenuButtons());
CAMERA_PANE.getChildren().add(layout);
}
private static void mainMenuBuilder()
{
VBox layout = new VBox();
layout.getChildren().addAll(topHalf(),
new Separator(Orientation.HORIZONTAL),
bottomHalf());
MAIN_PANE.getChildren().add(layout);
}
private static VBox topHalf()
{
VBox output = new VBox();
output.setSpacing(5.0);
output.getChildren().addAll(topButtons(),
new Separator(Orientation.HORIZONTAL),
setupSection(),
primeCheckbox(),
testFeedback());
return output;
}
private static CheckBox primeCheckbox()
{
CheckBox output = new CheckBox("Prime devices");
output.setTooltip(new Tooltip("This presses the button on the device under test twice for every iteration."));
output.setId("primeCheckbox");
output.selectedProperty().addListener(
(obeservableValue, oldValue, newValue) ->
{
GuiController.updatePrime();
});
return output;
}
private static HBox testFeedback()
{
HBox output = new HBox();
output.setSpacing(5.0);
Label textboxLabel = new Label("Test feedback: ");
Text textbox = new Text("Awaiting input...");
userFeedback = textbox;
textbox.setId("testOutputToUser");
output.getChildren().addAll(textboxLabel,textbox);
return output;
}
private static HBox setupSection()
{
HBox output = userTextField("Cycles:",GuiController.getIterationCount(), "Enter the number of times to test the devices in the fixture.");
TextField field = null;
for(Node child : output.getChildren())
{
if(child instanceof TextField)
{
field = (TextField)child;
break;
}
}
if(field == null)
{
ErrorLogging.logError("GUI INIT ERROR!!! - Failed text field setup.");
GuiController.closeModel();
}
iterationField = field;
//TextField textField = (TextField)(output.lookup("#cycles"));
field.textProperty().addListener(
(observable, oldValue, newValue) ->
{
try(Scanner sc = new Scanner(newValue);)
{ GuiController.setIterationCount(sc.nextInt()); }
catch(Exception e)
{
ErrorLogging.logError("USER INPUT ERROR: Illegal input in cycles count.");
newValue = oldValue;
}
});
return output;
}
private static HBox topButtons()
{
HBox topButtons = new HBox();
topButtons.setSpacing(5.0);
topButtons.setAlignment(Pos.CENTER);
topButtons.setMinWidth(Region.USE_COMPUTED_SIZE);
topButtons.setMinHeight(Region.USE_COMPUTED_SIZE);
final Button START = buttonBuilder("Start",true);
startButton = START;
final Button STOP = buttonBuilder("Stop",true);
START.setOnAction( (event) ->
{
START.setDisable(true);
STOP.setDisable(false);
GuiController.runTests();
});
START.setTooltip(new Tooltip("Configure cameras to start the program."));
STOP.setOnAction( (event) ->
{
GuiController.interruptTests();
START.setDisable(false);
STOP.setDisable(true);
});
STOP.setTooltip(new Tooltip("Pauses current iteration."));
Button calibrateCamera = buttonBuilder("Calibrate Cameras",false);
calibrateCamera.setOnAction( (event) -> STAGE.setScene(CAMERA_MENU) );
Button testMovement = buttonBuilder("Test Movement",false);
testMovement.setOnAction( (event) -> GuiController.testMotions() );
Button cancel = buttonBuilder("Close",false);
cancel.setOnAction( (event) ->
{
GuiController.closeModel();
STAGE.close();
});
topButtons.getChildren().addAll(START,
STOP,
calibrateCamera,
testMovement,
cancel);
return topButtons;
}
private static HBox bottomHalf()
{
HBox output = new HBox();
output.setAlignment(Pos.CENTER);
output.setSpacing(5.0);
int index = 0;
for(String camera : GuiModel.getCameras())
{
if(index != 0) output.getChildren().add(new Separator(Orientation.VERTICAL));
output.getChildren().add(camera(camera));
}
return output;
}
private static VBox camera(String cameraName)
{
VBox output = new VBox();
output.setAlignment(Pos.CENTER_LEFT);
output.setSpacing(5.0);
//HBox serialNumber = userTextField("DUT Serial Number:","","Enter the serial number for the device under test.");
output.getChildren().addAll(cameraHeader(cameraName),
//serialNumber,
cameraView(cameraName));
return output;
}
private static HBox cameraHeader(String cameraName)
{
HBox output = new HBox();
output.setAlignment(Pos.CENTER);
output.setSpacing(5.0);
output.getChildren().addAll(cameraCheckbox("Camera: " + cameraName));
return output;
}
private static HBox cameraCheckbox(String prompt)
{
HBox output = new HBox();
output.setSpacing(5.0);
output.setAlignment(Pos.CENTER);
output.setAlignment(Pos.CENTER);
Label label = new Label(prompt);
CheckBox checkBox = new CheckBox("Active");
checkBox.setId(prompt.toLowerCase());
output.getChildren().addAll(label,
checkBox);
return output;
}
private static HBox cameraView(String cameraName)
{
HBox output = new HBox();
output.setSpacing(5.0);
output.setAlignment(Pos.CENTER_LEFT);
Label label = new Label("OCR Read:");
Label ocrRead = new Label("[ ]");
ocrRead.setId("cameraOCR-" + cameraName);
ImageView imageView = new ImageView();
output.getChildren().addAll(label,
ocrRead,
imageView);
return output;
}
private static VBox cameraSetup(String cameraName)
{
VBox output = new VBox();
output.setSpacing(5.0);
output.setAlignment(Pos.CENTER_LEFT);
Label sectionHeader = new Label("Camera: " + cameraName);
output.getChildren().addAll(sectionHeader,
processingInputs(cameraName),
cropInputs(cameraName),
miscInputs(cameraName));
return output;
}
private static HBox processingInputs(String cameraName)
{
HBox output = new HBox();
output.setSpacing(5.0);
output.setAlignment(Pos.CENTER_LEFT);
Button preview = buttonBuilder("Preview");
preview.setId("previewButton-" + cameraName);
preview.setOnAction( (event) ->
{
GuiController.pressButton();
try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); }
GuiController.showImage(cameraName);
});
CheckBox cropPreview = new CheckBox("Crop preview");
cropPreview.setId("cropToggle-" + cameraName);
cropPreview.selectedProperty().addListener((obeservableValue, oldValue, newValue) ->
GuiController.toggleCrop(cameraName));
CheckBox thresholdPreview = new CheckBox("Threshold preview");
thresholdPreview.selectedProperty().addListener((obeservableValue, oldValue, newValue) ->
GuiController.toggleThreshold(cameraName));
cropPreview.setId("thresholdToggle-" + cameraName);
output.getChildren().addAll(preview,
cropPreview,
thresholdPreview);
return output;
}
private static HBox cropInputs(String cameraName)
{
HBox output = new HBox();
output.setSpacing(5.0);
output.setAlignment(Pos.CENTER_LEFT);
HBox cropX = userTextField("X:",
GuiController.getConfigValue(cameraName,ConfigProperties.CROP_X),
"X-value of the top left corner of the newly cropped image. Only accepts whole numbers.");
textFieldSetup(cropX,ConfigProperties.CROP_X,cameraName,"X");
HBox cropY = userTextField("Y:",
GuiController.getConfigValue(cameraName,ConfigProperties.CROP_Y),
"Y-value of the top left corner of the newly cropped image. Only accepts whole numbers.");
textFieldSetup(cropY,ConfigProperties.CROP_Y,cameraName,"Y");
HBox cropW = userTextField("Width:",
GuiController.getConfigValue(cameraName,ConfigProperties.CROP_W),
"Width, in pixels, of the newly cropped image. Only accepts whole numbers.");
textFieldSetup(cropW,ConfigProperties.CROP_W,cameraName,"Width");
HBox cropH = userTextField("Height:",
GuiController.getConfigValue(cameraName,ConfigProperties.CROP_H),
"Height, in pixels, of the newly cropped image. Only accepts whole numbers.");
textFieldSetup(cropH, ConfigProperties.CROP_H, cameraName, "Height");
output.getChildren().addAll(cropX,
cropY,
cropW,
cropH);
return output;
}
private static HBox miscInputs(String cameraName)
{
HBox output = new HBox();
output.setSpacing(5.0);
output.setAlignment(Pos.CENTER_LEFT);
HBox thresholdValue = userTextField("Threshold Value:",
GuiController.getConfigValue(cameraName,ConfigProperties.THRESHOLD),
"This value can be set from 0 to 255. Higher values mean more black in "+
"the thresholded image. For more information, see the documentation.");
textFieldSetup(thresholdValue,ConfigProperties.THRESHOLD_VALUE,cameraName,"Threshold Value");
HBox compositeFrames = userTextField("Composite Frames:",
GuiController.getConfigValue(cameraName,ConfigProperties.COMPOSITE_FRAMES),
"Number of frames to bitwise-and together.");
textFieldSetup(compositeFrames,ConfigProperties.COMPOSITE_FRAMES,cameraName,"Composite Frames");
output.getChildren().addAll(thresholdValue,
compositeFrames);
return output;
}
private static HBox cameraMenuButtons()
{
HBox output = new HBox();
output.setAlignment(Pos.CENTER);
output.setSpacing(10.0);
Button defaults = buttonBuilder("Save Defaults");
defaults.setOnAction( (event) ->
{
GuiController.saveDefaults();
});
Button save = buttonBuilder("Save");
save.setOnAction( (event) ->
{
GuiController.save();
});
Button saveClose = buttonBuilder("Save and Close");
saveClose.setOnAction( (event) ->
{
GuiController.saveClose();
STAGE.setScene(MAIN_MENU);
});
Button close = buttonBuilder("Close without Saving");
close.setOnAction( (event) ->
{
STAGE.setScene(MAIN_MENU);
});
output.getChildren().addAll(defaults,
save,
saveClose,
close);
return output;
}
private static void textFieldSetup(HBox hbox, ConfigProperties property, String cameraName, String oldId)
{
TextField field = null;
for(Node child : hbox.getChildren())
{
if(child instanceof TextField)
{
field = (TextField)child;
break;
}
}
if(field == null)
{
ErrorLogging.logError("GUI INIT ERROR!!! - Failed text field setup.");
GuiController.closeModel();
}
//GuiController.addToMap(cameraName,property,field);
Map<ConfigProperties,TextField> cameraFields = uiFields.get(cameraName);
if(cameraFields.containsKey(property))
{ ErrorLogging.logError("GUI Setup Error!!! - Duplicate field: " + cameraName + " " + property.getConfig()); }
cameraFields.put(property,field);
uiFields.replace(cameraName,cameraFields);
field.setId(property.getConfig() + cameraName);
field.textProperty().addListener(
(observable, oldValue, newValue) ->
{
try(Scanner sc = new Scanner(newValue);)
{ GuiController.setConfigValue(cameraName,property,sc.nextInt()); }
catch(Exception e)
{
ErrorLogging.logError("USER INPUT ERROR: Illegal input in " + property.getConfig() + " for " + cameraName + ".");
newValue = oldValue;
}
});
}
private static Button buttonBuilder(String name,boolean disabled)
{
String[] id = name.strip().substring(0, name.length() - 1).toLowerCase().strip().split(" ");
Button button = new Button(name);
button.setId(id[0]);
button.setDisable(disabled);
return button;
}
private static Button buttonBuilder(String name)
{ return buttonBuilder(name,false); }
private static HBox userTextField(String prompt, String baseValue, String description)
{
HBox output = new HBox();
output.setSpacing(5.0);
output.setAlignment(Pos.CENTER_LEFT);
Label label = new Label(prompt);
TextField field = new TextField();
String[] id = prompt.strip().substring(0, prompt.length() - 1).toLowerCase().strip().split(" ");
field.setId(id[0]);
output.setId(id[0] + "-box");
field.setPromptText(baseValue);
Tooltip tooltip = new Tooltip(description);
field.setTooltip(tooltip);
label.setTooltip(tooltip);
output.getChildren().addAll(label,field);
return output;
}
public static TextField getField(String cameraName, ConfigProperties property)
{ return uiFields.get(cameraName).get(property); }
public static Button getStart()
{ return startButton; }
public static Text getFeedbackText()
{ return userFeedback; }
public static TextField getIterationField()
{ return iterationField; }
}

View file

@ -1,5 +1,7 @@
package org.baxter.disco.ocr;
import java.util.concurrent.locks.Lock;
import com.pi4j.Pi4J;
import com.pi4j.context.Context;
import com.pi4j.io.gpio.digital.DigitalInput;
@ -19,10 +21,46 @@ import com.pi4j.io.pwm.PwmType;
* Currently missing Run switch compatibility.
*
* @author Blizzard Finnegan
* @version 1.1.0, 27 Jan. 2023
* @version 2.1.0, 03 Feb. 2023
*/
public class MovementFacade
{
/**
* Constructor for MovementFacade.
*
* @param LOCK A Lock object, used for interactions with
* the physical lock switch on the fixture.
*/
public MovementFacade(Lock LOCK)
{
//ErrorLogging.logError("DEBUG: Starting lock thread...");
runSwitchThread = new Thread(() ->
{
boolean unlock = false;
while(true)
{
if(runSwitch.isOn())
{
ErrorLogging.logError("Run switch turned off!");
while(!LOCK.tryLock())
{}
unlock = true;
}
else
{
//ErrorLogging.logError("Run switch on!");
if(unlock)
{ LOCK.unlock(); unlock = false; }
}
//try{ Thread.sleep(100); } catch(Exception e) { ErrorLogging.logError(e); }
}
}, "Run switch monitor.");
runSwitchThread.start();
//ErrorLogging.logError("DEBUG: Lock thread started!");
}
private static Thread runSwitchThread;
//Externally Available Variables
/**
* PWM Frequency
@ -37,7 +75,7 @@ public class MovementFacade
/**
* Number of seconds to wait before timing out a fixture movement.
*/
private static int TIME_OUT = 20;
private static int TIME_OUT = 10;
//PWM Addresses
//All addresses are in BCM format.
@ -95,7 +133,7 @@ public class MovementFacade
*/
private static DigitalInput lowerLimit;
/**TODO: Multithreading;
/**
* Lower limit switch object.
*
* Status: High; Test may continue.
@ -157,6 +195,7 @@ public class MovementFacade
//as the PWM signal is simply a clock for the motor.
pwm = pwmBuilder("pwm","PWM Pin",PWM_PIN_ADDR);
pwm.on(DUTY_CYCLE, FREQUENCY);
}
/**
@ -173,7 +212,6 @@ public class MovementFacade
PwmConfigBuilder configBuilder;
switch (address)
{
//TODO: Set PWM duty cycle
//The following pins allow for hardware PWM support.
case 12:
case 13:
@ -261,7 +299,7 @@ public class MovementFacade
*
* @return True if the value was set successfully; otherwise false.
*/
public static boolean setDutyCycle(int newDutyCycle)
public boolean setDutyCycle(int newDutyCycle)
{
boolean output = false;
if(newDutyCycle < 0)
@ -271,7 +309,6 @@ public class MovementFacade
else
{
DUTY_CYCLE = newDutyCycle;
pwmBuilder("pwm","PWM Pin",PWM_PIN_ADDR);
pwm.on(DUTY_CYCLE, FREQUENCY);
output = true;
}
@ -283,7 +320,7 @@ public class MovementFacade
*
* @return The current DutyCycle.
*/
public static int getDutyCycle() { return DUTY_CYCLE; }
public int getDutyCycle() { return DUTY_CYCLE; }
/**
* Setter for the fixture's time to give up on a movement.
@ -292,7 +329,7 @@ public class MovementFacade
*
* @return True if the value was set successfully; otherwise false.
*/
public static boolean setTimeout(int newTimeout)
public boolean setTimeout(int newTimeout)
{
boolean output = false;
if(newTimeout < 0)
@ -312,7 +349,7 @@ public class MovementFacade
*
* @return The current timeout.
*/
public static int getTimeout() { return TIME_OUT; }
public int getTimeout() { return TIME_OUT; }
/**
* Setter for the fixture's PWM frequency.
@ -321,7 +358,7 @@ public class MovementFacade
*
* @return True if the value was set successfully; otherwise false.
*/
public static boolean setFrequency(int newFrequency)
public boolean setFrequency(int newFrequency)
{
boolean output = false;
if(newFrequency < 0)
@ -331,7 +368,6 @@ public class MovementFacade
else
{
FREQUENCY = newFrequency;
pwmBuilder("pwm","PWM Pin",PWM_PIN_ADDR);
pwm.on(DUTY_CYCLE, FREQUENCY);
output = true;
}
@ -343,7 +379,7 @@ public class MovementFacade
*
* @return The current PWM frequency.
*/
public static int getFrequency() { return FREQUENCY; }
public int getFrequency() { return FREQUENCY; }
/**
* Internal function to send the fixture to one limit switch or another.
@ -352,7 +388,7 @@ public class MovementFacade
* @param timeout How long (in seconds) to wait before timing out.
* @return true if movement was successful; otherwise false
*/
private static boolean gotoLimit(boolean moveUp, int timeout)
private boolean gotoLimit(boolean moveUp, int timeout)
{
boolean output = false;
DigitalInput limitSense;
@ -388,7 +424,7 @@ public class MovementFacade
* @param timeout How long (in seconds) to wait before timing out.
* @return true if movement was successful; otherwise false
*/
public static boolean goDown(int timeout) { return gotoLimit(false, timeout); }
public boolean goDown(int timeout) { return gotoLimit(false, timeout); }
/**
* Send the fixture to the upper limit switch.
@ -396,7 +432,7 @@ public class MovementFacade
* @param timeout How long (in seconds) to wait before timing out.
* @return true if movement was successful; otherwise false
*/
public static boolean goUp(int timeout) { return gotoLimit(true, timeout); }
public boolean goUp(int timeout) { return gotoLimit(true, timeout); }
/**
* Send the fixture to the lower limit switch.
@ -404,7 +440,7 @@ public class MovementFacade
*
* @return true if movement was successful; otherwise false
*/
public static boolean goDown() { return goDown(TIME_OUT); }
public boolean goDown() { return goDown(TIME_OUT); }
/**
* Send the fixture to the upper limit switch.
@ -412,12 +448,12 @@ public class MovementFacade
*
* @return true if movement was successful; otherwise false
*/
public static boolean goUp() { return goUp(TIME_OUT); }
public boolean goUp() { return goUp(TIME_OUT); }
/**
* Extends the piston for 1 second, pushing the button on the DUT.
*/
public static void pressButton()
public void pressButton()
{
ErrorLogging.logError("DEBUG: Pressing button...");
pistonActivate.on();
@ -429,9 +465,13 @@ public class MovementFacade
/**
* Closes connections to all GPIO pins.
*/
public static void closeGPIO()
public void closeGPIO()
{
goUp();
if(runSwitchThread.isAlive())
{
runSwitchThread.interrupt();
}
pi4j.shutdown();
}
@ -440,7 +480,7 @@ public class MovementFacade
*
* @return True if all movements worked properly; otherwise False
*/
public static boolean testMotions()
public boolean testMotions()
{
boolean output = goUp();
if(!output) return output;
@ -452,7 +492,7 @@ public class MovementFacade
return output;
}
public static void iterationMovement(boolean prime)
public void iterationMovement(boolean prime)
{
goUp();
if(prime) pressButton();
@ -460,17 +500,9 @@ public class MovementFacade
pressButton();
}
public static void main(String[] args)
public void main(String[] args)
{
testMotions();
closeGPIO();
}
//TODO: Multithreading, to allow for RunSwitch Interrupts.
// protected class RunSwitchInterrupt implements Runnable
// {
// public void run()
// {
// }
// }
}

View file

@ -22,7 +22,7 @@ import java.util.List;
* Performs image capture, as well as image manipulation.
*
* @author Blizzard Finnegan
* @version 1.0.0, 27 Jan. 2023
* @version 1.2.0, 03 Feb. 2023
*/
public class OpenCVFacade
{
@ -73,9 +73,6 @@ public class OpenCVFacade
*
* @param name Name of the new camera
* @param location Location of the new camera
*
* @return false if camera already exists, or if camera does not exist.
* Otherwise, returns true.
*/
private static void newCamera(String name, String location)
{
@ -91,9 +88,6 @@ public class OpenCVFacade
* @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.
*
* @return false if camera already exists, or if camera does not exist.
* Otherwise, returns true.
*/
private static void newCamera(String name, String location, int width, int height)
{
@ -109,9 +103,6 @@ public class OpenCVFacade
* @param width Width of the camera's image, in pixels.
* @param height height of the camera's image, in pixels.
* @param codec Codec to use for the new camera.
*
* @return false if camera already exists, or if camera does not exist.
* Otherwise, returns true.
*/
private static void newCamera(String name, String location, int width, int height, String codec)
{
@ -270,11 +261,12 @@ public class OpenCVFacade
*
* @return Frame of the thresholded image
*/
public static Mat thresholdImage(Mat image)
public static Mat thresholdImage(Mat image,String cameraName)
{
Mat output = image;
Mat in = image;
threshold(in,output,50,255,THRESH_BINARY);
double thresholdValue = ConfigFacade.getValue(cameraName,ConfigProperties.THRESHOLD_VALUE);
threshold(in,output,thresholdValue,255,THRESH_BINARY);
return output;
}
@ -299,15 +291,12 @@ public class OpenCVFacade
/**
* Compose several images together.
* This will also perform thresholding, and cropping,
* based on boolean toggles.
* based on boolean toggles. Crop information is collected
* 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
* @param x X-coordinate of the top-left of the cropped portion of the image.
* @param y y-coordinate of the top-left of the cropped portion of the image.
* @param width width of the the cropped portion of the image.
* @param height height of the the cropped portion of the image.
*
* @return A single image, found by boolean AND-ing together all parsed images.
*/
@ -323,7 +312,7 @@ public class OpenCVFacade
image.copyTo(processedImage);
if(crop)
{
ErrorLogging.logError("DEBUG: Cropping image " + iterationCount + "...");
//ErrorLogging.logError("DEBUG: Cropping image " + iterationCount + "...");
processedImage = crop(processedImage,cameraName);
//String fileLocation = ConfigFacade.getImgSaveLocation() + "/debug/"
// + ErrorLogging.fileDatetime.format(LocalDateTime.now()) +
@ -333,8 +322,8 @@ public class OpenCVFacade
}
if(threshold)
{
ErrorLogging.logError("DEBUG: Thresholding image " + iterationCount + "...");
processedImage = thresholdImage(processedImage);
//ErrorLogging.logError("DEBUG: Thresholding image " + iterationCount + "...");
processedImage = thresholdImage(processedImage,cameraName);
//String fileLocation = ConfigFacade.getImgSaveLocation() + "/debug/"
// + ErrorLogging.fileDatetime.format(LocalDateTime.now()) +
// "." + iterationCount + "-post-threshold.jpg";
@ -355,7 +344,7 @@ public class OpenCVFacade
{
output = processedImage.clone();
}
ErrorLogging.logError("DEBUG: Thresholding image " + iterationCount + "...");
//ErrorLogging.logError("DEBUG: Thresholding image " + iterationCount + "...");
bitwise_and((iterationCount == 1 ? processedImage : output),processedImage, output);
iterationCount++;
@ -451,6 +440,7 @@ public class OpenCVFacade
for(String cameraName : getCameraNames())
{
output.add(completeProcess(cameraName, ConfigFacade.getImgSaveLocation()));
ErrorLogging.logError("DEBUG: ---------------------------------");
}
return output;
}

View file

@ -1,151 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Pane layoutX="42.0" layoutY="155.0" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
<children>
<VBox alignment="CENTER_LEFT" spacing="10.0">
<children>
<Label text="Camera Test">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Label>
<Separator prefWidth="200.0" />
<VBox>
<children>
<Label text="Main Toggles">
<font>
<Font name="System Italic" size="14.0" />
</font>
<tooltip>
<Tooltip text="These settings will only apply if the Threshold checkbox is ticked." />
</tooltip>
</Label>
<HBox spacing="20.0">
<children>
<CheckBox mnemonicParsing="false" text="Threshold Image">
<tooltip>
<Tooltip text="This will apply the gamma settings set below, then compose multiple images on top of each other." />
</tooltip>
</CheckBox>
<CheckBox mnemonicParsing="false" text="Crop Image">
<tooltip>
<Tooltip text="This will crop the raw image from the camera into a smaller image." />
</tooltip>
</CheckBox>
</children>
</HBox>
</children>
</VBox>
<Separator prefWidth="200.0" />
<VBox>
<children>
<Label disable="true" text="Threshold Settings">
<font>
<Font name="System Italic" size="14.0" />
</font>
<tooltip>
<Tooltip text="These settings will only apply if the &quot;Threshold Image&quot; checkbox is ticked." />
</tooltip>
</Label>
<HBox spacing="5.0" />
<HBox spacing="5.0" />
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label disable="true" text="Gamma:" />
<TextField disable="true" promptText="Default: 1.0" GridPane.columnIndex="1" />
<Label disable="true" text="Composite Frames" GridPane.rowIndex="1" />
<TextField disable="true" promptText="Default: 1" GridPane.columnIndex="1" GridPane.rowIndex="1" />
</children>
</GridPane>
</children>
</VBox>
<Separator prefWidth="200.0" />
<VBox spacing="5.0">
<children>
<Label disable="true" text="Crop Settings">
<font>
<Font name="System Italic" size="14.0" />
</font>
<tooltip>
<Tooltip text="These settings will only apply if the &quot;Crop Image&quot; checkbox is ticked." />
</tooltip>
</Label>
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<TextField disable="true" GridPane.columnIndex="1" />
<Label disable="true" text="X-Coordinate: ">
<tooltip>
<Tooltip text="This refers to the X coordinate of the top-left-most pixel in the newly cropped image." />
</tooltip>
</Label>
<Label disable="true" text="Y-Coordinate: " GridPane.rowIndex="1">
<tooltip>
<Tooltip text="This refers to the Y coordinate of the top-left-most pixel in the newly cropped image." />
</tooltip>
</Label>
<TextField disable="true" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label disable="true" text="Crop Width:" GridPane.rowIndex="2">
<tooltip>
<Tooltip text="This defines the width of the newly cropped image." />
</tooltip>
</Label>
<TextField disable="true" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Label disable="true" text="Crop Height:" GridPane.rowIndex="3">
<tooltip>
<Tooltip text="This defines the height of the newly cropped image." />
</tooltip>
</Label>
<TextField disable="true" GridPane.columnIndex="1" GridPane.rowIndex="3" />
</children>
</GridPane>
</children>
</VBox>
<Separator prefWidth="200.0" />
<HBox alignment="CENTER" spacing="20.0">
<children>
<Button mnemonicParsing="false" text="Test Camera" />
<Button mnemonicParsing="false" text="Close Camera Test Menu" />
</children>
</HBox>
</children>
</VBox>
</children>
</Pane>
</children>
</AnchorPane>

View file

@ -1,88 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Pane AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
<children>
<VBox spacing="10.0">
<children>
<Label text="Parse Log Files">
<font>
<Font name="System Bold" size="24.0" />
</font>
</Label>
<Separator prefWidth="200.0" />
<HBox alignment="CENTER_LEFT" spacing="5.0">
<children>
<Button mnemonicParsing="false" text="Load CSV Log File" />
<Label text="[CSV Location]">
<font>
<Font name="Monospaced Regular" size="12.0" />
</font>
</Label>
</children>
</HBox>
<HBox alignment="CENTER_LEFT" spacing="5.0">
<children>
<Button mnemonicParsing="false" text="Choose Output File" />
<Label text="[XLSX Location]">
<font>
<Font name="Monospaced Regular" size="12.0" />
</font>
</Label>
<Button mnemonicParsing="false" text="Use Defaults" />
</children>
</HBox>
<HBox alignment="CENTER_LEFT" spacing="5.0">
<children>
<Button mnemonicParsing="false" text="Image Folder" />
<Label text="[Image Folder Location]">
<font>
<Font name="Monospaced Regular" size="12.0" />
</font>
</Label>
</children>
</HBox>
<HBox spacing="5.0">
<children>
<Label text="Anomaly Detection Mode:" />
<RadioButton mnemonicParsing="false" text="Running Mean" />
<RadioButton mnemonicParsing="false" text="Difference" />
</children>
</HBox>
<HBox spacing="5.0">
<children>
<Label text="Max Size for Running Mean:" translateY="3.0" />
<TextField promptText="No max size" />
</children>
</HBox>
<HBox spacing="5.0">
<children>
<Label text="Max diff. for anomaly detection:" translateY="3.0" />
<TextField text="0.5" />
</children>
</HBox>
<Separator prefWidth="200.0" />
<HBox alignment="CENTER" spacing="20.0">
<children>
<Button mnemonicParsing="false" text="Parse Log Files" />
<Button mnemonicParsing="false" text="Close Log Parsing Menu" />
</children>
</HBox>
</children>
</VBox>
</children>
</Pane>
</children>
</AnchorPane>

View file

@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Pane layoutX="158.0" layoutY="137.0" AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
<children>
<VBox spacing="10.0">
<children>
<Label text="Fixture Movement Test">
<font>
<Font name="System Bold" size="24.0" />
</font>
</Label>
<Separator prefWidth="200.0" />
<HBox alignment="CENTER" spacing="10.0">
<children>
<Button mnemonicParsing="false" text="Test Movement" />
<Label text="Awaiting input...">
<font>
<Font name="Monospaced Regular" size="12.0" />
</font>
</Label>
</children>
</HBox>
<HBox alignment="CENTER" spacing="10.0">
<children>
<Label text="PWM Frequency:" />
<TextField />
</children>
</HBox>
<Separator prefWidth="200.0" />
<HBox alignment="CENTER" spacing="10.0">
<children>
<Button mnemonicParsing="false" text="Close Movement Test Menu" />
</children>
</HBox>
</children>
</VBox>
</children>
</Pane>
</children>
</AnchorPane>

View file

@ -5,11 +5,8 @@
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.MenuButton?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.HBox?>
@ -18,29 +15,25 @@
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" nodeOrientation="LEFT_TO_RIGHT" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
<AnchorPane minHeight="400.0" minWidth="600.0" xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Pane AnchorPane.bottomAnchor="10.0" AnchorPane.leftAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0">
<children>
<VBox alignment="CENTER" spacing="10.0">
<VBox alignment="CENTER" layoutX="10.0" layoutY="10.0" spacing="10.0">
<children>
<VBox alignment="CENTER_LEFT" spacing="10.0">
<children>
<HBox alignment="CENTER" spacing="40.0">
<children>
<Button mnemonicParsing="false" text="Start" />
<Button disable="true" mnemonicParsing="false" text="Start" />
<Button disable="true" mnemonicParsing="false" text="Stop" />
<Button mnemonicParsing="false" text="Calibrate" />
<Button mnemonicParsing="false" text="Calibrate Cameras" />
<Button mnemonicParsing="false" text="Test Movement" />
<Button mnemonicParsing="false" text="Cancel" />
<MenuButton mnemonicParsing="false" text="Debug Tests">
<items>
<MenuItem mnemonicParsing="false" text="Camera Test" />
<MenuItem mnemonicParsing="false" text="Movement Test" />
</items>
</MenuButton>
</children>
</HBox>
<Separator prefWidth="200.0" />
<Separator />
<HBox alignment="CENTER" spacing="40.0">
<children>
<HBox alignment="CENTER" spacing="5.0" HBox.hgrow="ALWAYS">
@ -70,14 +63,14 @@
<Label text="Test Feedback:" />
<Text fontSmoothingType="LCD" strokeType="OUTSIDE" strokeWidth="0.0" text="Awaiting input...">
<font>
<Font name="Monospaced Bold" size="13.0" />
<Font name="System Bold" size="13.0" />
</font>
</Text>
</children>
</HBox>
</children>
</VBox>
<Separator prefWidth="200.0" />
<Separator />
<HBox spacing="10.0">
<children>
<VBox spacing="5.0">
@ -92,21 +85,17 @@
<HBox alignment="CENTER_LEFT" spacing="5.0">
<children>
<Label alignment="CENTER_RIGHT" text="OCR Read:" translateY="-1.0" />
<Label text="36.6">
<Label text="[ ]">
<font>
<Font name="Monospaced Bold" size="12.0" />
<Font name="System Bold" size="12.0" />
</font>
</Label>
<ImageView fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../../../../../../../../../../Pictures/360_F_470298738_1eHqTZ0B5AvB3emaESPpvQ93227y7P0l.jpg" />
</image>
</ImageView>
<ImageView fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" />
</children>
</HBox>
</children>
</VBox>
<Separator orientation="VERTICAL" prefHeight="200.0" />
<Separator orientation="VERTICAL" />
<VBox spacing="5.0">
<children>
<HBox alignment="CENTER" spacing="20.0">
@ -119,16 +108,12 @@
<HBox alignment="CENTER_LEFT" spacing="5.0">
<children>
<Label alignment="CENTER_RIGHT" text="OCR Read:" translateY="-1.0" />
<Label text="36.6">
<Label text="[ ]">
<font>
<Font name="Monospaced Bold" size="12.0" />
<Font name="System Bold" size="12.0" />
</font>
</Label>
<ImageView fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true">
<image>
<Image url="@../../../../../../../../../../../Pictures/360_F_470298738_1eHqTZ0B5AvB3emaESPpvQ93227y7P0l.jpg" />
</image>
</ImageView>
<ImageView fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" />
</children>
</HBox>
</children>