//SporeTrackerX
//version 1.1w     
// with Check 
//05.06.23, 13:15
//bug rapid drop fixed//
//singlePlot

var //====== user-defined variables
	brightFactor = 0.4,//threshold in slice=1 :  default = 0.4 *upperRange;
	stillBrightFactor = 0.2,//** threshold in slice>1:  default = 0.2 *upperRange;
	brightThr=0, //use value>0 to set fixed threshold and ignore brightFactor
	stillBrightThr=0, //use value>0 to set fixed threshold and ignore stillBrightFactor
	darkThr="Default", //thresholding dark particles, auto (e.g. "Default") or fixed  number
	neighborDist = 4, // neighbors ignored if closer than: default = 4 um
	jumpTolerance = 22, //** acceptance frame-to-frame movement, default = 20 pixels
	startArea = 2.5, //log(2) area for measuring TD, default = 2.5  (2^2.5 = 5.6 um^2)
	minStopArea = 3, //log(2) area for measuring TD, default = 3 (2^3 = 8 um^2)
	minParticleB = 30, //smaller Bright particles are ignored, default = 30 pixel
	minParticleD = 60, //smaller Dark particles are ignored, default = 60 pixel
	peakRad = 4, //radius to average a peak's top intensity, default = 4 pixel
	germHigh = 0.8, //germination start defined as fraction of droprange, default = 0.9
	germLow = 0.2, //germination end defined as fraction of droprange, default = 0.1
	includeHoles = true, //when true (=default), sudden inclosures of re-bending filaments will stop history 
	plotTitles=split("Growth Germination Fluor"),
	signNames = split("Burst FirstDiv T1 T2 Germ"),
	lGray = 0xdddddd, white = 0xffffff, slopeColor = 0xccffcc,
	burstColor = 0xee9900, firstDivColor = 0x889900, t1Color= 0x00aa00, t2Color = 0x00aa00,
	signColors = newArray(burstColor, firstDivColor, t1Color, t2Color),
	autoBurst = false,//auto-detect burst
	afterBrightGrays = 50,//record intensity  n frames after transition, default=50
	//Plot ranges:
	plotAreaMin = -0.2,
	plotAreaMax = 6,
	plotGrayMax =60000,
	plotFlMax = 2000,
	fluorRad = 30, //radius to measure integr. fluorescence in chan 2
	navZoom = 2, //zoom factor while navigating, default = 2
	n_debug = false,
	enhanceC = true, //permanently enhance contrast in Collage
	//followGrowth = true, //if true, dark particles create groth plot; default: true
	minDropRatio = 0.7, //min brightness drop to detect germination
	timeFactor = 1, // default:=1, set to timeFactor=60 if frame interval is in seconds but plot & results should be minutes 

//automatic:
	maxFrames,
	n_channels,
	xLimit, //plot range [minutes] 
	frame_Interval,
	toShow = 0,

