Region growing from point list (FIJI/ImageJ)

We are trying to macro an automated analysis of large immunofluorescent images of tissue sections; 4-6 colours/image. We’re not trying to segment out individual cells perfectly, but rather are looking to score the presence or absence of 3-5 markers/cell. Our approach, which works when done semi-manually, is to generate a point-list of cell centroid positions, based on nuclear staining, and then to draw a circle around each centroid which is then used as an ROI for the other channels. This is as accurate as manual scoring, but we would like to automate it further as it is still overly laborious.

We have scripted the automated segmentation/detection of the nuclei, which works very well. The output is a list of x/y coordinates for the centroid of each nucleus. What we’d like to automate is a simple form of region growing - basically, we want to generate automatic circular ROI’s of a user-set diameter centred on each centroid. In addition, if two ROI’s “collide” we’d like the overlapping region to be divided between them. In principal I know what we need - region growing + collision detection. In practice, we’ve been unable to make it work.

Any advice is welcome.

Bryan

If you haven’t, and you have a nuclear marker, I’d recommend giving QuPath a try (Nucleus detection, blind cytoplasmic expansion, handles millions of cells). That’s basically what it does, and you can even get full project percentages and cell densities by class per marker using a few scripts as shown here:


QuPath download here:

Kind of a getting started guide here, though for an earlier version (0.1.2/3)

Some notes on classifying cytoplasmic markers here:

You didn’t list a software, so I’m not sure what you are using.

QuPath can also do the manual thing by turning set Points into detection objects of a set circular size:


You can then add features to them based on the intensity of the staining within the circle.

Sorry, to clarify we are doing this in FIJI, which is the tool we’d like to stick with as this task is part of a much larger scripted analysis, most of which is already working. Our issue is that most of the things we are trying to detect are surface proteins, so we have the challenge of sections passing through cells, giving many of the markers we are trying to score a punctuate appearance. This is why we’re trying to score markers in an assigned sized area around each centroid.

We’ll give QuPath a try using the later post you linked to, but a FIJI option would be preferred.

Good luck, and hopefully someone with more FIJI experience can help you there. If you do have QuPath questions, though, feel free to ask.

Added the FIJI tag

As far as I understood now. You have a spots in the image somewhere and want to get a defined radius around that spot and then get ROIs that are separated when they are touching.
One way you could aproach this using also a simple macro without much programming is:

  1. Get a mask with each marker being a value of 255 (other values are 0 and the image is 8-bit).
  2. Invert that image: Edit > Invert
  3. Then convert this image into an EDM: Process > Binary > Distance Map
    https://imagej.nih.gov/ij/docs/guide/146-29.html#sub:Distance-Map
    Each pixel in this image will be replaced with the distance from the nearest background image (i.e. your markers)
  4. Then threshold that with the radius in pixel you want to choose. Image > Adjust > Threshold…
  5. Finally apply a binary watershed: Process > Binary > Watershed
    Note: specify in the binary settings that output of the EDM properly, sometimes 8-bit is not enough: Process > Binary > Options
    Note: that there are more plugins with more expanded binary watersheds out there: https://imagej.net/Distance_Transform_Watershed

Maybe it can be also solved with a marker controlled watershed or seeded watershed:

The morphoLibJ plugin has solutions towards that: https://imagej.net/MorphoLibJ#Watershed_segmentation

The Find Maxima can also output Segmented Particles from a seed: https://imagej.nih.gov/ij/docs/guide/146-29.html#sub:Find-Maxima

1 Like

Sorry, I don’t think my origonal description was clear. What we have is a data table of x/y coordinates in the image of the center point (centroid) of each nucleaus in the DNA channel.

What we want to do is “draw” circular ROI’s of a set diameter centered on each of those points, eliminate any overlap between ROI’s, and then measure the presence/absence of 3-5 other fluorophores in the other image channels.

That said, the distance map idea may work just as well as our current method. I’ll give it a try and see how it works.

Thanks

Bryan

If you load the coordinates draw the marker on an empty image (same size as the one you want to do the measurement in) you can do what I outlined and perform your measurement.

Of course you can also get the coordinates calculate the area of the circle and draw that on the image. Both solutions are equivalent just the latter needs a bit more programming and the first might be even faster as a macro.

The binary watershed is one way to split such overlapping objects. Might be good enough already and would not require any programming to compute the overlap.

EDIT: you can load a .csv or .txt table: File > Import > Table…
Then get values by Table.get(columnName, rowIndex) from the table in the foreground.

Hi,

here’s an example of how to grow circles from a seed points (as x,y arrays) using morpholibJ.

Screen Shot 2020-07-08 at 10.45.06 am

The resulting LabelImage can then be used to measure the intensity of each region in other images (ie the fluorescence channels). Let me know if anyone would like and example of that step.

Cheers,

Chris



