Placing a point at divided lengths of a line

Hi,

I am new to ImageJ and I am trying to write a tool to place multiple points on a measured line at n divided segments of that measured line. However, I am not sure how to approach this problem… Any help would be great!

Thank you!

Hi Junshen,

Welcome to the ImageJ community!

I am only familiar with the ImageJ macro language, you can use the macro recorder to find out how to script functions you use in ImageJ.

I’ve made a script that does what you asked for a straight line:

getSelectionCoordinates(x, y); 				//returns array of x and y coordinates for selection (in this case a straight line has only two points and four coords)

ylength = y[0] - y[1]; 						//finds the length of the line in the y axis by taking the first y coordinate from the second
xlength = x[0] - x[1];						//finds the length in the x axis	the same way
n = getNumber("How many segments?", 10); 	//asks how many segments to break the line in to (points are plotted in between each segment and the ends of the line)	
yinc = (ylength/n); 						//the increment in the y axis between each point's y coordinate
xinc = (xlength/n); 						//the increment in the x axis between each point's x coordinate
for (j=0; j<=n; j++) { 						//a for loop to plot points, repeats for the total number of segments + 1 (as 0 is counted)
	xcoord = x[0] - (xinc*j); 				//calculates the x coordinate of the point to be plotted by subtracting the increment value (multiplied by the current segment number defined by 'j') from the first x coord of the line
	ycoord = y[0] - (yinc*j); 				//calculates the y coordinate as above from the top-most point on the line
	setKeyDown("shift"); 					//required to plot multiple points on the same selection (otherwise will only plot one point and overwrite them every time it loops)
	makePoint(xcoord, ycoord); 				//makes a point selection at the x and y coords calculated above
}

Let me know if that’s what you’re looking for, or if you need more explanation.

Cheers,

Rob

1 Like

Hi Rob!

Thank you for replying to my post so fast! I just tested out your script and it works perfectly. However, is there a way to overlay the points? I would like to keep them as references for measurements, or would I have to use ROI manager to keep track of them?

Thanks!

Yes, I would use the ROI manager, depending on how many lines you have and how many points you end up with. I have adjusted the script to send the points ROIs to the ROI manager and then save them in a directory of your choice. Each point ROI is saved as a separate ROI, whereas the script above actually included all points as a single ROI.

Here’s the new script:

saveDir = getDirectory("Choose directory for saving things..."); //user chooses folder to store ROIs in 
fileName = getTitle();						//takes the image name to name the ROI file at the end

getSelectionCoordinates(x, y); 				//returns array of x and y coordinates for selection (in this case a straight line has only two points and four coords)

ylength = y[0] - y[1]; 						//finds the length of the line in the y axis by taking the first y coordinate from the second
xlength = x[0] - x[1];						//finds the length in the x axis	the same way
n = getNumber("How many segments?", 10); 	//asks how many segments to break the line in to (points are plotted in between each segment and the ends of the line)	
yinc = (ylength/n); 						//the increment in the y axis between each point's y coordinate
xinc = (xlength/n); 						//the increment in the x axis between each point's x coordinate
for (j=0; j<=n; j++) { 						//a for loop to plot points, repeats for the total number of segments + 1 (as 0 is counted)
	xcoord = x[0] - (xinc*j); 				//calculates the x coordinate of the point to be plotted by subtracting the increment value (multiplied by the current segment number defined by 'j') from the first x coord of the line
	ycoord = y[0] - (yinc*j); 				//calculates the y coordinate as above from the top-most point on the line
	makePoint(xcoord, ycoord); 				//makes a point selection at the x and y coords calculated above
	Roi.setName("point" + j);				//renames the selection to something that can be distinguished from the rest
	roiManager("Add");						//add the point to the roi manager
}

roiManager("save", saveDir + fileName + "_ROIs.zip"); //saves all the point ROIs for this line to a .zip file in the folder specified at the start

exit("Macro finished"); //the ROIs can be reloaded by dragging the .zip in to imagej

