Overlay and take linear measurements on shape

Hi everyone,

I would like to overlay a grid on an image outline created via the threshold function in ImageJ. Ideally the grid extension should be constrained by the outline of the shape (i.e. they should not extend over the entire canvas). From this grid, I would like to extract a series of width measurements at set intervals (e.g. 25 %, 50 %, and 75 %) along the maximum length axis of the shape. I then want to save these measurements to a .csv file.

Can anyone provide guidance on how to do this in ImageJ?

I am hoping that the image below gives a better idea of what I am trying to do.

Thanks for your help in advance!

1 Like

To add: I would like to repeat this for several different objects each of which will have a different shape and maximum length line.

Good day Justin,

provided your image is binary and contains a single object, then the following experimental macro may do the job:

imgWidth = getWidth();
run("Select All");
setKeyDown("alt");
profile = getProfile();
run("Select None");
diffArray( profile );
maxA = Array.findMaxima( profile, 0 );
if ( maxA[0] < maxA[1] ) { objTop = maxA[0]; } else { objTop = maxA[1]; }
objHeight = abs( maxA[1] - maxA[0] );
horzWidth( imgWidth, objTop, objHeight, 0.25 );
horzWidth( imgWidth, objTop, objHeight, 0.5 );
horzWidth( imgWidth, objTop, objHeight, 0.75 );
run("Select None");
exit();

function diffArray( a ) {
    for ( i = 1; i < a.length; i++ ) {
        a[i-1] = abs( a[i] - a[i-1] );
    }
    a[a.length-1] = 0;
}
function horzWidth( w, t, h, pos ) {
    y = t + round( pos * h );
    makeLine( 0, y, w, y );
    profile = getProfile();
    diffArray( profile );
    maxA = Array.findMaxima( profile, 0 );
    print( "Width @" + pos + ": " +  abs(maxA[1] - maxA[0]) + "pel" );
}

The binarized image must be open.
Paste the above macro code to an empty macro window (Plugins >> New >> Macro) and run it.

Regards

Herbie

1 Like

Thank you very much for the code, Herbie.

I have a few questions:

  1. Is there a way to display the lines that are being measured (say at the 25%, 50%, and 75% increments)?
  2. What is the ‘pel’ that is returned with the measured values?
  3. Are the measured values in the same scale as the image?

All the best

Well Justin,

a lot is possible in the virtual world of informatics …

  1. One may draw the horizontal lines as overlays.
  2. pel stands for pixels,
  3. but I guess that a given scale of the image is respected.

I shall try to introduce the overlayed lines and test the scale question.

Regards

Herbie

Thank you, Herbie, I gather that a lot is possible in this realm and with the right help!

Here is an example image with a set millimeter scale in case that helps.

Thanks again

base_hi_side_hi_10032017_sillhouette

Justin,

here is the ImageJ macro code that draws the horizontal lines as red overlays:

run("Overlay Options...", "stroke=red width=0 fill=none set hide");
imgHalfHeight = 0.5 * getHeight();
if ( getPixel( 0, imgHalfHeight ) > 0 ) { setBackgroundColor(255, 255, 255); } else { setBackgroundColor(0, 0, 0); }
doWand( 0, imgHalfHeight );
run("Crop");
run( "Select None" );
objHeight = getHeight();
objWidth = getWidth() + 2;
run( "Canvas Size...", "width=" + objWidth + " height=" + objHeight + " position=Center" );
horzWidth( objWidth, objHeight, 0.25 );
horzWidth( objWidth, objHeight, 0.5 );
horzWidth( objWidth, objHeight, 0.75 );
run("Select None");
exit();

function diffArray( a ) {
    for ( i = 1; i < a.length; i++ ) {
        a[i-1] = abs( a[i] - a[i-1] );
    }
    a[a.length-1] = 0;
}
function horzWidth( w, h, pos ) {
    y = round( pos * h );
    makeLine( 0, y, w, y );
    run("Add Selection...");
    profile = getProfile();
    diffArray( profile );
    maxA = Array.findMaxima( profile, 0 );
    objW = abs(maxA[1] - maxA[0]);
    toScaled(  objW );
    getPixelSize(unit, pw, ph );
    print( "Width @" + pos + ": " + objW + unit );
}

I changed my code a bit and I hope that the cropping doesn’t bother you. You may work with copies of your image.

Overlays are non-destructive and can be shown and hidden. They are stored with the image if it is saved in TIF-format. Otherwise you may flatten the overlay which makes it permanent.

The scale appears to be respected now.

Please make sure that a single object is present in the image. The example image shows dirt, i.e. many disturbing little objects! Furthermore, the scale is lost in the sample JPG-image.

I hope that you don’t use JPG-compressed images because in general they are not binary.

Regards

Herbie

1 Like

Justin,

here is the result from your sample image:
Result

The three widths are 1253pel, 1878pel, and 1771pel.
(As mentioned before, the scale was lost.)

HTH

Herbie

Thanks, Herbie.

In the image you sent (and having run the code), I notice that the red lines extend beyond the perimeter of the object. Is the measurement returned that of the red line’s maximum length, or the length of the line between the perimeter’s boundaries? The latter is what I am concerned with.

I am using TIFF files, but for some reason, the forum uploader wouldn’t allow me to upload them (huh).

Good day Justin,

I think it is evident that all three lines cross the total image width and that the three object widths are different. Consequently, the object widths are just what you’re after.

