Improved Orthogonal Viewer

imagej

#1

I probably use the ImageJ “orthogonal views” option every day, but I have some ways I would like to develop it – including the ability to have more than one set of orthogonal views open at once, and contained all in a single window.

I propose to develop this orthogonal viewer myself. I am just posting to ask if there is another good orthogonal viewer that has already been made for ImageJ, and is perhaps hidden away in the Fiji plugins, or if not, available through a third party site. I would hate to duplicate work.

If not, I will head to the woodshed and let you know when it’s available.


#2

If you like the way three-view canvases are renderer in Simple Neurite Tracer (SNT), I can probably provided you with a minimum example on how to use ThreePanes (used by SNT) to display XY, XZ, and ZY views. Right now the current limitation is that it does not work with multichannel images. It would be great if you could help implementing it.

BTW, not really a true slicer, but the BAR update site provides Combine Orthogonal Views, a hacky macro that uses IJ1’s built-in ij.plugin.Slicer to render orthogonal views on the same window. There is a screenshot here.

If none of this suits your needs and you decide to start from scratch, do consider adopting SciJava while in the woodshed.


Building Fiji from source - maven-enforcer
#3

I have a macro that is kind of similar to the BAR Combine Orthogonal Views macro. Confusing code probably, but the outcome is OK for what I needed it. Produces a one window orthogonal view at a fixed xy position.

/*
 *  Macro to fullfill this task I asked for in the ImageJ-list:
 *  
 *  Dear list, 
 *  when I have a z-stack (RGB) and view this stack with the orthogonal views switched on, 
 *  I can play that stack, i.e. move through the z-stack and the yellow cross-hair is 
 *  moving in parallel in the yz and xz view. 
 *  Is there a way of saving this whole view, i.e. xy, yz and xz including the moving 
 *  yellow crosshair in one series/avi? 
 *
 * The idea how to solve this (use reslice and combine stacks) is from the reply from Tiago Ferreira.
 *  
 * Martin Hoehne 2012-10-08
 * 
 * - added color variables [2013-10-21, Martin Hoehne]
 * - changed the part where the x and y coordinates are read to work again with the current version of
 *   ImageJ (1.51g). [2016-10-06, Martin Hoehne]
 * - version 11b: removed all rgb stuff. If original is 8-bit, all intermediate images and the result image will be 8-bit as well  
 * - version 11c: uses Stack.stopOrthoViews instead if closing and reopening the image
 * - [2017-03-16] no new version, but I realized that removing rgb stuff is detrimental for 2ch hyperstacks. In this case use version 11
 */

// define z-color globally. Needs to be available in the macro and in a function
var colZ_arr=newArray(255,255,255); //=same color as colZ in RGB



macro "Ortho view movie" {
run("Overlay Options...", " "); // this ensures that the xy-crosshair and labels are shown in all stacks. Removes the 
// Overlay option "Set stack position"

//colors --- have to be defined in 2 ways, because Overlay.addSelection and setBackgroundColor require different inputs

//color for X view
colX="white";//red
colX_arr=newArray(255,0,0); //=same color as colX in RGB

//color for Y view
colY="white";//cyan
colY_arr=newArray(0,255,255); //=same color as colY in RGB

//color for Z view (xy-view)
colZ="white";
//colZ_arr=newArray(0,255,0); //=same color as colX in RGB //already defined globally

//color for background canvas
colCanvas_arr=newArray(40,40,40); //=dark grey


if (nImages()!=0) exit ("please close all other images before running the macro");

//open file
	//path = File.openDialog("Select the file");
	//open(path);  
	run("Bat Cochlea Volume (19K)"); //works as a testfile
 
getDimensions(width, height, channels, slices, frames); 
getVoxelSize(voxwidth, voxheight, depth, unit); 

run("Orthogonal Views");

msg1 =  "\nPostion the crosshair in the xy window."+
		"\n(original stack)"+
		"\n \nWhen done, continue with <OK>";
waitForUser(msg1);

setBatchMode(true);

// get the position where the user has put the yellow cross-hair in the xy-stack. The position is read from the titles
// of the YZ and XZ images using the function "coord" --> function is at the end of the macro

for (i=1; i<=3; i++){
    selectImage(i); 
	img=getTitle();
	
	if (substring(img,0,2)=="YZ") {
		x=coord(img);//returns x as an integer
		yztitle="YZ_"+x;
	}
	if (substring(img,0,2)=="XZ") {
		y=coord(img);//returns y as an integer
		xztitle="XZ_"+y;
	}
	
}
Stack.stopOrthoViews;//Stops the current Orthogonal Views and closes the "YZ" and "XZ" windows. 

//run("RGB Color");
imgID=getImageID();

setLineWidth(20);


// reslice YZ 
// produce a single y-slice at the x-position defined above (chosen by the user in the xy view by placing the crosshair)
	selectImage(imgID);
	makeLine(x,0,x,height);
    run("Reslice [/]...", "output="+ depth+" slice_count=1 rotate");
    getDimensions(w,h,c,s,f); //dimensions are dependent on depth, i.e. slice thickness
      	// colY-colored frame around the image
      		setBackgroundColor(colY_arr[0],colY_arr[1],colY_arr[2]);
      		run("Canvas Size...","width="+w+4+" height="+h+4+" position=Center");
	rename("YZ_"+ x);
	orthoYZ=getImageID();
	makestack(orthoYZ, slices, width, height, voxwidth, voxheight, depth, 1); //makestack function see below

		

// reslice XZ 
// produce a single x-slice at the y-position defined above (chosen by the user in the xy view by placing the crosshair)
	selectImage(imgID); 
	makeLine(0,y,width,y);
	   	run("Reslice [/]...", "output="+ depth+" slice_count=1");
   	getDimensions(w,h,c,s,f); //dimensions are dependent on depth, i.e. slice thickness
      	// colX-colored frame around the image
      		setBackgroundColor(colX_arr[0],colX_arr[1],colX_arr[2]);
      		run("Canvas Size...","width="+w+4+" height="+h+4+" position=Center");
	rename("XZ_"+ y);
	orthoXZ=getImageID();
	makestack(orthoXZ, slices, width, height, voxwidth, voxheight, depth, 2); //makestack function see below

// add the crosshair to the xy view
	selectImage(imgID);
	makeLine(x,0,x,height);
	//Overlay.addSelection;//(colY, 1);//add the vertical crosshair line to the overlay
	run("Draw", "stack");
	makeLine(0,y,width,y);
	//Overlay.addSelection;//(colX, 1);//add the horizontal crosshair line to the overlay
	run("Draw", "stack");
	

// flatten the crosshair lines onto the original xy-stack
	//selectImage(imgID);
	//run("Flatten", "stack");
	
// white frame around the xy image
	setBackgroundColor(colZ_arr[0],colZ_arr[1],colZ_arr[2]); 
	run("Canvas Size...", "width="+width+4+" height="+height+4+" position=Center");

// combine views 
	selectImage(imgID);
	stk1=getTitle();
	setBackgroundColor(colCanvas_arr[0],colCanvas_arr[1],colCanvas_arr[2]);
	run("Combine...", "stack1="+ stk1 +" stack2="+ yztitle); 
	rename("combi1");
	run("Combine...", "stack1=combi1" +" stack2="+ xztitle +" combine"); 
	setBatchMode(false);

// grey frame around the image + labeling
	getDimensions(w,h,c,s,f); //dimensions of the combined stacks
	run("Canvas Size...", "width="+w+20+" height="+h+40+" position=Center");
	setFont("Sanserif", 14);
	makeText("xy view", 20, 0);
	run("Add Selection...", "stroke="+colZ);
	makeText("xz view", 20, h+18);
	run("Add Selection...", "stroke="+colX);
	makeText("yz view", w-44,0);
	run("Add Selection...", "stroke="+colY);

	run("Select None");

}
	
//-FUNCTIONS-----------------------------------------------------------------------------------//

function coord(imgname) {
	// function returns the x or y value of the selected section. Extracted from the image title 
	//(e.g. title = YZ 123) --> 123 is returned

	string=substring(imgname,3,lengthOf(imgname));
	return parseInt(string);//Converts string to an integer and returns it. Returns NaN if the string cannot be converted into a integer.
}




function makestack(imgID, slices, xmax, ymax, voxwidth, voxheight, depth, orient) {
	// duplicate the YZ or the XZ image resp. z-times (i.e. make a stack with 
	// identical number of slices as the original x-y-z stack. 
	// Draw a line in each of the images indicating a different z-position. If the z-slice
	// is thicker than 1 pixel (interpolated) the line is drawn in the middle of the slice position
	// 
	// The last argument of the function (orient) is needed for the orientation. I.e. to differentiate between x and y view
	
	setForegroundColor(colZ_arr[0],colZ_arr[1],colZ_arr[2]);
	neuID = getImageID();
	titel = getTitle();
	run("Select All");
	run("Copy");
	run("Duplicate...", "title=["+titel+"]");
	dup=getImageID();
	selectImage(neuID);
	close();
	selectImage(dup);
	for (i=0; i<slices; i++) {
		run("Paste"); 
		// draw line indicating the z-plane 
			if (orient==1) {
				zspace=depth/voxwidth; //take thickness of z-slices into account
				drawLine(i*zspace+zspace/2,2,i*zspace+zspace/2,ymax+1);
			}
			if (orient==2) {
				zspace=depth/voxheight;
				drawLine(2,i*zspace+zspace/2,xmax+1,i*zspace+zspace/2);
			}
		run("Add Slice");
	}
	run("Delete Slice");
}
	






Building Fiji from source - maven-enforcer
#4

Hi Martin,

I seem to be having trouble getting this macro to work. I keep getting the error “file name” is not a valid choice for “stack 1”. I have almost no experience writing code but it seems like it’s trying to use my original xy image in the combine function instead of the resliced XZ and YZ views (I also don’t see those windows pop up as the macro is running so maybe I’m doing something else wrong too. I’m working with single channel RGB .tiff files. Here’s what I’m using:

// define z-color globally. Needs to be available in the macro and in a function
var colZ_arr=newArray(255,255,255); //=same color as colZ in RGB



macro "Ortho view movie" {
run("Overlay Options...", " "); // this ensures that the xy-crosshair and labels are shown in all stacks. Removes the 
// Overlay option "Set stack position"

//colors --- have to be defined in 2 ways, because Overlay.addSelection and setBackgroundColor require different inputs

//color for X view
colX="white";//red
colX_arr=newArray(255,0,0); //=same color as colX in RGB

//color for Y view
colY="white";//cyan
colY_arr=newArray(0,255,255); //=same color as colY in RGB

//color for Z view (xy-view)
colZ="white";
//colZ_arr=newArray(0,255,0); //=same color as colX in RGB //already defined globally

//color for background canvas
colCanvas_arr=newArray(40,40,40); //=dark grey


if (nImages()!=0) exit ("please close all other images before running the macro");

//open file
	//path = File.openDialog("Select the file");
	//open(path);
path = File.openDialog("Select the file");	 
run(path);
getDimensions(width, height, channels, slices, frames); 
getVoxelSize(voxwidth, voxheight, depth, unit); 

run("Orthogonal Views");

msg1 =  "\nPostion the crosshair in the xy window."+
		"\n(original stack)"+
		"\n \nWhen done, continue with <OK>";
waitForUser(msg1);

setBatchMode(true);

// get the position where the user has put the yellow cross-hair in the xy-stack. The position is read from the titles
// of the YZ and XZ images using the function "coord" --> function is at the end of the macro

for (i=1; i<=3; i++){
    selectImage(i); 
	img=getTitle();
	
	if (substring(img,0,2)=="YZ") {
		x=coord(img);//returns x as an integer
		yztitle="YZ_"+x;
	}
	if (substring(img,0,2)=="XZ") {
		y=coord(img);//returns y as an integer
		xztitle="XZ_"+y;
	}
	
}
Stack.stopOrthoViews;//Stops the current Orthogonal Views and closes the "YZ" and "XZ" windows. 

run("RGB Color");
imgID=getImageID();

setLineWidth(20);


// reslice YZ 
// produce a single y-slice at the x-position defined above (chosen by the user in the xy view by placing the crosshair)
	selectImage(imgID);
	makeLine(80,0,80,height);
    run("Reslice [/]...", "output="+ depth+" slice_count=1 rotate");
    getDimensions(w,h,c,s,f); //dimensions are dependent on depth, i.e. slice thickness
      	// colY-colored frame around the image
      		setBackgroundColor(colY_arr[0],colY_arr[1],colY_arr[2]);
      		run("Canvas Size...","width="+w+4+" height="+h+4+" position=Center");
	rename("YZ_"+ x);
	orthoYZ=getImageID();
	makestack(orthoYZ, slices, width, height, voxwidth, voxheight, depth, 1); //makestack function see below

		

// reslice XZ 
// produce a single x-slice at the y-position defined above (chosen by the user in the xy view by placing the crosshair)
	selectImage(imgID); 
	makeLine(0,80,width,80);
	   	run("Reslice [/]...", "output="+ depth+" slice_count=1");
   	getDimensions(w,h,c,s,f); //dimensions are dependent on depth, i.e. slice thickness
      	// colX-colored frame around the image
      		setBackgroundColor(colX_arr[0],colX_arr[1],colX_arr[2]);
      		run("Canvas Size...","width="+w+4+" height="+h+4+" position=Center");
	rename("XZ_"+ y);
	orthoXZ=getImageID();
	makestack(orthoXZ, slices, width, height, voxwidth, voxheight, depth, 2); //makestack function see below

// add the crosshair to the xy view
	selectImage(imgID);
	makeLine(80,0,80,height);
	Overlay.addSelection(colY, 1);//add the vertical crosshair line to the overlay
	run("Draw", "stack");
	makeLine(0,80,width,80);
	Overlay.addSelection(colX, 1);//add the horizontal crosshair line to the overlay
	run("Draw", "stack");
	

// flatten the crosshair lines onto the original xy-stack
	selectImage(imgID);
	run("Flatten", "stack");
	
// white frame around the xy image
	setBackgroundColor(colZ_arr[0],colZ_arr[1],colZ_arr[2]); 
	run("Canvas Size...", "width="+width+4+" height="+height+4+" position=Center");

// combine views 
	selectImage(imgID);
	stk1=getTitle();
	setBackgroundColor(colCanvas_arr[0],colCanvas_arr[1],colCanvas_arr[2]);
	run("Combine...", "stack1="+ stk1 +" stack2="+ yztitle); 
	rename("combi1");
	run("Combine...", "stack1=combi1" +" stack2="+ xztitle +" combine"); 
	setBatchMode(false);

// grey frame around the image + labeling
	getDimensions(w,h,c,s,f); //dimensions of the combined stacks
	run("Canvas Size...", "width="+w+20+" height="+h+40+" position=Center");
	setFont("Sanserif", 14);
	makeText("xy view", 20, 0);
	run("Add Selection...", "stroke="+colZ);
	makeText("xz view", 20, h+18);
	run("Add Selection...", "stroke="+colX);
	makeText("yz view", w-44,0);
	run("Add Selection...", "stroke="+colY);

	run("Select None");

}
	
//-FUNCTIONS-----------------------------------------------------------------------------------//

function coord(imgname) {
	// function returns the x or y value of the selected section. Extracted from the image title 
	//(e.g. title = YZ 123) --> 123 is returned

	string=substring(imgname,3,lengthOf(imgname));
	return parseInt(string);//Converts string to an integer and returns it. Returns NaN if the string cannot be converted into a integer.
}




function makestack(imgID, slices, xmax, ymax, voxwidth, voxheight, depth, orient) {
	// duplicate the YZ or the XZ image resp. z-times (i.e. make a stack with 
	// identical number of slices as the original x-y-z stack. 
	// Draw a line in each of the images indicating a different z-position. If the z-slice
	// is thicker than 1 pixel (interpolated) the line is drawn in the middle of the slice position
	// 
	// The last argument of the function (orient) is needed for the orientation. I.e. to differentiate between x and y view
	
	setForegroundColor(colZ_arr[0],colZ_arr[1],colZ_arr[2]);
	neuID = getImageID();
	titel = getTitle();
	run("Select All");
	run("Copy");
	run("Duplicate...", "title=["+titel+"]");
	dup=getImageID();
	selectImage(neuID);
	close();
	selectImage(dup);
	for (i=0; i<slices; i++) {
		run("Paste"); 
		// draw line indicating the z-plane 
			if (orient==1) {
				zspace=depth/voxwidth; //take thickness of z-slices into account
				drawLine(i*zspace+zspace/2,2,i*zspace+zspace/2,ymax+1);
			}
			if (orient==2) {
				zspace=depth/voxheight;
				drawLine(2,i*zspace+zspace/2,xmax+1,i*zspace+zspace/2);
			}
		run("Add Slice");
	}
	run("Delete Slice");
}

#5

Does the macro run as it is posted? Without any modification (just copy/paste to the macro editor) it works for me. The resulting image is only grayscale, because the cochlea sample stack that is loaded is in 8-bit format.

To mimick your RGB stack you can add one line (after line 55 [run(“Bat Cochlea…”]) to convert to RGB:
run(“Bat Cochlea Volume (19K)”); //works as a testfile
run(“RGB Color”);

On my system the intended result stack is generated and looks like this:

image


#6

It works as written, but when I change the file I want to run from “Bat Cochlea Volume” to a file I want to apply the macro to I’ll get the message: “File name” is not a valid choice for “stack 1”. Is there a requirement on the type of image I can use for this macro?

It’d be great if I could get this movie/viewer to work, but alternatively I’m really just looking for a way to save each orthoview window (XZ or YZ) as a JPEG for a particular XY image in a folder that I designate with file name titled: “name of my XY image” + XZ. If I could somehow do this in a batch process for a whole folder that’d be ideal (I have a lot of images that I want to save these views in). The position of the crosshair on the image doesn’t matter (I think the default is center? so that would work)

Thanks for the quick reply as well, I’ve been trying to learn how to write in this language as I go to make these macros and I’m particularly inexperienced to writing code, let alone in ImageJ.


#7

Learning to write macros by looking how others do it is an excellent idea - however my macros (especially the old ones like this) are for sure rather bad examples … have a look at the macros from e.g. @Herbie to learn from the pros.

But anyways, try this:
save the cochlea sample to your desktop (in Fiji: File > Open Samples… > Bat Cochlea Volume (19K). then save as mystack.tif (or any other name).

modify the macro as follows (around line 52):

//open file
	path = File.openDialog("Select the file");
	open(path);  
	//run("Bat Cochlea Volume (19K)"); //works as a testfile
	run("RGB Color");

then run the macro an select the mystack.tif. Does this work? Then select your stack instead mystack.tif.


#8

It works when I choose the sample image saved to my desktop, but not for any of the images I actually want to analyze.


#9

Can you please post the properties of one of your stacks? Open with Fiji/ImageJ > Image > Show Info …


#10

Sure thing, thanks for the help:

(Fiji Is Just) ImageJ 2.0.0-rc-64/1.51s; Java 1.8.0_66 [64-bit]; Windows 10 10.0; 449MB of 75000MB (<1%)
Title: Macro Test.tif
Width: 159.7250 microns (2075)
Height: 159.7250 microns (2075)
Depth: 7.8200 microns (17)
Size: 279MB
Resolution: 12.9911 pixels per micron
Voxel size: 0.0770x0.0770x0.4600 micron^3
ID: -19
Bits per pixel: 32 (RGB)
Image: 1/17
No threshold
Magnification: 0.33
ScaleToFit: false
Uncalibrated
Path: C:\Users\grnic\Desktop\Macro Test Pics\Merge\Macro Test.tif
Screen location: 12,114 (1536x864)
Coordinate origin: 0,0,0
No overlay
No selection


#11

I think the file name is the culprit. Try using a filename without spaces.


#12

That worked! Thanks so much for the help, I’ll just make sure to not put spaces in file names anymore. Is there a way to change it so spaces in the file name will work? Otherwise I have a lot of renaming to do.


#13

there is probably a way to deal with spaces. But I don´t know from the top of my head.
If you´re on windows I can recommend the free FreeCommander file explorer. It includes a powerful and convenient batch-renaming tool (besides lots of other great features)
http://freecommander.com


#14

Awesome, thanks again!


#15

@ericbarnhill Sorry I did not notice this thread earlier.
As I mentioned in another thread, I developed BigDataViewer based orthogonal views in BigCAT:


As I mentioned in the other thread, this is JavaFX based but can be ported to awt/swing fairly easily.