Is it remotely possible to have a macro that automatically set scale from an image

spatial-calibration
macro
Tags: #<Tag:0x00007fd5422bd788> #<Tag:0x00007fd5422bd580>

#1

Is it remotely possible to have a macro that automatically set scale multiple times from an image and then use it to calculate arae and other measurement parameters ?

for example i have over a thousand of the image below, with usually three views of the same organism. i will like to use the scale bar under each image (100 microns constant) to determine the length, width and area of each image. The scale bars are always located below each image and this is fairly constant for all the other images. Any help or guidance will be appreciated.


#2

Good day Amao Abduljamiu,

nice picture!

The following ImageJ-macro should do what you want.

// macro start
n = 3; // number of views per image
ref = 100; // real bar length in microns
run( "Set Scale...", "distance=0 known=0 pixel=1 unit=pixel" );
run( "Set Measurements...", "area bounding feret's redirect=None decimal=2" );
//run( "Set Measurements...", "area bounding redirect=None decimal=2" );
len = newArray( n );
name = getTitle();
w = getWidth();
run( "Select All" );
setKeyDown( "alt" );
proj = getProfile();
setKeyDown( "none" );
zeroBound( proj );
proj = Array.findMaxima( proj, 0 );
hq = round( 0.25 * proj[1] );
makeRectangle( 0, proj[1]-1, w, proj[0]-proj[1]+3 );
proj = getProfile();
zeroBound( proj );
proj = Array.findMaxima( proj, 0 );
Array.sort( proj );
for ( i = 0; i < n; i++ ) { len[i] = abs( proj[i*2+1] - proj[i*2] ); }
makeRectangle( 0, 0, w, hq );
proj = getProfile();
zeroBound( proj );
proj = Array.findMaxima( proj, 0 );
Array.sort( proj );
for ( i = 0; i < n; i++ ) {
	run( "Set Scale...", "distance=" + len[i] + " known=" + ref + " unit=µm" );
	doWand( proj[i*2]-2, hq );
	run( "Measure" );
}
run( "Select None" );
IJ.renameResults( "Results in [µm] of \""+ name + "\"" );
run( "Set Scale...", "distance=0 known=0 pixel=1 unit=pixel" );
exit();
///////////////////////
function zeroBound( a ) {
	for ( i = 1; i < a.length; i++ ) {
		if ( (a[i] > 0 && a[i-1] == 0) || (a[i-1] > 0 && a[i] == 0) ) a[i-1] = 1; else a[i-1] = 0;
	}
	a[a.length-1] = 0;
}
// macro end

Paste the above macro code to an empty macro window (Plugins >> New >> Macro) and run it.

The macro assumes that the background has value zero as in the provided sample image.
The values in the results table numbered “1, 2, 3” are for the views “left middle right” respectively.
The desired three measures are in the correspondigly named columns.
The columns “BX” and “BY” are irrelevant.
If you don’t need the Feret measures then please un-comment the second “Set Measurements…” command.
For an explanation of the Feret measures please consult the ImageJ-manual:
https://imagej.nih.gov/ij/docs/guide/146-30.html#toc-Subsection-30.7

Regards

Herbie


#3

Good Day Herbie and a big thank you !
You brought my imagination to life !!!

Thanks Herbie for such a wonderful macro and thanks for your kind comment on the image quality… this is unbelievable, i don’t know how you figured out even things i had in mind but did not specify in my earlier post… The BX and BY are relevant for me because they give idea of the bounding box defining the starting points where the measurement began. And adding the feret’s diameter makes volume calculations possible.

The code worked well for most of my images and I’ve added “shape” to the set measurement. For the few images where the code did not work, i noticed that the the scales where not on a straight line. I can manually modify this for each of the images but it will be nice if the macro can figure this out too.

i will like to ask for additional help, I’ve tried to follow the macro code you wrote but it appears its out of my league :joy:, . I’ve tried to use it in a batch process but got error because of some scale placement on some of the images. i will like to add the following commands to the macro

at the beginning

extension = ".tif"; // so as to read only images with tif extension
  dir1 = getDirectory("Choose Source Directory "); // i've added the macro you made to batch 
  dir2 = getDirectory("Choose Destination Directory ");

some where in the middle where the code is evaluating each image

 run("Make Binary");
 run("Outline");

at the end

saveAs(extension, dir2+name);

The idea is that, when the code finishes, i should be able to go through all saved images and find the ones that were not successful based on the outline/overlay that would be generated.

This was my attempt to modify the macro you wrote, but it didn’t work the way it it for single instances

