Docs
Search…
Fiji Python Scripting
This tutorial describe how to create an APEER module based on an existing ImageJ / Fiji python script or how to easily adapt such a script for APEER.
In this step-by-step tutorial we will use a python script example as a template for creating an APEER module out of it. The core functions of this script are quite simple:
  • Read the image using the BioFormats plugin
  • Apply a filter to that image using the specified radius
  • Save the resulting image as OME-TIFF using BioFormats
  • Create some logs to provide some information about what is happening
As we will create a Fiji Python module in this tutorial make sure that you've read the What you'll need section and you have ...
  • ... a basic understanding what modules and workflows are
  • ... successfully registered an account on APEER
  • ... running a proper Python development area with the Anaconda Distribution
  • ... installed Visual Studio Code and Git
  • ... basic python scripting knowledge

Introduction to ImageJ/Fiji

How to start?

Before one can start coding it is important to understand a few basic things about scripting in Fiji. In order to be able to create an APEER module using Fiji one needs:
  • a docker container with the latest version of Fiji inside
  • a python script that can run in headless mode (i.e. the UI will not be shown), since APEER modules do not allow for any dynamically generated UI elements
  • typically such a script is using script parameters to define its inputs and when used directly from Fiji those will even automatically create an user interface
  • when running such a script in headless mode one has to pass those script parameters as command line arguments or ...
  • ... in case of creating an APEER module, those parameters will be provided by APEER by specifying the in- and outputs.
Fiji Script Parameters
Complete Fiji Script Example
Below you see a typical definition of those scripting parameters inside a Fiji python script. In this case the required inputs are:
  • FILENAME - allows to select the input file
  • FILTERTYPE - defines the filter type via a dropdown list
  • FILTER_RADIUS - defines the desired kernel size via a numeric control
  • HEADLESS - option to disable some part of the scripts when using it locally in headless mode
