Crop, Scale, Origin, Distance

Below is the image I am attempting to analyze. I am new here and trying to track some data on the image below and it’s center of force. This will be done 1000s of times. I am hopeful that I can get some help writing a macro.

I would need to:

  • Crop the area of force (colored pixels - circle) applied
  • Scale the image to the diameter of that circle to 36mm
  • Find the origin/centroid of that circle
  • Measure the distance from the cross hairs to the origin.

I can do this pretty easily with the concentric circle macro and the centroid measurement and it only takes a couple of minutes. Over the next year I could be doing this 1000s of times and may also pass this off to an operator/maintenance tech. But I need to make sure it is done the same way every time.

Hi @benh937,

Converting this image to grey (‘8-bit’) lets the cursor stand out, so that is easy.

The current image is a jpg, with many colours and compression artifacts, so using a TIFF, an 8-bit colour image or a png will make things easier.

The sample image has a border, which for the thousands of images is probably not there?

Probably your colours have a specific weight factor. I guess red is the highest value, the conversion to 8-bit just doesn’t give the red pixels the highest grey value. So you have to convert the colour table to corresponding monochrome weight factors.

You can do this by creating a custom lut which you store for later use, load an image, load that lut and use ‘apply lut’.

An unweighted measurement (e.g. of a mask) of the center of mass gives the centroid/origin (I guess you mean the middle).

If not, (given the dark vertical stripe 5 pixels from the left), use a threshold and find the ROI, then get the Convex Hull of that selection. Redo the measurment of unweighted pixels to find the origin.

Then you can have a threshold just above background to find the object (‘circle’). Measuring the integrated density then gives you the center of force.

In short: it seems to be possible to write a macro so you don’t have to bother an operator or maintenance tech with this boring job.

Hi @benh937,

As @eljonco points out, using jpg is not the best choice for image processing due to compression artifacts. Also, the leftover of the containing window border should be better left out.

Here’s quick macro that accomplishes what you were looking for. You should modify it to suit your specific needs.

// crop the image [specific to image example]
makeRectangle(47, 41, 590, 587);
//**********************************************

run("Duplicate...", "title=temp_img");
run("8-bit");

// threshold the image (values chosen according to the color LUT used)
setThreshold(15, 255);

//isolate circle
run("Create Selection");
run("Convex Hull");
run("Fit Ellipse");
resetThreshold();

//get measurements form the ellipse
List.setMeasurements();
centroidX=List.getValue("X");
centroidY=List.getValue("Y");
MajorDiam=List.getValue("Major");
MinorDiam=List.getValue("Minor");

//adding the ellipse and center to overlay for visuals
Overlay.addSelection;
makeSelection("point", Array.concat(centroidX), Array.concat(centroidY));
Overlay.addSelection;
run("Select None");

//calibrate image
pixelSize = 36/((MajorDiam+MinorDiam)/2) //circle is 36 mm in diameter, average diameter is used.
setVoxelSize(pixelSize, pixelSize, 1, "mm");

//detect cursor (using the same image)
run("32-bit"); //convert to 32 bit to take advantage of negativa values in convolution kernel
run("Convolve...", "text1=[1 -1 1\n-1 -1 -1\n1 -1 1] normalize"); // filter to enhance a thin crosshair (gets more negative when crossng lines are black)
run("Find Maxima...", "prominence=10000 light output=[Point Selection]"); //find the lowest point

//get the cursor coordinates
getSelectionCoordinates(xpoints, ypoints);
cursorX=xpoints[0];
cursorY=ypoints[0];

//make line and get the distance (+ visuals)
makeLine(centroidX, centroidY, cursorX, cursorY);
List.setMeasurements;
print("Distance to cursor:", List.getValue("Length"), "mm");

Overlay.addSelection;
Overlay.show;


Cheers,

Nico

1 Like

Wow! Thanks alot on this! When I try to run the macro it states that “Polygonal or point selection required”. This happened on both jpeg and tiff images.

When looking at your code I noticed that running “convex hull” was before “Fit Ellipse”. Simply flipping these items worked.

Thank you so much for your help!

// isolate circle

run(“Create Selection”);

run(“Fit Ellipse”);

run(“Convex Hull”);

resetThreshold();

Glad to know that it suits your needs.

The problem you saw with the convex hull command probably arrises from using a slightly outdated version of ImageJ. The order of those operations does matter for the final result. Performing them in reverse order produces a slightly off-center circle (at least in the sample image, due to the missing vertical column on the left of the circle).

You should update ImageJ from the menu (Help > Update ImageJ…) and test the code again. You’ll notice a slight diference in the circle position and length reported. The ImageJ version I am using (within Fiji) is 1.52r. I just tested the code in an older plain ImageJ install (1.52a) and it gave me the same error, which was gone after updating as above.

Cheers,
Nico

I updated ImageJ to the correct version and it seems to be working. One thing I have noticed is at times the Center of Force calculation is skewed to one point of the cursor. See below. I believe for us this means that something is up in the “FindMaxima” command line. Any ideas?

I see. That’s probably due to jpg compression in the original image. The way I implemented the detection is quite simplistic, so I wouln’t be surprised if it’s not very robust to the noise introduced by the jpeg compression. Would you mind posting a couple of original images where you observed that behaviour so that I can try some alternatives on them?

Besides that, is there a way for you to store the captured image in a different format other than jpg? Tif would be ideal, but also png would probably be ok for this application.

Cheers,
Nico

You could threshold the image so only the cursor stands out. If you then calculate the center of mass of the cursor with ‘limit to threshold’ option in setMeasurements, you might obtain more precise results.

1 Like

Hi @eljonco !

You are quite right. Thanks for the suggestion! I was unnecessarily overcomplicating things. Simpler is better:

// crop the image [specific to image example]
makeRectangle(47, 41, 590, 587);
//**********************************************

run("Duplicate...", "title=temp_img");
run("8-bit");

// threshold the image (the lower value seems reasonable for the color LUT used)
setThreshold(15, 255);

//isolate circle
run("Create Selection");
run("Convex Hull");
run("Fit Ellipse");
resetThreshold();

//get measurements form the ellipse
List.setMeasurements();
centroidX=List.getValue("X");
centroidY=List.getValue("Y");
MajorDiam=List.getValue("Major");
MinorDiam=List.getValue("Minor");

//adding the ellipse and center to overlay for visuals
Overlay.addSelection;
makeSelection("point", Array.concat(centroidX), Array.concat(centroidY));
Overlay.addSelection;
run("Select None");

//detect cursor (using the same image)
setThreshold(210, 255);  // values selected for the cursor based on sample image
run("Create Selection");
List.setMeasurements;
cursorX=List.getValue("X");
cursorY=List.getValue("Y");

//calibrate image (done after getting cursor coordinates to avoid conversion of units)
pixelSize = 36/((MajorDiam+MinorDiam)/2) //circle is 36 mm in diameter, average diameter is used.
setVoxelSize(pixelSize, pixelSize, 1, "mm");

//make line and get the distance (+ visuals)
makeLine(centroidX, centroidY, cursorX, cursorY);
List.setMeasurements;
print("Distance to cursor:", List.getValue("Length"), "mm");

Overlay.addSelection;
Overlay.show;

@benh937, would you mind trying out this version?

Cheers!
Nico

This worked. Thank you very much. I just upgraded my machine and now we are able to check run by run drift of center of force. This is a huge help. Thanks!

1 Like

Great to know!

Good luck with your work!

Nico