//  Glomerular Analysis

//	Version 1.3b2

//	Ben Norrichs

//	2009.04.10


//	This macro works with ImageJ. Used for single fluorophore tif files to measure change in fluorescence intensity over time. To use, open a tif file in ImageJ, then run this macro. All file and folder names must contain no spaces. Outputs an excel file. 

//  This macro measures the centroid coordinates of glomeruli in a time sequence specified by the user.  
//  These centroids are then used to place the ROI shape from the first frame in the sequence in each
//  subsequent frame.  This new ROI set is then used to measure mean glomerular intensity for each frame.


CheckGlomOrder = 1;  	// Variable to set glomerular order checking.
VerboseMode = 0;		// Variable to show a lot of log messages
AssignGlomNames = 1;		// Variable to request glomerular identities with a dialog
setBatchMode(false);



roiManager("reset");
run("Set Measurements...", "  area centroid slice redirect=None decimal=1");
run("Clear Results");
run("Select None");

ImgID = getImageID();
ImgInfo = getImageInfo();
ImgPath = substring( ImgInfo, indexOf(ImgInfo,"Path: ")+6, indexOf(ImgInfo,getTitle(),indexOf(ImgInfo, "Path:")));

//-------------------------------------------
//	Choose how to get prototype ROIs
//-------------------------------------------
roiMethods = newArray("Make Selection","Direct Draw","Automatic");
Dialog.create("Select regions");
Dialog.addChoice("Choose a region selection method",roiMethods);
Dialog.show()
roiMethod = Dialog.getChoice();
print(roiMethod);

//------------------------------------------------------------
//	Prompt for selection of target regions for centroid ROIs
//------------------------------------------------------------
if(roiMethod != "Automatic"){
	run("Fire");	
	setSlice(1);
	getStatistics(area, mean, min, max, std);
	run("Threshold...");
	setThreshold(mean+std*1.2,255);
	setTool(3);
	waitForUser("Do it","Adjust intensity threshold\nand make region selection.");
} else{
	setSlice(1);
	getStatistics(area, mean, min, max, std);
	run("Threshold...");
	setThreshold(mean+std*1.2,max);
	waitForUser("Do it","Adjust intensity threshold");
}
//-----------------------------------------------
// 	Get ROIs and measurements from full sequence for centroids
//-----------------------------------------------
nFrames = nSlices;
RegionSelected = 0;
if(selectionType!=-1){
	RegionSelected = 1;
	roiManager("Add");
	roiManager("Select",0);
	roiManager("Rename", "Selection");
}
for(FrameNum=1;FrameNum<nFrames+1;FrameNum++){
	if(RegionSelected == 1)
		roiManager("Select", 0);
	setSlice(FrameNum);
	run("Analyze Particles...", "size=200-Infinity circularity=0.00-1.00 show=Nothing include add");  // Automatically measures and adds to Results
}	
		// Removed in v1.21, redundant, causes error -->  roiManager("Measure");
if(RegionSelected == 1){
	roiManager("Select", 0);
	roiManager("Delete");
	run("Select None");
}
updateResults();
//	saveAs("Measurements", "c:\\home\\school\\Temp\\Results0.xls");
//	roiManager("Deselect");
//	roiManager("Measure");
//	updateResults();
//	saveAs("Measurements", "c:\\home\\school\\Temp\\Results1.xls");

//---------------------------------------------------------
//  	GlomCount = Number of unique Glomeruli to be tracked
//  		Calculated by stepping through unsorted results from frame 0
//		and checking for Frame>1
//----------------------------------------------------------
i = 0;
while (i>-1){
	GlomCount = i+1;
	if (getResult("Slice",i+1) != 1)
		i = -1;
	else {
		i++;
	}
}
print("There are "+GlomCount+" glomeruli detected");
//----------------------------------------------------------------------------------------
//	Re-sort the results table to put slices together into single rows.
//----------------------------------------------------------------------------------------
ResCol = newArray( "Area","X","Y");					// Result columns to rearrange
nFrames = nSlices();
nResultsUnordered = nResults();
print(nResultsUnordered);
for(FrameNum = 0;FrameNum < nFrames;FrameNum++){
	FrameNumSorted = nResultsUnordered + FrameNum;		// Results row after which to add sorted rows
	for(GlomNum = 0;GlomNum < GlomCount;GlomNum++){
		ResultIndex = GlomCount*FrameNum + GlomNum;  	// Form (innerMax * outer + inner)
		for(i=0;i<lengthOf(ResCol);i++){		// Requires existence of ResultColumns[]	
			setResult(ResCol[i] + "_" + GlomNum, FrameNumSorted, getResult(ResCol[i],ResultIndex));
		} 
	}
	setResult("Slice",FrameNumSorted,getResult("Slice",ResultIndex));
}
StartOrdered = nResultsUnordered;
EndOrdered = nResultsUnordered+nFrames-1;
ResultsRows = newArray(StartOrdered,EndOrdered);
print("Sorted results from: "+ResultsRows[0]+" to: "+ResultsRows[1]);
//   updateResults();
//------------------------------------------------------------------
//	Store Prototype ROIs
// 		if not Direct Draw Save ROIs as XY coordinates for Frame1
//		else Draw directly, then store
//------------------------------------------------------------------
if(roiMethod != "Direct Draw"){
	for (GlomNum=0;GlomNum<GlomCount;GlomNum++){
print("Glom "+GlomNum);
		roiManager("Select", GlomNum);
		
		saveAs("XY Coordinates", ImgPath+"ProtoROI_"+GlomNum+".txt");
	}
}else{
	print("Error - Direct Draw not implemented");
	exit();
}
//-------------------------------------------------------------------------------
// 	Calculate Interglomerular offsets, relative to glom0 in Frame1
//  		Calculated from frame 0, regardless of prototype ROI source
//  		There will by X and Y offsets fore each Glomerulus > 0
//  		Calculated as ( IGoffset_#_X = CentX_1 - CentX_0  etc)
//  		Stored as IGoffsets array.  Array elements will be in GlomNum order, X, Y
//  		This will be used error check glomerulus number when making ROI assignments
//-------------------------------------------------------------------------------------------
if(GlomCount > 1){
	print("InterGlomerular Offsets:");
	IGoffsets = newArray( (GlomCount-1) * 2 );
	for(GlomNum=1;GlomNum<GlomCount;GlomNum++){
		IGoffsets[2*GlomNum-2]   = getResult("X_"+GlomNum,ResultsRows[0]) - getResult("X_0",ResultsRows[0]);
		IGoffsets[1+2*GlomNum-2] = getResult("Y_"+GlomNum,ResultsRows[0]) - getResult("Y_0",ResultsRows[0]);
		print("IGoffsets0_"+GlomNum+" = ("+IGoffsets[2*GlomNum-2]+","+IGoffsets[1+2*GlomNum-2]+")");
	}
}
//---------------------------------------------------------------------------------------
//  	Check all ROIs for allowable IGoffset fluctuation
//  		Iterratively rearrange order of ROIs with IGoffset out of range
// 		If an arrangement results in falling in range, proceed
//  		else exit
//-------------------------------------------------------------------------------------------
if(GlomCount > 1 && CheckGlomOrder == 1){
	IGOffsetDiffMax = 20;			//  Pixels by which relative glomerular coordinates can change without triggering error
	nFrames = nSlices;			//  This will let some rearrangements in one direction past

	for(FrameNum = 0;FrameNum<nFrames;FrameNum++){			// Step through 1 frame at a time
		if(VerboseMode==1)
			print("||-------- Computing offsets Frame "+FrameNum);
		//  Permute Glomerular order.  For each permutation, test if that order gives good offsets
		//  If it does, halt permutation and return that order.
		FrameNumIndex 	= ResultsRows[0]+FrameNum;		//	Set results address counter for this frame
		GlomOrder = newArray(GlomCount);				//
		for(i=0;i<GlomCount;i++){					//	Initialize default Glomerulus Order = 0,1,2,...
			GlomOrder[i] = i;						//		for this frame
		}
		GlomCountFactorial = 1;						//	Initialize Permutation counters
		for(Factor=1;Factor<=GlomCount;Factor++){				//		N! is the number of permutations
			GlomCountFactorial = GlomCountFactorial*Factor;	//
		}
		PermCounter = newArray(GlomCount+1);			//	PermCounter keeps track of iterations
		for(i=0;i<=GlomCount;i++){					//
			PermCounter[i] = i;					//
		}
		i = 1;
		PermNum = 0;	// Counts permutation number
		while (i<GlomCount){
		//print("Frame "+FrameNum+" Perm "+PermNum+"["+GlomOrder[0]+","+GlomOrder[1]+","+GlomOrder[2]+"]");
		PermNum = PermNum +1;
		//print("Diffs in bounds -> "+CheckOffsets(FrameNumIndex, GlomOrder, IGOffsetDiffMax));
		InBounds = CheckOffsets(FrameNumIndex, GlomOrder, IGOffsetDiffMax);
		if( InBounds == 0){		//	Calls function to check whether offsets are in bounds
			PermCounter[i] = PermCounter[i] - 1;
			if(i%2 != 0){
				j = PermCounter[i];
			} else {
				j = 0;
			}
			k = GlomOrder[j];			//
			GlomOrder[j] = GlomOrder[i];	//	Swap GlomOrder[i] with GlomOrder[j]
			GlomOrder[i] = k;			//
				i = 1;
				while(PermCounter[i] == 0){
					PermCounter[i] = i;
					i++;
				}
			}else{
				i=GlomCount;			// Get out of while loop
			}
		}		// End while(i<GlomCount)	//Which means final permutation has been generated
		if(VerboseMode==1)
			print("____Final perm");
		InBounds = CheckOffsets(FrameNumIndex, GlomOrder, IGOffsetDiffMax);
		if( InBounds == 0){	 			//	Check final permutation
			//print(PermNum+"["+GlomOrder[0]+","+GlomOrder[1]+","+GlomOrder[2]+"]");
			print("Error - No Glomerular ordering successful - Try a larger Offset Difference Maximum");
			exit();
		}else if( PermNum > 0){				//	Reorder results row
			NewResults = newArray(GlomCount*2);
			for(GlomNum = 0;GlomNum<GlomCount;GlomNum++){
				NewResults[2*GlomNum]   = getResult("X_"+GlomOrder[GlomNum],FrameNumIndex);
				NewResults[2*GlomNum+1] = getResult("Y_"+GlomOrder[GlomNum],FrameNumIndex);
			}
			for(GlomNum = 0;GlomNum<GlomCount;GlomNum++){
				setResult("X_"+GlomNum,FrameNumIndex,NewResults[2*GlomNum]);
				setResult("Y_"+GlomNum,FrameNumIndex,NewResults[2*GlomNum+1]);
			}
				
		}
					
	}
}
updateResults();
//--------------------------------------------------------------------------
//	Function - Checks if intraglomerular centroid offsets in a particular frame
//	are different than the IG offsets from frame 0  (may want to change to be previous frame)
//		if offset diff < offset diff max
//			return 1
//		else
//			return 0
//---------------------------------------------------------------------------
function CheckOffsets(FrameNumIndex, GlomOrder, IGOffsetDiffMax){
	//print("CheckOffsets GlomOrder = "+GlomOrder[0]+","+GlomOrder[1]+","+GlomOrder[2]);
	GlomCount = lengthOf(GlomOrder);
	FrameNumIGoffsets = newArray((GlomCount-1)*2);		// Array to transiently store offsets
	FrameNumIGdiffs	= newArray((GlomCount-1)*2);		// Array to transiently store difference to frame0 offsets		
	for(GlomNum=1;GlomNum<GlomCount;GlomNum++){
		FrameNumIGoffsets[2*(GlomNum-1)]   	= getResult("X_"+GlomOrder[GlomNum],FrameNumIndex) - getResult("X_"+GlomOrder[0],FrameNumIndex);
		FrameNumIGoffsets[2*(GlomNum-1)+1] 	= getResult("Y_"+GlomOrder[GlomNum],FrameNumIndex) - getResult("Y_"+GlomOrder[0],FrameNumIndex);
		FrameNumIGdiffs[2*(GlomNum-1)]     	= FrameNumIGoffsets[2*(GlomNum-1)]   - IGoffsets[2*(GlomNum-1)];
		FrameNumIGdiffs[2*(GlomNum-1)+1]	= FrameNumIGoffsets[2*(GlomNum-1)+1] - IGoffsets[2*(GlomNum-1)+1];
	}
	OutofBounds = 0;
	for(DiffNum=0;DiffNum<lengthOf(FrameNumIGdiffs);DiffNum++){
		if(FrameNumIGdiffs[DiffNum]>IGOffsetDiffMax || -FrameNumIGdiffs[DiffNum]>IGOffsetDiffMax){
			if(VerboseMode==1){
				print("Error in Frame "+FrameNum+": IG offsets out of bounds  Diff="+DiffNum);
				print("IGOffsetDiff =");
				for(DiffGlomNum=0;DiffGlomNum<lengthOf(FrameNumIGdiffs)/2;DiffGlomNum++){
					print("["+FrameNumIGdiffs[2*DiffGlomNum]+","+FrameNumIGdiffs[2*DiffGlomNum+1]+"]");
				}
			}
			OutofBounds = 1;				// 	Offsets difference is larger than max allowed
			DiffNum = lengthOf(FrameNumIGdiffs);	//	Exit For Loop
		}else{
			if(VerboseMode==1){
				print(" *** No Error in Frame "+FrameNum+": IG offsets in bounds  Diff="+DiffNum);
				print(" *** IGOffsetDiff =");
				for(DiffGlomNum=0;DiffGlomNum<lengthOf(FrameNumIGdiffs)/2;DiffGlomNum++){
					print("	["+FrameNumIGdiffs[2*DiffGlomNum]+","+FrameNumIGdiffs[2*DiffGlomNum+1]+"]");
				}
			}
			OutofBounds = 0;					// Offsets are within bounds
		}
	}
	if(OutofBounds == 0){
		return 1;							// Offsets are within bounds so check is positive
	}else{
		return 0;							// Offsets are out of bounds so check is negative
	}
}
// Compute inter-frame intraglomerular offsets (frame_n centroid) - (frame_0 centroid) = offset
if( roiMethod != "Direct Draw"){
	PrototypeCentroids = newArray(2*GlomCount);
	for(GlomNum=0;GlomNum<GlomCount;GlomNum++){
		PrototypeCentroids[2*GlomNum]   = getResult("X_"+GlomNum,ResultsRows[0]);
		PrototypeCentroids[2*GlomNum+1] = getResult("Y_"+GlomNum,ResultsRows[0]);
if(VerboseMode==1)
print("ResultRow "+ResultsRows[0]+"||"+PrototypeCentroids[2*GlomNum]+" , "+PrototypeCentroids[2*GlomNum+1]);
	}
	nFrames = nSlices();
	print(nFrames);
	for(FrameNum=2; FrameNum<nFrames; FrameNum++){
		for(GlomNum=0; GlomNum<GlomCount; GlomNum++){
			if(VerboseMode==1)
				print(getResult("X_"+GlomNum,FrameNum+ResultsRows[0])+" - "+PrototypeCentroids[2*GlomNum]);
			OffsetX = getResult("X_"+GlomNum,FrameNum+ResultsRows[0])-PrototypeCentroids[2*GlomNum];
			OffsetY = getResult("Y_"+GlomNum,FrameNum+ResultsRows[0])-PrototypeCentroids[2*GlomNum+1];
			if(VerboseMode==1)
				print("Frame "+FrameNum+" OffsetX_"+GlomNum+" = "+OffsetX+"  ||  Frame "+FrameNum+" OffsetY_"+GlomNum+" = "+OffsetY);
			setResult("OffX_"+GlomNum, FrameNum+ResultsRows[0], OffsetX);
			setResult("OffY_"+GlomNum, FrameNum+ResultsRows[0], OffsetY);
		}
	}
} 
//  updateResults();
//------------------------------
//  Load Prototype ROIs
//------------------------------
roiManager("reset");
for(GlomNum=0;GlomNum<GlomCount;GlomNum++){
	run("ROI Importer", "open="+ImgPath+"ProtoROI_"+GlomNum+".txt");
	roiManager("Add");
}
//-----------------------------------------------------------------------------------------------
//	Add back repositioned prototype ROIs
//  	Step through frames, applying offset to Frame1 ROI and adding to manager
//-----------------------------------------------------------------------------------------------
for(FrameNum=1;FrameNum<nFrames;FrameNum++){
	for(GlomNum=0;GlomNum<GlomCount;GlomNum++){
		FrameNumIndex = ResultsRows[0]+FrameNum;
		roiManager("select",GlomNum);
if(VerboseMode==1)
print("Frame = "+FrameNum);
		setSlice(FrameNum+1);
		getSelectionBounds(GlomX,GlomY,Width,Height);
		setSelectionLocation(GlomX+getResult("OffX_"+GlomNum,FrameNumIndex),GlomY+getResult("OffY_"+GlomNum,FrameNumIndex));
		roiManager("Add");
	}
}
//---------------------------------------------------------------------------------------------
//  Measure new ROI set
//---------------------------------------------------------------------------------------------
run("Set Measurements...", "area mean centroid slice redirect=None decimal=1");
run("Clear Results");
roiManager("Deselect");
roiManager("Measure");
//  updateResults();
//----------------------------------------------------------------------------------------
//	Re-sort the results table to put slices together into single rows.
//----------------------------------------------------------------------------------------
nFrames = nResults() / GlomCount;
nResultsUnordered = nResults();
ColumnNames = fIDGlom(GlomCount,AssignGlomNames);