1
# @File(label = "Image File", persist=True) FILENAME
2
# @String(label = "Select Filter", choices={"NONE", "MEDIAN", "MIN", "MAX", "MEAN", "VARIANCE", "OPEN", "DESPECKLE"}, style="listBox", value="MEDIAN", persist=True) FILTERTYPE
3
# @Integer(label = "Filter Radius", value=5.0, persist=False) FILTER_RADIUS
4
# @Boolean(label = "Run in headless mode", value=False, persist=False) HEADLESS
5
# @OUTPUT String FILENAME
6
# @OUTPUT String FILTERTYPE
7
# @OUTPUT Integer FILTER_RADIUS
8
# @OUTPUT Boolean HEADLESS
Copied!
When running this script from your Fiji script editor one will see the following UI:
Fiji Script Parameter User Interface
1
# @File(label = "Image File", persist=True) FILENAME
2
# @String(label = "Select Filter", choices={"NONE", "MEDIAN", "MIN", "MAX", "MEAN", "VARIANCE", "OPEN", "DESPECKLE"}, style="listBox", value="MEDIAN", persist=True) FILTERTYPE
3
# @Integer(label = "Filter Radius", value=5.0, persist=False) FILTER_RADIUS
4
# @Boolean(label = "Run in headless mode", value=False, persist=False) HEADLESS
5
# @OUTPUT String FILENAME
6
# @OUTPUT String FILTERTYPE
7
# @OUTPUT Integer FILTER_RADIUS
8
# @OUTPUT Boolean HEADLESS
9
10
#@UIService uiService
11
#@LogService log
12
13
# required imports
14
import os
15
import json
16
from java.lang import Double, Integer
17
from ij import IJ, ImagePlus, ImageStack, Prefs
18
from ij.process import ImageProcessor, LUT
19
from ij.plugin.filter import RankFilters
20
from loci.plugins import BF
21
from loci.plugins.in import ImporterOptions
22
from loci.plugins import LociExporter
23
from loci.plugins.out import Exporter
24
from ij.io import FileSaver
25
import time
26
27
28
def apply_filter(imp,
29
radius=5,
30
filtertype='MEDIAN'):
31
32
# initialize filter
33
filter = RankFilters()
34
35
# create filter dictionary
36
filterdict = {}
37
filterdict['MEAN'] = RankFilters.MEAN
38
filterdict['MIN'] = RankFilters.MIN
39
filterdict['MAX'] = RankFilters.MAX
40
filterdict['MEDIAN'] = RankFilters.MEDIAN
41
filterdict['VARIANCE'] = RankFilters.VARIANCE
42
filterdict['OPEN'] = RankFilters.OPEN
43
filterdict['DESPECKLE'] = RankFilters.DESPECKLE
44
45
# get the stack and number of slices
46
stack = imp.getStack() # get the stack within the ImagePlus
47
nslices = stack.getSize() # get the number of slices
48
49
for index in range(1, nslices + 1):
50
# get the image processor
51
ip = stack.getProcessor(index)
52
# apply filter based on filtertype
53
filter.rank(ip, radius, filterdict[filtertype])
54
55
return imp
56
57
############################################################################
58
59
if not HEADLESS:
60
# clear the console automatically when not in headless mode
61
uiService.getDefaultUI().getConsolePane().clear()
62
63
64
def run(imagefile, useBF=True, series=0):
65
66
log.info('Image Filename : ' + imagefile)
67
68
if not useBF:
69
# using IJ static method
70
imp = IJ.openImage(imagefile)
71
72
if useBF:
73
74
# initialize the importer options
75
options = ImporterOptions()
76
options.setOpenAllSeries(True)
77
options.setShowOMEXML(False)
78
options.setConcatenate(True)
79
options.setAutoscale(True)
80
options.setId(imagefile)
81
82
# open the ImgPlus
83
imps = BF.openImagePlus(options)
84
imp = imps[series]
85
86
# apply the filter
87
if FILTERTYPE != 'NONE':
88
89
# apply filter
90
log.info('Apply Filter : ' + FILTERTYPE)
91
log.info('Filter Radius : ' + str(FILTER_RADIUS))
92
93
# apply the filter based on the choosen type
94
imp = apply_filter(imp,
95
radius=FILTER_RADIUS,
96
filtertype=FILTERTYPE)
97
98
if FILTERTYPE == 'NONE':
99
log.info('No filter selected. Do nothing.')
100
101
return imp
102
103
104
#########################################################################
105
106
# convert the filename into a string
107
IMAGEPATH = FILENAME.toString()
108
109
# suffix for the filename of the saved data
110
SUFFIX_FL = '_FILTERED'
111
SAVEFORMAT = 'ome.tiff'
112
113
log.info('Starting ...')
114
log.info('Filename : ' + IMAGEPATH)
115
log.info('Save Format used : ' + SAVEFORMAT)
116
log.info('------------ START IMAGE ANALYSIS ------------')
117
118
##############################################################
119
120
# define path for the output
121
outputimagepath = '/output/' + os.path.basename(IMAGEPATH)
122
basename = os.path.splitext(outputimagepath)[0]
123
124
# remove the extra .ome before reassembling the filename
125
if basename[-4:] == '.ome':
126
basename = basename[:-4]
127
log.info('New basename for output :' + basename)
128
129
# save processed image
130
outputimagepath = basename + SUFFIX_FL + '.' + SAVEFORMAT
131
132
############# RUN MAIN IMAGE ANALYSIS PIPELINE ##########
133
134
# get the starting time of processing pipeline
135
start = time.clock()
136
137
# run image analysis pipeline
138
filtered_image = run(IMAGEPATH,
139
useBF=True,
140
series=0)
141
142
# get time at the end and calc duration of processing
143
end = time.clock()
144
log.info('Duration of whole Processing : ' + str(end - start))
145
146
###########################################################
147
148
start = time.clock()
149
150
# create the argument string for the BioFormats Exporter and save as OME.TIFF
151
paramstring = "outfile=" + outputimagepath + " " + "windowless=true compression=Uncompressed saveROI=false"
152
plugin = LociExporter()
153
plugin.arg = paramstring
154
exporter = Exporter(plugin, filtered_image)
155
exporter.run()
156
157
# get time at the end and calc duration of processing
158
end = time.clock()
159
log.info('Duration of saving as OME-TIFF : ' + str(end - start))
160
161
# show the image
162
filtered_image.show()
163
164
# finish
165
log.info('Done.')
Copied!

How will our Project Folder look like?

Below you see an overview of the files we will generate for this tutorial. All these files except the Dockerfile and the module_specification.json will be utilized within the Docker Container.
1
├── Dockerfile (the file neccessary to generate the Docker container)
2
├── module_specification.json (module input/output and GUI specification for the WFE)
3
├── my_fijipyscript.py (the actual Fiji script)
4
├── start.sh (shell script to run Fiji script from the command line)
5
└── wfe.env (file for defining environment variables for local test runs)
Copied!
More information and additional examples related to ImageJ/Fiji and Python can be found here:

Step #0: Prerequisites - How to create a Docker Image with Fiji inside

In order to create APEER modules based on Fiji it is useful to create a docker image with Fiji first which will be referenced later when creating the actual module.
Straight Forward (fast lane)
Step by step (for interested coders)
To keep things simple team APEER already created such an image with the name czsip/fiji_linux64_baseimage:latest
One can find it here: Team APEER - Fiji Docker Image
For those who are interested in how to create such an image the required steps are described below.
  • Download latest Fiji for Linux and unpack it.
  • There should be a folder called Fiji.app inside the same folder as the Dockerfile.
  • Add extra script(s) one might want to use like fijipytools below (optional)
  • Make sure docker is running and open a terminal.
