AppBlock example — run a Fiji macro
FijiMacro (block.py) sends an image to Fiji (ImageJ), runs the
gaussian_blur.ijm macro on it, and reads the result back.
AppBlock turns any file-based external tool into a workflow step — headless
(as here) or interactive, where the user edits the data in the app's GUI and the
block waits for them to finish.
You declare, the base class runs
The striking thing about AppBlock is how little you write. You override a few
class attributes and do not write run():
| You set | What it does |
|---|---|
app_command |
The program to launch (a list of command parts) |
input_ports |
What data the block accepts |
output_ports |
What it returns |
output_patterns |
Glob(s) for the files the app will produce (["*.tif"]) |
The inherited run() then does all of this for you:
- Writes inputs into an exchange folder:
<exchange>/inputs/. - Launches the app, appending the exchange-folder path as the final command argument and validating the command first (no shell injection).
- Watches
<exchange>/outputs/for files matchingoutput_patterns, waiting until they stop changing (so half-written files are never read). - Collects those files back into your output ports.
The data flow, concretely
<exchange>/
inputs/ <- your "image" input lands here
outputs/ <- the macro writes here; SciStudio watches for *.tif
The macro learns the exchange path from getArgument() and reads/writes the two
sub-folders — see gaussian_blur.ijm. That contract (inputs
in, outputs out, exchange path as the last argument) is the same for any app;
only the macro/script changes.
Why Artifact ports here
This example uses Artifact (an opaque file) for both ports because Fiji reads
and writes TIFF natively, so passing the image file straight through is the
simplest, most honest thing. If your input is instead an Array (a SciStudio
image), the bridge serialises it to the exchange folder as a NumPy .npy file —
fine for an app that reads .npy, but Fiji would need a .npy reader plugin.
For Fiji, keep the data as image files (Artifact).
Interactive use
Drop --headless from app_command and Fiji opens its GUI. The user does the
work by hand and saves the result into outputs/; the block waits. If the user
closes the app without producing output, the base run() raises
BlockCancelledByAppError and the run is marked cancelled rather than failed.
What to look up
AppBlock, FileExchangeBridge, FileWatcher, validate_app_command, and the
two exceptions live in scistudio.blocks.app — see the API reference for the
full contract.