macro "Analyze Spores [F1]"{
	requires("1.54a");
	ojRequires("1.04f");
	if(n_debug) debug;
	deleteAll = false;
	ojHideResults();
	ojSetVirtualFlag(true);
	if(ojNObjects() > 0)
		deleteAll = getBoolean("Delete all " + ojNObjects() + " Objects?", "Delete All", "Process unmarked images");
	if(deleteAll)
		ojDeleteAllObjects();
	close("Plot_*");
	close("Collage*");
	close("Single*");
	calcMaxFrames();
	startObjects = ojNObjects();
	targetFlag = true;//a kind of batchMode for objects
	if(ojNObjects() == 0)
		initColumns();
	
	run("Clear Results");
	roiManager("reset");
	print("\\Clear");
	startTime = getTime;
	run("Set Measurements...", "area mean centroid");
	openAllVirtually();
	nLinkedImgs = ojNImages();
	nd2Names = newArray(nLinkedImgs);
	prepTimes = newArray(nLinkedImgs);
	for(img = 1; img <= ojNImages(); img++){
		ojHideResults();
		frames = ojGetImageValue(1, "frames");
		if(frames == 1) 
			exit("Hyperstack Error: nFrames == 1");
		maxRois = 0;//so we can judge number of of noise particles
		if(ojFirstObject(img) <= 0){
			showProgress(-img/ojNImages() + 0.01);
			ojShowImage(img);
			Stack.getUnits(dummy, dummy, dummy, tiffUnit, dummy);
			if((toLowerCase(tiffUnit) == "min") && (timeFactor != 1))
				exit("Set timefactor to 1");
			if(!n_debug)
				setBatchMode("hide"); 
			evalMetadata(img - 1, nd2Names, prepTimes);

//=== Frame #1 only ===
//put spore properties into result columns
//prepare pair arrays to be filled in subsequent frames
			setSlice(1);
			run("Select None");
			run("Remove Overlay");
			setThreshold(1, 65000);//don't spoil mode if saturated
			List.setMeasurements("limit");
			resetThreshold;
			mode =  List.getValue("Mode");
			mean =  List.getValue("Mean");
			max =  List.getValue("Max");
			upperRange = max- mode;
			if(brightThr == 0)//automatic
				isBright = mode + brightFactor * upperRange;
			else
				isBright = brightThr;//user defined
			if(stillBrightThr == 0)//automatic
				stillBright = mode + stillBrightFactor * upperRange;
			else
				stillBright = stillBrightThr;//user defined
			getPixelSize(dummy, pxWidth, dummy);
			objIndexes = "";//growing string
			setThreshold(isBright, 1e6);

			run("Analyze Particles...", "size=&minParticleB-Infinity pixel display exclude clear add slice");
			checkCapsLock();
 			print(getTitle);
			removeNeighbors();
			nSpores = nResults;//spores in first slice
			if(nSpores == 0)
				continue;
			currentX = newArray(nSpores);
			currentY = newArray(nSpores);
			areaPairs = newArray(nSpores);
			grayPairs = newArray(nSpores);
			fluorPairs = newArray(nSpores);
			lifeStage = newArray(nSpores);
			BRIGHT=0; DARK=1; LOST=2;//life stages
		
			for(spore = 0; spore < nSpores; spore++){ 
				currentX[spore] = getResult("X", spore)/pxWidth;
				currentY[spore] = getResult("Y", spore)/pxWidth;
				if(targetFlag) ojSetTarget("image=" + img);
				//if(targetFlag) ojSetTarget("slice=1");
				ojSwitchToItem("Bright");
				ojSetMarker(currentX[spore], currentY[spore]);
				ojCloseObject();
				thisObj = ojNewestObject();
				ojSetResult("nd2Name", thisObj, nd2Names[img-1]);
				ojSetResult("File", thisObj, getTitle);
				ojSetResult("prepTime", thisObj, prepTimes[img-1]);
				ojSetResult("x", thisObj, currentX[spore]);
				ojSetResult("y", thisObj, currentY[spore]);
				objIndexes += " " + thisObj;
				makeOval(currentX[spore] -peakRad, currentY[spore] -peakRad, peakRad*2, peakRad*2);//measure gray value
				getRawStatistics(area, gray);
				grayPairs[spore] = "0 " + round(gray) + ";";// first starts with frame: t= 0
				if(n_channels == 2){
					fl = getFluorescence(thisObj, currentX[spore], currentY[spore]);
					fluorPairs[spore] = "0 " + d2s(fl, -4) + ";";
				}

			}
			objIndexes = split(objIndexes);//strings
			for(jj = 0; jj < objIndexes.length; jj++){
				objIndexes[jj] = parseInt(objIndexes[jj]);//numbers
			}	
			nFrames = nSlices/n_channels;
			chn = 1;

//====== Frame >= 2  
//- analyze  bright  particles, 
//- analyze dark particles, 
//- re-identify per frame.
//- update 'lifeStage' status per spore BRIGHT, DARK or LOST
			prevArea = newArray(ojNObjects());//detect abrupt growth or shrink
			for(frame = 2; frame <= nFrames; frame++){//value pairs per spore
				slc = (frame - 1) * n_channels + chn;
//				print("slice=", slc);
				frameTime = (frame -1) * frame_Interval;//start with time=0
				checkCapsLock();			
				showProgress(-frame/nFrames);
				setSlice(slc);
				if(targetFlag) ojSetTarget("image=" + img);
				if(targetFlag) ojSetTarget("slice=1");
				if(targetFlag) ojSetTarget("channel=1");
				if(targetFlag) ojSetTarget("frame=" + frame);
				
				//======  Bright Particles in Frame >= 2
				setThreshold(stillBright, 1e6);
				roiManager("reset");
				run("Select None");

				run("Analyze Particles...", "size=&minParticleB-Infinity pixel display exclude clear include add slice");
				resetThreshold;
				nRois = nResults;
				maxRois = maxOf(maxRois, nRois);
				roisX = newArray(nRois);
				roisY = newArray(nRois);
				for(jj = 0; jj < nRois; jj++){ 
					roisX[jj] = getResult("X", jj)/pxWidth;
					roisY[jj] = getResult("Y", jj)/pxWidth;
				}
				for(spore = 0; spore < nSpores; spore++){ 
					if(lifeStage[spore] == BRIGHT){ 
						minDist = 9999;
						closestRoi = -1;
						for(roi = 0; roi<nRois; roi++){                   
							dist = sqrt(pow(roisX[roi] - currentX[spore], 2) + pow(roisY[roi] - currentY[spore], 2));
							if (dist < minDist && dist < jumpTolerance){
								minDist = dist;
								closestRoi = roi;
							}
						}
						if(closestRoi >= 0){//found bright
							currentX[spore] = roisX[closestRoi];
							currentY[spore] = roisY[closestRoi];
							makeOval(currentX[spore] -peakRad, currentY[spore] -peakRad, peakRad*2, peakRad *2);//measure gray value
							getRawStatistics(area, thisMean);//sample intensity
							run("Select None");
							grayPairs[spore] = grayPairs[spore] + "" + d2s(frameTime, 2) + " " + d2s(thisMean, 0) + ";";	//store array as text 						

							obj= objIndexes[spore];
							ojOpenObject(obj);
							ojSwitchToItem("Bright");
							ojSetMarker(currentX[spore], currentY[spore]);
							ojCloseObject();
							if(n_channels == 2){	
								fl = getFluorescence(obj, currentX[spore], currentY[spore]);
								fluorPairs[spore] = fluorPairs[spore] + "" + d2s(frameTime, 2) + " " + d2s(fl, 0) + ";";
							}
						}
						else if(lifeStage[spore] == BRIGHT){
							lifeStage[spore] = DARK; //bright-dark transition
						}
					}
				}

				//======  Dark Particles in Frame >= 2
				
				
//				if(followGrowth){//31.03.23, 0:55
					
				if(isNaN(parseInt(darkThr)))
					setAutoThreshold(darkThr);//string for auto-threshold, e.eg. "Default"
				else
					setThreshold(0, darkThr);//number for fixed threshold
				
				roiManager("reset");
				run("Select None");
				if (includeHoles)
					run("Analyze Particles...", "size=&minParticleD-Infinity pixel display exclude clear add slice include");
				else
					run("Analyze Particles...", "size=&minParticleD-Infinity pixel display exclude clear add slice");
				resetThreshold;
				nRois = nResults;
				maxRois = maxOf(maxRois, nRois);
// 				}
// 				else{
// 					nRois = 0;
// 				}
				roisX = newArray(nRois);
				roisY = newArray(nRois);
				for(jj = 0; jj < nRois; jj++){ 
					roisX[jj] = getResult("X", jj)/pxWidth;
					roisY[jj] = getResult("Y", jj)/pxWidth;
				}

				for(spore = 0; spore < nSpores; spore++){ 
					if(lifeStage[spore] == DARK){ 
						minDist = 9999;
						closestRoi = -1;
						for(roi = 0; roi<nRois; roi++){
							dist = sqrt(pow(roisX[roi] - currentX[spore], 2) + pow(roisY[roi] - currentY[spore], 2));
							if (dist < minDist && dist < jumpTolerance){
								minDist = dist;
								closestRoi = roi;
							}
						}
						if(closestRoi >= 0){
							currentX[spore] = roisX[closestRoi];
							currentY[spore] = roisY[closestRoi];
							roiManager("select", closestRoi);
							run("Interpolate", "interval=3 smooth");
							if(selectionType != -1){
								obj= objIndexes[spore];
								ojSelectObject(obj);
								nBrights = ojNItems("Bright");

							
								area = getResult("Area", closestRoi);
								prevA = prevArea[obj-1];
								growth = area/prevA;
								if(prevA > 10 && (growth < 0.9 || growth > 1.2)){//no size jump above 10 um^2
									lifeStage[spore] =LOST; 
									continue;
								}
								prevArea[obj-1] = area;	
								
								
								
								ojOpenObject(obj);
								ojSwitchToItem("Contour");
								ojRoiToItem();
								ojCloseObject();
				
								//transitionFinished = (frame > nBrights + afterBrightCircles);
								collectionFinished = (frame > nBrights + afterBrightGrays);
								if(!collectionFinished){
									makeOval(currentX[spore] -peakRad, currentY[spore] -peakRad, peakRad*2, peakRad *2);//measure gray value				
									getRawStatistics(dummyArea, thisMean);
									grayPairs[spore] = grayPairs[spore] + "" + d2s(frameTime, -3) + " " + d2s(thisMean, -3) + ";";//27.03.23, 2:00
									if(n_channels == 2){
										fl = getFluorescence(obj, currentX[spore], currentY[spore]);
										fluorPairs[spore] = fluorPairs[spore] + "" + d2s(frameTime, -3) + " " + d2s(fl, -3) + ";";
									}
								}
								thisPair = d2s(frameTime, 2) + " " + d2s(area, 2)+ ";";
								areaPairs[spore] = "" + areaPairs[spore] + thisPair;

							}
						}
						else if(lifeStage[spore] == DARK)
							lifeStage[spore] =LOST; //end of tracing
					}
				}
				roiManager("reset");	
			}//for (frame= ...
		
		
			for(spore = 0; spore < nSpores; spore++){
				obj= objIndexes[spore];
				ojSetResult("AreaPairs", obj, areaPairs[spore]);
				ojSetResult("GrayPairs", obj, grayPairs[spore]);
				ojSetResult("FluorPairs", obj, fluorPairs[spore]);
			}

			run("Select None");
			run("Remove Overlay");

			if(targetFlag) ojSetTarget("exit");
			setSlice(1);
			ojSetTool(0);
		}
		print("maxRois = ", maxRois);//indicate excessive noise particles
	}// for (img=1
	setBatchNumbers();
	close("Results");
	beep;
	msg = " " + (ojNObjects() - startObjects) + " spores analyzed in " + d2s((getTime - startTime)/60e3, 1) + " min";
	msg += "\n \nShow Plots in Navigation Mode?";
	toShow = 0;
	for(plot = 0; plot < n_channels +1; plot++)//0=growth, 1=brightness, 2=fluor
		makeCollectivePlot(plot);
	setBatchMode("exit and display");//28.03.18 23:50	
	ojSelectObject(0);
	if(getBoolean(msg))
		activateNavigation();
}//===== End Analyze Spores