1
docker login
2
user:yourusername
3
pwd:yourpwd
Copied!
The Dockerfile for updating and creating Fiji docker image looks like this:
1
# Pull ubuntu
2
FROM ubuntu:latest
3
4
# copy Fiji
5
COPY ./Fiji.app /Fiji.app
6
7
# copy other fiji scripts or files
8
COPY ./fijipytools.py /Fiji.app/scripts
9
10
# activate or deactivate Fiji-Update sites as needed (just a few examples)
11
RUN ./Fiji.app/ImageJ-linux64 --update add-update-site BAR http://sites.imagej.net/Tiago/
12
#RUN ./Fiji.app/ImageJ-linux64 --update add-update-site BASIC http://sites.imagej.net/BaSiC/
13
#RUN ./Fiji.app/ImageJ-linux64 --update add-update-site BioVoxxel http://sites.imagej.net/BioVoxxel/
14
#RUN ./Fiji.app/ImageJ-linux64 --update add-update-site CMP-BIAtools http://sites.imagej.net/CMP-BIA/
15
RUN ./Fiji.app/ImageJ-linux64 --update add-update-site IJPB-plugins http://sites.imagej.net/IJPB-plugins/
16
RUN ./Fiji.app/ImageJ-linux64 --update add-update-site ImageScience http://sites.imagej.net/ImageScience/
17
RUN ./Fiji.app/ImageJ-linux64 --update add-update-site IMCFUniBasel http://sites.imagej.net/UniBas-IMCF/
18
RUN ./Fiji.app/ImageJ-linux64 --update add-update-site PTBIOP http://biop.epfl.ch/Fiji-Update
19
RUN ./Fiji.app/ImageJ-linux64 --ij2 --headless --update update
20
RUN ./Fiji.app/ImageJ-linux64 --ij2 --headless
Copied!
When the login was successful start the build and push the new image.
1
docker build -t username/fiji_linux64_baseimage:tag .
2
3
docker push username/fiji_linux64_baseimage:tag
Copied!
That's it. Now this image can be used later when creating the actual module

Step #1: Define inputs and outputs

There is no specific order in which the files from above have to be created, so feel free to start with the file you prefer. We will start with the module_specification.json file
In brief you need to supply a JSON file that specifies your inputs, outputs and if necessary how the UI for the module looks like in case the user can interact with the module. In our case this specification file looks like this:
module_specification.json
1
{
2
"spec": {
3
"inputs": {
4
"IMAGEPATH": {
5
"type:file": {
6
"format": [
7
"czi",
8
"tiff",
9
"png",
10
"jpeg",
11
"ome-tiff"
12
]
13
}
14
},
15
"FILTERTYPE": {
16
"type:choice_single": [
17
"NONE",
18
"MEAN",
19
"MIN",
20
"MAX",
21
"MEDIAN",
22
"VARIANCE",
23
"OPEN",
24
"DESPECKLE"
25
],
26
"default": "MEDIAN"
27
},
28
"FILTER_RADIUS": {
29
"type:integer": {
30
"min": 1,
31
"max": 20
32
},
33
"default": 5
34
}
35
},
36
"outputs": {
37
"FILTERED_IMAGE": {
38
"type:file": {
39
"format": [
40
"ome-tiff"
41
]
42
}
43
}
44
}
45
},
46
"ui": {
47
"inputs": {
48
"IMAGEPATH": {
49
"label": "Input Image",
50
"index": 1,
51
"widget:none": null,
52
"description": "Choose the image to be processed"
53
},
54
"FILTERTYPE": {
55
"label": "Filter Method",
56
"index": 2,
57
"widget:dropdown": {
58
"items": [
59
{
60
"key": "NONE",
61
"label": "NONE"
62
},
63
{
64
"key": "MEAN",
65
"label": "MEAN"
66
},
67
{
68
"key": "MIN",
69
"label": "MIN"
70
},
71
{
72
"key": "MAX",
73
"label": "MAX"
74
},
75
{
76
"key": "MEDIA",
77
"label": "MEDIAN"
78
},
79
{
80
"key": "VARIANCE",
81
"label": "VARIANCE"
82
},
83
{
84
"key": "OPEN",
85
"label": "OPEN"
86
},
87
{
88
"key": "DESPECKLE",
89
"label": "DESPECKLE"
90
}
91
]
92
},
93
"description": "Choose filter method"
94
},
95
"FILTER_RADIUS": {
96
"label": "Kernel Size",
97
"index": 3,
98
"widget:slider": {
99
"step": 1
100
},
101
"description": "Choose kernel size for filter"
102
}
103
},
104
"outputs": {
105
"FILTERED_IMAGE": {
106
"label": "Filtered image",
107
"index": 1,
108
"description": "Filtered Image as OME-TIFF"
109
}
110
}
111
}
112
}
Copied!

Step #2: Main Python Script

