GPU accelerated image processing for everyone

CLIJ2 home

3D Image Segmentation


Image segmentation in 3D is challenging for several reasons: In many microscopy imaging techniques, image quality varies in space: For example intensity and/or contrast degrades the deeper you image inside a sample. Furthermore,

To demonstrate the workflow, we’re using cropped and resampled image data from the Broad Bio Image Challenge: Ljosa V, Sokolnicki KL, Carpenter AE (2012). Annotated high-throughput microscopy image sets for validation. Nature Methods 9(7):637 / doi. PMID: 22743765 PMCID: PMC3627348. Available at

We start by cleaning up ImageJ, opening an example image and configuring an interesting sub-region.

// clean up first
run("Close All");
run("Clear Results");

// open example image stack [available online](


Next, we initialize the GPU, push the image to GPU memory and read out its size.

run("CLIJ2 Macro Extensions", "cl_device=");

stack = getTitle();

// read out image size
Ext.CLIJ2_getDimensions(stack, width, height, depth);
print("Image size:", width, height, depth );
> Image size: 393 396 86

For convenience, we make a function for visualizing the image in projections from three sides:

function show(image_to_show, is_label_image) {
	Ext.CLIJ2_maximumXProjection(image_to_show, max_x_projection);
	Ext.CLIJ2_maximumYProjection(image_to_show, max_y_projection);
	Ext.CLIJ2_maximumZProjection(image_to_show, max_z_projection);

	// show the images, potentially using the glasbey lookup table
	if (is_label_image) {
	if (is_label_image) {
	if (is_label_image) {

// show the image
show(stack, false);

CLIJ2_maximumXProjection_result103 CLIJ2_maximumYProjection_result104 CLIJ2_maximumZProjection_result105

Obviously, voxel size is not isotropic. Thus, we scale the image with the voxel size as scaling factor to get an image stack with isotropic voxels.

// read voxel size from original image using ImageJ functions
getVoxelSize(voxel_width, voxel_height, voxel_depth, unit);

// create another stack of different size
Ext.CLIJ2_create3D(isotropic_stack, width * voxel_width, height * voxel_height, depth * voxel_depth, bitDepth());

// Scale the image so that it becomes [isotropic](
Ext.CLIJ2_scale3D(stack, isotropic_stack, voxel_width, voxel_height, voxel_depth, false);

show(isotropic_stack, false);

CLIJ2_maximumXProjection_result107 CLIJ2_maximumYProjection_result108 CLIJ2_maximumZProjection_result109

Intensity and background correction

As we can see, intensity is decreasing in Z-direction (from slice to slice) and contrast as well. At least the intensity decay can be corrected.

reference_slice = depth / 2;
Ext.CLIJ2_equalizeMeanIntensitiesOfSlices(isotropic_stack, equalized_intensities_stack , reference_slice);

show (equalized_intensities_stack, false);

CLIJ2_maximumXProjection_result111 CLIJ2_maximumYProjection_result112 CLIJ2_maximumZProjection_result113

Furthermore, background intensity appears to increase, potentially a result if more scattering deep in the sample. We can compensate for that by using a background subtraction technique:

radius = 5;
Ext.CLIJ2_topHatBox(equalized_intensities_stack, backgrund_subtracted , radius, radius, radius);

show(backgrund_subtracted, false);

CLIJ2_maximumXProjection_result115 CLIJ2_maximumYProjection_result116 CLIJ2_maximumZProjection_result117


For segmentation, we use a combination of thresholding, spot detection and seeded watershed, called Voronoi-Otsu-Labeling

Ext.CLIJx_voronoiOtsuLabeling(backgrund_subtracted, segmented, spot_sigma, outline_sigma);
show(segmented, true);

CLIJ2_maximumXProjection_result119 CLIJ2_maximumYProjection_result120 CLIJ2_maximumZProjection_result121

If you run this macro from ImageJ, the last two opening windows allows to tip and tilt the view by clicking and dragging:

synchronized_views = true;
Ext.CLIJx_show3DWithTipTilt(backgrund_subtracted, "backgrund subtracted", synchronized_views);
run("In [+]");
run("In [+]");
run("In [+]");
Ext.CLIJx_show3DWithTipTilt(segmented, "segmented", synchronized_views);
run("In [+]");
run("In [+]");
run("In [+]");

backgrund subtracted segmented

In this case, cleaning up by the end would break the tip-tilt-viewer. If you’re not using it, please clean up by the end: