-
Notifications
You must be signed in to change notification settings - Fork 5
13: How to write a JEX function
Copy the Template maven project that we have created for others to start from here where there are instructions on setting up the project in Eclipse. Within the project are example functions and a template function with examples of different parameter inputs that can be used.
See AdjustImage.java for a current template on how to write a JEX funciton. In general, we use annotation processing to mark these classes as JEX functions or, more precisely, as classes that implement the JEXPlugin interface. Annotations are anything with the "@" symbol preceding them. The four types of annotations we use are @Plugin, @InputMarker, @ParameterMarker, @OutputMarker. @Plugin provides information about the function's name, description, and the toolbox is should be placed in. @InputMarker's and @OutputMarkers define the default names and types of database object that are used as inputs and outputs to the function. @ParameterMarker's defines the name, description, order in the user interface, and type of "widget" that should be used to allow the user to enter a value for the parameter. The type of the parameter (i.e., a String, Boolean, or Number) is defined by the type of variable (e.g., "String pathToFile;")that is listed in the line directly below the annotation. This way of doing things provides a very explicit, compact, and simple way to define function inputs, outputs, and parameters and enables automatic generation of user interfaces for each function. Search other functions in folders adjacent to the AdjustImage.java file for examples on different widgets that are available. Bascially, there are single-line text boxes, dropdowns, check boxes, file-choosers, and multi-line text boxes/editors intended for entering scripting commands to pass to functions (e.g., for functions that leverage R or Octave).
Typically to develop such functions, it is easiest to just clone the repository and run JEX from source-code using a java IDE such as eclipse. We would suggest creating a folder adjacent to the folder used for the AdjustImage.java function or to add to one of the existing adjacent folders.
Packages and classes referred to in the template can be found at
https://github.com/jaywarrick/JEX
https://github.com/jaywarrick/JEX-CTCPlugins
The following is a template for coding a JEX Function.
Guidelines for structure/order of function code.
-
Import necessary classes
-
Use @Plugin annotation to describe the function
-
Define an empty constructor (necessary for instantiating new instances for threading capabilities).
-
Use @InputMarker annotations to mark JEXData variables as function inputs.
-
Use @ParameterMarker annotations to mark primitive variables (i.e., String, double, integer, boolean etc…) as variables to be defined by the user in the user interface. Examples of different types of parameter “types” or widget interfaces are provided as examples in the “Template.java” file listed below.
-
Use @OutputMarker annotations to mark JEXData variables as function outputs.
-
Override “getMaxThreads()” method to return the maximum number of possible threads for this function. If using a static variable to share information between different instances of JEXFunctions, this should return 1 to avoid unintended function behavior.
-
Define the “run” using the same signature for defining the actual operations to be performed on the inputs with the defined parameters for creating the defined outputs.
-
If possible, within the “run” method include the example if statement to check if the user has canceled the function to be able to quit early before completion of calculations.
-
If possible, within the “run” update the user interface as shown in the example to indicate the percent progress of the function.
-
Set the JEXData output of the function using the classes in the Database.DataWriters package
-
Place any additional methods needed to help simplify code in the “run” method near the end of the file.
See the JEXPlugin abstract class for other methods that can be overridden if necessary / desired such as those to initialize or finalize a function, or ticket (i.e., the group of functions of a single type run together either in parallel or in sequence depending on the value of "getMaxThreads").
// Define package name as "plugins" as show here
package function.plugin.mechanism;
// Import needed classes here
import java.util.TreeMap;
import jex.statics.JEXStatics;
import logs.Logs;
import org.scijava.plugin.Plugin;
import tables.DimensionMap;
import Database.DBObjects.JEXData;
import Database.DBObjects.JEXEntry;
import Database.DataReader.ImageReader;
import Database.DataWriter.ImageWriter;
import function.plugin.mechanism.InputMarker;
import function.plugin.mechanism.JEXPlugin;
import function.plugin.mechanism.MarkerConstants;
import function.plugin.mechanism.OutputMarker;
import function.plugin.mechanism.ParameterMarker;
// Specify plugin characteristics here
@Plugin(
type = JEXPlugin.class,
name="Template Function",
menuPath="Templates",
visible=true,
description="Example of a file to define a JEX function/plugin. Doesn't do anything but print the parameters provided by the user."
)
public class Template extends JEXPlugin {
// Define a constructor that takes no arguments.
public Template()
{}
/////////// Define Inputs here ///////////
@InputMarker(uiOrder=1, name="Image", type=MarkerConstants.TYPE_IMAGE, description="Image to be adjusted.", optional=false)
JEXData inputData;
/////////// Define Parameters here ///////////
@ParameterMarker(uiOrder=6, name="Checkbox", description="A simple checkbox for entering true/false variables.", ui=MarkerConstants.UI_CHECKBOX, defaultBoolean=true)
boolean checkbox;
@ParameterMarker(uiOrder=1, name="Value", description="This textbox is automatically parsed for conversion to whatever primitive type this annotation is associated with (e.g., String, double, int, etc).", ui=MarkerConstants.UI_TEXTFIELD, defaultText="0.0")
double value;
@ParameterMarker(uiOrder=2, name="Choice", description="A dropdown provides a set number of choices, and is used to assign a String value.", ui=MarkerConstants.UI_DROPDOWN, choices={"Choice 0", "Choice 1", "Choice 2"}, defaultChoice=0)
String choice;
@ParameterMarker(uiOrder=3, name="New Min", description="Opens a file chooser dialog box and returns the path String of the user choice (file or folder).", ui=MarkerConstants.UI_FILECHOOSER, defaultText="~/")
String path;
@ParameterMarker(uiOrder=4, name="Password", description="Passwords don't show in the user interface and are not saved anywhere by JEX (e.g., when you save a workflow). Useful for scripts that send emails or upload/download from storage sites.", ui=MarkerConstants.UI_PASSWORD, defaultText="")
String password;
@ParameterMarker(uiOrder=5, name="Script", description="The script interface provides a multi-line textbox to input code to pass along to your function providing greater flexibility beyond a simple text box.", ui=MarkerConstants.UI_SCRIPT, defaultText="# Replace with user code #")
String script;
/////////// Define Outputs here ///////////
// See Database.Definition.OutpuMarker for types of inputs that are supported (File, Image, Value, ROI...)
@OutputMarker(uiOrder=1, name="Output Image", type=MarkerConstants.TYPE_IMAGE, flavor="", description="The resultant adjusted image", enabled=true)
JEXData outputData;
// Define threading capability here (set to 1 if using non-final static variables shared between function instances).
@Override
public int getMaxThreads()
{
return 10;
}
// Code the actions of the plugin here using comments for significant sections of code to enhance readability as shown here
@Override
public boolean run(JEXEntry optionalEntry)
{
// Validate the input data
if(inputData == null || !inputData.getTypeName().getType().equals(JEXData.IMAGE))
{
return false;
}
// Run the function
// Read in the input with one of the many "reader" classes (see package "Database.DataReader")
TreeMap<DimensionMap,String> inputMap = ImageReader.readObjectToImagePathTable(inputData);
TreeMap<DimensionMap,String> outputMap = new TreeMap<DimensionMap,String>();
int count = 0, percentage = 0;
// Loop through the items in the n-Dimensional object
for (DimensionMap map : inputMap.keySet())
{
// Cancel the function as soon as possible if the user has hit the cancel button
// Perform this inside loops to check as often as possible.
if(this.isCanceled())
{
return false;
}
// Print the value contained in the input map.
Logs.log(inputMap.get(map), this);
// Print parameter values by calling a helper method
String pathToSave = printParameters(checkbox, value, choice, path, password, script);
// Store returned data
outputMap.put(map, pathToSave);
// Update the user interface with progress
count = count + 1;
percentage = (int) (100 * ((double) (count) / ((double) inputMap.size())));
JEXStatics.statusBar.setProgressPercentage(percentage);
}
if(outputMap.size() == 0)
{
return false;
}
// Set output (see package "Database.DataWriter" for other types of data that can be created)
this.outputData = ImageWriter.makeImageStackFromPaths("tempName", outputMap);
// Return status
return true;
}
// Helper functions should go below the run function
public String printParameters(boolean checkbox, double value, String choice, String path, String password, String script)
{
Logs.log(""+checkbox, this);
Logs.log(""+value, this);
Logs.log(""+choice, this);
Logs.log(""+path, this);
Logs.log(""+password, this);
Logs.log(""+script, this);
return(path);
}
}