//Re-builds two collective plots using 'pairs' with manual entries in ObjectJ Results
macro "Create Plots [F2]"{
	toShow = getNumber("Include Active Plot of obj:", NaN);
	ojHideResults();
	calcMaxFrames();
	for(plot = 0; plot < n_channels +1; plot++)
		makeCollectivePlot(plot);

	showMessageWithCancel("Show Plots in Navigation Mode?");
	ojSelectObject(0);
	activateNavigation();
}


macro "Open All Virtually [F3]"{
 	openAllVirtually();
 }

function openAllVirtually(){
	for (img = 1; img <= ojNImages(); img++)
		close(ojGetImageName(img));//clean start 23.05.23
	for (img = 1; img <= ojNImages(); img++)
		openVirtually(img);
}

//If image is stack and not virtual, it first is closed
function openVirtually(img){
	ojSetVirtualFlag(true);
	ojShowImage(img);
	return;
}


function checkCapsLock(){
	if(is("Caps Lock Set")){ //handle controlled interrupt
		stopTime = getTime;
		ojSetTool(0);
		run("View 100%");				
		id = getImageID;
		selectImage(id);
		if(!n_debug)
			setBatchMode("show");
		if(targetFlag) 
			ojSetTarget("exit");
		waitForUser("Caps Lock is down for inspection.\n \nPress 'Esc' to abort.\nClick 'OK' to continue.");
		startTime += getTime - stopTime;
		ojHideResults();
		ojShowImage(img);
		selectImage(id);
		if(!n_debug)
			setBatchMode("hide");
	}
}

//Create new Plot-Stack
function makeCollectivePlot(plot){
	setFont("SansSerif", 12, "antiliased bold");
	yMinLimits = newArray(plotAreaMin, 0, 0);//growth-brightness
	yMaxLimits = newArray(plotAreaMax, plotGrayMax, plotFlMax);
	legends = split("log(2, Area [um^2]);Brightness;Fluorescence", ";");
	if(!n_debug)
		setBatchMode(true);
	title = plotTitles[plot];	
// 	if(title == "Growth" && followGrowth == false)//31.03.23, 1:31
// 		return;

	close("Plot_" + title);
	yLegend = legends[plot];
	setPasteMode("Copy");
	for(obj = 1; obj <= ojNObjects(); obj++){
		q = ojResult("Q", obj);
		if(isNaN(q) || q !=0)
			ojSetResult("Q", obj, 1);
		if(plot == 0){//"Growth"
			xx = extractArray("AreaPairs", obj, 0);
			yy = extractArray("AreaPairs", obj, 1);
			for(jj= 0; jj< yy.length; jj++)
				yy[jj] = log(yy[jj])/log(2);
		}
		if(plot == 1){ // "Germination"
			xx = extractArray("GrayPairs", obj, 0);
			yy = extractArray("GrayPairs", obj, 1);
		}
		if(plot == 2){ // "Fluor Ch2"
			xx = extractArray("FluorPairs", obj, 0);
			yy = extractArray("FluorPairs", obj, 1);
		}
		len = xx.length;
		yMin= yMinLimits[plot];
		yMax= yMaxLimits[plot];
		onePlotToClip(xx, yy, yMin, yMax, obj, toShow);//puts plot to clipboard
		if(obj == 1){
			//run("Internal Clipboard");
			selectImage("Base");
			rename("Plot_" + title);//e.g. Plot_Growth
		}
		else{
			selectImage("Plot_" + title);
			run("Add Slice");
			run("Paste");
		}	
		id= ojIndexToId(obj);
		setMetadata("Label", "obj=" + obj + "  id=" + id);	
		
		if((ojResult("Q", obj) == 0) && title ==  "Growth"  ){
			changeValues(white, white, lGray);
		}
	}
	close("Projection_" + title);

	if(nSlices > 1)
		run("Z Project...", "projection=[Min Intensity]");
	rename("Projection_" + title);
	changeValues(lGray, lGray, white);
	changeValues(0x0dd, 0x0dd, 0x0ff);//recover blue
	run("Select All");
	run("Copy");	
	close;
	selectImage("Plot_" + title);
	setSlice(nSlices);
	run("Add Slice");
	run("Paste");

	run("Select None");
	if(plot == 0){//Growth
		calcAndPaintAllTDs();
		calcAndPaintAllBursts();
		paintAllFirstDivs();
	}
	if(plot == 1)//Germination
		calcAndPaintGermination();
	setSlice(nSlices);
	
	if(!n_debug)
		setBatchMode("show");
	setSlice(1);//work-around
	setSlice(nSlices);
	if(toShow > 0  && toShow <= nSlices)
		setSlice(toShow);
	setOption("Changes", false);
}

//Calculates doubling times TD of all objects
function calcAndPaintAllTDs(){
	for(obj = 1; obj <= ojNObjects(); obj++){
		calcAndPaintTD(obj);
	}
	mean = ojGetStatistics("TD", "mean");
	stdev = ojGetStatistics("TD", "stdev");
	count = ojGetStatistics("TD", "count");
	msg = "nSpores= " + ojNObjects();
	msg += "   TD=" + d2s(mean, 1) + "   (std=" + d2s(stdev, 1) + ", n=" + count + ")";
	if(getTitle == "Plot_Growth"){
		setSlice(nSlices);
		drawString(msg, 10, getHeight -4);
	}
}

