Compare commits

...
Sign in to create a new pull request.

22 commits
stable ... gui

Author SHA1 Message Date
e064440944
Add back documentation notes to GUI branch 2023-03-21 09:58:06 -04:00
fa9bbca345
Merge branch 'stable' into gui 2023-03-21 09:56:42 -04:00
554eaf7f9d
Merge branch 'stable' into gui 2023-03-21 09:10:05 -04:00
db57360234
Merge branch 'stable' into gui 2023-03-20 15:07:17 -04:00
abf2c88f03
Merge branch 'stable' into gui 2023-03-16 11:01:41 -04:00
888f52e681
Merge branch 'stable' into gui 2023-03-16 10:46:02 -04:00
7cff5f386a
Merge branch 'stable' into gui 2023-03-13 10:22:41 -04:00
b6597f0892
Merge branch 'stable' into gui 2023-03-10 12:22:02 -05:00
1e91125afa
Merge branch 'stable' into gui 2023-03-09 13:14:35 -05:00
7546ecb9fc
Merge branch 'stable' into gui 2023-03-08 15:43:32 -05:00
2a9d7722af
Merge branch 'stable' into gui 2023-02-28 11:30:58 -05:00
58a1c1a360
Merge branch 'stable' into gui 2023-02-24 13:00:25 -05:00
e95001ce25
Merge branch 'stable' into gui 2023-02-20 08:32:54 -05:00
1ee1348d63
Merge branch 'stable' into gui 2023-02-17 16:02:20 -05:00
0be18b3af8
Merge branch 'stable' into gui 2023-02-17 14:18:00 -05:00
af0ae959e8
Merge branch 'stable' into gui 2023-02-15 10:09:25 -05:00
2ed037e91a
Merge branch 'stable' into gui 2023-02-10 14:42:20 -05:00
cb38ac0b43
Merge branch 'stable' into gui 2023-02-10 10:33:59 -05:00
1b3258e580
Merge remote-tracking branch 'origin/stable' into gui 2023-02-09 15:54:04 -05:00
c9dc800a8d
Merge branch 'stable' into gui 2023-02-09 12:52:17 -05:00
a19a2dce0a
Merge branch 'stable' into gui 2023-02-08 15:42:28 -05:00
36926be30b
Add current state of Gui files 2023-02-08 08:57:13 -05:00
8 changed files with 1509 additions and 5 deletions

BIN
MVC Diagram 2.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
MVC diagram.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View file

@ -14,7 +14,7 @@
</organization>
<properties>
<uitype>Cli</uitype>
<uitype>GuiStarter</uitype>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>

View file

@ -3,9 +3,8 @@ module org.baxter.disco.ocr {
requires com.pi4j.plugin.raspberrypi;
requires com.pi4j.plugin.pigpio;
requires com.pi4j.library.pigpio;
//requires javafx.fxml;
//requires javafx.controls;
requires javafx.swing;
requires javafx.fxml;
requires javafx.controls;
requires org.apache.poi.poi;
requires org.apache.commons.configuration2;
requires org.apache.xmlbeans;
@ -13,7 +12,7 @@ module org.baxter.disco.ocr {
requires org.bytedeco.leptonica;
requires org.bytedeco.opencv;
requires org.bytedeco.javacpp;
//requires javafx.graphics;
requires javafx.graphics;
requires org.apache.poi.ooxml;
requires org.apache.poi.ooxml.schemas;
requires org.apache.commons.io;

View file

@ -0,0 +1,261 @@
package org.baxter.disco.ocr;
import java.util.List;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
/**
* Controller portion of MVC for Accuracy over Life test fixture.
* Mostly wrapper interface between View and Model.
*
* {@link GuiView}, GuiController, and {@link GuiModel} versions are tied together, and are referred to collectively as Gui.
*
* @author Blizzard Finnegan
* @version 0.2.0, 06 Feb, 2023
*/
public class GuiController
{
/**
* Wrapper function to get available cameras.
*
* @return List[String] of the names of cameras
*/
public static List<String> getCameras()
{ return GuiModel.getCameras(); }
/**
* Wrapper function used to show an image in a separate window
*
* @param cameraName The camera whose image should be shown.
*/
public static String showImage(String cameraName)
{ return GuiModel.showImage(cameraName); }
/**
* Wrapper function to toggle cropping for a given camera
*
* @param cameraName The camera whose image should be shown.
*/
public static void toggleCrop(String cameraName)
{ GuiModel.toggleCrop(cameraName); }
/**
* Wrapper function to toggle threshold for a given camera
*
* @param cameraName The camera whose image should be shown.
*/
public static void toggleThreshold(String cameraName)
{ GuiModel.toggleThreshold(cameraName); }
/**
* Wrapper function to save the default config
*/
public static void saveDefaults()
{ GuiModel.saveDefaults(); }
/**
* Wrapper function to save the current config
*/
public static void save()
{ GuiModel.save(); }
/**
* Wrapper function to save the current config, and re-enable image processing if necessary.
*/
public static void saveClose()
{ GuiModel.save(); GuiModel.enableProcessing(); }
/**
* Wrapper function to get a config value, for a given camera
*
* @param cameraName The name of the camera being inspected
* @param property The config property to be returned
*
* @return String of the value of the current object
*/
public static String getConfigString(String cameraName, ConfigProperties property)
{ return GuiModel.getConfigString(cameraName,property); }
/**
* Wrapper function to get a config value, for a given camera
*
* @param cameraName The name of the camera being inspected
* @param property The config property to be returned
*
* @return String of the value of the current object
*/
public static double getConfigValue(String cameraName, ConfigProperties property)
{ return GuiModel.getConfigValue(cameraName,property); }
/**
* Wrapper function to set a config value for a given camera.
*
* @param cameraName The name of the camera being modified
* @param property The property to be modified
* @param value The new value to set the property to
*/
public static void setConfigValue(String cameraName, ConfigProperties property, double value)
{
GuiModel.setConfigVal(cameraName,property,value);
if(property == ConfigProperties.CROP_W)
GuiView.updateImageViewWidth(cameraName);
if(property == ConfigProperties.CROP_H)
GuiView.updateImageViewHeight(cameraName);
}
/**
* Setter for the number of iterations
*
* @param iterationCount The new iteration count to be saved
*/
public static void setIterationCount(int iterationCount)
{ GuiModel.setIterations(iterationCount); }
/**
* Wrapper function to interrupt testing.
*/
public static void interruptTests()
{ GuiModel.interruptTesting(); }
/**
* Wrapper function to run tests.
*/
public static void runTests()
{ GuiModel.runTests(); }
/**
* Wrapper function to test the movement of the fixture.
*/
public static void testMotions()
{
testingMotions();
GuiModel.testMovement();
}
/**
* If the Model is ready, set the view to allow the Start button to be pressed.
*/
public static void updateStart()
{
boolean ready = GuiModel.isReady();
GuiView.getStart().setDisable(ready);
if(ready) GuiView.getStart().setTooltip(new Tooltip("Start running automated testing."));
}
/**
* Wrapper function to write to the user feedback Label, stating that the test is starting.
*/
public static void startTests()
{ userUpdate("Starting tests..."); }
/**
* Updates the View's state wit hthe current iteration count
*/
public static void updateIterations()
{
String newIterations = Integer.toString(GuiModel.getIterations());
GuiView.getIterationField().setText(newIterations);
}
/**
* Update a given config value, given a camera
*
* @param cameraName The name of the camera being updated
* @param property The property being updated
*/
public static void updateConfigValue(String cameraName, ConfigProperties property)
{
TextField field = GuiView.getField(cameraName,property);
field.setText(GuiModel.getConfigString(cameraName,property));
field.setPromptText(GuiModel.getConfigString(cameraName,property));
}
/**
* Wrapper function to write the current iteration ot the user feedback Label.
*
* @param index The current iteration number
*/
public static void runningUpdate(int index)
{ userUpdate("Running iteration " + index + "..."); }
/**
* Wrapper function used to set a custom message to the user.
*
* @param output What should be sent to the user.
*/
public static void userUpdate(String output)
{ GuiView.getFeedbackText().setText(output); }
/**
* Wrapper function to tell the user that fixture movement testing has begun.
*/
public static void testingMotions()
{ userUpdate("Testing fixture movement..."); }
/**
* Wrapper function to tell the user that fixture movement testing was successful.
*/
public static void testingMotionSuccessful()
{ userUpdate("Fixture movement test successful!"); }
/**
* Wrapper function to tell the user that fixture movement testing failed, and where it failed.
*
* @param failurePoint Where the movement failed.
*/
public static void testingMotionUnsuccessful(String failurePoint)
{ userUpdate("Fixture movement unsuccessful! Fail point: " + failurePoint);}
/**
* Wrapper function for the Model's pressButton function.
*/
public static void pressButton()
{ GuiModel.pressButton(); }
/**
* Wrapper function used to update whether or not the DUTs should be primed before testing.
*/
public static void updatePrime()
{ GuiModel.updatePrime(); }
/**
* Wrapper function to set the Serial for a given camera.
*
* @param cameraName The name of the camera to modify
* @param serial The serial of the DUT under the given camera
*/
public static void setSerial(String cameraName, String serial)
{ GuiModel.setSerial(cameraName,serial); }
/**
* Getter for the current iteration count
*
* @return String of the current iteration count.
*/
public static String getIterationCount()
{ return Integer.toString(GuiModel.getIterations()); }
/**
* Wrapper function around the GuiModel's calibrateCameras function
*/
public static void calibrateCameras()
{ GuiModel.calibrateCameras(); }
/**
* Close function for the Model; used to end the program
*/
public static void closeModel() { GuiModel.close(); }
/**
* Function used to update the ImageView of the GUID
*
* @param cameraName Name of the camera the image is from
* @param fileURL The URL of the file to be shown
*/
public static void updateImage(String cameraName, String fileURL)
{
GuiView.getViewMap().get(cameraName).setImage(new Image(fileURL));
}
}

View file

@ -0,0 +1,312 @@
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.
*
* {@link GuiView}, {@link GuiController}, and GuiModel versions are tied together, and are referred to collectively as Gui.
*
* @author Blizzard Finnegan
* @version 0.2.0, 06 Feb, 2023
*/
public class GuiModel
{
/**
* Whether or not the backend is prepared to start running.
*/
private static boolean readyToRun = false;
/**
* The number of iterations.
*/
private static int iterationCount = 3;
/**
* The Lock object, used for multithreading of the testing function
*/
public static final Lock LOCK = new ReentrantLock();
/**
* The testing thread object
*/
private static Thread testingThread = new Thread();
/**
* The Movement Facade instance
*/
private static final MovementFacade fixture = new MovementFacade(LOCK);
/**
* The function called to define the GUI as ready to start testing.
*/
public static void ready() { readyToRun = true; GuiController.updateStart(); }
/**
* Getter for {@link #readyToRun}
*
* @return boolean of whether or not testing can be started
*/
public static boolean isReady() { return readyToRun; }
/**
* Setter for the number of iterations
*
* @param iterations The number of times to run the tests
*/
public static void setIterations(int iterations)
{
iterationCount = iterations;
GuiController.userUpdate("Iterations set to: " + iterationCount);
GuiController.updateIterations();
}
/**
* Getter for the number of iterations
*
* @return int of the number of iterations to be perfomed.
*/
public static int getIterations() { return iterationCount; }
/**
* Wrapper around the MovementFacade's testMotions function.
*
* Updates the GUI with whether the testing was successful.
*/
public static void testMovement()
{
GuiController.testingMotions();
boolean success = fixture.testMotions();
if(success) GuiController.testingMotionSuccessful();
else GuiController.testingMotionUnsuccessful("Unknown");
}
/**
* Getter for the list of cameras.
*
* @return List[String] of camera names.
*/
public static List<String> getCameras()
{ return new ArrayList<>(OpenCVFacade.getCameraNames()); }
/**
* Function that calls the OpenCVFacade image generation function.
* @return String url of the location of the new image
*/
public static String showImage(String cameraName)
{
return OpenCVFacade.showImage(cameraName, new Object());
}
/**
* Setter for a given camera's config value
*
* @param cameraName Name of the camera to be configured
* @param property Property to be changed
* @param value New value for the given property
*/
public static void setConfigVal(String cameraName, ConfigProperties property, double value)
{
ConfigFacade.setValue(cameraName,property,value);
GuiController.updateConfigValue(cameraName,property);
}
/**
* Getter for a given camera's config value, in String format
*
* @param cameraName Name of the camera to get the config value from
* @param property Property to get the value of
*
* @return String of the current value in the config
*/
public static String getConfigString(String cameraName, ConfigProperties property)
{ return Double.toString(ConfigFacade.getValue(cameraName,property)); }
/**
* Getter for a given camera's config value
*
* @param cameraName Name of the camera to get the config value from
* @param property Property to get the value of
*
* @return double of the current value in the config
*/
public static double getConfigValue(String cameraName, ConfigProperties property)
{ return ConfigFacade.getValue(cameraName,property); }
/**
* Wrapper function around the MovementFacade's pressButton function.
*/
public static void pressButton()
{ fixture.pressButton(); }
/**
* Function used to update whether or not cameras should be primed.
*/
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));
}
}
/**
* Wrapper function to enable all image processing.
*/
public static void enableProcessing()
{
for(String camera : getCameras())
{
ConfigFacade.setValue(camera,ConfigProperties.CROP, 1.0);
ConfigFacade.setValue(camera,ConfigProperties.THRESHOLD, 1.0);
}
}
/**
* Wrapper function to save the default config values.
*/
public static void saveDefaults()
{ ConfigFacade.saveDefaultConfig(); }
/**
* Save the current config, and ensure it is loaded properly.
*/
public static void save()
{ ConfigFacade.saveCurrentConfig(); ConfigFacade.loadConfig(); }
/**
* Toggles the threshold processing for the given camera.
*
* @param cameraName The name of the camera to be modified
*/
public static void toggleThreshold(String cameraName)
{
boolean old = (ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) == 0.0 );
ConfigFacade.setValue(cameraName,ConfigProperties.THRESHOLD,(old ? 1 : 0));
}
/**
* Toggles the cropping of the image for the given camera.
*
* @param cameraName The name of the camera to be modified
*/
public static void toggleCrop(String cameraName)
{
boolean old = (ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) == 0.0 );
ConfigFacade.setValue(cameraName,ConfigProperties.CROP,(old ? 1 : 0));
}
/**
* Function used to run all tests.
*
* Currently not working. Will need to rewrite.
*/
public static void runTests()
{
//testingThread = new Thread(() ->
//{
GuiController.startTests();
DataSaving.initWorkbook(ConfigFacade.getOutputSaveLocation(),OpenCVFacade.getCameraNames().size());
boolean prime = false;
List<String> cameraList = new ArrayList<>();
for(String cameraName : OpenCVFacade.getCameraNames())
{
if(ConfigFacade.getValue(cameraName,ConfigProperties.PRIME) != 0)
{
prime = true;
}
cameraList.add(cameraName);
}
fixture.iterationMovement(prime);
fixture.pressButton();
fixture.iterationMovement(prime);
Map<File,Double> resultMap = new HashMap<>();
Map<String,File> cameraToFile = new HashMap<>();
for(String cameraName : cameraList)
{
cameraToFile.put(cameraName,new File("/dev/null"));
}
for(int i = 0; i < iterationCount; i++)
{
while(!LOCK.tryLock()) {}
fixture.iterationMovement(prime);
LOCK.unlock();
for(String cameraName : cameraList)
{
while(!LOCK.tryLock()) {}
File file = OpenCVFacade.completeProcess(cameraName);
GuiController.updateImage(cameraName,file.getPath());
LOCK.unlock();
while(!LOCK.tryLock()) {}
cameraToFile.replace(cameraName,file);
LOCK.unlock();
}
LOCK.unlock();
for(String cameraName : cameraList)
{
while(!LOCK.tryLock()) {}
File file = cameraToFile.get(cameraName);
LOCK.unlock();
while(!LOCK.tryLock()) {}
Double result = TesseractFacade.imageToDouble(file);
LOCK.unlock();
while(!LOCK.tryLock()) {}
resultMap.put(file,result);
LOCK.unlock();
while(!LOCK.tryLock()) {}
ErrorLogging.logError("DEBUG: Tesseract final output: " + result);
LOCK.unlock();
}
while(!LOCK.tryLock()) {}
DataSaving.writeValues(i,resultMap,cameraToFile);
LOCK.unlock();
GuiController.runningUpdate(i);
}
//println("=======================================");
ErrorLogging.logError("Testing complete!");
//});
//testingThread.run();
}
/**
* Wrapper function to close everything.
*/
public static void close()
{
ErrorLogging.logError("DEBUG: PROGRAM CLOSING.");
fixture.closeGPIO();
ErrorLogging.logError("DEBUG: END OF PROGRAM.");
ErrorLogging.closeLogs();
}
/**
* Function used to interrupt the testing thread.
*
* As of Gui 0.2.0, this does not work properly.
*/
public static void interruptTesting() { testingThread.interrupt(); }
/**
* Function to set the serial number for a given camera
*
* @param cameraName name of the camera to be modified
* @param serial serial number to be set
*/
public static void setSerial(String cameraName, String serial)
{ ConfigFacade.setSerial(cameraName,serial); }
/**
* Function to force fixture down before starting to calibrate the cameras.
*/
public static void calibrateCameras()
{ fixture.goDown(); }
}

