Nearest neighbor analysis

Hello guys:

I would like to ask if there are methods to analyze the distance between a series of isolated pores in a 3d image stack. I would like to set the boundary distance as 10 pixels. It will analyze the number of neighbours for each pore. These neighbour pores are all within the distance of 10 pixels (edge to edge distance). It seems hard to achieve something like this. Is anyone who can manage this?
I upload 50 images here. Thanks very much for your help!


Hi @Charles_hoo,

you can take your binary image and apply connected components labeling to it to identify objects. You can then increase the size of the objects with a given radius until they touch. Afterwards, you count how many each one is touching. There are methods for all of this in CLIJ2. A short macro doing this with one of your images looks like this:

open("C:/Users/rober/Downloads/pores_in _calcite_022.tif");

// invert image and LUT to make a binary image with (true=255, false=0)
run("Invert LUT");

// init GPU
run("CLIJ2 Macro Extensions", "cl_device=");

// push image to GPU memory
binary = getTitle();

// connected components labeling
Ext.CLIJ2_connectedComponentsLabelingBox(binary, labels);

// extend the labels with a given radius
radius = 100;
larger_labels = "larger_labels";
Ext.CLIJx_extendLabelsWithMaximumRadius(labels, larger_labels, radius);

// determine which labels are touching
Ext.CLIJ2_generateTouchMatrix(larger_labels, touch_matrix);

// determine how many touching neighbors each label has
Ext.CLIJ2_countTouchingNeighbors(touch_matrix, touching_neighbors_count);
// set number of neighbors for background = 0
Ext.CLIJ2_setColumn(touching_neighbors_count, 0, 0);

// visualize touch count in colours
parametric_image = "parametric_image";
Ext.CLIJ2_replaceIntensities(labels, touching_neighbors_count, parametric_image);
setMinAndMax(0, 16);

It will generate an image where the colour represents the number of neighbors which are closer than the given radius:

There are some CLIJ tutorials about the concepts behind, e.g.:

Let me know if this helps!


Thanks very much, Robert! The radius you set here is 100 pixels or distance? This plugin is dealing with 3D objects? It will be great to have a result table poping out after the computation. It shows how many neighbors for each pore. I am totally a freshman for coding language in macros.

1 Like

You’re welcome @Charles_hoo!

I chose 100 because after extending the particles with 10 there were no touching.

Yes. You just need to replace the first line containing open(...); with the following. Btw. this line is recorded. I didn’t type it:

run("Image Sequence...", "open=[C:/Users/rober/Downloads/FIB-SEM images-20200906T073825Z-001/FIB-SEM images/pores_in _calcite_022.tif] sort");

Furthermore, many classical ImageJ Macro run() calls can be extended so that they work slice-by-slice on a stack:

run("Invert", "stack");

The macro will then operate in 3D:

No problem, just output the resulting vector by the end to a table.

// rotate vector so that touch-count sits in lines, not in rows:
Ext.CLIJ2_transposeXY(touching_neighbors_count, smal_table);

// show table

This is how the table looks then. The first entry is 0 because it corresponds to background:

Furthermore, as good scientific practice, I would measure the centroid position of the objects:

Ext.CLIJ2_centroidsOfBackgroundAndLabels(labels, coordinate_list);

and add it to the table:

// put positions and neighbor count in one table/image
Ext.CLIJ2_getDimensions(coordinate_list, number_of_points, dimensionality, _);
Ext.CLIJ2_create2D(vertical_table, number_of_points, dimensionality + 1, 32);
Ext.CLIJ2_paste2D(coordinate_list, vertical_table, 0, 0);
Ext.CLIJ2_paste2D(touching_neighbors_count, vertical_table, 0, dimensionality);


You can download the whole script here.

In order to understand what it’s doing, I would recommend learning some Image Macro, a bit CLIJ2 and especially the pull() command as it allows you to look at intermediate results. E.g. if you want to know what the coordinate_list variable contains, you can pull it:


It will open a window and show an image with 3 lines, which correspond to X, Y and Z coordinates:

Let me know if you need any further hint!


Hi Robert:

Thanks a lot for your scripts. I just run it on my computer. I input another 500 images into the Fiji software. But it stops at extending the labels with a given radius. An exception pops out and I attached the file here.
Exception.txt (4.3 KB)

Hey @Charles_hoo,

so the error message says

... 12017MB of 16000MB (75%)
... java.lang.OutOfMemoryError: Java heap space

There a three images open of about 4000 MB each and there isn’t more space in your memory to open another one. If you want to prevent images from being shown, comment out the pull() commands, e.g. like this:

// Ext.CLIJ2_pull(larger_labels);

Furthermore, you can try to increase available memory in Edit > Options > Memory Threads.... How far you can go, depends on your computer.

If the issue persists, please tell us some details about the computer you’re using and the grpahics card.


Hi Robert:

I was using a workstation but with an old setting for the memory. I think it will be no problem because I have 120 GB for the ram. Thanks again for your reminder.

1 Like

Hi Robert:

Yesterday I did the computation and it works quite well. But this morning I found I forget to set the unit (pixel). Should I redo the computation?

Besides, the 50 images I uploaded are only a part of 498 images. I identified about 2182 isolated pores in total by 3D Analysis plugin . But when I set the radius as 1 pixel to do neighbour analysis, I found 2176 pores have at least 1 neighbour for each pore. It looks weird to me. Because you also do the analysis for 50 images. right? You extended to 100 pixels and then found connections.


No, you don’t. CLIJ doesn’t take physical units into account anyway.

Good catch! If you pull the touch matrix for radius 1 (for testing, I did it on one-slice analysis macro from the beginning), you can see why:


The first column is white; this means all labels are touching the background. We need to eliminate this before counting:

Ext.CLIJ2_setColumn(touch_matrix, 0, 0);

Then it should work. Could you please confirm?


Hi Robert:

Thanks! Where should I put this in the scripts? Between “determine which labels are touching” and “determine how many touching neighbors each label has”?

Ext.CLIJ2_setColumn(touch_matrix, 0, 0);


Hi Robert:

Thanks! This revision works. Each pore has one neighbor less after include this code. One last question is about the principle of the neighbor counting. Let me take two identical circle with radius R for example. Circle 1 is 2R far away from circle 2. Unless circles 1 and 2 extend ≥R, circle 1 has one neighbor of circle 2, right? The plugin always calculates the edge to edge distance between two pores and compares it with 2× radius? Because all pores are extending.

1 Like

You summarized everything perfectly :+1: