/*
Marking tree rings
vischer at science.uva.nl
21-sep-2010 17:14
*/
var 
minValley=8,   //minimum valley depth between two detected peaks
laneWidth = 20,  //lane width used for the peak detector
minOrMax = "min",// set peak at min or max value 

x00 , y00, //starting point (zero if uninitialized)
yearArr, //year numbers belonging to ringWidthArr
ringWidthArr, //distances between subsequent tree rings
xxArr, yyArr, //contains Begin and Ring positions
xxAllArr, yyAllArr, //contains Begin, Link and Ring positions
markedImg = 0,
;

//
macro "Start a Sequence [F1]"{
	ojCloseObject();
	ojSwitchToItem("Begin");
	run("Select None");
}

macro "Auto-detect Rings [F2]"{
	setOption("QueueMacros");
	getCursorLoc(x2, y2, z, flags);
	if (x00 != x2 || y00 != y2){
		x00 = x2;
		y00 = y2;
		setBatchMode(true);
		
		obj = ojGetOpenObject();
		if (obj == 0)
			exit;
		ojCloseObject();
		ojSelectObject(obj);
		ojSelectItem("*", -1);//last item
		x1 = ojXPos(1);
		y1 = ojYPos(1);
		ojOpenObject(obj);
		if (x1 != x2 || y1 != y2){
			makeLine(x1, y1, x2, y2, laneWidth);
			makeSegments("min", minValley);
		}
	}
}

// Converts a line selection into a segmented line so
// vertices coincide with minima or maxima.
// minValley is the minimum contrast to separate peaks

function makeSegments(minOrMax, minValley) {
	findMax = (minOrMax != "min");
	if (selectionType == 5) {//straight line
		xx= newArray(2);
		yy= newArray(2);
		getLine(xx[0], yy[0], xx[1], yy[1], lw);
	}
	if (selectionType == 6) {//polyLine
		getSelectionCoordinates(xx, yy);
	}
	sx = newArray(1000);
	sy = newArray(1000);
	sx[0] = xx[0];
	sy[0] = yy[0];
	seg = 1;

	for (segment = 0; segment < xx.length-1; segment++){
		x0 = xx[segment];
		x1 = xx[segment + 1];
		y0 = yy[segment];
		y1 = yy[segment + 1];
		dx = x1- x0;
		dy = y1 - y0;	
		len = sqrt(dx* dx + dy * dy);
		makeLine(x0, y0, x1, y1);
		profile = getProfile();
		up = false;
		minVal = 9999;
		maxVal = 0;
		thr = 128;
		if (findMax)
			up = false;
		else
			up = true;
		for (jj=0; jj<profile.length; jj++) {
			val = profile[jj];
			if (!up){
				if (minVal > val){
					minVal = val;
					thr = minVal + minValley;
					minPos = jj;
				}
				if (val > thr){
					minVal = 9999;
					thr = val - minValley;
					up = true;
					if (!findMax){
						sx[seg] = x0 + minPos*dx/len;
						sy[seg] = y0 + minPos*dy/len;
						seg++;
					}
				}
			}	
			else {
				if (maxVal < val){
					maxVal = val;
					thr = maxVal - minValley;
					maxPos = jj;
				}
				if (val < thr){
					maxVal = 0;
					thr = val - minValley;
					up = false;
					if (findMax){
						sx[seg] = x0 + minPos*dx/len;
						sy[seg] = y0 + minPos*dy/len;
						seg++;
					}
				}
			}
		}		
	}		
	sx = Array.trim(sx, seg);
	sy = Array.trim(sy, seg);
	makeSelection("polyline", sx, sy);
}

macro "Mark Detected Rings [F3]" {
	ojHideResults();
	if (selectionType != 6){
		exit;
	}
	getSelectionCoordinates(xx, yy);
	len = xx.length;
	ojSwitchToItem("Ring");
	for (pt = 1; pt < len; pt++){
		ojSetMarker(xx[pt], yy[pt]);
	}
	run("Select None");
}


macro "Enter year [F4]"{
	ojHideResults();
	openObj = ojGetOpenObject();
	selObj = ojSelectedObject();
	if (openObj > 0){
		ojCloseObject();
		ojSelectObject(openObj);
	}
	obj = ojSelectedObject();
	msg = "Year of object " + obj+ "   \n(+pith or -bark)   ";
	
	if (obj == 0) {
		obj = getNumber("Object must be open or selected; Show Object# :", 0);
		ojSelectObject(obj);
	}
	if (obj > 0){
		year = ojResult("knownYear", obj);
		if (isNaN(year)) 
			year = -2009;
		year = getNumber(msg, year);
		ojSetResult("KnownYear", obj, year);
		updateYearColumns(obj);
	}
	showHideAnnotation(true);
}
	
