JIPipe Java extensions can define new data types.
Custom JIPipe data types must inherit from JIPipeData. Please implement all required functions as suggested by the interface if it is a non-abstract or interface data type.
JIPipeData classes have various requirements regarding annotations and static functions.
Required: annotations
- @JIPipeDocumentation provides a name and description
- @JIPipeDataStorageDocumentation explains the structure of the storage folder. This documentation must include a human-readable description of the items contained in the storage directory, as well as a URL to a JSON Schema that describes the structure of the directory, including any nested JSON data (if possible)
Examples for storage documentations
- Single text file (
*.txt
): https://jipipe.org/schemas/datatypes/string-data.schema.json - One of multiple image files: https://jipipe.org/schemas/datatypes/imageplus-data.schema.json
- Single JSON file with known structure: https://jipipe.org/schemas/datatypes/path-data.schema.json
- Multiple image files plus JSON metadata with known structure: https://jipipe.org/schemas/datatypes/imageplus-fft-data.schema.json
- Multiple CSV files plus JSON metadata with known structure: https://jipipe.org/schemas/datatypes/plot-data.schema.json
Tip: Feel free to re-use schemata for different types if the structure matches.
Required: static import function
JIPipe reads the data type by calling a static function importData(JIPipeReadDataStorage, JIPipeProgressInfo)
, which must be always present in any data type class.
To allow future abstraction of data storage, JIPipe provides a JIPipeReadDataStorage instance that contains methods for storing data into arbitrary locations. The current only implementation is JIPipeFileSystemReadDataStorage that encapsulates an empty and unique directory where data can be written.
You can use the getFileSystemPath()
function to get the absolute path to this directory. As a file system mapping is already present, this has no additional cost.
We recommend to not make use of this function, as non-filesystem backends might create a full copy of all data in a temporary file cache for reading, slowing down the data transfer. We recommend to utilize the provided methods of the interface:
resolve
for navigating to sub directorieslist
for listing itemsisFile
/isDirectory
findFilesByExtension
to find files of interestreadJSON
/readText
/readBytes
to read dataopen
to create an InputStream for reading the data
Required: export function
On implementing JIPipeData interface, you will implement a method exportData(JIPipeWriteDataStorage, String name, boolean forceName, JIPipeProgressInfo)
As with reading data, the storage is encapsulated into a dedicated interface, here JIPipeWriteDataStorage. Here, you can again access the file system path via getFileSystemPath()
.
We again recommend to utilize non-filesystem functions to improve the performance of other backends:
resolve
for navigating to sub directorieslist
for listing itemswriteJSON
/writeText
/writeBytes
to write datawrite
to create an Output for writing the data
Additionally, a name parameter is present and contains information about possible file names. It can be ignored unless forceName
is true. The reason behind this is that then the name is pre-generated in a unique way for saving the data at a non-standard location (e.g. exporting the data). In such cases all file or folder names within the storage path should contain the name in some way, even if the import function cannot load the exported data anymore.
Optional: display, string, and preview
There are two optional functions that you can override:
display()
shows the data in ImageJ, JIPipe, or any other GUIpreview()
generates a GUI component that acts as thumbnail/preview of the contained data- Override
toString()
to show a short summary of the data in cache browsers
Registering the data type
Register the data type in your extension service.
@Plugin(type = JIPipeJavaExtension.class) public class MyExtension extends JIPipeDefaultJavaExtension { // ... See previous tutorial for other methods @Override public void register() { // The two null parameters will be handled in the next tutorials // You can leave them null if you want. This is valid. registerDataType("my-data", MyData.class, RESOURCES.getIcon16("my-data-type.png")); } }
Example: data type
// Documentation for this data type (for the GUI) @JIPipeDocumentation(name = "My data", description = "This is some data") // Human-readable description plus a JSON Schema that describe the structure of a storage folder @JIPipeStorageDocumentation(humanReadableDescription = "Contains exactly one *.json file that stores the string value.", jsonSchemaURL = "https://jipipe.org/schemas/datatypes/jipipe-json-data.schema.json") // You can use @JIPipeHidden to hide this data from data type list UIs public class MyData implements JIPipeData { String value; public MyData() { } // Constructor that initializes the data public MyData(String value) { this.value = value; } @JsonGetter("value") public String getValue() { return value; } @JsonSetter("value") public String setValue(String value) { this.value = value; } // This should return a deep copy @Override public JIPipeData duplicate() { return new MyData(value); } // The display method is optional, but recommended @Override public void display(String displayName, JIPipeWorkbench workbench) { JIPipeTextEditor editor = JIPipeTextEditor.openInNewTab(workbench, displayName); editor.setMimeType(getMimeType()); editor.setText(data); } // The preview method is optional, but recommended for many cases // The width and height are guidelines you should adhere to (especially the height) @Override public Component preview(int width, int height) { // This example would overlap with toString() return new JLabel(value); } // Do not forget to override this @Override public String toString() { return StringUtils.orElse(value, ""); } @Override public void exportData(JIPipeWriteDataStorage storage, String name, boolean forceName, JIPipeProgressInfo progress) { // This uses the helper function of JIPipeWriteDataStorage storage.writeJSON(name + ".json", this); // Alternative 1: If you require an OutputStream try(OutputStream stream = storage.write(name + ".json")) { // Write to OutputStream [...] } // Alternative 2: You can generate an absolute file path that can be used if no stream-based writing is available: Path outputPath = storage.getFileSystemPath().resolve(name + ".json"); // write to output path [...] // The storage backend will automatically ensure that the file is included } // Do not forget to add this method or JIPipe will refuse to start public static MyData importData(JIPipeReadDataStorage storage, JIPipeProgressInfo progress) { Path path = storage.findFileByExtension(".json").get(); return storage.readJSON(path, MyData.class); // Alternative options are equivalent to exportData() } }