// 20.03.20, 14:24
// version 9.5
// https://sils.fnwi.uva.nl/bcb/objectj/examples//Weibel/MD/weibel.html
var 
// --- user globals ---
margin = 30,//min Margin on all four sides (px)
Zvalue = 150, // the grid constant (px)
gridColor = "red",

swatterRadius = 60, //px
swatterColor = "#8066ff00",
focusRad = 20,  //px
backgroundColor="none", // use colors like "none" or "yellow" or "#66ffee00"
withPercent = false, //show relative frequency, default = false

// --- internal globals ---
frameLeft, frameRight, frameTop, frameBottom, frameWidth, frameHeight,
xPairs = 0, //number of grid lines per row
yLines = 0, //number of rows
xPoints, yPoints, IDs, //arrays holding current grid positions and object iDs

currentID = 0,
disty = round(Zvalue *sqrt(3)/2),//equilateral triangular network 
gridID = false,
stampTime = 0,
gridID = 0,//ID of image where grid was painted
oldCX, oldCY,

keyTime,
tens, ones,
keyTable="",
jokerCode = NaN,
repeatCode = NaN,
keyCodes, keyItems, keyLabels, keyColors, panelID, 	
panelLineHeight = 26,
markTime = 0,

function defineKeyTable(){
	if(keyTable != "")
		return;//already defined
	keyTable = "";
	//       code, item, label
	keyTable += "#1,  A, PVO1";
	keyTable += "#2,  B, PVO2";
	keyTable += "#3,  C, PVO3";
	keyTable += "#4,  D, PVO4a";
	keyTable += "#5,  E, PVO4b";
	keyTable += "#66, F, PVO4c";
	keyTable += "#67, F, CA";
	keyTable += "#68, F, VTO1";
	keyTable += "#69, A, VTO2";
	keyTable += "#70, B, VTO3";
	keyTable += "#77, A, MN";
	keyTable += "#78, F, GVBD";
	keyTable += "#79, A, HYD";
	keyTable += "#84, A, POF";
	keyTable += "#85, B, earlyAtr";
	keyTable += "#86, C, lateAtr";
	keyTable += "#87, D, betaAtr";
	keyTable += "#88, E, UKpvo";
	keyTable += "#95,  D, Follicle";
	keyTable += "#96, C, Others";
	keyTable += "#97, D, Space";
	keyTable += "#98, E, UKvto";
	keyTable += "#99, E, Out*";

	keyTableAr = split(keyTable, "#");
	itmColors = split(ojGetItemColors());
	itmNames = split(ojGetItemNames());
	len = keyTableAr.length;
	keyCodes = newArray(len);
	keyColors = newArray(len);
	keyItems = newArray(len);
	keyLabels = newArray(len);
	for (jj = 0; jj < len; jj++){
		parts = split(keyTableAr[jj], ", \t");
		if (parts.length != 3)
			exit("Error in KeyTable: \n" + keyTableAr[jj]);
		keyCodes[jj] = parseInt(parts[0]);
		keyItems[jj] = parts[1];
		keyLabels[jj] = parts[2];
		keyColors[jj] = "gray";
		if (startsWith(toLowerCase(keyLabels[jj]), "empty"))
			exit("Label cannot start with 'empty'");
		
		for (kk = 0; kk < itmNames.length; kk++){
			if(parts[1] == itmNames[kk])
				keyColors[jj] = itmColors[kk];
		}
	}
	//check ambiguity
	for (jj = 0; jj < len; jj++){
		code1 = keyCodes[jj];
		label1 = keyLabels[jj];
		//if(code1 < 10){
			for (kk = 0; kk < len; kk++){
				if(kk != jj){
				code2 = keyCodes[kk];
				label2 = keyLabels[kk];
				if(code2 >= code1 * 10 && code2 <= code1 * 10 +9)
					exit("Ambiguity: \n\nkey #" + code1 +" conflicts with: \nkey #" + code2);
				if(code2 == code1 || label2 == label1)
					exit("Ambiguity : \n" + code1 + ": " + label1 + "\n" + code2 +": " + label2);
			}
		}
	
	}
}

macro "Initialize Grid [F3]"{
	initializeGrid();
}