If any of it needs explaining, let me know. The built-in macro functions webpage is my resource for anything I do, it’s so useful!

You could, for instance, adapt the script to repeat for each new line drawn by including a ‘waitForUser’ function to draw a new line and just repeat over and over, saving the point ROIs as ‘line x point y’ where x is the current line # and y is the current point #. The world is your oyster.

Cheers,

Rob

1 Like

So would it be possible to add adjustable straight lines center fixed onto the points? Also, thank you for all your help today!

Jason

I didn’t think it was possible, but it is. If you still want the point ROIs, you’d have to edit the script below. This script asks you to make a line, then how many segments you want to split it in to. It then divides the line in to those segments and plots a line centred on each end of those segments. The lines are saved as ROIs and can be subsequently loaded, the ROIs are saved by indexing the image name, line and segment. To adjust the lines, click on one of them in the ROI manager and use ‘ctrl’ or ‘command’ (mac) to adjust the line while keeping it centred.

saveDir = getDirectory("Choose directory for saving things..."); //user chooses folder to store ROIs in 
fileName = getTitle();						//takes the image name to name the ROI file at the end
fileNameEdit = replace(fileName, ".tif", "");
roiManager("reset");

for (k=1; k<9999; k++) {
setTool("Line");
waitForUser("Make line " + k);
getSelectionCoordinates(x, y); 				//returns array of x and y coordinates for selection (in this case a straight line has only two points and four coords)
getDimensions(width, height, channels, slices, frames); //returns dimensions of the current image

ylength = y[0] - y[1]; 						//finds the length of the line in the y axis by taking the first y coordinate from the second
xlength = x[0] - x[1];						//finds the length in the x axis	the same way
n = getNumber("How many segments?", 10); 	//asks how many segments to break the line in to (points are plotted in between each segment and the ends of the line)	
yinc = (ylength/n); 						//the increment in the y axis between each point's y coordinate
xinc = (xlength/n); 						//the increment in the x axis between each point's x coordinate
for (j=0; j<=n; j++) { 						//a for loop to plot points, repeats for the total number of segments + 1 (as 0 is counted)
	xcoord = x[0] - (xinc*j); 				//calculates the x coordinate of the point to be plotted by subtracting the increment value (multiplied by the current segment number defined by 'j') from the first x coord of the line
	ycoord = y[0] - (yinc*j); 				//calculates the y coordinate as above from the top-most point on the line
	xend = width/4;							//calculates x coord for end of adjustable line based on size of open image (so line isn't too big)
	yend = height/4;						//calculates y coord for end of adjustable line as above
	makeLine(xend, 0, xend, yend);			//makes adjustable line at coordinates specified above
	lineLength = yend;						//the length of the adjustable line is yend (as the line starts at y coord 0)
	lineY = ycoord - (yend/2);				//calculates the y coord of the first point of the adjustable line (e.g. the centre must pass through the point between the segments you've defined, so add on half of the line to get the coordinate to start the line at) The line is straight, so we only have to calculate this for one coordinate (the variable one).
	Roi.move(xcoord, lineY);				//moves the line ROI to calculated coordinate above
	Roi.setName("line" + k + "_segment" + j); //renames the selection to something that can be distinguished from the rest
	roiManager("Add");						//adds the adjustable line to the roi manager	
}

roiManager("save", saveDir + fileNameEdit + "_line_" + k + "_lineROIs.zip"); //saves all the ROIs for this line to a .zip file in the folder specified at the start
roiManager("reset");						//resets ROI manager to clear previous ROIs

status = getBoolean("Finished?");
if (status == 1) exit("Macro finished"); 	//the ROIs can be reloaded by dragging the .zip in to imagej
						
}
1 Like

Hi Rob, I tested out your line script and it works well, however I want to also add an overlay which labels what the length of each line is. How would I go about doing this? I was thinking there would be a macro that would look for the lines and ask what I want to label, and add the units and length to the end of what I type in.