During the following steps the main function of the my_fijipyscript.py script will be explained in more detail. The crucial things are directly commented inside the respective code snippets.
Step by step (doing it right)
Cop & paste (quick and dirty)
Import of modules
One important thing one has to to at the beginning is the define the required inputs. Which one are needed obviously depend from the actual script. Worth mentioning is the first line # @LogService log, which is required to enable the logging. This is very useful for debugging purposes later on.
1
# @LogService log
2
3
# required import
4
import os
5
import json
6
from java.lang import Double, Integer
7
from ij import IJ, ImagePlus, ImageStack, Prefs
8
from ij.process import ImageProcessor, LUT
9
from ij.plugin.filter import RankFilters
10
from loci.plugins import BF
11
from loci.plugins.in import ImporterOptions
12
from loci.plugins import LociExporter
13
from loci.plugins.out import Exporter
14
from ij.io import FileSaver
15
import time
Copied!
Define useful helper functions needed
Often it is useful to define some helper functions inside the script, that can be used later inside the main part of the script code in order to structure the script and make it readable easily. This one is applying the selected filter to an image based on the selected parameters. As an input it needs:
  • an ImagePlus object, which is the actual image
  • the kernel size to be used for the filter
  • the type of filter to be applied
Inside the function the correct command to apply the filter is looked up inside the dictionary by using the filtertype string. Finally the selected filter is applied to every slice of the image stack using a simple loop.
1
# helper function to apply the filter
2
3
def apply_filter(imp,
4
radius=5,
5
filtertype='MEDIAN'):
6
7
# initialize filter
8
filter = RankFilters()
9
10
# create filter dictionary
11
filterdict = {}
12
filterdict['MEAN'] = RankFilters.MEAN
13
filterdict['MIN'] = RankFilters.MIN
14
filterdict['MAX'] = RankFilters.MAX
15
filterdict['MEDIAN'] = RankFilters.MEDIAN
16
filterdict['VARIANCE'] = RankFilters.VARIANCE
17
filterdict['OPEN'] = RankFilters.OPEN
18
filterdict['DESPECKLE'] = RankFilters.DESPECKLE
19
20
# get the stack and number of slices
21
stack = imp.getStack() # get the stack within the ImagePlus
22
nslices = stack.getSize() # get the number of slices
23
24
for index in range(1, nslices + 1):
25
# get the image processor
26
ip = stack.getProcessor(index)
27
# apply filter based on filtertype
28
filter.rank(ip, radius, filterdict[filtertype])
29
30
return imp
Copied!
Define the main image analysis pipeline
This part of the code contains the main function that is used to run the actual image analysis pipeline. The reason for creating such an extra function is that one can easily cut out this part of the script an re-use somewhere else. For our example the crucial steps inside this function are:
  • Open the image by using BioFormats
    • when using BioFormats it is required to set the respective options
    • since an image can have many image series, it is also needed to extract the desired ImageSeries. In our example we just use the 1st one for simplicity reasons
  • Apply the filter to the image by calling the helper function apply_filter() from above
    • In case NONE was selected, the script will basically just do nothing and return the unfiltered image
    • additional it creates some logging output
1
def run(imagefile, useBF=True, series=0):
2
3
log.info('Image Filename : ' + imagefile)
4
5
if not useBF:
6
# using IJ static method
7
imp = IJ.openImage(imagefile)
8
9
if useBF:
10
11
# initialize the importer options
12
options = ImporterOptions()
13
options.setOpenAllSeries(True)
14
options.setShowOMEXML(False)
15
options.setConcatenate(True)
16
options.setAutoscale(True)
17
options.setId(imagefile)
18
19
# open the ImgPlus
20
imps = BF.openImagePlus(options)
21
imp = imps[series]
22
23
# apply the filter
24
if FILTERTYPE != 'NONE':
25
26
# apply filter
27
log.info('Apply Filter : ' + FILTERTYPE)
28
log.info('Filter Radius : ' + str(FILTER_RADIUS))
29
30
# apply the filter based on the choosen type
31
imp = apply_filter(imp,
32
radius=FILTER_RADIUS,
33
filtertype=FILTERTYPE)
34
35
if FILTERTYPE == 'NONE':
36
log.info('No filter selected. Do nothing.')
37
38
return imp
Copied!
Parsing the inputs from the module
The main task is to read the JSON parameters for the APEER module UI and use them instead of the script parameters of the original local Fiji version. Thus, this part of the script is now fundamentally different compared to a local version of the script, where the parameters are directly read by using the script parameters. In case of APEER those parameters now have to be read from the JSON file.
Additionally we define some essential other parameters like the desired output format. Since APEER prefers to use OME-TIFF this is the best choice.
1
# Parse Inputs of Module
2
INPUT_JSON = json.loads(os.environ['WFE_INPUT_JSON'])
3
IMAGEPATH = INPUT_JSON['IMAGEPATH']
4
5
# suffix for the filename of the saved data
6
SUFFIX_FL = '_FILTERED'
7
8
# parameters for filter
9
FILTERTYPE = INPUT_JSON['FILTERTYPE']
10
FILTER_RADIUS = INPUT_JSON['FILTER_RADIUS']
11
SAVEFORMAT = 'ome.tiff'
12
13
log.info('Starting ...')
14
log.info('Filename : ' + IMAGEPATH)
15
log.info('Save Format used : ' + SAVEFORMAT)
16
log.info('------------ START IMAGE ANALYSIS ------------')
Copied!
Defining the file paths
In order to save the processed image with a correct name, it is required to define this outputimagepath correctly.
  • get the basename of the image file and add /output/ in front of it
  • get the basename of the outputimagepath without the extension
  • get the file extension and check this file already ended with ".ome"
    • yes - cut out that .ome in oder to avoid filenames like test.ome_FILTERED.ome.tiff later on
    • no - just leave it as it is
  • put together the final path for the output image