If you should prefer the lines drawn within the object, then this can of course be done.

For uploading to the Forum, you may use the PNG-format but I don’t think it will keep the ImageJ scale information.

Regards

Herbie

Justin,

here is a streamlined macro version that draws the horizontal lines (in green) only within the object. Furthermore, it is more robust to dirt and spots in the object surround. It requires that the point in the exact center of the image lies within the object, which of course holds for the sample image.

// ImageJ macro //
run( "Overlay Options...", "stroke=green width=1 fill=none set hide" );
getPixelSize( u, w, h );
h = 0.5 * getHeight();
w = 0.5 * getWidth();
if ( getPixel( w, h ) > 0 ) { setBackgroundColor(0,0,0); } else { setBackgroundColor(255,255,255); }
doWand( w, h );
run( "Crop" );
run( "Select None" );
h = getHeight();
w = getWidth() + 4;
run( "Canvas Size...", "width=" + w + " height=" + h + " position=Center" );
for ( i=1; i<4; i++ ) {
    horzObjectWidth( w, h, 0.25*i, u );
}
exit();

//  ImageJ macro functions //
function horzObjectWidth( ww, hh, pos, uu ) {
    y = round( pos * hh );
    makeLine( 0, y, ww, y );
    profile = getProfile();
    diffArray( profile );
    maxA = Array.findMaxima( profile, 0 );
    makeLine( maxA[0], y, maxA[1], y );
    run( "Add Selection..." );
    run( "Select None" );
    ww = abs( maxA[0] - maxA[1] );
    toScaled( ww );
    print( "Width @" + pos + ": " + ww + uu );
}
function diffArray( a ) {
    for ( i = 1; i < a.length; i++ ) {
        a[i-1] = abs( a[i] - a[i-1] );
    }
    a[a.length-1] = 0;
}

Result

Have success

Herbie

1 Like

3 posts were split to a new topic: Uploading IJ tifs to IJ forum error

Hi Herbie,

Sorry for the delay in replying to you and thanks once again for the code update.

I’ve run your code with a jpeg file (no scale adjustment, scale in pixels) and the same file in TIFF format (scale adjusted to x pixels / mm) and the results are different. In one case the green lines plot on the image, in the other they are off-set to the right of the image. Of course, this effects the measurements. here is a link to the .tff version of my previous image with mm scale set in ImageJ (https://drive.google.com/file/d/0Bw4tqC-df9ScaDhZSUVCc1JtTU0/view?usp=sharing).

Is the reasons this occurs because your code is looking for the image pixel size (e.g. “getPixelSize( u, w, h )”)? And if so, can the code be adjusted to account for images that have a different scale?

All the best

Here is an example of the tiff file in mm scale processed with your code.

Dear,

please be so kind and read my posts carefully:
I wrote:
JPG-compressed images are in general not binary-valued.

From the beginning you said that your images are binary.

Don’t use JPG-compressed files or make them binary before you run my macro.
This has nothing to do with the scale setting that works perfectly.

Regards

Herbie

Make sure that your image doesn’t have an inverted LUT.

Here is what I get with your latest sample image after having removed the inverted LUT:
base_hi_side_hi_10032017_sillhouette

Width @0.25: 62.5238mm
Width @0.5: 93.7109mm
Width @0.75: 88.3717mm

Regards

Herbie

Thanks, Herbie.

I was using a tiff file, not a JPG file, but you were right that the inverted LUT was the problem. I can now replicate your results, thanks.

Which part of the code would I adjust to produce a results table showing, for example, width at 10% increments? I understand that “horzObjectWidth( w, h, 0.25*i, u )” controls the width increments.

Justin,

you wrote:

I’ve run your code with a jpeg file […]

Regards

Herbie

Sorry for the confusion, Herbie. The problem is fixed with your suggestion to remove the inverted LUT.

Justin,

this is the last gratis version of the macro that I code for you:

// ImageJ macro //
n = 9; // number of horizontal width measurements
run( "Overlay Options...", "stroke=green width=1 fill=none set hide" );
getPixelSize( u, w, h );
h = 0.5 * getHeight();
w = 0.5 * getWidth();
if ( getPixel( w, h ) > 0 ) { setBackgroundColor(0,0,0); } else { setBackgroundColor(255,255,255); }
doWand( w, h );
run( "Crop" );
run( "Select None" );
h = getHeight();
w = getWidth() + 4;
run( "Canvas Size...", "width=" + w + " height=" + h + " position=Center" );
n++;
inc = round( h / n );
for ( i=1; i<n; i++ ) {
	horzObjectWidth( w, inc*i, i/n, u );
}
exit();
//  ImageJ macro functions //
function horzObjectWidth( ww, y, fract, uu ) {
	makeLine( 0, y, ww, y );
	profile = getProfile();
	diffArray( profile );
	maxA = Array.findMaxima( profile, 0 );
	makeLine( maxA[0], y, maxA[1], y );
	run( "Add Selection..." );
	run( "Select None" );
	ww = abs( maxA[0] - maxA[1] );
	toScaled( ww );
	print( "Width @" + fract + ": " + ww + uu );
}
function diffArray( a ) {
	for ( i = 1; i < a.length; i++ ) {
		a[i-1] = abs( a[i] - a[i-1] );
	}
	a[a.length-1] = 0;
}

The first line sets the variable n to the number of horizontal width measurements.

n = 3 for the originally defined task of three widths (your first post)
n = 9 for 10% increments

Enjoy

Herbie