macro "Set Joker [I]"{
	prevID = getImageID;
	defineKeyTable();
	index = -1;
	code = getNumber("Joker code:", jokerCode);
	for(jj = 0; jj < keyCodes.length; jj++){
		if(code == keyCodes[jj])
			index = jj;
	}
	if (index == -1)
		exit("Code does not exist: "+ code);

	jokerCode = keyCodes[index];
	showStatus("Joker key 'J', code= " + jokerCode + ", Label=" + keyLabels[index]);
	
	showPanel();
	setForegroundColor(0,0,0);
	fillRect(0, 0, 24, getHeight);

	selectImage(panelID);
	setForegroundColor(0xfefefe);
	fillRect(3, index * panelLineHeight + 4, 18, 20);
	fillRect(2, index * panelLineHeight + 5, 20, 18);//rounded rect
	setForegroundColor(25,0,0);
	setFont("Monospaced", 18, "antiliased");
	drawString("J", 6, index * panelLineHeight + 25);
	run("Select None");
	setOption("Changes", false);
	selectImage(prevID);
}


function isSingleKey(key){
	for(jj = 0; jj < keyCodes.length; jj++)
		if (key == keyCodes[jj])
			return true;
	return false;
}

function initializeGrid(){
	ojSetTool(0);
	link = ojImageLink();
	prepare();
	ojSetColumnProperty("Cat", "label background=" + backgroundColor, 1);
	Overlay.clear;
	gridToManager();//create xPoints, yPoints and IDs
	assignIDs();
	roiManager("Show All without labels");
	run("From ROI Manager");
	addFrameToOverlay();
	myUpdate();
	close("ROI Manager");
	selectWindow("ImageJ");
}

macro "-"{}


// calculate parameters for current image size
// sets "Require Command key for macros"
function prepare(){
	ojRequires("1.04v");
	defineKeyTable();
	img = ojImageLink();
	if(img == 0)
		exit("Linked Image is not in front");
	if(nSlices != 1)
		exit("Does not work with Stacks");
	if (gridID != getImageID){
		run("Misc...", "require run");
		setOption("QueueMacros" , true);
		gridID = getImageID;
		labels = replace(labels, " ", "\n");
	}
	if(nImages == 0) exit;
	if (currentID == getImageID)
		return;
	currentID = getImageID;
	
	calcFrame(img);
	if (xPairs < 1 || yLines < 1){ 
		currentID = 0;
		exit(" Image is too small");
	}
	if(!isOpen(panelID))
		close("Panel");
}

//Sets global frame parameters for a linked image
function calcFrame(img){
	ww = ojGetImageValue(img, "width");
	hh = ojGetImageValue(img, "height");

	frameWidth = ww - 2 * margin;
	xPairs = floor(frameWidth/Zvalue/2);
	frameWidth = xPairs * Zvalue * 2;
	frameLeft = round((ww - frameWidth)/2);
	frameRight = ww - frameLeft;

	frameHeight = hh - 2 * margin;
	yLines = floor(frameHeight/disty);
	frameHeight = disty * yLines ;
	frameTop = round((hh - frameHeight)/2);
	frameBottom = hh - frameTop;
}

//Initializes xPoints, yPoints and IDs, and adds gridlines to roiManager
function gridToManager(){
	xPoints = newArray(50000);
	yPoints = newArray(50000);
	IDs = newArray(50000);
	
	gridID = getImageID;
	pt = 0;
	offx = round(0.25 * Zvalue);
	for (y = frameTop + 0.5 * disty; y < (frameTop + frameHeight); y += disty){
		showStatus("!y= " + round(y));
		for (pair = 0; pair < xPairs; pair++){
			x = frameLeft + offx + pair*2 * Zvalue;
			xPoints[pt] = round(x);
			xPoints[pt + 1] = round(x + Zvalue);
			yPoints[pt] = round(y);
			yPoints[pt + 1] = round(y);
			pt += 2;
		}
		if (offx < (0.5 * Zvalue)) 
			offx = round(0.75 * Zvalue);
		else 
			offx = round(0.25 * Zvalue);
	}
	nGridLines = pt/2;
	run("Select None");
	run("Show Overlay");
	xPoints = Array.trim(xPoints, pt);
	yPoints = Array.trim(yPoints, pt);
	IDs = Array.trim(IDs, pt);
	
	roiManager("Reset");
	roiManager("Show None");
	time = getTime;
	thisID = getImageID;
	newImage("Dummy", "8-bit White", 2, 2, 1);//RoiMng needs an image
	showStatus ("!Creating " + nGridLines + " Lines");
	for (jj = 0; jj < nGridLines; jj++){
		makeLine(xPoints[jj*2], yPoints[jj*2], xPoints[jj*2] + Zvalue, yPoints[jj*2]);
		roiManager("Add", gridColor);
	}
	close("Dummy");;
	selectImage(thisID);
	showStatus ("!" + nGridLines +  " Lines;  "  + (getTime - time)/1000 + "  sec. ");
}