//Calculates doubling time TD from manual or automatic time range, paint green slope
function calcAndPaintTD(obj){	
	xx = extractArray("AreaPairs", obj, 0);
	yy = extractArray("AreaPairs", obj, 1);
	
	len = xx.length;
	if(len <2)
		return;
	for(jj= 0; jj< len; jj++)
		yy[jj] = log(yy[jj])/log(2);//log(area)
	t1 = ojResult("T1", obj);
	t2 = ojResult("T2", obj);
	tEnd = t2;

	if (isNaN(tEnd)){
		tEnd = xx[len-1];
		if(yy[len-1] < minStopArea)
			return;
	}
	startIndex = -1;
	stopIndex = -1;
	for(jj= 0; jj< len; jj++){	
		if(isNaN(t1) && startIndex == -1 && yy[jj] > startArea){//auto start
			startIndex = jj;
		}	
		else if(xx[jj] == t1 && startIndex == -1)//manual start
			startIndex = jj;
			
			
		kk = len - jj - 1;//count backwards
		if(xx[kk] == tEnd && stopIndex == -1)
			stopIndex = kk;
			
		if(startIndex != -1 && stopIndex != -1)
			break;
	}
	if(startIndex == -1 || stopIndex == -1)
		return;
			
	lineX = newArray(4);//4 polygon vertexes
	lineY = newArray(4);

	lineX[0] = xx[startIndex];
	lineY[0] = yy[startIndex];
	lineX[1] = xx[stopIndex];
	lineY[1] = yy[stopIndex];
	TD = (lineX[1] - lineX[0])/(lineY[1] - lineY[0]);
	ojSetResult("TD", obj, TD);
	if(getTitle == "Plot_Growth"){
		setSlice(obj);
		toUnscaled(lineX, lineY);
		
		lineX[0] = lineX[0] +1;//1 px right
		lineX[1] = lineX[1] +1;
		lineY[0] = lineY[0] +1;//1 px lower
		lineY[1] = lineY[1] +1;
		if(t1>0){		
			makeRectangle(lineX[0] - 2, lineY[0]- 6, 4, 14);
			changeValues(white, white, t1Color);
		}
		if(t2>0){
			makeRectangle(lineX[1] - 2, lineY[1]- 6, 4, 14);
			changeValues(white, white, t2Color);
		}

		lineX[2] = lineX[1];
		lineX[3] = lineX[0];
		lineY[2] = lineY[1] + 8;
		lineY[3] = lineY[0] + 8;
		lineY[0] -= 8;
		lineY[1] -= 8;
		run("Select None");
		changeValues(slopeColor, slopeColor, white);
		makeSelection("polygon", lineX, lineY);
		changeValues(white, white, slopeColor);
		makeRectangle(0, getHeight - 24, 300, 24);
		changeValues(0, white, white);
		setColor(0x00aa00);
		drawString("TD=" + d2s(TD, 1) + " min", 10, getHeight -4);			
		run("Select None");
	}
}

//Paints magenta dots for begin and end of germination
function calcAndPaintGermination(){
	for(obj = 1; obj <= ojNObjects(); obj++){
		ojSelectObject(obj);
		ojSetResult("Germ1", obj, NaN);
		ojSetResult("Germ2", obj, NaN);
		//if(followGrowth && ojNItems("Contour") == 0){//31.03.23
		if(ojNItems("Contour") == 0){
			continue;	
		}
		xx = extractArray("grayPairs", obj, 0);
		yy = extractArray("grayPairs", obj, 1);
		if(xx.length<2 || yy.length<2)
			continue;//16.04.18 19:44
		Array.getStatistics(yy, min, max, mean, stdDev);
		len = yy.length * 10;//refine
		xx = Array.resample(xx, len);
		yy = Array.resample(yy, len);
		if(min/max > minDropRatio)
			continue;
		highLevel = germHigh * (max - min) + min;
		lowLevel = germLow * (max - min) + min;
		lowIndex = 0;
		highIndex = 0;
		for(jj = 0; jj < len; jj++){
			y = yy[jj];
			if(y > highLevel)
				highIndex = jj;
			if(y < lowLevel){
				lowIndex = jj;
				break;
			}
		}

		germX1 = xx[highIndex];
		germY1 = yy[highIndex];

		germX2 = xx[lowIndex];
		germY2 = yy[lowIndex];
		
		ojSetResult("Germ1", obj, germX1);
		ojSetResult("Germ2", obj, germX2);
		if(getTitle == "Plot_Germination"){
			msg = "Germ1 =" + d2s(germX1, 1);// + " min";
			setSlice(obj);
			toUnscaled(germX1, germY1);		
			makeOval(germX1 -5, germY1 - 5, 11, 11);
			changeValues(white, white, 0xffaaff);
			toUnscaled(germX2, germY2);		
			makeOval(germX2 -5, germY2 - 5, 11, 11);
			changeValues(white, white, 0xffaaff);
			
			makeRectangle(0, getHeight - 24, 300, 24);
			changeValues(0, white, white);
			setColor(0xaa0000);

			drawString(msg, 10, getHeight -4);
			run("Select None");
		}
	}
}

//Uses manual entries for "First Division" and paints them in plot
function paintAllFirstDivs(){
	for(obj = 1; obj <= ojNObjects(); obj++){
		firstDiv = ojResult("FirstDiv", obj);
		
		if(!isNaN(firstDiv)){
			xx = extractArray("AreaPairs", obj, 0);
			yy = extractArray("AreaPairs", obj, 1);

			len = xx.length;
			for(jj= 0; jj< len; jj++){
				if(xx[jj] == firstDiv){
					x = firstDiv;
					y = log(parseFloat(yy[jj]))/log(2);
					setSlice(obj);
					toUnscaled(x, y);
					makeOval(x -4, y - 6, 8, 12);
					changeValues(white, white, firstDivColor);
					break;
				}
			}
		}
	}
	run("Select None");
}

