4.0.0-rc2 #1
22 changed files with 1343 additions and 749 deletions
30
README.md
30
README.md
|
@ -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)
|
||||
|
|
215
antrun/build.xml
215
antrun/build.xml
|
@ -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>
|
|
@ -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
12
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
{
|
||||
}
|
102
src/main/java/org/baxter/disco/ocr/GuiController.java
Normal file
102
src/main/java/org/baxter/disco/ocr/GuiController.java
Normal 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(); }
|
||||
}
|
160
src/main/java/org/baxter/disco/ocr/GuiModel.java
Normal file
160
src/main/java/org/baxter/disco/ocr/GuiModel.java
Normal 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(); }
|
||||
}
|
16
src/main/java/org/baxter/disco/ocr/GuiStarter.java
Normal file
16
src/main/java/org/baxter/disco/ocr/GuiStarter.java
Normal 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); }
|
||||
}
|
519
src/main/java/org/baxter/disco/ocr/GuiView.java
Normal file
519
src/main/java/org/baxter/disco/ocr/GuiView.java
Normal 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; }
|
||||
}
|
|
@ -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()
|
||||
// {
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 "Threshold Image" 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 "Crop Image" 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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
Loading…
Add table
Reference in a new issue