Thanks,
Jason

Hi Jason,

I have come up with a solution to your problem. The best way I could think of doing it was to essentially measure each line ROI in turn and rename the label of the ROI to the length of that ROI (although you lose which segment it is from). For this to work, the settings for labels needs to be changed in the ‘more >>’ tab of the ROI manager, towards the bottom of the list is ‘labels…’. The settings should be changed to ‘use names as labels’ and ‘draw background’ (so you can always see the text over the image).

A script for this would look as follows:

roiCount = roiManager("count");            // counts the number of ROIs in the ROI manager window

for (i=0; i<roiCount; i++) {			   //loop for each ROI
	roiManager("select", i);			   //selects first ROI in the ROI manager
	roiManager("update");				   //updates the ROI to what you have adjusted it to
	run("Measure");						   //same as pressing 'm' key, measures the line
	lengthLabel = getResult("Length", i);  //retrieves the 'Length' results from the results window in row i (currently selected ROI)
	roiManager("rename", lengthLabel);	   //renames the currently selected ROI as the length
}

N.B. I’m not sure how to add the scaled units to this label (the labelled length is measured in the scaled units though, so this shouldn’t be a problem if you know the units). You will also need to manually save the .zip file by selecting all the ROIs and navigating to ‘more>>’ and ‘save…’ in the ROI manager window.

I originally made a longer macro (see below) to use with the results of the macro in post 6 of this thread. It assumes the user first goes through all of their images using that macro to make lines/segments with ROIs saved. Then once all the lines are made, the new macro is used to open each image and it will pick the ROI file corresponding to the image (as long as it is in the same folder) and ask you how many segmented lines were drawn on it (if only one line is ever drawn, the script can be edited easily for this). The script then asks the user to adjust all the lines (as with the shortened script). Here is this longer script:

filePath = File.openDialog("Choose file to analyse..."); 
open(filePath);
filePathEdit = replace(filePath, ".tif", "");

lines = getNumber("How many segmented lines for this file?", 1);

for (k=1; k<=lines; k++) {
	roiManager("open", filePathEdit + "_line_" + k + "_lineROIs.zip");
	roiCount = roiManager("count");            // counts the number of ROIs in the ROI manager window
	waitForUser("Adjust ALL lines as necessary");
		
	for (i=0; i<roiCount; i++) {			   //for loop for each ROI
		roiManager("select", i);			   //selects first ROI in the ROI manager
		roiManager("update");				   //updates the ROI to what you have adjusted it to
		run("Measure");						   //same as pressing 'm' key, measures the line
		lengthLabel = getResult("Length", i);  //retrieves the 'Length' results from the results window in row i (currently selected ROI)
		roiManager("rename", lengthLabel);	   //renames the currently selected ROI as the length
	}
		
	roiManager("save", filePathEdit + "_line_" + k + "_lineROIs.zip");
	roiManager("reset");
	run("Clear Results");
}

Note: I’ve also made an edit to the macro in post 6 to alter the naming of the ROI file so it reads in to this macro properly.

Any problems, or questions, let me know.

Cheers,

Rob

Hi Rob, thank you for your help. I want to modify the segmented lines code (i.e post #6) so that when I pull on one side of the node to adjust the length, I only want it to modify that side but not the other. Right now, pulling on either side would change both sides.

Thanks,

Jason

Hi Jason,

So what you’re talking about is a segmented line (different from our ‘segment’ lines, so we don’t get confused), instead of a straight line. You can see the differences and how to create them in ImageJ here.

Using the macro recorder you can work out how to code segmented lines and work out how to place the centre of the segmented line at our anchor points between segments.

See how you get on trying to edit the code.

Best,

Rob

EDIT:

I realised that all you need to do is make the line segmented in the ‘makeLine’ part of the code, by adding in an additional set of coordinates it is no longer straight.

E.g.

    ymid = yend/2;
    makeLine(xend, 0, xend, ymid, xend, yend);