//associate object IDs to grid points
function assignIDs(){
	img = ojImageLink();
	for(obj = ojFirstObject(img); obj <= ojLastObject(img); obj++){
		ojSelectObject(obj);
		index = getPointIndex(ojXPos(1), ojYPos(1));
		id = ojIndexToId(obj);
		IDs[index] = id;
	}
}

//add red/green frame to Overlay
function addFrameToOverlay() {
		makeSelection("polyline", newArray(frameLeft,frameRight,frameRight), newArray(frameTop,frameTop,getHeight));
		run("Add Selection...", "stroke=green width=2");
		makeSelection("polyline", newArray(frameRight,frameLeft,frameLeft), newArray(frameBottom,frameBottom,0));
		run("Add Selection...", "stroke=red width=2");
		run("Select None");
	}
	run("Show Overlay");
}

macro "<Key>1 [1]"{ doKey(1); }
macro "<Key>2 [2]"{ doKey(2); }
macro "<Key>3 [3]"{ doKey(3); }
macro "<Key>4 [4]"{ doKey(4); }
macro "<Key>5 [5]"{ doKey(5); }
macro "<Key>6 [6]"{ doKey(6); }
macro "<Key>7 [7]"{ doKey(7); }
macro "<Key>8 [8]"{ doKey(8); }
macro "<Key>9 [9]"{ doKey(9); }
macro "<Key>0 [0]"{ doKey(0); }

macro "<Key>n1 [n1]"{ doKey(1); }
macro "<Key>n2 [n2]"{ doKey(2); }
macro "<Key>n3 [n3]"{ doKey(3); }
macro "<Key>n4 [n4]"{ doKey(4); }
macro "<Key>n5 [n5]"{ doKey(5); }
macro "<Key>n6 [n6]"{ doKey(6); }
macro "<Key>n7 [n7]"{ doKey(7); }
macro "<Key>n8 [n8]"{ doKey(8); }
macro "<Key>n9 [n9]"{ doKey(9); }
macro "<Key>n0 [n0]"{ doKey(0); }
macro "<Key>Joker [J]"{ doKey("-1"); }
macro "<Key>Repeat [R]"{ doKey("-2"); }

//handle single and dual key strokes
function doKey(key){
	setOption("QueueMacros" , true);
	if(ojImageLink() == 0)
		exit("First bring linked image to front");

	inPanel = ((getImageID == panelID) && key == -1);
	if((keyTable == "" || (gridID != getImageID)) && !inPanel){
		if(getBoolean("First Initialize Grid?"))
			initializeGrid();
		exit;
	}
	cmd = getInfo("command.name");
	if (!startsWith(cmd, "^"))
		exit("Command must be invoked via shortcut key, while cursor is at desired position");
	if (key == -1){//joker
		if (getImageID == panelID){
			beep;
			return;
		}
		if(isNaN(jokerCode))
			exit("Joker is not defined yet \n(choose 'ObjectJ>Set Joker [I]')"); 		
		doMark(-1);
		return;		
	}
	
	if (key == -2){ //repeat
		doMark(-2);
		return;
	}
	
	timeOut = getTime-keyTime > 1500;//1.5 sec time between dual keys
	if (isSingleKey(key) && timeOut){
			tens = 0;
			ones = key;
			doMark(tens *10 + ones);
			return;
	}
	if (!timeOut && tens > 0){
			ones = key;
			doMark(tens *10 + ones);
			return;
	}
	if (timeOut && key >= 6 && key <=9){
		tens = key;
		keyTime = getTime;
		return;
	}
}

function focusCircleExists(){
   	if (selectionType == 1){//oval
		getSelectionBounds(left, top, ww, hh);
		if (ww ==focusRad *2)
			return true;
		}
	return false;
}

/*3 different cases:
case a) focus exists: mark single point in focus circle
case b) swatter exists: mark empty points in swatter roi
case c) mark point closest to cursor position
*/
function doMark(code){
	if(getTime - markTime < 2500){
		beep;
		showMessage("Too fast");
		markTime = 0;
		exit;
	}
	markTime = getTime;
	if (code == -1)
		code =  jokerCode;
	if (code == -2)
		code =  repeatCode;
	if(!(code > 0)){
		showStatus("Code does not exist: " + code);
		beep;
		return;
	}
	cIndex = -1;
	for (jj = 0; jj < keyCodes.length; jj++)
		if(keyCodes[jj] == code)
			cIndex = jj;
	if(cIndex == -1) {beep; exit;}
	itemType = keyItems[cIndex];
	label = keyLabels[cIndex];


	toBeMarked =  getPointsOfInterest();
	keyTime = 0;
	tens = 0;


	time = getTime;
	ojUpdateMarkers(false);
	for(mm = 0; mm < toBeMarked.length; mm++){
		pIndex = toBeMarked[mm];
		if(pIndex >= 0){
			if(IDs[pIndex] == 0){//not occupied
				ojSwitchToItem(itemType);
				ojSetMarker(xPoints[pIndex], yPoints[pIndex]);
				ojCloseObject();
				obj = ojNewestObject();
				id = ojIndexToId(obj);
				if (obj > 0){
					ojSetResult("Cat", obj, label);
					ojSetResult("Code", obj, code);
					IDs[pIndex] = id;
				}
			}
		}
	}
	repeatCode = code;
	ojUpdateMarkers(true);
	ojSetTool(0);
	
	if (focusCircleExists())
		advanceFocus();
	myUpdate();
	markTime = 0;
}

/* returns an array of indexes
3 different cases:
case a) focus exists:  single point in focus circle
case b) swatter exists: all points in swatter roi
case c) mark point closest to cursor position
*/
function getPointsOfInterest(){

//-- now handle the 3 cases ---
	if (focusCircleExists()){      //(case a)
		getSelectionBounds(x, y, w, h);//toBeMarked = newArray(arr[0], arr[1]);
		poiArr = newArray(1);
		poiArr[0] = getPointIndex(x + w/2, y + h/2);
	}
	else if (isSwatter()) //(case b)
		poiArr = getSwatterPoints();

	else{//mark at cursor (case c)
		getCursorLoc(x, y, z, flags);
		poiArr = newArray(1);
		poiArr[0] = getPointIndex(x, y);
	}
	return poiArr;
}

function isSwatter(){
	if(selectionType <0)
		return false;
	a = Roi.getFillColor;
	b = swatterColor;
	if(a==b)
		return true;
	return false;
}


macro "Sequential Focus [F]"{
	if(selectionType==1){
		run("Select None");
		return;
	}
	advanceFocus();
}

//move focus circle to next free grid point
function advanceFocus(){
	arr = getPointsOfInterest();
	theIndex = arr[0];
	while(IDs[theIndex] != 0 && theIndex < xPoints.length-1)
		theIndex++;
	if(theIndex < xPoints.length){
		x = xPoints[theIndex];
		y = yPoints[theIndex] ;
		setTool("oval");
		makeOval(x- focusRad, y - focusRad, focusRad*2, focusRad*2);
		run("Properties... ", "  stroke=magenta width=4");
		
		getDisplayedArea(left, top, w, h);
		zoom = getZoom;
		centerX = round(left + w/2) & 0xfffffe;
		centerY = round(top + h/2) & 0xfffffe;
		if (x<left+margin || x > left + w - margin)
			centerX =  round(x + w/2 -margin) & 0xfffffe;
		
		if(y<top+margin ||y > top + h - margin)
			centerY =  round(y + h/2 -margin) & 0xfffffe;
		if (centerX != oldCX || centerY != oldCY){
			wait(333);
			ojZoom(zoom, centerX, centerY);
			oldCX = centerX;
			oldCY = centerY;
		}
		myUpdate();

	}
}

macro "Start/Stop Swatter [S]"{
	if(ojImageLink() == 0)
		exit("Linked Image is not in front");
	getCursorLoc(x, y, z, modifiers);
	if(x <0 || x >= getWidth || y < 0 || y > getHeight)
		exit;
	if(selectionType == 1){
		run("Select None");
		setTool(0);
		return;
	}
	if (gridID != getImageID){
		if(getBoolean("First Initialize Grid?"))
			initializeGrid();
		exit;
	}
	ojSetTool(0);
	setTool("brush");
	call("ij.gui.Toolbar.setBrushSize", swatterRadius * 2); 
	makeOval(x - swatterRadius, y-swatterRadius, swatterRadius * 2, swatterRadius * 2);
	run("Properties... ", "name=[] stroke=magenta width=1 fill=" + swatterColor);
}