1
# define path for the output
2
outputimagepath = '/output/' + os.path.basename(IMAGEPATH)
3
basename = os.path.splitext(outputimagepath)[0]
4
5
# remove the extra .ome before reassembling the filename
6
if basename[-4:] == '.ome':
7
basename = basename[:-4]
8
log.info('New basename for output :' + basename)
9
10
# save processed image
11
outputimagepath = basename + SUFFIX_FL + '.' + SAVEFORMAT
Copied!
Running the main image analysis pipeline
Now it is time to call the actual run() function to start the processing. For the example there is an optional extra step built-in, which is measuring the time of execution. This can be quite useful to get a feeling for which step actually takes how long. Put this is purely optional.
  • get the starting time by using time.clock()
  • filter the image by starting the main processing pipeline by calling run() with the respective parameters
  • get the time again and calculate the execution time
1
# get the starting time of processing pipeline
2
start = time.clock()
3
4
# run image analysis pipeline
5
filtered_image = run(IMAGEPATH,
6
useBF=True,
7
series=0)
8
9
# get time at the end and calc duration of processing
10
end = time.clock()
11
log.info('Duration of whole Processing : ' + str(end - start))
Copied!
Save the processed image and write the required output specifications of the module
Once the got the filtered_image as a result it must be saved as OME-TIFF using the BioFormats library. When running a script in headless mode it is required to use the LociExporter method with the respective paramstring that defines the options.
  • define the paramstring with the correct outputimagepath as part of the argument string
  • save the image by calling exporter.run()
  • at the end it is crucial to write all required output parameters to the JSON file. Check the module_specification.json for the correct naming
  • finally exit your script using os._exit()