// This macro processes all the images in a folder and any subfolders.
extension = “.tif”;
dir1 = getDirectory("Choose Source Directory ");
dir2 = getDirectory("Choose Destination Directory ");
setBatchMode(true);
n = 0;
processFolder(dir1);

function processFolder(dir1) {
list = getFileList(dir1);
for (i=0; i<list.length; i++) {
if (endsWith(list[i], “/”))
processFolder(dir1+list[i]);
else if (endsWith(list[i], extension))
processImage(dir1, list[i]);
}
}

function processImage(dir1, name) {
open(dir1+name);
print(n++, name);
// Added the macro made by Herbie here
// macro start
ref = 100; // real bar length in microns
run( “Set Scale…”, “distance=0 known=0 pixel=1 unit=pixel” );
run( “Set Measurements…”, “area bounding shape feret’s redirect=None decimal=2” );
len = newArray( 3 );
name = getTitle();
w = getWidth();
run( “Select All” );
setKeyDown( “alt” );
proj = getProfile();
setKeyDown( “none” );
zeroBound( proj );
proj = Array.findMaxima( proj, 0 );
hq = round( 0.25 * proj[1] );
makeRectangle( 0, proj[1]-1, w, proj[0]-proj[1]+3 );
proj = getProfile();
zeroBound( proj );
proj = Array.findMaxima( proj, 0 );
Array.sort( proj );
barLengths( proj, len );
makeRectangle( 0, 0, w, hq );
proj = getProfile();
zeroUp( proj );
proj = Array.findMaxima( proj, 0 );
Array.sort( proj );
for ( i = 0; i < 3; i++ ) {
run( “Set Scale…”, “distance=” + len[i] + " known=100 unit=µm" );
doWand( proj[i]-2, hq );
run( “Measure” );

}
run( “Select None” );
IJ.renameResults( “Results in [µm] of “”+ name + “”” );
run( “Set Scale…”, “distance=0 known=0 pixel=1 unit=pixel” );
// i added the following ( i’m not sure if this is the right spot)
run(“Duplicate…”, " ");
run(“Find Edges”);
run(“Convert to Mask”);
run(“Fill Holes”);
run(“Outline”);
// end of my additions
exit();
///////////////////////
function zeroBound( a ) {
for ( i = 1; i < a.length; i++ ) {
if ( (a[i] > 0 && a[i-1] == 0) || (a[i-1] > 0 && a[i] == 0) ) a[i-1] = 1; else a[i-1] = 0;
}
a[a.length-1] = 0;
}
function zeroUp( a ) {
for ( i = 1; i < a.length; i++ ) {
if ( (a[i] > 0 && a[i-1] == 0) ) a[i-1] = 1; else a[i-1] = 0;
}
a[a.length-1] = 0;
}
function barLengths( a, b ) {
j = 0;
for ( i = 0; i < 3; i++ ) {
b[i] = abs( a[j+1] - a[j] );
j += 2;
}
}
// macro end
saveAs(extension, dir2+name);
close();
}
saveAs(“Results”, “C:\Users\hamma\Desktop\Results for batch process.csv”);

Also, in few case, i have less than 3 views while in some cases i have more, i was hoping you could help me add some conditional statement to fix this issue. Here is a folder with some sample images https://www.dropbox.com/sh/v1awerv7t1calqw/AAAoxV6FfwfPu_TGcQ_DEHLQa?dl=0


#4

Dear,

nice to hear that the macro works for some of your images and that it fails sometimes is mostly your fault because you didn’t tell us about the variability of your images. The sample image doesn’t appear to be a typical image.

Please note that I modified the macro today and it is a good idea to use the current version that allows you to change the number of views per image that I set to n = 3 for the provided sample image.

The macro assumes that the scale bars are at the same height within an image. Although it is possible to remove this restriction, I won’t do it for you because it would require a complete rewrite of the macro.

Several of your images don’t have a black background and that’s why the macro gives no or definitely wrong measures. This could be changed as well, but I won’t do it for you because it would require a complete rewrite of the macro.

Views showing concave boundaries in the upper part may not be recognized. Again this wasn’t clear from your sample image.

It is far from clear to me why you want to make the images binary at some point. You didn’t mention it before.

Finally, I won’t debug your code for you.

I must admit that due to the lack of information about the variability of images my macro is useless for the processing of all the images made accessible recently. This situation appears highly dangerous, because you will never know whether the results are correct or not.
So please don’t use my macro for any scientific analyses!

