Creating a mask for an image stack in FIJI/ ImageJ
Making a simple exterior/ interior mask based on contrast thresholding with a different shape specific to each image slice in the stack
I have a reconstructed tomogram of an electrode. It’s a data array with size (1700 x 500 x 1960), with 1960 virtual slices through the electrode thickness that look like this:
The area of interest to analyse is the granular part in the middle, which is coated on an aluminium current collector (the uniform horizontal stripe near the bottom of the image), and surrounded by space (the relatively uniform grey areas above and below). However, the greyscale value of some of the particles to be analysed is similar to the greyscale value of the pixels in the space, so I want to make a mask covering the space. I need to apply the selection conditions consistently to all 1960 slices in the stack.
Using the ROI manager, there is a Plugin available called ‘Mask from ROIs’. I’ve used this successfully before on a different PC, but for some reason this time I got an error about NoneType data which I couldn’t fix by googling, hence the workaround below.
Before you start
In FIJI/ ImageJ, you can start a new macro (for copying the code below) by going to: Plugins > New > Macro.
Comments and annotations can be added to macros using two forward slashes:
// A comment
For each code block below, you can copy and paste them into the macro editor and click run. After you run each code block, you can delete the code from the editor and copy and paste the next block of code: only run each code block once!
To quickly find out the command to include in future macros, go to Plugins > Macros > Record
Do the task that you want to know the command for, and it will appear in the macro recording window.
Step 0: Using a macro to crop the image
The image above is actually from a slightly cropped stack to remove partial data from the edges and saturated data from the top and bottom reconstructed slice.
// All the numerical values here are specific to my image
// Change them as required
// Deleting slices 1 - 10
for (i = 1; i < 10; i++) {
run("Delete Slice");
}
// Deleting slices 1970 - 2000
for (i = 1970; i < 2001; i++) {
run("Delete Slice");
}
// Cropping partial data from edges
makeRectangle(0, 80, 500, 1780);
run("Crop");Step 1: Creating a mask over the external areas
The macro below applies the following steps:
Selecting the bulk of the exterior regions by thresholding. As described above, some of the pixels within the bulk of the electrode also fall within this threshold range - this is dealt with in the next step
Fill holes: this makes the bulk areas above and below the electrode continuous and removes the local speckle from the first image
Opening: this removes most of the narrow outlines so that the bulk areas above and below are more isolated
Erode x 2: this removes more of the small local effects from the edges and smooths the mask to avoid covering the outer edges of the sample:
setAutoThreshold("Default dark no-reset");
//run("Threshold...");
setAutoThreshold("Default dark stack no-reset");
setThreshold(21845, 23901, "raw");
setOption("BlackBackground", true);
run("Convert to Mask", "background=Dark black create");
run("Fill Holes", "stack");
run("Open", "stack");
run("Erode", "stack");
run("Erode", "stack");Step 2: Select the areas to include in the mask
This step uses the ROI manager tool to save the selection. This probably isn’t best practice (!), but it’s possible to select the required areas in each slice by clicking in the right place on the first slice, and then setting a macro to repeat this process for all the slices in the stack:
(macro structure copied from Applying macro to all slices in an image stack - Image Analysis - Image.sc Forum) :
macro batch_select_roi{
run("ROI Manager...");
setBatchMode(true);
for (i = 1; i <= nSlices; i++) {
setSlice(i);
//setTool("wand");
doWand(30, 1008);
roiManager("Add");
doWand(486, 999);
roiManager("Add");
}
}Step 3: Making the selection from the ROI manager into a mask for the image stack
I very reluctantly turned to Chat GPT for this step, and the code it produced works really well:
// Create an empty binary mask stack from ROIs on multiple slices
stackWidth = getWidth();
stackHeight = getHeight();
stackSize = nSlices();
newImage("ROI Mask", "8-bit black", stackWidth, stackHeight, stackSize);
// Loop through each ROI
roiManager("deselect");
n = roiManager("count");
for (i = 0; i < n; i++) {
roiManager("select", i);
slice = getSliceNumber();
setSlice(slice); // Set to the correct slice
roiManager("select", i); // Select ROI again
run("Fill", "slice"); // Fill ROI in that slice
}The end result is a mask tailored to each slice of the stack. It’s not perfect, but it’s a useful starting point to enable regions within the electrode to be selected using the thresholding tool while avoiding selecting pixels in the surrounding space.
(Normally I’d do tasks like this in Avizo 3D but we’re in the process of resolving a licensing snag on the workstation, so everything here is a workaround for a workaround!)