def growRegions( ip, rad ):
	'''
	grow circular regions from binary seeds point (255 on a 0 )
	input:
		ip  - Binary image processor with seeds
		rad - Radius of circle in pixels
	output:
		ip_lab - an LabelImage 
	'''
	# label the image
	from inra.ijpb.binary  import BinaryImages
	ip_lab = BinaryImages.componentsLabeling( ip, 8, 32 )

	# create the mask image by dilating the binary seeds	
	from inra.ijpb.morphology import Morphology, Strel
	strel= Strel.Shape.DISK.fromRadius(rad)
	ip_mask = Morphology.dilation(ip,strel)

	# seeded watershed
	from inra.ijpb.watershed import  MarkerControlledWatershedTransform2D as MCWT
	mcwt = MCWT( ip_mask, ip_lab, ip_mask )
	ip_seg = mcwt.applyWithPriorityQueue()

	return ip_seg

def points2ip( ip, xs,ys ):
	'''
	sets the pixel in the ImageProcess at the coordiantes xs,ys to 255
	'''
	ip_out = ip.duplicate()
	for x,y in zip(xs,ys):
		ip_out.putPixel(x,y,255)
	return ip_out	



from ij import IJ,ImagePlus
imp = IJ.createHyperStack("seeds",200,200,1,1,1,8)
ip  = imp.getProcessor()

xs = [ 50, 65, 85, 150, 30, 50]
ys = [ 50, 50, 75, 100,100,120]
ip_seed = points2ip( ip, xs,ys)
imp.setProcessor(ip_seed)
imp.show()

ip_seg = growRegions(ip_seed,20)
imp_seg =ImagePlus("circular regions",ip_seg)
IJ.run(imp_seg, "Set Label Map", "colormap=[Golden angle] background=Black shuffle");
imp_seg.show()

2 Likes

This tool is exactly what I’ve been looking for for my analysis! As I understand, the way it is now, it takes not a binary image, but something which is generated (?) using ImageProcessor (which I assume is some kind of ImageJ machinery for drawing things).

Is there a way to use a simple (any) binary image as an input? Or somehow convert a set of ROIs to match the input for this tool?

Growing circles: through the menu

This approach uses the Marker-controlled watershed in MorpholibJ. It’s kind of like bunch of different coloured paints flooding out of holes an image:

  • the seed image is where the paint leaks from
  • the input / gradient image slows down the spread of paint (not used here)
  • the mask is limits where the paint can spread to

Install

This requires the excellent MorpholibJ library to be installed - easiest way to install is

  • Help > Update... > Manage Update Sites
  • Tick IJPB-plugins
  • Close, Close anf restart Fiji

Seed image

One way to this is with a binary image with black background and white points at the origin of the circles.

Make the mask

The mask is used to constrain the watershed

  • Plugins > MorpholibJ > Morphological Filters
  • Select Disk to make a circles
  • The radius is the size of the circles
  • optional - rename the image to mask

Run the plugin

  • MorpholibJ > Segmentation > Marker-controlled Watershed
  • select Binary Markers - if your seeds are all white check this, if each seed is coloured differently (a label image) uncheck this.
  • if Create Dams is selected there is a thin black line between each region

Result

A image with each region a different value

1 Like

Getting a label image into the Roi Manager

I’ve never found a great way to to do this.

Easy way

  • Create the watershed with dams. This puts lines between the regions.
  • Threshold the image from [1,\infty ] to make a binary version
  • Erode the image by 1 pixel to fatten up the dam lines (Process > Binary > Erode)
  • Analyse > Analyse Particles, make sure Add to Manager is selected.

This gives you a set of rois that are one pixel smaller than they should be, but its very fast and is may be good enough for you:

Enlarging the roi by 1-pixel gets it close to right but it will miss some parts (Edit > Selection > Enlarge)

Extracting each label

A better way to do this is the extract each label separately and creating an Roi. For each label in the image:

  • Set threshold [ i, i ] and convert
  • Edit > Selection > Create Selection
  • Add to Manager

This is much slower and needs to scripted, but correct.

There is a function in CLIJ2 to do this, the code looks like this:

selectWindow('ws');
input = getTitle();

run("CLIJ2 Macro Extensions", "cl_device=");
Ext.CLIJ2_clear();

// push data to GPU
Ext.CLIJ2_push(input);

/*
## Visualization of labelled objects
*/
// show the image
Ext.CLIJ2_pull(input);
// show label as ROIs on top of the image
Ext.CLIJ2_pullLabelsToROIManager(input);
roiManager("show all");

1 Like
  1. Make a blank image the same size as your image. File > New > Image

    • Width and height the same as your image
    • Type 8-bit
    • Fill with Black
  2. Set the fill colour to white (255). The eye dropper in the toolbar is a handy way to do this.

  3. In the RoiManager Deselect to have no rois selected the in the RoiManager More >> Fill

1 Like