Again, this is not my fault, because my macro works as desired for the image made accessible in your first post.

Have success

Herbie


#5

Hello Herbie,

Again, thanks for your prompt reply… the macro you made is not useless, in fact the reason i’m creating the overlay is to be able to randomly check the validity of each measurement. The complaint about my images created on different background is simply not true, i place this images on same template myself from photoshop using “#00000” as the background color. The problem might be when i trieed converting to jpg so i can easily share with you…

These are composite images i made from individual SEM images myself because i had no idea its possible to measure on imagej, there might be some variations. But again, by checking the overlay, i can figure out which image wrong and which is right.

Normally, while seeking for help on any issue… it is advisable that you don’t complicate your question otherwise no one will be willing to help. But again, i appreciate your fast response it just unfortunate you’re not willing to help.

Maybe my second question is not clear, i’m trying to run the macro you made earlier in batch process but its impossible, can you help to fix this?

for cases were 3<n<3, i can easily fix this from the images… they are few cases.

Thank You in anticipation for your kind consideration, between i don’t need your help to debug. if you decide not to help further because your upset, can you refer me to some reference material on imagej macro development ?


#6

Dear,

You are right, JPEG-compression will change the contour/background, but your first image was in TIF-format. Why didn’t you post the other images as TIF?

I strongly recommend not to use my macro in the present form because there are too many aspects of your images that will cause it to fail.

What exactly do you mean by batch processing?
Run the macro on all images in a folder and save the results to the same or a different folder?

[…] it just unfortunate you’re not willing to help.

That’s interesting because I’ve already invested several gratis hours for you.
All you need now is learn how to use ImageJ and how to code ImageJ-macros:
https://imagej.nih.gov/ij/index.html

Good luck

Herbie


#7

Run the macro on all images in a folder and save the results to the same or a different folder? This is exactly what i want to do, but it fails, i have a basic knowledge of the scripting language and i’ll be able to make other adjustment myself.
i will appreciate if you can help with this…

it just unfortunate you’re not willing to help. i apologise for this statement, because my intention was to convey to you that your refusal to help me is understandable.

Again, i apologize and appreciate your help…


#8

Good day Amao Abduljamiu,

the following macro performs correctly when applied to all of the provided images (jpg-artifacts removed), except for the first object of “Oolina sp_preview” for which it gives a warning in the log file. I think the reason for this is evident: “Badly positioned scale bar”.

The results are saved as tab-delimited text files.

// ImageJ-Macro start
ref = 100; // real bar length in microns
run( "Set Measurements...", "area bounding shape feret's redirect=None decimal=2" );
srceDir = getDirectory( "Choose a source directory" ) ;
destDir = getDirectory( "Choose a destination directory" ) ;
fileList = getFileList( srceDir );
setBatchMode( true );
for ( i = 0; i < fileList.length; i++ ) {
	if ( endsWith( fileList[i], ".tif" ) ) {
		open( srceDir + fileList[i] );
		run( "Set Scale...", "distance=0 known=0 pixel=1 unit=pixel" );  
		name = File.nameWithoutExtension;
		w = getWidth();
		h = getHeight();
		qh = round( 0.3 * h );
		xP = 0; 
		do {
			run( "Set Scale...", "distance=0 known=0 pixel=1 unit=pixel" );
			doWand( xP, qh );
			getSelectionBounds( x0, y0, w0, h0 );
			doWand( x0+w0, qh );
			getSelectionBounds( x1, y1, w1, h1 );
			if ( w1 == w )  x1 = w;
			xS = 0.5 * ( xP + x0 );
			wS = 0.5 * ( x0 + x1 + w0 ) - xS;
			makeRectangle( xS, y0+h0+2, wS, h-y0-h0 );
			proj = getProfile();
			zeroBound( proj );
			proj = Array.findMaxima( proj, 0 );
			if ( proj.length == 2 ) {
				len = abs( proj[1] - proj[0] );
				//print( len );
				run( "Set Scale...", "distance=" + len + " known=" + ref + " unit=µm" );
				doWand( xP, qh );
				run( "Measure" );
			} else {
				print( "Badly positioned scalebar in file \"" + name + "\"" );
			}
			xP = x0 + w0;
		} while ( x1 < w );
		close();
		saveAs( "results", destDir + name + "_results.txt" );
		run( "Close" );
	}
}
setBatchMode( false );
exit();
///////////////////////
function zeroBound( a ) {
	for ( i = 1; i < a.length; i++ ) {
		if ( (a[i] > 0 && a[i-1] == 0) || (a[i-1] > 0 && a[i] == 0) ) a[i-1] = 1; else a[i-1] = 0;
	}
	a[a.length-1] = 0;
}
// ImageJ-Macro end