function calcAndPaintAllBursts(){
	setColor(burstColor);
	for(obj = 1; obj <= ojNObjects(); obj++){
		//showProgress(obj/ojNObjects());
		bIndex = -1;
		burstM = ojResult("BurstM", obj);
		xx = extractArray("AreaPairs", obj, 0);
		yy = extractArray("AreaPairs", obj, 1);

		len = xx.length;
		for(jj= 0; jj< len; jj++){
			yy[jj] = log(parseFloat(yy[jj]))/log(2);
		}
		if(!isNaN(burstM)){//was marked manually
			for(jj= 0; jj< len; jj++){
				if(abs(xx[jj] - burstM) < 0.001){
					bIndex = jj-1;
					break;
				}
			}
		}
		else if(autoBurst){//Automatic Burst Sensor
			for(jj= 8; jj< len; jj++){//does area jump >15% within 3 frames?
				if (yy[jj] - yy[jj - 3] > 0.15 && yy[jj] >1.0){//area > 2^1.0 sqUm?
					bIndex = jj-1;
					break;
				}
			}
		}
	
		if(bIndex >=0){//paint
			burstX = xx[bIndex];
			burstY = yy[bIndex];
			ojSetResult("Burst", obj, burstX);
			if(getTitle == "Plot_Growth"){
				setSlice(obj);
				toUnscaled(burstX, burstY);		
				makeOval(burstX -5, burstY - 5, 10, 10);
				if(!isNaN(burstM))
					makeRectangle(burstX -5, burstY - 5, 10, 10);
				changeValues(white, white, burstColor);
				run("Select None");
				setSlice(1);
			}
		}
	}
}

//Creates a plot for one object and one type (name starts with "tmp-").
function onePlotToClip(plotX, plotY, yMin, yMax, obj, toShow){
		color = "blue";	
		xLegend = "Time";
		if(timeFactor == 60)	
			xLegend += " [min]";	
		Plot.create("tmp-", xLegend, yLegend);
		Plot.setFrameSize(550, 230);
 		Plot.setLimits(0, xLimit, yMin, yMax);
		Plot.setColor(color);
		Plot.add("line", plotX, plotY);
		
// 		if(toShow == obj){
// 			g1 = ojResult("Germ1", obj);
// 			g2 = ojResult("Germ2", obj);
// 			Plot.drawLine(g1, 0, g1, 1e6);
// 			Plot.drawLine(g2, 0, g2, 1e6);
// 			Plot.setStyle(1, "orange,none,1.0,Linehidden");
// 			Plot.setStyle(2, "orange,none,1.0,Linehidden");
// 		}
		
		Plot.show();
		
		run("Select All");
		run("Copy");
		run("Select None");
// 		if(toShow == obj){	
// 			Plot.setStyle(1, "orange,none,1.0,Line");
// 			Plot.setStyle(2, "orange,none,1.0,Line");
// 		}
		id = getImageID;
		if(obj == 1)
			run("Duplicate...", "title=Base");
		selectImage(id);
		if(toShow == obj){
			name = "Single Plot_" + plot +" obj = " + toShow;
			close(name);
			rename(name);
		}
		if(obj > 1){
			close("Base");
		}
		close("tmp-");
}

//If metadata contains .nd2 file name:
//.nd2 file name and preparation time in seconds is extracted:
//Label Example "t:1/271 - 20170430 168 tsb bl1 0.06xTNO4_p318.nd2 (series 1)"
//                       A                               B    C   
//fName = "20170430 168 tsb bl1 0.06xTNO4_p318.nd2"
//seconds = 318
function evalMetadata(img, names, prepTimes){
	label = getMetadata("label");
	posA = indexOf(label, "- ");
	posB = lastIndexOf(label, "_p");
	posC = lastIndexOf(label, ".nd2");
	fName = "";
	seconds = 0;
	if(posA > 0 && posC > 0){
		fName = substring(label, posA + 2, posC + 4);
		if(posC - posB < 7 && posB > 0){
			secStr = substring(label, posB + 2, posC);
			seconds = parseInt(secStr);
		}
	}
	names[img] = fName;
	prepTimes[img] = seconds;
}


function removeNeighbors(){
	flags = newArray(nResults);
	for(jj = 0; jj < nResults; jj++){
		x1 = getResult("X", jj);
		y1 = getResult("Y", jj);
		for (kk = jj +1; kk < nResults; kk++){
			x2 = getResult("X", kk);
			y2 = getResult("Y", kk);
			dist = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
			if (dist < neighborDist){
				flags[jj] = 1;
				flags[kk] = 1;
			}
		}
	}
	for(jj = nResults -1; jj >=0; jj--){
		if(flags[jj] == 1)
			IJ.deleteRows(jj, jj); 
	}
	print("Removed Neighbors: " + (flags.length - nResults));
}


// ObjectJ Tools: use tool with logo "N":
//dragging the cursor across the plot plays the movie synchronously 
macro "Navigate Plot Tool - C059o00ffCfffT4c11N" {
	batchFlag = true;
	plotTitle = getTitle;
	clickedID = getImageID;//so we can return
	if(!startsWith(plotTitle, "Plot_")){
		ojSetTool(0);
		beep;exit;
	}

	getCursorLoc(x, y, z, flags);
	if (getSliceNumber == nSlices){
		selectSinglePlot(x, y);	
		exit;
	}
	obj = z+1;
	if (obj > ojNObjects()) return;
	ojSelectObject(obj);
	img = ojOwnerIndex(obj);
	ojShowImage(img);
	if(!n_debug)
		setBatchMode(true);//synchronize both: begin
	navTitles = Array.copy(plotTitles);
	for(jj = 0; jj < navTitles.length; jj++)
		navTitles[jj] = "Plot_" + navTitles[jj];

	slc = getSliceNumber;
	for(jj = 0; jj < navTitles.length; jj++){
		if(isOpen(navTitles[jj])){
			selectImage(navTitles[jj]);
			setSlice(z+1);
		}
	}
	if(!n_debug)
		setBatchMode(false);
	selectImage(plotTitle);//synchronize both: end

	leftButton=16;
	rightButton=4;
	plotID = getImageID;
	
	x2=-1; y2=-1; z2=-1; flags2=-1;
	getCursorLoc(x, y, z, flags);
	if (ojNObjects() != nSlices-1)
		exit("nObjects != nSlices-1: You should rebuild the plot");
	obj = getSliceNumber;
	if (obj > ojNObjects()) 
		return;
	ojShowImage(img);
	
	stackID = getImageID;
 	Stack.getDimensions(dummy, dummy, nChannels, numSlices, nFrames);
	xc = ojXPos(1);
	yc = ojYPos(1);
	ojZoom(navZoom, xc, yc);//zoom at factor "navZoom"
	makeOval(xc - 30, yc - 30, 60, 60);
	selectImage(plotID);
 	if(!n_debug)
		setBatchMode(true & batchFlag);

	prevFrame = 0;
	while (flags&(leftButton)!=0) {
		getCursorLoc(x, y, z, flags);
		if (x!=x2 || y!=y2 || z!=z2 || flags!=flags2) {
			x3 = x; y3 = y;
			toScaled(x3, y3);
			x3 *= timeFactor;
			frm = round(x3/frame_Interval) + 1;
			showStatus("frame=" + frm);
			if (frm < 1) frm = 1;
			if (frm > nFrames) frm = nFrames;
			
			selectImage(stackID);
			FR=(minOf(frm, nFrames));
			if(FR != prevFrame){
				prevFrame = FR;
				Stack.setFrame(FR);
			
				run("Select None");	
				for(itm = 1; itm <= ojNItems("*"); itm++){
					ojSelectItem("*", itm);
					if(ojZPos(1) == getSliceNumber || ojZPos(1) == (getSliceNumber - nChannels + 1)){// || ojZPos(1) == getSliceNumber - nChannels+1 ){
						if(ojNPoints() == 1)
							makeOval(ojXPos(1) - 22, ojYPos(1) - 22, 44, 44);
						else{
							ojItemToRoi();
							run("Properties... ", "  stroke=magenta width=2");
							getSelectionBounds(bx, by, bwidth, bheight);
							ojZoom(navZoom, bx + bwidth/2, by + bheight/2);//zoom at factor "navZoom"
						}
						break;
					}
				}
			
			
				updateDisplay;//needed if hyperstack-not working
			}
			selectImage(plotID); 
		}
		x2=x; y2=y; z2=z; flags2=flags;
		wait(10);
	}
	if(!n_debug)
		setBatchMode("exit and display");
	selectImage(stackID);
	run("Select None");
	selectImage(clickedID);//return to original window ####
	showStatus("clickedID=" + clickedID);
}