1
start = time.clock()
2
3
# create the argument string for the BioFormats Exporter and save as OME.TIFF
4
paramstring = "outfile=" + outputimagepath + " " + "windowless=true compression=Uncompressed saveROI=false"
5
plugin = LociExporter()
6
plugin.arg = paramstring
7
exporter = Exporter(plugin, filtered_image)
8
exporter.run()
9
10
# get time at the end and calc duration of processing
11
end = time.clock()
12
log.info('Duration of saving as OME.TIFF : ' + str(end - start))
13
14
# write output JSON
15
log.info('Writing output JSON file ...')
16
output_json = {"FILTERED_IMAGE": outputimagepath}
17
18
with open("/output/" + INPUT_JSON['WFE_output_params_file'], 'w') as f:
19
json.dump(output_json, f)
20
21
# finish
22
log.info('Done.')
23
os._exit()
Copied!
This is the complete python script example which runs inside the APEER module.
1
# @LogService log
2
3
# required import
4
import os
5
import json
6
from java.lang import Double, Integer
7
from ij import IJ, ImagePlus, ImageStack, Prefs
8
from ij.process import ImageProcessor, LUT
9
from ij.plugin.filter import RankFilters
10
from loci.plugins import BF
11
from loci.plugins.in import ImporterOptions
12
from loci.plugins import LociExporter
13
from loci.plugins.out import Exporter
14
from ij.io import FileSaver
15
import time
16
17
# helper function to apply the filter
18
def apply_filter(imp,
19
radius=5,
20
filtertype='MEDIAN'):
21
22
# initialize filter
23
filter = RankFilters()
24
25
# create filter dictionary
26
filterdict = {}
27
filterdict['MEAN'] = RankFilters.MEAN
28
filterdict['MIN'] = RankFilters.MIN
29
filterdict['MAX'] = RankFilters.MAX
30
filterdict['MEDIAN'] = RankFilters.MEDIAN
31
filterdict['VARIANCE'] = RankFilters.VARIANCE
32
filterdict['OPEN'] = RankFilters.OPEN
33
filterdict['DESPECKLE'] = RankFilters.DESPECKLE
34
35
# get the stack and number of slices
36
stack = imp.getStack() # get the stack within the ImagePlus
37
nslices = stack.getSize() # get the number of slices
38
39
for index in range(1, nslices + 1):
40
# get the image processor
41
ip = stack.getProcessor(index)
42
# apply filter based on filtertype
43
filter.rank(ip, radius, filterdict[filtertype])
44
45
return imp
46
47
48
############################################################################
49
50
51
def run(imagefile, useBF=True, series=0):
52
53
log.info('Image Filename : ' + imagefile)
54
55
if not useBF:
56
# using IJ static method
57
imp = IJ.openImage(imagefile)
58
59
if useBF:
60
61
# initialize the importer options
62
options = ImporterOptions()
63
options.setOpenAllSeries(True)
64
options.setShowOMEXML(False)
65
options.setConcatenate(True)
66
options.setAutoscale(True)
67
options.setId(imagefile)
68
69
# open the ImgPlus
70
imps = BF.openImagePlus(options)
71
imp = imps[series]
72
73
# apply the filter
74
if FILTERTYPE != 'NONE':
75
76
# apply filter
77
log.info('Apply Filter : ' + FILTERTYPE)
78
log.info('Filter Radius : ' + str(FILTER_RADIUS))
79
80
# apply the filter based on the choosen type
81
imp = apply_filter(imp,
82
radius=FILTER_RADIUS,
83
filtertype=FILTERTYPE)
84
85
if FILTERTYPE == 'NONE':
86
log.info('No filter selected. Do nothing.')
87
88
return imp
89
90
91
#########################################################################
92
93
# Parse Inputs of Module
94
INPUT_JSON = json.loads(os.environ['WFE_INPUT_JSON'])
95
IMAGEPATH = INPUT_JSON['IMAGEPATH']
96
97
# suffix for the filename of the saved data
98
SUFFIX_FL = '_FILTERED'
99
100
# parameters for filter
101
FILTERTYPE = INPUT_JSON['FILTERTYPE']
102
FILTER_RADIUS = INPUT_JSON['FILTER_RADIUS']
103
SAVEFORMAT = 'ome.tiff'
104
105
log.info('Starting ...')
106
log.info('Filename : ' + IMAGEPATH)
107
log.info('Save Format used : ' + SAVEFORMAT)
108
log.info('------------ START IMAGE ANALYSIS ------------')
109
110
##############################################################
111
112
# define path for the output
113
outputimagepath = '/output/' + os.path.basename(IMAGEPATH)
114
basename = os.path.splitext(outputimagepath)[0]
115
116
# remove the extra .ome before reassembling the filename
117
if basename[-4:] == '.ome':
118
basename = basename[:-4]
119
log.info('New basename for output :' + basename)
120
121
# save processed image
122
outputimagepath = basename + SUFFIX_FL + '.' + SAVEFORMAT
123
124
############# RUN MAIN IMAGE ANALYSIS PIPELINE ##########
125
126
# get the starting time of processing pipeline
127
start = time.clock()
128
129
# run image analysis pipeline
130
filtered_image = run(IMAGEPATH,
131
useBF=True,
132
series=0)
133
134
# get time at the end and calc duration of processing
135
end = time.clock()
136
log.info('Duration of whole Processing : ' + str(end - start))
137
138
###########################################################
139
140
start = time.clock()
141
142
# create the argument string for the BioFormats Exporter and save as OME.TIFF
143
paramstring = "outfile=" + outputimagepath + " " + "windowless=true compression=Uncompressed saveROI=false"
144
plugin = LociExporter()
145
plugin.arg = paramstring
146
exporter = Exporter(plugin, filtered_image)
147
exporter.run()
148
149
# get time at the end and calc duration of processing
150
end = time.clock()
151
log.info('Duration of saving as OME.TIFF : ' + str(end - start))
152
153
# write output JSON
154
log.info('Writing output JSON file ...')
155
output_json = {"FILTERED_IMAGE": outputimagepath}
156
157
with open("/output/" + INPUT_JSON['WFE_output_params_file'], 'w') as f:
158
json.dump(output_json, f)
159
160
# finish
161
log.info('Done.')
162
os._exit()
Copied!

Step #3: Creating the Dockerfile for the APEER module

Each module on the platform is packaged into what is called a "Docker Container". Introducing Docker would far exceed this tutorial so if you don't know what Docker is we recommend that you start by reading some resources online (e.g. Getting Started) to get familiar with the technology.
Below you see the Dockerfilefor this module. In this file it is specified what the container should include.
Dockerfile
1
# czsip/fiji_linux64_baseimage
2
# Author: sebi06
3
# Version: 1.0
4
5
# Use existing image: Pull Fiji base image from docker hub
6
FROM czsip/fiji_linux64_baseimage:latest
7
8
# add Fiji to path
9
ENV PATH $PATH:/Fiji.app/
10
11
# mount volumes
12
VOLUME [ "/input", "/output" ]
13
14
# copy scripts and files
15
COPY ./my_fijipyscript.py /Fiji.app/scripts
16
17
# define the starting script
18
COPY ./start.sh /
19
ENTRYPOINT ["sh","./start.sh"]
Copied!
  • pull the container image we want to build our module on from the main Docker website (i.e. the Docker Hub) this is accomplished by using the FROM command and specifying the container image, which is based on Ubuntu and included Fiji:
FROM czsip/fiji_linux64_baseimage:latest
  • Using COPY we then add all additional files we need into the Docker container.
  • with the VOLUME command we specify the additional directories that are going to be mounted later by the Dockerfile for sending files to and from the container. This is WFE convention and will always be the same.
  • Finally we need to define the ENTRYPOINT for the Docker container. The entry point is the command executed during the start of the container. Here we execute a shell script once the container starts.

Step #4: Shell script for the Docker Entrypoint

As discussed above when a container is executed it will run the command supplied by the ENTRYPOINT parameters. On our example we execute a shell script that contains the following command below. In essence we start ImageJ with the argument to run our python script
Make sure that the variable SCRIPT inside the bash script is specified correctly.
start.sh
1
#!/bin/sh
2
SCRIPT=/Fiji.app/scripts/my_fijipyscript.py
3
4
/Fiji.app/ImageJ-linux64 --ij2 --headless --console --run $SCRIPT
Copied!

Step #5: Defining the WFE file for local testing

With a working local Docker installation you can build and run the container we prepared during the tutorial locally on your machine.
Windows
Unix
First you should create a testing folder. For convenience we place this directly on C-Drive and create:
  • C:\Temp\input\
  • C:\Temp\output\
Place the test image inside the input folder, e.g. C:\Temp\input\cell.ome.tiff
First you should create a testing folder. For convenience we place this directly into your project directory:
  • /Users/username/pathtodirectory/input
  • /Users/username/pathtodirectory/output
Place the test image inside the input folder, e.g. /Users/username/pathtodirectory/input/cell.ome.tiff
Now it is time to specify the wfe.env file.
wfe.env
1
WFE_INPUT_JSON={"SCRIPT":"/Fiji.app/scripts/my_fijipyscript.py",
2
"IMAGEPATH":"/input/3d_nuclei_image_holes.ome.tiff",
3
"FILTERTYPE":"MEDIAN",
4
"FILTER_RADIUS":5,
5
"WFE_output_params_file":"/output.json"}
Copied!

Step #6: Testing your module locally

If you are developing modules for the platform it is a good idea to install a local Docker environment. Docker is available for all major OS. For this tutorial we assume that you already have a local Docker installation and some basic knowledge how to use it.

Building and running your module locally

To build your module container locally navigate to the folder containing all your files and run
1
docker build --rm -t test/apeer_test_fijimodule:latest .
Copied!
This command tells Docker to execute a build:
  • --rm: will remove intermediary containers after a successful build
  • -t: specifies the name of your container
  • .: docker will look for a file name Dockerfileand use it to build the container accordingly.
If you are running the build for the first time this may take a while because Docker needs to download the base container as well as executing all commands you specified in your Dockerfile. Once the build completed you can use the following command to display all local available docker containers.
1
docker images
Copied!
With the build completed you now have the container available and can start it locally to see if everything is working as intended. Run the container using the input specified inside the wfe.env file:

Run the module locally

Windows
Unix
1
docker run -it --rm -v /datadisk1/tuxedo/temp/input:/input -v /datadisk1/tuxedo/temp/output:/output -e "WFE_INPUT_JSON=$(<wfe.env)" test/apeer_test_fijimodule:latest
Copied!
1
docker run -it --rm -v $(pwd)/input:/input -v $(pwd)/output:/output -e "WFE_INPUT_JSON=$(<wfe.env)" test/apeer_test_fijimodule:latest
Copied!
  • -v: docker will use a local folder and map it to a path inside the docker container. Here we are making two local folders available inside of the docker container as \input and \output.
  • If all works out you should see the container starting and log output appearing on the command line. After the container completes the results can be found in C:\Test\output\ in this example.
