ImageJ Macro Integration

Hi there everyone! I’m experienced in scripting in ImageJ macros and Visual Basic. I noticed the template provided is for python based modules and I have very limited experience with Docker. I was wondering if anyone is more familiar with these tools and can help me understand how to hook an ImageJ script into a Docker container. I could simply be ahead of the curve if I just need to wait for the ImageJ template.

Nate,

While we wait for the experts to respond I can share a repository for a module that uses imageJ macro to perform basic ‘Find Edges’ task. That should give you a good start. Please let me know if you’re interested in this repo. It contains the macro file (ijm), Dockerfile and module specification json.

Thanks

Sreeni

That would be phenomenal! I’ve spent a considerable amount of time starting to build a Docker image and have one for FIJI, but if you guys have one that is already working, I’d love to grab it!

Thanks!

Nate, I’ll email it to you. I will use the same email that I’ve used to invite you to the webinar.

Regards,

Sreeni

Thank you @sreenivas.bhattiprol! I successfully utilized my own build of the edge detection module! I am now working toward inserting my own macro!

Awesome!!! Please let me know if you run into any issues.

Next up, how do I reference the input variables within an ImageJ macro? I was thinking I could print the variable to a file with jq then call the file string as a variable in the imageJ macro. That seemed like a roundabout approach though. Is there a simpler, leaner solution?

Thanks!

Dear nmiller5381,
I am currently writing up a more extensive guide on how to use ImageJ/Fiji macros on the platform. There are some hoops to jump if you want to use macros because of the limitation both within the macro language and imageJ. In general if you have the choice I would recommend using complete Python scripting of ImageJ (or similar) because it gives you better control.

The limitations when using the macro language you have to watch out for are the following

  • You have no direct access to the env variable supplied by the workflow engine like you do with e.g. Python
  • You need to prevent all GUI output, however ImageJ although it has a “headless” mode for macros will not catch all UI output. Therefore you need to start ImageJ with a virtual screen buffer to prepare for this eventuality. Otherwise you macro can get stuck waiting for a mousclick. This can be done with Xvfb
  • If you want to write output files be aware that the macro language can only handle one open files. Therefore you need to always close and reopen the files if you handle multiple ones (e.g. a log file in addition to others)
  • If you use a virtual screen buffer environment you need a workaround to print messages to the command line because “print” statements in the macro language now go to the “Log window” in ImageJ.

But back to your question:

The way I currently reading the input variables is the following
I am leveraging the option that imageJ macros can execute other script snippets such as javascript or Python.
In these scripting languages you have JSON reading support.