macro "Swatter All/None [A]"{
	if (gridID != getImageID){
		if(getBoolean("First Initialize Grid?"))
			initializeGrid();
		exit;
	}
	if(isSwatter()){//brush or rect
		run("Select None");
		setTool(0);
		return;
	}
	makeRectangle( frameLeft, frameTop, frameWidth, frameHeight);
	run("Make Inverse");
	run("Make Inverse");
	run("Properties... ", "name=[] stroke=magenta width=1 fill=" + swatterColor);
}


macro "Kill [K]"{
	if (gridID != getImageID){
		if(getBoolean("First Initialize Grid?"))
			initializeGrid();
		exit;
	}
	ojUpdateMarkers(false);
	if (focusCircleExists())
		run("Select None");

	poiIndexes = getPointsOfInterest();
	for(jj = 0; jj < poiIndexes.length; jj++){
		theIndex = poiIndexes[jj];
		id = IDs[theIndex];
		obj = ojIdToIndex(id);
		if(obj > 0){
			ojDeleteObject(obj);
			IDs[theIndex] = 0;
		}
	}
	ojUpdateMarkers(true);
	myUpdate();
}

//checks if a point is contains several markers are on exactly the same place and allows to kill them sequentially
macro "Kill Duplicates & Unnamed Points"{
	if(ojNImages() >1)
		gridID = 0;
	for(loop = 1; loop <=2; loop++){
		wrongOnes = 0;
		for(obj = ojNObjects(); obj >=1; obj--){
			cat = ojResultString("Cat", obj);
			if(cat == ""){
				if(loop == 2)
					ojDeleteObject(obj);
				wrongOnes++;
			}
		}
		for(img = 1; img <= ojNImages(); img++){
			calcFrame(img);
			indexes = newArray(50000);
			for(obj = ojLastObject(img); obj >= ojFirstObject(img); obj--){
				ojSelectObject(obj);	
				x = ojXPos(1);
				y = ojYPos(1);
				index = getPointIndex(x, y);
				indexes[index]++;
				isDouble = indexes[index] > 1;
				if (isDouble){
					wrongOnes++;
					if(loop == 2)
						ojDeleteObject(obj);
				}
			}
		}
		if(wrongOnes == 0)
			exit("No problems found");
		if(loop == 1)
			showMessageWithCancel("Delete " + wrongOnes + "  duplicate or unnamed objects?");
	}
	
}

//returns array of indexes inside swatter area
function getSwatterPoints(){
	pointIndexes = newArray(99999);
	ff = 0;
	for (pt = 0; pt < xPoints.length; pt++){
		if (selectionContains(xPoints[pt], yPoints[pt])){
			x = xPoints[pt];
			y = yPoints[pt];
			index = getPointIndex(x, y);
			pointIndexes[ff++] = index;			
		}
	}
	pointIndexes = Array.trim(pointIndexes, ff);
	return pointIndexes;
}

function 	myUpdate(){
	ojHideResults();
	run("Show Overlay");
	run("Labels...", "   ");//avoid IJ labels
}


macro "Show Panel [P]"{
	showPanel();
}

function showPanel(){
	if(isOpen(panelID)){
		selectImage(panelID);
		return;
	}
	close("Panel");
	defineKeyTable();
	hh = keyCodes.length * panelLineHeight + panelLineHeight; 
	newImage("Panel", "RGB black", 256, hh, 1);
	changeValues(0, 0, 0x777777);//gray
	panelID = getImageID;
	setFont("SansSerif", 18, " antialiased");
	y = 0;
	for (jj = 0; jj < keyCodes.length; jj++){
 		setColor(keyColors[jj]);
		drawString(keyCodes[jj], 30, y+24);
		drawString(keyLabels[jj], 60, y+24);
		y+= panelLineHeight;
	} 
	run("Select None");
	setOption("Changes", false);
}