View file

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

View file

@ -0,0 +1,916 @@
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.Image;
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.
*
* GuiView, {@link GuiController}, and {@link GuiModel} versions are tied together, and are referred to collectively as Gui.
*
* @author Blizzard Finnegan
* @version 0.2.0, 06 Feb, 2023
*/
public class GuiView extends Application
{
/**
* Scene used for the Main Menu.
*/
public static final Scene MAIN_MENU;
/**
* The base Node object for the Main menu; used to define window borders.
*/
private static final AnchorPane MAIN_ANCHOR;
/**
* The Node object within the {@link #MAIN_ANCHOR}, where all portions of the main menu are stored.
*/
private static final Pane MAIN_PANE;
/**
* Scene used for the camera configuration menu
*/
public static final Scene CAMERA_MENU;
/**
* The base Node object for the camera config menu; used to define window borders.
*/
private static final AnchorPane CAMERA_ANCHOR;
/**
* The node object within the {@link #CAMERA_ANCHOR}, where all portions of the camera config menu are stored.
*/
private static final Pane CAMERA_PANE;
/**
* An easily-accessible map of text fields used in the Camera config menu.
* The outer map's keys are the respective cameras, with the inner map being config properties and the associated text field.
*/
private static final Map<String,Map<ConfigProperties,TextField>> uiFields = new HashMap<>();
/**
* An easily-accessible location for the user-feedback Text object.
*/
private static Text userFeedback;
/**
* An easily accessible location for the TextField the user uses to set the current amount of iterations.
*/
private static TextField iterationField;
/**
* Easily-accessible Button object for the start button.
*/
private static Button startButton;
/*
* Easily-accessible Button object for the stop button.
*/
//private static Button stopButton;
/**
* The main Stage object, used in the GUI.
*
* The Stage object is analogous to the window generated.
*/
private static Stage STAGE;
/**
* Value used for spacing within VBoxes and HBoxes
*/
private static double INTERNAL_SPACING = 5.0;
/**
* Value used for window border spacing
*/
private static double EXTERNAL_SPACING = 10.0;
/**
* Map used to store ImageViews
*/
private static Map<String,ImageView> viewMap = new HashMap<>();
/**
* The wrapper function to spawn a new JavaFX Stage.
*/
public static void main(String[] args) { launch(args); }
/**
* Initialiser for the static objects.
*/
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();
//Set the window border
AnchorPane.setTopAnchor(MAIN_PANE,EXTERNAL_SPACING);
AnchorPane.setLeftAnchor(MAIN_PANE,EXTERNAL_SPACING);
AnchorPane.setRightAnchor(MAIN_PANE,EXTERNAL_SPACING);
AnchorPane.setBottomAnchor(MAIN_PANE,EXTERNAL_SPACING);
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();
//Set the window border
AnchorPane.setTopAnchor(CAMERA_PANE,EXTERNAL_SPACING);
AnchorPane.setLeftAnchor(CAMERA_PANE,EXTERNAL_SPACING);
AnchorPane.setRightAnchor(CAMERA_PANE,EXTERNAL_SPACING);
AnchorPane.setBottomAnchor(CAMERA_PANE,EXTERNAL_SPACING);
CAMERA_ANCHOR.getChildren().add(CAMERA_PANE);
CAMERA_MENU = new Scene(CAMERA_ANCHOR);
//Initialise the camera fields map and imageview map
for(String camera : GuiModel.getCameras())
{
uiFields.put(camera, new HashMap<>());
ImageView view = new ImageView();
view.setId(camera + "-view");
view.setFitWidth(GuiController.getConfigValue(camera,ConfigProperties.CROP_W));
view.setFitHeight(GuiController.getConfigValue(camera,ConfigProperties.CROP_H));
viewMap.put(camera, view);
}
STAGE.setOnCloseRequest( (event) -> GuiController.closeModel() );
}
@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.");
}
/**
* Camera Configuration Menu builder function.
*
* Creates a {@link VBox}, creates a {@link #cameraSetup(String)} object, and adds a separator between each camera.
* Finally, sets the created VBox to be the child of the {@link #CAMERA_PANE}, so it can be shown.
*/
private static void cameraMenuBuilder()
{
VBox layout = new VBox();
layout.setSpacing(INTERNAL_SPACING);
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(cameraSection(cameraName));
index++;
}
layout.getChildren().add(cameraMenuButtons());
CAMERA_PANE.getChildren().add(layout);
}
/**
* Main Menu builder function.
*
* Creates a VBox, fills it with the {@link #topHalf()}, a {@link Separator}, and the {@link #bottomHalf()}
* Finally, sets the created VBox to be the child of the {@link #MAIN_PANE}, so it can be shown.
*/
private static void mainMenuBuilder()
{
VBox layout = new VBox();
layout.getChildren().addAll(topHalf(),
new Separator(Orientation.HORIZONTAL),
bottomHalf());
MAIN_PANE.getChildren().add(layout);
}
/**
* Builder for the top half of the main menu.
*
* Creates a VBox, fills it with the {@link #topButtons()}, a {@link Separator}, the {@link #setupSection()},
* the {@link #primeCheckbox()}, and the {@link #testFeedback()}.
*
* @return VBox described above
*/
private static VBox topHalf()
{
VBox output = new VBox();
output.setSpacing(INTERNAL_SPACING);
output.getChildren().addAll(topButtons(),
new Separator(Orientation.HORIZONTAL),
setupSection(),
primeCheckbox(),
testFeedback());
return output;
}
/**
* Builder for the priming section of the main menu.
*
* Builds a pre-defined checkbox for setting whether the DUTs should be primed.
*
* @return CheckBox with a preset Tooltip, Id, and Listener.
*/
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.setSelected(true);
output.setId("primeCheckbox");
output.selectedProperty().addListener(
(obeservableValue, oldValue, newValue) ->
{
GuiController.updatePrime();
});
return output;
}
/**
* Builder for the user feedback section of the main menu.
*
* Creates an HBox, fills it with a {@link Label} and a {@link Text} used for communicating
* program status.
*
* @return HBox defined above
*/
private static HBox testFeedback()
{
HBox output = new HBox();
output.setSpacing(INTERNAL_SPACING);
Label textboxLabel = new Label("Test feedback: ");
Text textbox = new Text("Awaiting input...");
userFeedback = textbox;
textbox.setId("testOutputToUser");
output.getChildren().addAll(textboxLabel,textbox);
return output;
}
/**
* Builder function for the iteration count user input.
*
* Creates an HBox, filled with a Label and a TextField for user input.
* This TextField is used for setting the number of iterations to complete.
*
* @return HBox defined above
*/
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;
}
/**
* Builder function for the top buttons of the Main Menu.
*
* Creates an HBox for the top buttons, then fills it with a
* - start Button
* - Runs the tests. As of Gui 0.2.0, this only partially runs the first portion of the test.
* - stop Button
* - Intended to stop the test. As of Gui 0.2.0, this has not yet been properly implemented.
* - calibrate cameras Button
* - Changes Scene to {@link #CAMERA_MENU}, allowing for camera setup.
* - test movement Button
* - Tests the movement of the fixture, informs the user of the test's success/failure
* - close Button
* - Closes the window, and the program. Note that as of Gui 0.2.0, this errors out the JVM
*
* @return HBox containing the above-listed buttons.
*/
private static HBox topButtons()
{
//Initial HBox creation
HBox topButtons = new HBox();
topButtons.setSpacing(INTERNAL_SPACING);
topButtons.setAlignment(Pos.CENTER);
topButtons.setMinWidth(Region.USE_COMPUTED_SIZE);
topButtons.setMinHeight(Region.USE_COMPUTED_SIZE);
//Start button creation
final Button START = buttonBuilder("Start",true);
startButton = START;
//Stop button created early, as it is affected by Start, and must be passed in
final Button STOP = buttonBuilder("Stop",true);
//Start button action and tooltip setting.
START.setOnAction( (event) ->
{
START.setDisable(true);
STOP.setDisable(false);
GuiController.runTests();
});
START.setTooltip(new Tooltip("Configure cameras to start the program."));
//Stop button action and tooltip setting.
STOP.setOnAction( (event) ->
{
GuiController.interruptTests();
START.setDisable(false);
STOP.setDisable(true);
});
STOP.setTooltip(new Tooltip("Pauses current iteration."));
//Calibrate Cameras button creation
Button calibrateCamera = buttonBuilder("Calibrate Cameras",false);
calibrateCamera.setOnAction(
(event) ->
{
GuiController.calibrateCameras();
STAGE.setScene(CAMERA_MENU);
});
//Test Movement button creation
Button testMovement = buttonBuilder("Test Movement",false);
testMovement.setOnAction( (event) -> GuiController.testMotions() );
//Close button creation
Button cancel = buttonBuilder("Close",false);
cancel.setOnAction( (event) ->
{
GuiController.closeModel();
STAGE.close();
});
//Put the above buttons into the HBox
topButtons.getChildren().addAll(START,
STOP,
calibrateCamera,
testMovement,
cancel);
return topButtons;
}
/**
* Builder function for the bottom half of the main menu.
*
* Creates an HBox, with however many cameras exist, and their associated {@link #camera(String)} views.
*
* @return Hbox described above
*/
private static HBox bottomHalf()
{
HBox output = new HBox();
output.setAlignment(Pos.CENTER);
output.setSpacing(INTERNAL_SPACING);
int index = 0;
for(String camera : GuiModel.getCameras())
{
if(index != 0) output.getChildren().add(new Separator(Orientation.VERTICAL));
output.getChildren().add(camera(camera));
index++;
}
return output;
}
/**
* Builder for a camera view for the main menu.
*
* Creates a VBox, containing:
* - {@link #cameraHeader(String)}
* - HBox with a Label and TextField for the user to set a DUT's serial number.
* - {@link #cameraView(String)}
*
* @param cameraName The name of the camera to be attached to.
*
* @return VBox described above
*/
private static VBox camera(String cameraName)
{
VBox output = new VBox();
output.setAlignment(Pos.CENTER_LEFT);
output.setSpacing(INTERNAL_SPACING);
HBox serialNumber = userTextField("DUT Serial Number:","","Enter the serial number for the device under test.");
TextField field = null;
for(Node child : serialNumber.getChildren())
{
if(child instanceof TextField)
{
field = (TextField)child;
break;
}
}
field.setId("serial" + cameraName);
field.textProperty().addListener(
(observable, oldValue, newValue) -> GuiController.setSerial(cameraName, newValue));
output.getChildren().addAll(cameraHeader(cameraName),
serialNumber,
cameraView(cameraName));
return output;
}
/**
* Builder for the camera header, for the main menu.
*
* Creates an HBox, containing a Label with the camera's name, and a checkbox to mark whether it is active.
* As of Gui 0.2.0, the checkbox does not work properly.
*
* @param cameraName The name of the camera being accessed.
*
* @return HBox described above.
*/
private static HBox cameraHeader(String cameraName)
{
HBox output = new HBox();
output.setSpacing(INTERNAL_SPACING);
output.setAlignment(Pos.CENTER);
Label label = new Label("Camera: " + cameraName);
CheckBox checkBox = new CheckBox("Active");
checkBox.setSelected(true);
checkBox.setOnAction( (event) -> {/*implement*/});
checkBox.setId(cameraName.toLowerCase());
output.getChildren().addAll(label,
checkBox);
return output;
}
/**
* Builder for the camera view, used in the main menu.
*
* Creates an HBox, containing:
* - A Label for defining what the following label means (OCR Read:)
* - A Label for showing what the OCR reading is.
* - As of Gui 0.2.0, this has not been implemented
* - An ImageView object for showing the final image
* - As of Gui 0.2.0, this has not been implemented
* @param cameraName Name of the camera being accessed
*
* @return HBox described above.
*/
private static HBox cameraView(String cameraName)
{
HBox output = new HBox();
output.setSpacing(INTERNAL_SPACING);
output.setAlignment(Pos.CENTER_LEFT);
Label label = new Label("OCR Read:");
Label ocrRead = new Label("[ ]");
ocrRead.setId("cameraOCR-" + cameraName);
ImageView imageView = viewMap.get(cameraName);
//imageView.setImage(new Image(GuiController.showImage(cameraName)));
output.getChildren().addAll(label,
ocrRead,
imageView);
return output;
}
/**
* Builder function for the user-editable section in the camera config menu.
*
* Creates a VBox, containing:
* - A Label (used for a section header)
* - A series of CheckBoxes used to define whether to crop and/or threshold the image
* - An HBox of inputs, used to define cropping values. (Defined by {@link #cropInputs(String)})
* - An HBox of inputs, used to define the threshold value, and how many images to compose together (Defined by {@link #miscInputs(String)})
*
* @param cameraName The name of the camera being modified
*
* @return The VBox described above
*/
private static VBox cameraSetup(String cameraName)
{
VBox output = new VBox();
output.setSpacing(INTERNAL_SPACING);
output.setAlignment(Pos.CENTER_LEFT);
Label sectionHeader = new Label("Camera: " + cameraName);
output.getChildren().addAll(sectionHeader,
processingInputs(cameraName),
cropInputs(cameraName),
miscInputs(cameraName));
return output;
}
/**
* Builder function for a complete section in the camera config menu.
*
* Creates an HBox, containing:
* - A VBox, created by {@link #cameraSetup(String)}
* - An ImageView, which will be used to show the image to the user
*/
private static HBox cameraSection(String cameraName)
{
HBox output = new HBox();
output.setSpacing(INTERNAL_SPACING);
output.setAlignment(Pos.CENTER_LEFT);
output.getChildren().add(cameraSetup(cameraName));
ImageView imageView = viewMap.get(cameraName);
output.getChildren().add(imageView);
return output;
}
/**
* Builder for the processing section of the {@link #cameraSetup(String)}, used in the Camera Config section.
*
* Creates an HBox containing:
* - A Button for creating a temporary preview
* - A CheckBox to toggle the cropping of the image
* - A CheckBox to toggle the thresholding of the image
*
* @param cameraName The name of the camera being modified
*
* @return HBox, as described above
*/
private static HBox processingInputs(String cameraName)
{
HBox output = new HBox();
output.setSpacing(INTERNAL_SPACING);
output.setAlignment(Pos.CENTER_LEFT);
//Preview button generation
Button preview = buttonBuilder("Preview");
preview.setId("previewButton-" + cameraName);
preview.setOnAction( (event) ->
{
GuiController.pressButton();
try{ Thread.sleep(2000); } catch(Exception e){ ErrorLogging.logError(e); }
String imageURL = GuiController.showImage(cameraName);
viewMap.get(cameraName).setImage(new Image(imageURL));
});
//Crop image toggle checkbox creation
CheckBox cropPreview = new CheckBox("Crop preview");
cropPreview.setSelected(true);
cropPreview.setId("cropToggle-" + cameraName);
cropPreview.selectedProperty().addListener((obeservableValue, oldValue, newValue) ->
GuiController.toggleCrop(cameraName));
cropPreview.setOnAction( (event) -> GuiController.toggleCrop(cameraName) );
//Threshold image toggle switch creation
CheckBox thresholdPreview = new CheckBox("Threshold preview");
thresholdPreview.setSelected(true);
thresholdPreview.selectedProperty().addListener((obeservableValue, oldValue, newValue) ->
GuiController.toggleThreshold(cameraName));
thresholdPreview.setId("thresholdToggle-" + cameraName);
thresholdPreview.setOnAction( (event) -> GuiController.toggleThreshold(cameraName) );
output.getChildren().addAll(preview,
cropPreview,
thresholdPreview);
return output;
}
/**
* Builder function for the crop values, stored within a {@link #cameraSetup(String)} in the Camera Config menu.
*
* Creates an HBox, containing:
* - A Label and TextField for each of the following:
* - Crop X
* - Crop Y
* - Crop Width
* - Crop Height
*
* @param cameraName The name of the camera being modified
*
* @return HBox, as defined above
*/
private static HBox cropInputs(String cameraName)
{
HBox output = new HBox();
output.setSpacing(INTERNAL_SPACING);
output.setAlignment(Pos.CENTER_LEFT);
HBox cropX = userTextField("X:",
GuiController.getConfigString(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);
HBox cropY = userTextField("Y:",
GuiController.getConfigString(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);
HBox cropW = userTextField("Width:",
GuiController.getConfigString(cameraName,ConfigProperties.CROP_W),
"Width, in pixels, of the newly cropped image. Only accepts whole numbers.");
textFieldSetup(cropW,ConfigProperties.CROP_W,cameraName);
HBox cropH = userTextField("Height:",
GuiController.getConfigString(cameraName,ConfigProperties.CROP_H),
"Height, in pixels, of the newly cropped image. Only accepts whole numbers.");
textFieldSetup(cropH, ConfigProperties.CROP_H, cameraName);
output.getChildren().addAll(cropX,
cropY,
cropW,
cropH);
return output;
}
/**
* Builder function for the other modifiable values for the {@link #cameraSetup(String)} portion of the camera config menu.
*
* Creates an HBox, containing a Label and TextField for:
* - threshold value
* - number of composite frames
*
* @param cameraName The name of the camera being configured
*
* @return HBox, defined above
*/
private static HBox miscInputs(String cameraName)
{
HBox output = new HBox();
output.setSpacing(INTERNAL_SPACING);
output.setAlignment(Pos.CENTER_LEFT);
HBox thresholdValue = userTextField("Threshold Value:",
GuiController.getConfigString(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);
HBox compositeFrames = userTextField("Composite Frames:",
GuiController.getConfigString(cameraName,ConfigProperties.COMPOSITE_FRAMES),
"Number of frames to bitwise-and together.");
textFieldSetup(compositeFrames,ConfigProperties.COMPOSITE_FRAMES,cameraName);
output.getChildren().addAll(thresholdValue,
compositeFrames);
return output;
}
/**
* Builder function for the final buttons in the Camera Config menu.
*
* Creates an HBox, containing:
* - Save Defaults button
* - Save Current button
* - Save and Close button
* - Close without Saving button
*
* @return HBox, as described above
*/
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();
GuiController.updateStart();
});
Button save = buttonBuilder("Save");
save.setOnAction( (event) ->
{
GuiController.save();
GuiController.updateStart();
});
Button saveClose = buttonBuilder("Save and Close");
saveClose.setOnAction( (event) ->
{
GuiController.saveClose();
GuiController.updateStart();
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;
}
/**
* Modifying function for a text field.
*
* Brings in an HBox, stores it in the Map with the correct {@link ConfigProperties} value.
* Also sets the action of the text box to be correct.
*
* @param hbox The HBox containing the TextField to be modified and remembered
* @param property The property to be associated with the TextField
* @param cameraName The name of the camera to be associated with the TextField
*/
private static void textFieldSetup(HBox hbox, ConfigProperties property, String cameraName)
{
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;
}
});
}
/**
* Builder function for a button.
*
* Creates a button with a set ID, name, and disabled/enables status.
*
* @param name The name of the new button
* @param disabled Whether or not the button should be disabled on startup
*
* @return Button , with a preset ID, name, and optionally disabled.
*/
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;
}
/**
* Builder function for an enabled button.
*
* Creates a button with a set ID and name.
*
* @param name The name of the new button
* @return Button , with a preset ID and name.
*/
private static Button buttonBuilder(String name)
{ return buttonBuilder(name,false); }
/**
* Builder function for a user-interactable TextField, with built-in label.
*
* Creates an HBox, with a Label for the TextField, along with the TextField itself.
*
* @param prompt The name used for the Label
* @param baseValue The default value used in the TextField
* @param description The Tooltip of the TextField/Label
*
* @return Hbox, described above
*/
private static HBox userTextField(String prompt, String baseValue, String description)
{
HBox output = new HBox();
output.setSpacing(INTERNAL_SPACING);
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;
}
/**
* Getter for a given TextField, associated with a camera and property.
*
* @param cameraName The name of the camera the TextField is associated with
* @param property The name of the property the TextField is associated with
*
* @return TextField
*/
public static TextField getField(String cameraName, ConfigProperties property)
{ return uiFields.get(cameraName).get(property); }
/**
* Getter for the Start button.
*
* @return Button used for starting the tests.
*/
public static Button getStart()
{ return startButton; }
/**
* Getter for the user feedback Text object.
*
* @return Text object used for communicating statuses to the user.
*/
public static Text getFeedbackText()
{ return userFeedback; }
/**
* Getter for the TextField used by the user to set the number of iterations.
*
* @return TextField
*/
public static TextField getIterationField()
{ return iterationField; }
/**
* Getter for the ImageView Map.
*
* @return Map with keys of the names of cameras, and the value of the corresponding imageview
*/
public static Map<String,ImageView> getViewMap()
{ return viewMap; }
/**
* Updater for a given camera's ImageView width
*
* @param cameraName Name of the camera being updated
*/
public static void updateImageViewWidth(String cameraName)
{
viewMap.get(cameraName).setFitWidth(GuiController.getConfigValue(cameraName,ConfigProperties.CROP_W));
}
/**
* Updater for a given camera's ImageView height
*
* @param cameraName Name of the camera being updated
*/
public static void updateImageViewHeight(String cameraName)
{
viewMap.get(cameraName).setFitHeight(GuiController.getConfigValue(cameraName,ConfigProperties.CROP_H));
}
}