//Q flag = 1 :qualified = yes
macro "Toggle Q-Flag [Q] "{
	obj = objFromMetadata();
	//setColumns();
	q = ojResult("Q", obj);
	q = !q;//toggle
	ojSetResult("Q", obj, q);
	run("Select All");
	if(q)
		changeValues(lGray, lGray, white);
	else
		changeValues(white, white, lGray);
	run("Select None");
	for(obj = 1; obj <= ojNObjects(); obj++){
		q = ojResult("Q", obj);
		ojQualify(obj, (q > 0));
	}
}

//Updates qualification from Q flag and asks to kill.			
macro "Qualification..."{
	unQualified = 0;
	for(obj = 1; obj <= ojNObjects(); obj++){
		q = ojResult("Q", obj);
		ojQualify(obj, (q > 0));
		unQualified += (q == 0);
	}
	msg = "Qualification is now updated.";
	msg += "\n Kill " + unQualified +  " of " + ojNObjects() + " objects ?";
	if(getBoolean(msg, "Don't kill", "Kill"))
		return;//don't kill
	for(obj = ojNObjects(); obj >0; obj--){
		if(ojResult("Q", obj) == 0)
			ojDeleteObject(obj);
	}
	ojRecalculate();
	for(plot = 0; plot < n_channels +1; plot++)
		makeCollectivePlot(plot);

}

//different nd2Names (biological repeats) result in different batch numbers (1, 2, 3)
function setBatchNumbers(){
	allNames = "";
	List.clear();
	bb = 0;
	for(obj=1; obj <= ojNObjects(); obj++){
		name = ojResultString("nd2Name", obj);
		if(indexOf(allNames, "@" + name + "#") == -1){	
			List.set(name, ++bb);
			allNames += "@" + name + "#";
		}
		batch = List.getValue(name);
		ojSetResult("Batch", obj, batch);	
	}
	List.clear();
}
//Lists project path, long and short names, and batch number
/* Disabled 24.05.23
macro "Show Metadata"{
	print("\\Clear");
	print("--- Path ---\n", ojGetProjectPath(), "\n");
	openAllVirtually();
	imgCount = ojNImages();
	
	batchStarts = newArray(99);
	prevCkSum = "";
	batch = 0;
	setBatchMode(true);
	for (img = 1; img <=imgCount; img++){
		ojShowImage(img);
		parts = split(getTitle, "_");
		if(parts.length < 2)
			exit("Metadata not supported");
		ckSum = parts[1];
		if(ckSum != prevCkSum){
			batchStarts[batch] = img;
			print("\n --- Batch ", ++batch, " ---"); 
			prevCkSum = ckSum; 
		}
		label = getMetadata("label");
		print(getTitle, "  -->  ", label);
	}
	selectWindow("Log");
}
*/
macro "Set Sign Burst [B]"{
	setSign(0);
}

macro "Set Sign FirstDiv [F]"{
	setSign(1);
}

macro "Set Sign T1 [1]"{
	setSign(2);
}

macro "Set Sign T2 [2]"{
	setSign(3);
}

//set coloured sign into growth Plot
function setSign(signIndex){
	cmd = getInfo("command.name");
	if (!startsWith(cmd, "^"))
		exit("Command must be invoked via shortcut key, while cursor is at desired position");

	sign = signNames[signIndex];

	if(signIndex < 4 && getTitle != "Plot_Growth"){
			if(getBoolean("First bring 'Plot_Growth' window to front?"))
				selectImage("Plot_Growth");	
			return;
	}
	if(signIndex == 4 && getTitle != "Plot_Germination"){//not implemented yet
			if(getBoolean("First bring 'Plot_Germination' window to front?"))
				selectImage("Plot_Germination");	
			return;
	}
	getCursorLoc(x, y, z, flags);
	toScaled(x, y);
	obj = objFromMetadata();
	
	
	if(signIndex != 4){
		xx = extractArray("AreaPairs", obj, 0);
		yy = extractArray("AreaPairs", obj, 1);
		dMin = 1e6;  
		foundLocX = 1e6;              
		for(jj = 0; jj < xx.length; jj++){
			d = abs(xx[jj] -x);
			if(d < dMin){
				dMin = d;
				foundLocX = xx[jj];
				foundLocY = log(yy[jj])/log(2);
			}
		}
		if(foundLocX == 1e6) return;
		if(ojResult(sign, obj) == foundLocX)//already marked? Then unmark!
			foundLocX = NaN;

		ojSetResult(sign, obj, foundLocX);
		if(signIndex == 0)	//burst	
			ojSetResult("BurstM", obj, foundLocX);

		run("Select All");
		sign = signNames[signIndex];
		color = signColors[signIndex];
		changeValues(color, color, white);
		if(!isNaN(foundLocX)){
			unscaledX = foundLocX;
			unscaledY = foundLocY;
			toUnscaled(unscaledX, unscaledY);
			if(signIndex == 0)	//burst	
				makeRectangle(unscaledX -5, unscaledY -5+1, 10, 10);			
			if(signIndex == 1)	//firstDiv	
				makeOval(unscaledX -4, unscaledY -6, 8, 12);//was: -7
			if(signIndex >= 2){	//t1, t2	
				changeValues(slopeColor, slopeColor, white);
				makeRectangle(unscaledX -2, unscaledY -6, 4, 14);
			}
			changeValues(white, white, color);
			changeValues(lGray, lGray, color);
			changeValues(slopeColor, slopeColor, color);
		
			calcAndPaintTD(obj);
		}
		run("Select None");		
	}	
	
	if(signIndex == 4){//
		xx = extractArray("GrayPairs", obj, 0);
		yy = extractArray("GrayPairs", obj, 1);
		Array.getStatistics(yy, min, max, mean, stdDev);
		mid = (min + max)/2;
		hitIndex = 0;
		theX = 0;
		if(y < max && y > min){
			for(jj = xx.length-1;  jj > 1; jj--){
				if(y> mid && y > yy[jj] && y < yy[jj -1]){	
					hitIndex = jj;
					break;
				}
				if(y < mid && y > yy[jj] && y < yy[jj -1]){	
					hitIndex = jj;
					break;
				}
			}
			yFraction = (y - yy[hitIndex])/(yy[hitIndex - 1] - yy[hitIndex]);
			xFraction = (xx[hitIndex] - xx[hitIndex - 1]) * yFraction;
			theX = xx[hitIndex] - xFraction;
		}
		Array.show(xx, yy);
		print("\\Clear");
		germX1 = -1;
		germX2 = -1;
		if(y > mid){
			germX1 = theX;
			germY1 = y;
			ojSetResult("Germ1", obj, germX1);
		}
		if(y < mid){
			germX2 = theX;
			germY2 = y;
			ojSetResult("Germ2", obj, germX2);
		}
		if(germX1 > -1)	
			ojSetResult("Germ1", obj, germX1);
		if(germX2 > -1)	
			ojSetResult("Germ2", obj, germX2);
			if(getTitle == "Plot_Germination"){
				setSlice(obj);
				toUnscaled(germX1, germY1);		
				makeOval(germX1 -5, germY1 - 5, 11, 11);
				changeValues(white, white, 0xffaaff);
				toUnscaled(germX2, germY2);		
				makeOval(germX2 -5, germY2 - 5, 11, 11);
				changeValues(white, white, 0xffaaff);
				//run("Select None");	
				msg = "Germ1 =" + d2s(xx[germX1], 1) + " min";
				
				makeRectangle(0, getHeight - 24, 300, 24);
				changeValues(0, white, white);
				setColor(0xaa0000);
				drawString(msg, 10, getHeight -4);
				run("Select None");
			}
	}	
}