if(VerboseMode==1)
	print(nResultsUnordered);
for(FrameNum = 0;FrameNum < nFrames;FrameNum++){
	FrameNumSorted = nResultsUnordered + FrameNum;
	for(GlomNum = 0;GlomNum < GlomCount;GlomNum++){
		ResultIndex = FrameNum+GlomNum+FrameNum*(GlomCount-1);
		setResult(ColumnNames[4*GlomNum+0],FrameNumSorted,getResult("Area",ResultIndex));
		setResult(ColumnNames[4*GlomNum+1],FrameNumSorted,getResult("Mean",ResultIndex));
		setResult(ColumnNames[4*GlomNum+2],FrameNumSorted,getResult("X",ResultIndex));
		setResult(ColumnNames[4*GlomNum+3],FrameNumSorted,getResult("Y",ResultIndex));
	}
	setResult("Frame",FrameNumSorted,getResult("Slice",ResultIndex));
}
//--------------------------------------------------
//  Re-sort Results into an easy format
//--------------------------------------------------
nFrames = nSlices();
ResultsRows = newArray(2);
ResultsRows[0] = (nResults - nFrames);
ResultsRows[1] = nResults;
if(VerboseMode == 1)
	print(ResultsRows[0]+","+ResultsRows[1]);



if(VerboseMode==1){
	for(i=0;i<lengthOf(ColumnNames);i++)
		print(ColumnNames[i]);
}
updateResults();
print("Are the results still there?");

printArray("ColumnNames",ColumnNames);

ResultsCount = lengthOf(ColumnNames)*(ResultsRows[1]-ResultsRows[0]);
ResultsBuffer = newArray(ResultsCount);
for(FrameNum = 0;FrameNum<nFrames;FrameNum++){
	for(ColumnNum = 0;ColumnNum<lengthOf(ColumnNames);ColumnNum++){
		if(VerboseMode==1)
			print("Copying Row/Col "+FrameNum+ResultsRows[0]+"/"+ColumnNames[ColumnNum]);
		ResultIndex = lengthOf(ColumnNames) * FrameNum + ColumnNum;
		ResultsBuffer[ResultIndex] = getResult(ColumnNames[ColumnNum],FrameNum+ResultsRows[0]);

print(ResultIndex,"=",ResultsBuffer[ResultIndex]);
print("ResultWindow ",getResult(ColumnNames[ColumnNum],FrameNum+ResultsRows[0]));

	}
}

printArray("this array",ResultsBuffer);
run("Clear Results");
for(FrameNum = 0;FrameNum<nFrames;FrameNum++){
	for(ColumnNum = 0;ColumnNum<lengthOf(ColumnNames);ColumnNum++){
		ResultIndex = lengthOf(ColumnNames) * FrameNum + ColumnNum;
		setResult(ColumnNames[ColumnNum],FrameNum,ResultsBuffer[ResultIndex]);
	}
}


//----------------------------------------------------------------------
//    Save the results to an XLS file but don't overwrite existing records
//    Count the number of already existing records
//-----------------------------------------------------------------------
	imageDir = getDirectory("image");
	imageTitle = getTitle();
	imageDirFileList = getFileList(imageDir);
	savedROICount = 0;
	savedResultsCount = 0;
	for(FileNum=0;FileNum<lengthOf(imageDirFileList);FileNum++){
		if(startsWith(imageDirFileList[FileNum],imageTitle)){
			if(endsWith(imageDirFileList[i],"xls"))
				savedResultsCount = savedResultsCount + 1;
			else if(endsWith(imageDirFileList[i],"zip"))
				savedROICount = savedROICount +1;
		}
	}
//    Based on existing record count, save new files
	updateResults();              // This update is needed to save data as xls file
	if(savedROICount <=10)
		roiManager("Save", ImgPath+getTitle()+".ROI-0"+savedROICount+".zip");
	else
			roiManager("Save", ImgPath+getTitle()+".ROI-"+savedROICount+".zip");
	if(savedResultsCount <=10)
		saveAs("Measurements", ImgPath+getTitle()+".tracking.Results-0"+savedResultsCount+".xls");
	else
		saveAs("Measurements", ImgPath+getTitle()+".tracking.Results-"+savedResultsCount+".xls");


//-----------------------------------------------
print("AutoGlomerulizer Done");
//-----------------------------------------------












//------------------------------------------------
//  Functions
//------------------------------------------------


  function printArray(title, a) {
      print(title);
      for (i=0; i<a.length; i++)
          print("  "+i+" "+a[i]);
  }







// Function to assign glomerular identities (v1.3)
//   Requires changes in Excel macros

function fIDGlom(GlomCount,AssignNames){
	if(AssignNames == 1){
		ColumnNames = newArray(4*GlomCount);
		run("ROI Manager...");
		setSlice(1);
		Dialog.create("Identify Regions");
		for(GlomNum = 0;GlomNum<GlomCount;GlomNum++){
			Dialog.addString("Region "+GlomNum,"?");
		}
		Dialog.show();
		for(GlomNum=0;GlomNum<GlomCount;GlomNum++){
			GlomID = Dialog.getString();
			ColumnNames[0+GlomNum*4] = "Area_"+GlomNum+"_"+GlomID;
			ColumnNames[1+GlomNum*4] = "Mean_"+GlomNum+"_"+GlomID;
			ColumnNames[2+GlomNum*4] = "X_"+GlomNum+"_"+GlomID;
			ColumnNames[3+GlomNum*4] = "Y_"+GlomNum+"_"+GlomID;
		}
	}else{
		for(GlomNum=0;GlomNum<GlomCount;GlomNum++){
			ColumnNames[0+GlomNum*4] = "Area_"+GlomNum;
			ColumnNames[1+GlomNum*4] = "Mean_"+GlomNum;
			ColumnNames[2+GlomNum*4] = "X_"+GlomNum;
			ColumnNames[3+GlomNum*4] = "Y_"+GlomNum;
		}
	}
	return ColumnNames;
}