One can also store all local files in one folder and map this location to the three different paths inside of the docker
You might need to enable local file system access for the Docker client so it has permission to access the local folders (e.g Windows you need to open the "Setting > Sharing" in the Docker client and share the respective drive).
In order to avoid typing those commands all the time it is also possible to put them into a short bash script test_locally.sh (Unix), which can look like this:
test_locally.sh
1
#!/bin/sh
2
3
# sh test_locally.sh executes the script
4
docker build --rm -t test/apeer_test_fijimodule:latest .
5
6
docker run -it --rm -v $(pwd)/input:/input -v $(pwd)/output:/output -e "WFE_INPUT_JSON=$(<wfe.env)" test/apeer_test_fijimodule:latest
Copied!
In order to run this script just use
1
sh test_locally.sh
Copied!
Usually the result should like this inside the terminal:
1
C:\Users\test\Apeer\fiji_module_template_b88fae21-48c18efc9fa8>docker run -it --rm -v c:\Temp\input:/input -v c:\Temp\output:/output --env-file wfe.env sebi/apeer_test_fijimodule:latest
2
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=128m; support was removed in 8.0
3
Java HotSpot(TM) 64-Bit Server VM warning: Using incremental CMS is deprecated and will likely be removed in a future release
4
[INFO] Overriding BIOP Run Macro...; identifier: command:ch.epfl.biop.macrorunner.B_Run_Macro; jar: file:/Fiji.app/plugins/BIOP/B_Run_Macro-1.0.0-SNAPSHOT.jar
5
[INFO] Overriding Get Spine From Circle Rois; identifier: command:Cirlces_Based_Spine; jar: file:/Fiji.app/plugins/Max_Inscribed_Circles-1.1.0.jar
6
[INFO] Starting ...
7
[INFO] Filename : /input/3d_nuclei_image_holes.ome.tiff
8
[INFO] Save Format used : ome.tiff
9
[INFO] ------------ START IMAGE ANALYSIS ------------
10
[INFO] New basename for output :/output/3d_nuclei_image_holes
11
[INFO] Image Filename : /input/3d_nuclei_image_holes.ome.tiff
12
[INFO] Apply Filter : MEDIAN
13
[INFO] Filter Radius : 5
14
[INFO] Duration of whole Processing : 18.8419955
15
[INFO] Duration of saving as OME.TIFF : 28.9042946
16
[INFO] Writing output JSON file ...
17
[INFO] Done.
18
19
C:\Users\test\Apeer\fiji_module_template_b88fae21-48c18efc9fa8>
Copied!
Now commit the complete code and push it to the repository. This will trigger a new build of your module. After that you are able to run your module.

Fast lane for impatient coders ;)

Of course we have prepared the zipped project folder and some ready to run files for copy and paste:
Dockerfile
module_specification.json
my_fijipyscript.py
start.sh
test_locally.sh
wfe.env
1
# Use exiszing image: Pull base Fiji baseimage from docker hub
2
FROM czsip/fiji_linux64_baseimage:latest
3
4
# add Fiji to path
5
ENV PATH $PATH:/Fiji.app/
6
7
# mount volumes
8
VOLUME [ "/input", "/output" ]
9
10
# copy other scripts or files (when required)
11
COPY ./my_fijipyscript.py /Fiji.app/scripts
12
13
# define the starting script
14
COPY ./start.sh /
15
ENTRYPOINT ["sh","./start.sh"]
Copied!
1
{
2
"spec": {
3
"inputs": {
4
"IMAGEPATH": {
5
"type:file": {
6
"format": [
7
"czi",
8
"tiff",
9
"png",
10
"jpeg",
11
"ome-tiff"
12
]
13
}
14
},
15
"FILTERTYPE": {
16
"type:choice_single": [
17
"NONE",
18
"MEAN",
19
"MIN",
20
"MAX",
21
"MEDIAN",
22
"VARIANCE",
23
"OPEN",
24
"DESPECKLE"
25
],
26
"default": "MEDIAN"
27
},
28
"FILTER_RADIUS": {
29
"type:integer": {
30
"min": 1,
31
"max": 20
32
},
33
"default": 5
34
}
35
},
36
"outputs": {
37
"FILTERED_IMAGE": {
38
"type:file": {
39
"format": [
40
"ome-tiff"
41
]
42
}
43
}
44
}
45
},
46
"ui": {
47
"inputs": {
48
"IMAGEPATH": {
49
"label": "Input Image",
50
"index": 1,
51
"widget:none": null,
52
"description": "Choose the image to be processed"
53
},
54
"FILTERTYPE": {
55
"label": "Filter Method",
56
"index": 2,
57
"widget:dropdown": {
58
"items": [
59
{
60
"key": "NONE",
61
"label": "NONE"
62
},
63
{
64
"key": "MEAN",
65
"label": "MEAN"
66
},
67
{
68
"key": "MIN",
69
"label": "MIN"
70
},
71
{
72
"key": "MAX",
73
"label": "MAX"
74
},
75
{
76
"key": "MEDIA",
77
"label": "MEDIAN"
78
},
79
{
80
"key": "VARIANCE",
81
"label": "VARIANCE"
82
},
83
{
84
"key": "OPEN",
85
"label": "OPEN"
86
},
87
{
88
"key": "DESPECKLE",
89
"label": "DESPECKLE"
90
}
91
]
92
},
93
"description": "Choose filter method"
94
},
95
"FILTER_RADIUS": {
96
"label": "Kernel Size",
97
"index": 3,
98
"widget:slider": {
99
"step": 1
100
},
101
"description": "Choose kernel size for filter"
102
}
103
},
104
"outputs": {