//evaluate object id which is part of metadata label in the growth plot
function objFromMetadata(){
	obj = 0;
	if(!(getTitle == "Plot_Growth" || getTitle == "Plot_Germination"))
		exit("Works only in 'Plot_Growth' or 'Plot_Germination' window");
	selectImage(getTitle);
	s = getMetadata("label");
	parts = split(s);
	if(lengthOf(parts) > 1){
		parts = split(parts[1], "= ");
		if(lengthOf(parts) > 1){
			id = parseInt(parts[1]);
			obj = ojIdToIndex(id);
		}
	}
	if(obj == 0) exit("Could not resolve object ID");
	return obj;
}


//Extract numerical array from string. If string == "3 4; 2 8", then: 
// index = 0: returns newArray(3, 2);
// index = 1: returns newArray(4, 8);

function extractArray(colTitle, obj, index){
	pairs = split(ojResultString(colTitle, obj), ";;");//dual-semicolon: avoid empty strings
	len = pairs.length;
	arr = newArray(len);
	for(jj= 0; jj< len; jj++){
		pair = split(pairs[jj]);
		if(pair.length < 2)
			return newArray(0);	
		arr[jj] = parseFloat(pair[index]);
		if(index == 0)//
			arr[jj] /= timeFactor;
	}
	return arr;
}

//ObjectJ result columns are created from scratch
function initColumns(){
	ojDeleteColumn("*");
 	ojInitTextColumn("nd2Name File AreaPairs GrayPairs FluorPairs");
 	ojInitColumn("Batch PrepTime T1 T2 TD Germ1 Germ2 Burst BurstM FirstDiv Q x y");
 	ojSetColumnProperty("T1 T2 TD Germ1 Germ2 Burst  BurstM ", "digits", 1);
	ojSetColumnProperty("Batch PrepTime Q x y", "digits", 0);
	ojSetColumnProperty("nd2Name File Batch T1 T2 PrepTime Germ2 BurstM *Pairs x y", "visible", 0);
	ojSetColumnProperty("Germ1", "color", 0xff00ff);
	ojSetColumnProperty("Burst", "color", burstColor);
	ojSetColumnProperty("TD", "color", 0x0000aa);	
}


//when clicking with N-tool on collective plot, it jumps to that single plot
function selectSinglePlot(x, y){
	xhit = 0;
	yhit = 0;
	minRad = 1e9;
	for(dx = -4; dx <= 4; dx++){
		for(dy = -4; dy <= 4; dy++){
			v=getPixel(x+dx, y+dy) & 0x0ffffff;
			//v = v &0xffffff;
			if (v == 0x0000ff){
				rad2 = dx * dx + dy * dy;
				if (rad2 < minRad){
					xhit = x + dx;
					yhit = y + dy;
					minRad = rad2;
				}
			}
		}
	}
	if (xhit > 0){
		for(slc = 1; slc < nSlices; slc++){
			setSlice(slc);
			if(getPixel(xhit, yhit)&0x0ffffff == 0x0000ff){
				break;
			}
		}
	}	
}

function zoomTo(obj){
	if(obj == 0)
		return;
	img = ojOwnerIndex(obj);
	openVirtually(img);
	ojShowObject(obj);
	xc = ojXPos(1);
	yc = ojYPos(1);
	ojZoom(navZoom, xc, yc);//zoom at factor 2
	makeOval(xc - 30, yc - 30, 60, 60);
}

macro "Activate Navigation [N]"{
	calcMaxFrames();//08.03.23 only one needs to be open
	if(!isOpen("Plot_Germination") && !isOpen("Plot_Growth")|| (n_channels == 2 && !isOpen("Plot_Fluor"))){
		showMessageWithCancel("Recreate Plot and Germination Plots?");
		for(plot = 0; plot < n_channels +1; plot++)
			makeCollectivePlot(plot);
		if(!n_debug)
			setBatchMode("exit and display");
	}
	obj = ojSelectedObject();
	zoomTo(obj);
	activateNavigation();
}

function activateNavigation(){
	setBatchMode("exit and display");
	obj = ojSelectedObject();
	
	top = 40;
	if(isOpen("Plot_Fluor")){
		selectWindow("Plot_Fluor");
		run("Select None");
		setLocation(screenWidth-720, top );
		if(obj > 0 && obj < nSlices){
			setSlice(obj);
		}
		top += 350;
	}
	if(isOpen("Plot_Germination")){
		selectWindow("Plot_Germination");
		run("Select None");
		setLocation(screenWidth-720, top);
		if(obj > 0 && obj < nSlices){
			setSlice(obj);
		}
		top += 350;
	}
	if(isOpen("Plot_Growth")){
		selectWindow("Plot_Growth");
		run("Select None");
		setLocation(screenWidth-720, top);
		if(obj > 0 && obj < nSlices){
			setSlice(obj);
		}
		top += 350;
	}
	ojSetTool(-101);//N-tool
}

