Measure the length of a single simple bone in 3D, advice request

Hi
I am rather a newbie to 3D analysis, I am more comfortable with 2D.

I have mouse whole body CT scans from DCM stacks that I can happily view with the 3D viewer plugin.

I only want to measure the lengths of limb bones (ulna and tibia). I have been using the landmark (.points file) in the 3D viewer plugin, see below. However, I wondered if there is an easier, quicker and more reproducible way to do this.

Is there for instance a way I can click near the middle of a bone of interest and have an object / volume propagate out from this point to find the both ends of the bone?

This the sort of thing I have done in 2D but am a bit lost in 3D. I am happy to try other plugins or programs.

Cheers

Guy

I’ve found it to be much easier to place the points on slices than the 3D viewer. Just scroll through the stack and click on the extrema of the bone you care about. This macro is handy to speed up that process:

//ImageJ macro to find the distance between two points and the midslice of a stack.
//Michael Doube

var bones = newArray("other bone", "scapula", "humerus", "radius", "ulna", "metacarpal", "pelvis", "femur", "tibia", "fibula", "metatarsal");

macro "MidPoint [g]" {
name = getTag("0010,0010");
title = getTitle();
getVoxelSize(width, height, depth, unit);
//depth = getNumericTag("0018,0050");
print("Pixel Scale:",width, height, depth, unit);
//Initialise the results window
if (isOpen("Results")) row = nResults;
else row = 0;
//ask us which bone we are looking at
bonedef = "scapula";
for (n=0; n<lengthOf(bones);n++)  if (matches(title, ".*"+bones[n]+".*")) bonedef = bones[n];
Dialog.create("Options");
Dialog.addChoice("Bone: ", bones, bonedef);
Dialog.show();
bone = Dialog.getChoice();
for (n=0; n<lengthOf(bones); n++) {
	if(matches(bones[n], ".*"+bone+".*")) bonen = n;
}

//Locate important landmarks in the stack
//with left click plus shift then left click plus ctrl

leftButton=16;
shift=1;
rightButton=23;
ctrl=2;
x2=-1;
y2=-1;
z2=-1;
flags2=-1;

//set the last value to be determined to be the loop escape value
endz = -1;

while (endz==-1) {
          getCursorLoc(x, y, z, flags);
          if (x!=x2 || y!=y2 || z!=z2 || flags!=flags2) {

//capture the start position with left click plus shift
		if (flags == 17){
			startx = x*width;
			starty = y*height;
			startz = z*depth;
			//voxel counting starts from 0 while slice number starts from 1...
			slicea = z+1;
			print("Start pixel position captured",x, y,z,"\n");
		}		
//capture the end point with left click plus ctrl
		if (flags == 18){
			endx = x*width;
			endy = y*height;
			endz = z*depth;
			sliceb = z+1;
			print("End pixel position captured",x,y,z,"\n");
		}		
	}
	x2=x; y2=y; z2=z; flags2=flags;
	wait(10);
}

distance = sqrt(sqr(startx-endx)+sqr(starty-endy)+sqr(startz-endz));
perpdist = abs(slicea-sliceb)*depth;
midslice = floor((slicea+sliceb)/2);
setSlice(midslice);
print("Distance between clicks:",distance, unit);
print("Slice A is:", slicea);
print("Slice B is:", sliceb);
print("Mid slice is:", midslice);

setResult("Label",row,name);
setResult("Bone Code", row, bonen);
setResult("SliceA",row,slicea);
setResult("SliceB",row,sliceb);
setResult("MidSlice",row,midslice);
setResult("Length ("+unit+")",row,distance);
setResult("Depth ("+unit+")",row,perpdist);
updateResults();
}

function sqr(n) {
	return n*n;
}

//DICOM meta-info handling code

// This function returns the numeric value of the 
// specified tag (e.g., "0018,0050"). Returns NaN 
// (not-a-number) if the tag is not found or it 
// does not have a numeric value.

function getNumericTag(tag) {
	value = getTag(tag);
	if (value=="") return NaN;
	index3 = indexOf(value, "\\");
	if (index3 > 0) value = substring(value, 0, index3);
	return value;
}

// This function returns the value of the specified 
// tag  (e.g., "0010,0010") as a string. Returns "" 
// if the tag is not found.

function getTag(tag) {
	info = getImageInfo();
	index1 = indexOf(info, tag);
	if (index1 == -1) return "";
	index1 = indexOf(info, ":", index1);
	if (index1 == -1) return "";
	index2 = indexOf(info, "\n", index1);
	value = substring(info, index1+1, index2);
	return value;
}

You’ll need to ‘install’ the macro to have it appear in your menu and be triggered by a keypress of [g]. If you don’t need all the DICOM or labelling stuff, just edit the macro. You can see the effect of your edits very quickly if you open the macro in the macro editor, then save and install each time you make a change.

1 Like

Dear Michael Doube

Thank you so much. This looks super useful. I will spend the day working with your Macro, is it used in a scientific publication.

Thanks

Guy

Guy

Dear Michael Doube
I have been playing around with your macro and and an image sequence, I suspect you are right that this is easier that using the 3D plugin. It is great.

Do you have a way to be able to place your landmarks from your macro on a 3D model? One way I can imagine is to modify the macro so XZY coordinates are also recorded as part of the results file and then use that to generate a .points file from the results .csv (maybe outside imageJ). The .points could be loaded by somebody wanting to see your placement in the 3D viewer plugin. Does that sound like a reasonable plan ?

Don’t I am not asking you do do this for me.

Cheers

Guy

There might be some ways using the 3D Viewer or SciView APIs to display e.g. point ROIs from a stack on a 3D visualisation of the stack. You may have to delve into Java to get it to go.

I wrote something like this several years ago but it never made it into the main release.

1 Like

Thanks I will look into you approach it but this looks rather complex for my skills , I think I will try and text parse a .points file when I get to this.