function updateYearColumns(obj){	
	ojSelectObject(obj);
	year = ojResult("KnownYear", obj);
	age = ojNItems("ring");
	if (year < 0){
		ojSetResult("BeginYear", obj, -year - age);
		ojSetResult("EndYear", obj, -year);
	}
	else{
		ojSetResult("BeginYear", obj, year);
		ojSetResult("EndYear", obj, year + age);
	}	
}
			
macro "Show/Hide annotation [F5]"{
	requires("1.43r");
	ojRequires("0.97i");
	id = getImageID;
	ojHideResults();
	show = (markedImg != id);
	showHideAnnotation(show);
	selectImage(id);
}

function showHideAnnotation(show){
	requires("1.43r");
	frontImg = ojImageLink();
	if (frontImg == 0) exit;
	run("Remove Overlay");
	if (!show) {
		markedImg = 0;
		return;
	}
	markedImg = getImageID;
	setBatchMode(true);
//	new = "new";
	for (obj = ojFirstObject(frontImg); obj <= ojLastObject(frontImg); obj++){
		ojShowObject(obj);
		ok = calcOneObject(obj);
		makeSelection("polyLine", xxAllArr, yyAllArr);
//		run("Add Selection...", "stroke=cyan width=1 " + new);//connection line
		run("Add Selection...", "stroke=cyan width=1");//connection line
//		new = "";
		if (ok)
			for (jj = 0; jj< xxArr.length; jj++){
				year = yearArr[jj];
				if (year % 5 == 0){
					makeText(year, xxArr[jj] +3, yyArr[jj]);
					run("Add Selection...", "stroke=orange font=12 fill=#44000000");//text
					makeOval(xxArr[jj] - 5, yyArr[jj] - 4, 9, 9);
					run("Add Selection...", "stroke=red width=1");//circle
				}
	}
	}	
	run("Select None");
	setForegroundColor(160,0,0);
	ojSelectObject(0);
	ojShowImage(frontImg);
}

macro "Set Link Marker[F6]" {
	if (ojGetOpenObject()> 0){
		ojSwitchToItem("Link");
		getCursorLoc(x2, y2, z, flags);
		ojSetMarker(x2, y2);
		ojSwitchToItem("Ring");
	}
}

macro "Insert Ring [F7]"{
	ojHideResults();
	selectImage(getImageID);
	obj = ojSelectedObject();
	if(obj == 0 || ojImageLink() != ojOwnerIndex(obj))
		exit("Before inserting a point, select an object with the Finger tool");
	count = ojNItems("*");
	getCursorLoc(x, y, z, flags);
	if (x <=0 || y<= 0)
		exit("Cursor must be positioned in the image");
	minItem = findInsertion(x,y);
	ojSelectItem("*", 2);	
	ojOpenObject(obj);
	if(isKeyDown("alt"))
		ojSwitchToItem("Link");
	else
		ojSwitchToItem("Ring");
	ojSetMarker(x, y);
	ojCloseObject();
	ojSelectObject(obj);
	ojRepositionItem(count+1, minItem +2);
	ojSwitchToItem("Begin");
	
	showHideAnnotation(true);
	ojSelectObject(obj);
}

macro "Check for Errors"{
	print("\\Clear");
	badObj = 0;
	for (obj = 1; obj <= ojNObjects(); obj++)
		check(obj);
	if (badObj > 0){
		showMessage ("" + badObj + " errors found - see Log window");
		selectWindow("Log");
	}
	else showMessage("No errors found");
}


//checks if first and only item is begin, and that deviation at a
// ring marker is < 15 deg, if prev neighbor is ring or begin, and
// next neighbor is ring or link
function check(obj){
	ojRequires("0.97i1");
	ojSelectObject(obj);
	nBegins = ojNItems("Begin");
	itemName = ojGetItemName();
	a = (itemName != "Begin" ||(nBegins != 1));
	b = isNaN(ojResult("KnownYear", obj));
	if (a || b) {
		if (a) 			
			print("Obj " + obj + " must start with one single 'Begin' marker");
		if (b) 			
			print("Obj " + obj + ": Year is unknown");
		badObj++;
	}
	
	//check if deviation is <15 deg
	itmArr = newArray(ojNItems("*")+1);//1-based
	ojvInitStack("2d");

	for (itm = 1; itm <= ojNItems("*"); itm++){
		ojSelectItem("*", itm);
		ojvPushItem();
		name = ojGetItemName();
		itmArr[itm] = name;
		if (itm >=3){
			thisItm = itmArr[itm];
			prevItem = itmArr[itm-1];
			if ((thisItm != "Link") && (prevItem == "Ring")){
				dev = ojvCalculate("deviation");
				if (abs(dev) >= 15) {
					badObj++;
					print("obj=", obj, " itm=", itm, " dev=", d2s(dev, 1));
				}
			}
		}
	}

}

macro "Output (List)"{
	doOutput("List");
}
macro "Output (Table)"{
	doOutput("Table");
}