function getFluorescence(obj, x, y){
	Stack.getPosition(thisChn, thisSlc, thisFrm);
	Stack.setChannel(2)
	makeOval(x - fluorRad, y - fluorRad, fluorRad*2, fluorRad*2);
	List.setMeasurements;
	run("Select None");
	mean = List.getValue("Mean");
	mode = List.getValue("Mode");
	area = List.getValue("Area");
	//fl = area*(mean-mode);
	fl = mean;//13.12.18, 23:53
	Stack.setChannel(1)
	return fl;
}

function calcMaxFrames(){

	maxFrames = 0;
	for(img = 1; img <= ojNImages(); img++){
		if (img == 1)		
		fi = ojGetImageValue(img, "frameinterval");
		chns = ojGetImageValue(img, "channels");
		
		if(img == 1){
			frame_Interval = fi;
			n_channels = chns;
		}
		if (abs(frame_Interval - fi) > 0.01)
			exit("Images have different frame intervals");
		if (chns != n_channels)
			exit("Images have different number of channels");
		frames = ojGetImageValue(img, "frames");
		maxFrames = maxOf(maxFrames, frames);
	}

	
	xLimit = maxFrames * frame_Interval;
	xLimit /=  timeFactor;


}

macro "Collage [F8]"{
	calcMaxFrames();
	if(n_channels !=2)
		exit("Only available in 2-channel stacks");
	setBatchMode(true);
	close("Collage");
	left = 50;
	top = 0;
	kk = 30; //chunk 30 px
	height = ojNObjects() * 2 * kk + 100;
	width = maxFrames * kk + 50;
	newImage("Collage", "16-bit black", width, height, 1);
	Overlay.drawString("", 0, 0, 0.0);
	for(img = 1; img <= ojNImages(); img++){
		ojShowImage(img);
		setBatchMode("hide");
		for(obj = ojFirstObject(img); obj <= ojLastObject(img); obj++){
			if(obj%5 == 0){
				for(frm = 0; frm < 77; frm+=2)
					Overlay.drawString(d2s(frm * frame_Interval, 0), frm * kk  + 54, top + 22, 0.0);
				top += 36;
			}
			showStatus("obj " + obj + " of " + ojNObjects());
			ojShowObject(obj);
			showProgress(obj/ojNObjects());
			srcID = getImageID;
			Overlay.drawString("" + obj, 5, top + 14, 0.0);

			for(itm = 1; itm <= ojNItems("*"); itm++){
				selectImage(srcID);
				ojSelectItem("*", itm);
				x = ojXPos(1);
				y = ojYPos(1);
				z = ojZPos(1);
				setSlice(z);
				iName = ojGetItemName();
				if(iName == "Contour"){
					ojItemToRoi();
					getSelectionBounds(bx, by, bw, bh);
					x = bx + bw/2;
					y = by + bh/2;
				}
				makeRectangle(x - kk/2, y- kk/2, kk, kk);
				run("Copy");//phC
				selectImage("Collage");
				makeRectangle(left, top, kk, kk);
				run("Paste");
				if(!enhanceC)
					run("Divide...", "value=10");
				selectImage(srcID);
				setSlice(z+1);//fluor
				run("Copy");
				selectImage("Collage");
				makeRectangle(left, top + kk, kk, kk);
				run("Paste");
				left += kk;
			}
			left = 50;		
			if(enhanceC){
				makeRectangle(left, top, getWidth-left, kk);
				run("Enhance Contrast", "saturated=0.35");
				run("Apply LUT");
				makeRectangle(left, top + kk, getWidth-left, kk);
				run("Enhance Contrast", "saturated=0.35");
				run("Apply LUT");
			}

			top = top +kk*2 + 8;
		}
		ojShowImage(img);
	}
	selectImage("Collage");
	setBatchMode("show");
	run("Select None");
	run("Enhance Contrast", "saturated=0.35");
	Overlay.show();
	setOption("changes", false);
}


macro "Create Tables [F9]"{
	calcMaxFrames();
	close("Pairs.txt");
	tblTitles = split("AreaPairs GrayPairs FluorPairs");
	digits = newArray(2,0,2);
	for(tableNo = 0; tableNo < n_channels + 1; tableNo++){
// 		if(tableNo == 0 && !followGrowth)//skip AreaPairs
// 			continue; //17.04.23
		titleOj = tblTitles[tableNo];
		nColumns = 0;
		for(obj = 1; obj <= ojNObjects(); obj++){
			s = ojResultString(titleOj, obj);
			parts = split(s, ";;");
			nColumns = maxOf(parts.length, nColumns);
			if(nColumns == parts.length)
				longestObj = obj;
		}
		Table.create("Results");
	
		allXX = extractArray(titleOj, longestObj, 0);
		for(obj = 1; obj <= ojNObjects(); obj++){
			Table.set("obj", obj-1, obj);
			for(col = 0; col < nColumns; col++){
				tTitle = "T" + d2s(allXX[col], 1);
				Table.set(tTitle, obj - 1, "");
			}
			xx = extractArray(titleOj, obj, 0);
			yy = extractArray(titleOj, obj, 1);
			for(col = 0; col < xx.length; col++){
				tTitle = "T" + d2s(xx[col], 1);
				Table.set(tTitle, obj - 1, d2s(yy[col],digits[tableNo]));
			}
		}
		Table.update;

		tableName = tblTitles[tableNo] + ".txt";
		close(tableName);
		Table.rename(tableName);
		selectWindow(tableName);
		setLocation(20, 100 + tableNo * 40);

	}
	for(tableNo = 0; tableNo < n_channels + 1; tableNo++)
		selectWindow(tblTitles[tableNo] + ".txt");
}

				
macro "Check Frame Intervals"{
	//print("\\Clear");
	setBatchMode(true);
	nn = ojNImages();
	imgNo = newArray(nn);
	TiffInterval = newArray(nn);
	TiffUnit = newArray(nn);
	TimeFactor = newArray(nn);
	OjjInterval = newArray(nn);
	for(img = 1; img <= ojNImages(); img++){
		imgNo[img-1] = img;
		ojShowImage(img);
		TiffInterval[img-1] = Stack.getFrameInterval();
		Stack.getUnits(X, Y, Z, tiffUnit, Value);
		TiffUnit[img-1] = tiffUnit;
		ojjInterval = d2s(ojGetImageValue(img, "frameinterval"), 3);
		OjjInterval[img-1] = ojjInterval;
		TimeFactor[img-1] = timeFactor;
	}
	Array.show(imgNo, TiffInterval, TiffUnit, OjjInterval, TimeFactor);
}