If you use this macro for any kind of scientific work, thesis or publication, you must acknowledge and clearly indicate therein that this ImageJ-macro was entirely written and provided by a member of the ImageJ-forum.

Regards

Herbie


#9

WOW !!! again thank you for being patient and kind…
and thank your for all the hours dedicated helping me, you restore my hope in humanity !

A BIG thank you for the macro, i take the issue of acknowledgement seriously and will duly acknowledge the effort as advised.

The macro works very well now, i’ve adjusted the position of the scale for the oolina file and it now fine… Again Thank you.

Just a question, though not very important at the moment… the result are generated for individual files, is it possible to append them to a single list ?

THANK YOU


#10

Dear,

this

[…] is it possible to append them to a single list ?

is really easy to achieve and you should be able to perform the change but it’s always easier to ask …

If you write all results from image files in a folder to a single Results-window, you need to specify the image files by their names which is done by adding “display” to the “Set Measurements…”-command.

Here is the macro code:

// ImageJ-Macro start
ref = 100; // real bar length in microns
run( "Set Measurements...", "area bounding shape feret's display redirect=None decimal=2" );
srceDir = getDirectory( "Choose a source directory" );
destDir = getDirectory( "Choose a destination directory" );
fileList = getFileList( srceDir );
setBatchMode( true );
for ( i = 0; i < fileList.length; i++ ) {
	if ( endsWith( fileList[i], ".tif" ) ) {
		open( srceDir + fileList[i] );
		run( "Set Scale...", "distance=0 known=0 pixel=1 unit=pixel" );
		name = File.nameWithoutExtension;
		w = getWidth();
		h = getHeight();
		qh = round( 0.3 * h );
		xP = 0; 
		do {
			run( "Set Scale...", "distance=0 known=0 pixel=1 unit=pixel" );
			doWand( xP, qh );
			getSelectionBounds( x0, y0, w0, h0 );
			doWand( x0+w0, qh );
			getSelectionBounds( x1, y1, w1, h1 );
			if ( w1 == w )  x1 = w;
			xS = 0.5 * ( xP + x0 );
			wS = 0.5 * ( x0 + x1 + w0 ) - xS;
			makeRectangle( xS, y0+h0+2, wS, h-y0-h0 );
			proj = getProfile();
			zeroBound( proj );
			proj = Array.findMaxima( proj, 0 );
			if ( proj.length == 2 ) {
				len = abs( proj[1] - proj[0] );
				run( "Set Scale...", "distance=" + len + " known=" + ref + " unit=µm" );
				doWand( xP, qh );
				run( "Measure" );
			} else {
				print( "Badly positioned scalebar in file \"" + name + "\"" );
			}
			xP = x0 + w0;
		} while ( x1 < w );
		close();
	}
}
saveAs( "results", destDir + File.getName( srceDir ) + "-Results.txt" );
setBatchMode( false );
exit();
///////////////////////
function zeroBound( a ) {
	for ( i = 1; i < a.length; i++ ) {
		if ( (a[i] > 0 && a[i-1] == 0) || (a[i-1] > 0 && a[i] == 0) ) a[i-1] = 1; else a[i-1] = 0;
	}
	a[a.length-1] = 0;
}
// ImageJ-Macro end

HTH

Herbie


#11

Thank You Again Herbie,

Sadly this new macro doesn’t give any output… i tried several times and nothing happens. I think i will stick to the previous macro and append manually…

I’m sincerely grateful for all your efforts, patience and kindness.

THANK YOU

Amao


#12

Dear,

I’ve just copied the code from the forum as is and it works perfectly.

During the macro action you can observe how the Results-window is populated. It is finally saved as a single file named after the folder in which the images reside like this:

destinationDir/Foldername-Results.txt

I have no idea what goes wrong on your side. (Actually mine, see below.)

Please try again

Herbie

EDIT:
Because I work with your JEPG-files there is a JPEG-selector.
I’ve just changed the code and it should now work for your TIF-files.
Sorry for the confusion!


#13

Works Like a charm !!!

A big THANK YOU to you !!!

THANK YOU… THANK YOU …THANK YOU … You’re far too kind !


#14

For the archives: this question was cross-posted on StackOverflow where some additional discussion occurred.


#15

Thanks @ctrueden for your help and for pointing that out…