The Workflow Engine supplies the inputs in two ways

  • by ENV variables
  • by an actual file located in “/params/WFE_input_params.json” (you need to mount /params in you dockerfile besides /input and /output")

I have a javascript helper file that I call to read the JSON file with the input variables.
In this file you need to open the JSON file with the input parameters that the Workflow Engines supplies
this is path below i.e. “/params/WFE_input_params.json” this will always be the same in the docker container

The file looks like this “JSON_Read.js”

/*
read JSON and return the supplied key
not very nice!
Version 1
Robert Kirmse

*/

importClass(Packages.ij.IJ);

function readJSON(call) {
//Path to the WFE_input_params.json
var jsonDir = “/params/WFE_input_params.json”;
var settings = JSON.parse(IJ.openAsString(jsonDir));

returncall = eval(call);
return (returncall);
}

var arg = getArgument();
readJSON(arg);

In your main macro you can use the following to get the input JSON variables into your macro

JSON_READER = “/JSON_Read.js”;
WFEOUTPUT = runMacro(JSON_READER, “settings.WFE_output_params_file”);

The two lines above first make the extra javascript available in your macro (supply the path to the js file)
then as an example I initialze the variable “WFEOUTPUT” with the value from the inputs
the “runMarco” then runs the javascript code which opens the json file and retrieves the respective key:value

You need to call “runMacro” for every variable you want to fill with the respective input from the JSON file.

Hope this helps for your next steps.
If someone has a more elegant/efficient solution please feel free to share.

Best
Robert

2 Likes

Thanks Robert!

Here’s an ImageJ forum post I found where you can open a file’s contents as a string. I bet we can use this to our advantage here instead of using it in the helper file. http://imagej.1557.x6.nabble.com/read-values-from-txt-file-td3689457.html

I realize it’s not the leanest implementation, but having a temp .txt file for each input variable that is then read by ImageJ might be the ticket. If we combine that approach with parsing a string as an integer (http://imagej.1557.x6.nabble.com/convert-string-in-a-number-td3687303.html), we can probably get the flexibility we need. Since this is all containerized anyway, paths shouldn’t change and we can output the variable to a text file using jq in the command line BEFORE calling the imageJ macro. Then use the input variables on the fly when needed in the macro itself. This would avoid the need for the helper file too, I think.

Forgive me if I’m missing something…I’m new to Docker and Javascript, so I’m still learning everything other than ImageJ macro language.

Hi nmiller
yes you could do it this way too. The path of the JSON file with the inputs stays the same and how you handle your files later in the container is yours to decide and will stay constant too with each container start.

The reason why I choose to go with the Javascript helper for the time being is that javascript includes JSON parsing capability already. Since JSON files are always “key: values” pairs you can just querry the key and get the value when using Javascript or other languages that include JSON parsing capability.

If you would go the way of opening the json as a text file you would need to program the parsing logic yourself and parse for the keywords yourself and also recognize the corresponing values.

You can do that for sure but it seemed too much effort to me over just using an additional script file.
Essentially you would need to write your own json parser directly in the main macro yourself.

Best
Robert

Thanks Robert!

Here’s what I was thinking, please shoot holes in it if it’s a dumb solution.

I don’t think I explained well what I was trying to do. Here’s what I’ve got so far. In run_imj script:

lowthresh=echo $WFE_INPUT_JSON | jq -r '.lowthresh'
echo $lowthresh > /tmp/lowthresh.txt

which I then use in the macro as

lowthresh = parseInt(File.openAsString("/tmp/lowthresh.txt"));

This seems to work locally with local files, I don’t see why it wouldn’t work in Docker.

I finally tested it in CA, and it does indeed accurately pass the values. Since variables in imagej are typeless until called, we don’t even need to use ParseInt unless you only want an integer! My log files show the appropriate input value printed from the macro itself!

I’m receiving several errors related to the headless implementation, but I wrote my macros to not require human intervention, so they should not get hung up waiting for a mouse click. I’ll report back what I discover playing with the --headless tag.

Hi nmiller
no no worries I did understand, and for sure you can do it like this also and as you already checked this is working in the docker container. For the --headless operation this normally should work. I however found a few instances where a GUI window pop up was not prevented by --headless. This happened when calling certain function such as the “Analyze” functions. With Pyhton scripting for example you can prevent the window popping up (this behaviour is also mentioned in the Fiji documentation).

The --headless tag doesn’t help in this case and if something like this happens the script will hang or crash because no monitor output is found.
Therefore I used the workaround mention also in the Fiji wiki to run imageJ with a virtual screen.

Here is my docker file

# czsip/fiji with additional xvfb support
# Author: Robert Kirmse
# Version: 0.1
# Pull base CZSIP/Fiji.
# FROM czsip/fiji_linux64_baseimage:latest

FROM czsip/fiji

#get additional stuff
RUN apt-get update
RUN apt-get install -y apt-utils software-properties-common
RUN apt-get upgrade -y

# get Xvfb virtual X server and configure
RUN apt-get install -y xvfb x11vnc x11-xkb-utils xfonts-100dpi xfonts-75dpi xfonts-scalable xfonts-cyrillic 
x11-apps
RUN apt-get install -y libxrender1 libxtst6 libxi6
                      
# Install additional Fiji Plugins
COPY ./Morphology /Fiji.app/plugins
COPY ./Particle_Remover.class /Fiji.app/plugins
COPY ./CallLog.class /Fiji.app/plugins
COPY ./CA_NeuriteGrow_docker.ijm /
COPY ./JSON_Read.js /
COPY ./start.sh /

VOLUME [ "/input", "/output", "/params" ]

# Setting ENV for Xvfb and Fiji
ENV DISPLAY :99
ENV PATH $PATH:/Fiji.app/

# Entrypoint for Fiji script has to be added below!
ENTRYPOINT ["sh","/start.sh"]

in the COPY lines you see the files I put into the container, this would need to be adapted to your case and your files.

VOLUME indicates the virtual drives I expect to mount which are the standards for the Workflow Engine

We generated two own fiji docker images on docker hub as you can see from the “FROM” lines above
either one should work if you want to give it a try.

My start.sh script from the entry point that calls Fiji into the vritual frame buffer looks like this

#!/bin/bash
xvfb-run -a /Fiji.app/ImageJ-linux64 --ij2 -macro CA_NeuriteGrow_docker.ijm

Hope that helps a bit

Cheers
Robert

Thanks again Robert! I really enjoy working on these problems and appreciate your help!

I played around with the headless stuff and experienced the exact issues you mentioned I would. I even managed to time out a module because it was waiting for an input because the macro failed but xfvb prevented the output going to the shell…exactly as you warned.

Your docker file is very similar to what I have now, but you install a few extra packages for X.

I have been playing with the WORKDIR command and following everything from that. Is there an advantage to using VOLUME instead?

I’ll be out of the lab for the next several days but will follow up with any findings when I get the chance. I’ll keep trying to make the imageJ macro language work for a bit, because I think it brings a ton of extra flexibility to the platform!

Thanks again!