macro "Show Frequency Table [T]"{
	while(isOpen("Frequency Table.xls")){
		selectWindow("Frequency Table.xls");
		run("Close");
	}
	totalGridPoints = 0;
	for (img = 1; img <= ojNImages(); img++){
		calcFrame(img);
		totalGridPoints +=  xPairs * 2 *yLines;	
	}
	currentID = 0;
	emptyPoints = totalGridPoints - ojNObjects();
	close("Frequency Graph.tif");
	defineKeyTable();
	
//extend three arrays by one "empty" element
	oneElement = newArray(1);
	nLabels = keyLabels.length;
	Code = Array.concat(keyCodes, oneElement);
	Code[nLabels] = "";
	Cat = Array.concat(keyLabels, oneElement);
	Cat[nLabels] = "Empty*";
	keyColors1 = Array.concat(keyColors, oneElement);
	keyColors1[nLabels] = "gray";
	
	percent = newArray(nLabels +1);
	count = newArray(nLabels + 1);
	nMarked = 0;
	for (obj = 1; obj <= ojNObjects(); obj++){
		cat = ojResultString("Cat", obj);
		for (jj = 0; jj < nLabels; jj++){
			if(cat == keyLabels[jj]){
				count[jj]++;
				if(!endsWith(cat, "*"))
					nMarked ++;
				break;
			}
		}
	}

	for (jj = 0; jj < nLabels; jj++)
		if(!endsWith(Cat[jj], "*"))
			percent[jj] = count[jj] / nMarked * 100;//percent
 	count[nLabels] = emptyPoints; 	
	
	if(withPercent)
		Array.show("Frequency Table.xls", Code, Cat, count, percent);
	else
		Array.show("Frequency Table.xls", Code, Cat, count);
	newImage("Frequency Graph.tif", "RGB white", 500, nLabels * 24 + 36, 1);
	x1 = 150;
	x2 = x1 + 80;
	y = 6;
	setJustification("right");
	setFont("SansSerif", 18, " antialiased");
	setLineWidth(1);
	for (jj = 0; jj < nLabels +1; jj++){
		if(withPercent){
			val = percent[jj];
			s1 = "" + d2s(val, 1) + " %";
		}
		else{
			val = count[jj];
			s1 = "" + d2s(val, 0);
		}
		setColor(keyColors1[jj]);
		drawString(Cat[jj], x1, y + 20);		
		flag = endsWith(Cat[jj], "*") || Cat[jj] =="Empty*";
		if (flag)
			s1 = "[" + count[jj] + "]";
		drawString(s1 , x2-4, y + 20);
		amp = 1; //horizontal amplification
		if(!flag){
			for (kk = 0; kk < 20; kk++) 
				drawLine(x2, y+kk, x2 + amp * val, y+kk);
		}
		y += 24;
	}
	setOption("Changes", false);
}

macro "Summary [F1]"{
	ojHideResults();
	len = ojNImages();
	Image = newArray(len + 1);
	Width = newArray(len + 1);
	Height = newArray(len + 1);
	GridPoints = newArray(len + 1);
	GridConstant = newArray(len + 1);
	Marked = newArray(len + 1);
	FrameArea = newArray(len + 1);
	PxSize = newArray(len + 1);
	link = ojImageLink();
	totalPoints = 0;
	totalFrmArea = 0;
	for(img = 0; img < len; img++){
		pxWidth = ojGetVoxelSize(img + 1, "x");
		calcFrame(img + 1);
		PxSize[img] = pxWidth;
		Image[img] = "" + ojGetImageName(img+1);
		Width[img] = pxWidth * ojGetImageValue(img+1, "width");
		Height[img] = pxWidth * ojGetImageValue(img+1, "height");
		frmArea = frameWidth * frameHeight * pxWidth * pxWidth;
		
		totalFrmArea += frmArea;
		FrameArea[img] = frmArea;
		GridPoints[img] = xPairs * 2 * yLines;
		GridConstant[img] = Zvalue * pxWidth;
		pointObjects = 0;
		for(jj = ojFirstObject(img+1); jj <= ojLastObject(img+1); jj++){
			ojSelectObject(jj);
			if(ojNPoints() == 1){
				pointObjects++;
				totalPoints++;
			}
		}
		Marked[img] = pointObjects;
	}
	if(ojImageLink() > 0)
		calcFrame(ojImageLink());//reCalc globals for this image
	Image[len] = "-- Total --";
	Width[len] = "";
	Height[len] = "";
	GridPoints[len] = "";
	Marked[len] = totalPoints;
	FrameArea[len] = totalFrmArea;
	PxSize[len] = "";
	GridConstant[len] = "";
	Array.show("Summary", Image, Width, Height, GridPoints, Marked, FrameArea, PxSize, GridConstant);
}

//Calculates the nearest gridpoint and returns 
//its index, or -1 if outside frame.
function getPointIndex(x, y){
	row = ((y-frameTop - disty/2)/disty);
	rowI = round(row);
	if(rowI%2 == 0)
		col = ((x-frameLeft - Zvalue * 0.25)/Zvalue);
	else
		col = ((x-frameLeft - Zvalue * 0.75)/Zvalue);	
	pointNo = round(row) *xPairs * 2 + round(col);
	if(x < frameLeft|| x > frameRight || y < frameTop || y > frameBottom)
		pointNo = -1;
	return pointNo;
}