function doOutput(format){
	maxObj = ojNObjects();
	for (obj = 1; obj<= maxObj; obj++)
		updateYearColumns(obj);
	minYear = ojGetStatistics("BeginYear", "min");
	maxYear = ojGetStatistics("EndYear", "max");
	yearRange = maxYear - minYear + 1;
	if (!(yearRange > 0 && yearRange <= 1000))
		exit("Years out of range");
	if (format == "List")
		print("\\Clear");
	newImage("matrix", "32-bit Black", maxObj + 1, yearRange, 1);
	run("Set...", "value=NaN");
	for (y = 0; y< yearRange; y++)//column 0 holds year
		setPixel(0, y, minYear + y);
	for (obj = 1; obj<= maxObj; obj++){
		calcOneObject(obj);
		ojSelectObject(obj);
		updateYearColumns(obj);
		len = ringWidthArr.length;
		if (format == "List") {
			print("\n"+ ojOwnerName(obj), " --- Object "+ obj);
			print("Year\tWidth\n");
		}
		for(row = 1; row < len; row++ ){//don't include begin-year
			year = yearArr[row];
			ringWidth = ringWidthArr[row];
			print(year, "\t", ringWidth);
			putPixel(obj, year-minYear, ringWidth);
		}
	}

	if (format == "Table") {
		title1 = "Matching Table";
		title2 = "["+title1+"]";
		f = title2;
		if (isOpen(title1))
			 print(f, "\\Clear");
		else
			 run("Table...", "name="+title2+" width=250 height=600");
		hdr = "\\Headings:Year";
		for (obj = 1; obj<= maxObj; obj++)
			hdr = hdr + "\t" + "obj-"+obj;
		print(f, hdr);
		for (yy = 0; yy < getHeight; yy++){
			String.resetBuffer;
			String.append(getPixel(0, yy));//start with year
			for (xx = 1; xx < getWidth; xx++){
				String.append("\t");
				rWidth = getPixel(xx, yy);
				if (!isNaN(rWidth))
					String.append(d2s(rWidth, 3));
			}	
			print(f, String.buffer);
		}
	}
	selectWindow("matrix");
	close;

	if (format == "List") 
		selectWindow("Log");
	if (format == "Table") 
		selectWindow("Matching Table");		
}


//calculates ring distances and returns the 
//results in two global arrays: ringWidthArr and yearArr
function calcOneObject(obj) {
	scale = ojGetVoxelSize(ojOwnerIndex(obj), "x");
	ojSelectObject(obj);
	sizeAll =ojNItems("*");// all markers
	size = sizeAll -ojNItems("Link");//rings only
	yearArr = newArray(size);
	ringWidthArr = newArray(size);
	xxArr = newArray(size);
	yyArr = newArray(size);
	xxAllArr = newArray(sizeAll);
	yyAllArr = newArray(sizeAll);
	//print ("object = ", obj, "size = ", size);
	beginYear = ojResult("BeginYear", obj);	
	year = beginYear;
	rr = 0;//rings
	all = 0;//all items
	for (itm = 1; itm <= ojNItems("*"); itm++){
		ojSelectItem("*", itm);
		xx = ojXPos(1);
		yy = ojYPos(1);		
		xxAllArr[all] = xx;
		yyAllArr[all] = yy;
		name = ojSelectedItemName();
		if (name != "Link" ){
			xxArr[rr] = xx;
			yyArr[rr] = yy;
			if (itm > 1){
				dx = xx - prevX;
				dy = yy - prevY;
				width = scale * sqrt(dx * dx + dy * dy);
				ringWidthArr[rr] = width;
				}
			yearArr[rr] = year;
			year++;
			rr++;
		}
		all++;
		prevX = xx;
		prevY = yy;
	}
	ojSelectObject(0);
	if (isNaN(beginYear))
		return false;
	return true;
}
	
function showFrontObject(){
	link = ojImageLink();
	if (link > 0){
		if (ojFirstObject(link) > 0){
			ojShowObject(ojFirstObject(link));
			return true;
			}
	}
	ojSelectObject(0);
	return false
}

//Uses the vertex calculator to check if new point can
//be placed between two existing vertices.
//if yes, returns item index after which point should be inserted
//otherwise macro is aborted
function findInsertion(xx, yy){
	startSeg = 0;
	nItems = ojNItems("*");
	ojvInitStack("2d");
	for (itm = 1; itm<= nItems; itm++) {
		ojSelectItem("*", itm);
		ojvPushItem();
	}
	loc = "x=" + xx + " y=" + yy;
	ojvPushVertex(loc);
	
	left = ojvCalculate("PartialPath leftedge");
	right = ojvCalculate("PartialPath rightedge");
	if (left == right){
		beep;
		showMessage("Impossible to insert point here");
		selectImage(getImageID);
		ojSelectObject(0);
		exit;
		}
	return left;
}




