//StampFishEggs_04p
//https://sils.fnwi.uva.nl/bcb/objectj/examples/stampfisheggs/md/stampfisheggs.html
//N.Vischer 
//23.06.2026, 11:24 (bugfix: adding Oil 'LATER' could change Label, line 616) 
var 
backgroundColor="#aaffee00", // used for labels,  colors like "none" or "yellow" or "#66ffee00"
visibleResults = false, //set false to hide ObjectJ results during marking
clickableKeyTable = true,//if true, Preset can be selected by clicking in Key Table

keyTime,
Keys, Label, Comment,//=table titles
keyCodes, keyItems, 
nKeys = 0,//number of keys
myZoom = 1, //for browsing
lastShowed,
prevChars = "",
shellPoints = 24,//circular polygon
currentLabel = "",//for repeat
currentIType = "",//for repeat 
avoidFlicker = false,//set true to hide objects while tracking

keyTable = "" // structure: key, label, comment;
// keys starting with * will be marked with circular 'Shell' items
// keys  without * will be marked with single dot
/////// keyTable begin /////// 

	+ "*0, dummy;"
	
	+ "*10, Gad-0, Gadoid;"
	+ "*11, Gad-1;"
	+ "*12, Gad-2;"
	+ "*13, Gad-3;"
	+ "*14, Gad-4;"
	+ "*15, Gad-5;"
	+ "*16, Gad-6;"
	+ "*17, Gad-7;"
	+ "*18, Gad-8;"
	+ "*19, Gad-9;"
	+ "*20, Gad-10;"
	+ "21, Gad;"
	
	+ "*30, lip-0, Lipid Droplet present;"
	+ "*31, lip-1;"
	+ "*32, lip-2;"
	+ "*33, lip-3;"
	+ "*34, lip-4;"
	+ "*35, lip-5;"
	+ "*36, lip-6;"
	+ "*37, lip-7;"
	+ "*38, lip-8;"
	+ "*39, lip-9;"
	+ "*40, lip-10;"
	+ "41, lip;"

	+ "*50, lper-0, Large Perivitelline space;"
	+ "*51, lper-1;"
	+ "*52, lper-2;"
	+ "*53, lper-3;"
	+ "*54, lper-4;"
	+ "*55, lper-5;"
	+ "*56, lper-6;"
	+ "*57, lper-7;"
	+ "*58, lper-8;"
	+ "*59, lper-9;"
	+ "*60, lper-10;"
	+ "61, lper;"

	+ "*70, oth-0, Other;"
	+ "*71, oth-1;"
	+ "*72, oth-2;"
	+ "*73, oth-3;"
	+ "*74, oth-4;"
	+ "*75, oth-5;"
	+ "*76, oth-6;"
	+ "*77, oth-7;"
	+ "*78, oth-8;"
	+ "*79, oth-9;"
	+ "*80, oth-10;"
	+ "81, oth;"
	/////// keyTable end /////// 
;

//checks if Table exists and has correct length
function checkTable(){
	if(nKeys == 0){
		defineTable();
		exit("Key Table was restored _");
	}
	a = isOpen("Key Table");
	if(!a)
		Array.show(Keys, Label, Comment, "Key Table");
	b = nKeys == Table.size("Key Table");
	if(!b)
		Array.show(Keys, Label, Comment, "Key Table");
	if(!a || !b)
		exit("Key Table was restored");		
	return;
}



//converts keyTable string into arrays: Keys,Label,Comment,keyCodes,keyItems
function defineTable(){//one-time operation
	//checkTable();
	requires("1.54j");
	if(nKeys > 0)
		 return; //already defined
	if(ojNObjects() == 0){
		ojHideResults();
		ojDeleteColumn("OilDia*");
	}
	keyTime = 1;
	setOption("QueueMacros", true);
	lines = split(keyTable, ";;");
	nKeys = lines.length;
	keyCodes = newArray(nKeys);//numbers e.g. 22
	keyItems = newArray(nKeys);//e.g. "Shell"
	
	Keys = newArray(nKeys);//Strings e.g. "*22"
	Label = newArray(nKeys);//Strings e.g. "hom-2"
	Comment = newArray(nKeys);//Strings e.g. "Mackerel"
	for (jj = 0; jj < nKeys; jj++){
		partsBl = split(lines[jj], ",,");//leave blanks
		parts = split(lines[jj], ", \t");
		if(lengthOf(parts[1]) > 20) exit("Label > 20 characters");
		if(indexOf(parts[1], "^") >=0) exit("Label contains ^");
		if (partsBl.length < 2 || partsBl.length > 3 )
			exit("Error in KeyTable: \n" + lines[jj]);
		Keys[jj] =parts[0];
		code = parts[0];
		if(startsWith(code, "*")){
			code = substring(code, 1, lengthOf(code));
			keyItems[jj] = "Shell";
		}
		else
			keyItems[jj] = "SpotA";
		keyCodes[jj] = parseInt(code);
		Label[jj] = toLowerCase (parts[1]);
		Comment[jj] = "";
		if (partsBl.length >= 3)
			Comment[jj] = partsBl[2];
	}
}

macro "Key Table [T]"{
	ojSetTool(-101);//egg tool
	defineTable();
	checkTable();
	if(keyTime == 0)
		close("Key Table");//any previous table
	checkTable();
	if(isOpen("Key Table"))
		selectWindow("Key Table");
	else{	
		Array.show(Keys, Label, Comment, "Key Table");
		setLocation(0, 180);
		Table.setSelection(1, 1, "Key Table");
	}
	//ojSetTool(-101);
}


macro "Preset [P]"{
	checkTable();	
	if(nImages > 0)
		selectImage(getImageID);
	ojSetTool(-101);
	
	if(clickableKeyTable && isOpen("Key Table")  ){
		index = Table.getSelectionStart("Key Table");
		if(index >= 0){
			Table.setSelection(index, index, "Key Table");
			currentIType = keyItems[index];
			currentLabel = Label[index];
		}
	}	


	str = getString("Number or Label: ", currentLabel);
	code = parseInt(str);
	if(isNaN(code)){// a string
		str = toLowerCase(str);
		code =lookUpCode(str);
		if(code == -1)
			exit("Unknown label: " + str);

		label =  toLowerCase(str);
		
	}
	for(jj = 0; jj < keyCodes.length; jj++){
		if(code == keyCodes[jj]){
			index = jj;
			Table.setSelection(index, index, "Key Table");
			currentIType = keyItems[index];
			currentLabel = Label[index];
			ojSetTool(-101);
			msg = "" + keyCodes[index] + ":  " + currentLabel +"     ";
			if( keyItems[index] == "Shell")
				msg = "*" + msg;
			if(ojImageLink() > 0)
				if(nSlices == 1)//not in stacks
				 	setMetadata("Label", msg);
			return;
		}
	}
	exit("Code not found");
}


macro "Egg Tool - Ca00T2f20E "{
	//defineTable();
	checkTable();
	run("Select None");
	
	index = -1;
	if(clickableKeyTable && isOpen("Key Table")  && getVersion >= "1.52k"){
		index = Table.getSelectionStart("Key Table");
		if(index >= 0){
			currentIType = keyItems[index];
			currentLabel = Label[index];
		}
	}	

	if(currentLabel == ""){//No code was chosen
		currentIType = keyItems[0];
		currentLabel = Label[0];
		return;
	}
	if(index != -1)
		updateMetadata(index);
	doMark(currentIType, currentLabel);
}

macro "Select Object [S]"{
	owner = doSelect();
	ojSetTool(-101);
}

macro "Kill object [K]"{
	prevObj = ojSelectedObject();
	obj = doSelect();
	wait(100);
	if(obj > 0){
		if(obj != prevObj && prevObj >0)
			showMessageWithCancel("Selection has changed, continue to kill?");
		ojDeleteObject(obj);
	}
	ojSetTool(-101);
}

//select object under cursor
function doSelect(){
	cmd = getInfo("command.name");
	if (!startsWith(cmd, "^"))
		exit("Command must be invoked via shortcut key, while cursor is at desired position");
	selectImage(getImageID);
	getCursorLoc(x0, y0, z, flags);
	owner = findObject(x0, y0, false);
	if(owner > 0)
		ojShowObject(owner);
	else 
		showMessage("Cursor was not inside shell or close to point");
	return owner;
}

macro "ReName Selected Egg [R] "{
	while (true)
		doRename("selected");
}

function doRename(s){
	checkTable();
	if (ojImageLink()== 0)
		exit("front image is not linked");
		
	if(s=="undercursor"){
		cmd = getInfo("command.name");
		if (!startsWith(cmd, "^"))
			exit("Command must be invoked via shortcut key, while cursor is at desired position");
		prev = ojSelectedObject();
		obj = doSelect();
		if(obj == 0)
			return;
		wait(100);
		if(prev > 0 && obj != prev)
				showMessageWithCancel("Selection has changed, continue?");
	}
	
	obj = ojSelectedObject();
	
	if (obj == 0)
		exit("No object was selected.\nPlace cursor onto Object and press key 'S'");
	if(getSliceNumber != ojZPos(1))
		exit("Selected Object was not in current slice.\nPlace cursor onto Object and press key 'S'");
	showZoomed(obj, myZoom);	
	oldLabel = ojResultString("Label", obj);
	oldKey = lookUpCode(oldLabel);
	
	msg = "  \nObject number: "+obj + "\n";
	msg += "Image:  " + getTitle + "\n  \n";
	

	dia = ojResult("EggDia", obj);
	if(!isNaN(dia))
		msg += "Dia:  " + dia + "\n";
	nOils = ojNItems("Oil");
	for(jj = 1; jj <= nOils; jj++){
		col = "OilDia" +jj;
		thisOil = ojResult(col, obj);
		msg += "Oil" + jj +":  " + thisOil + "\n";
	}
	
	Dialog.create("Rename Egg  #" + obj);
	Dialog.addMessage("Current Key = " + oldKey + "\nCurrent Label = " + oldLabel);
	Dialog.addString("New Key or Label: ", "" + oldKey, 20);
	Dialog.addMessage(msg);
	Dialog.show();	
	
	str =Dialog.getString();
	num = parseInt(str);
	if(isNaN(num)){// a string
		label =  toLowerCase(str);
		ojSetResult("Label", obj,label);//accept every string
	}
	else {// a number
		label =lookUpLabel(str);
		if(label != "")
			ojSetResult("Label", obj, label);
		else 
			exit("Numerical code "+ str + " is not defined");//accept only defined codes
	}
	splitSpecAndStage(obj);
	ojSelectObject(obj);//refresh
	wait(10);
	
	if(s=="selected"){
		selectWindow("ImageJ");
		showMessageWithCancel("Rename next object?");
		if (obj == ojNObjects())
 			exit("End reached");
 
		navigate(1, myZoom);
	}
}	


//split species and stage and fill their columns
function splitSpecAndStage(obj){
	label = ojResultString("Label", obj);
	parts = split(label, "-");
	ojSetResult("Spec", obj, parts[0]);
	stage = NaN;
	if(parts.length > 1)
		stage = parseInt(parts[1]);
	ojSetResult("Stage", obj, stage);
}

macro "Batch Rename Eggs..."{
	checkTable();	
	Dialog.create("Rename Objects");
	Dialog.addString("Old Label*:", "dummy");
	Dialog.addString("New Label:", "");
	Dialog.addCheckbox("Confirm before changing", true);
	Dialog.addMessage("* supports wildcards '*' and '?'", 10, "gray");
	Dialog.show;
	oldName = toLowerCase(Dialog.getString);
	newName = toLowerCase(Dialog.getString);
	confirm = Dialog.getCheckbox;	
	if (newName == "") exit;
	
	hits = 0;
	for (obj = 1; obj <= ojNObjects(); obj++){
		thisCat = ojResultString("Label", obj);
		if(thisCat.contains("*") || thisCat.contains("?"))
			exit("Batch-rename not possible: cat = " + thisCat);
		if (ojMatches(thisCat, oldName))
			hits++;
	}
	if (hits == 0) exit ("No objects found");
	if (!confirm)
		ojSetItemVisible("*", false);
	for (obj = 1; obj <= ojNObjects(); obj++){
		thisCat = ojResultString("Label", obj);
		if (ojMatches(thisCat, oldName)){
			flag = true;
			if (confirm){
				showZoomed(obj, 1);
				flag = getBoolean("Rename '" + oldName + "' to '" + newName + "'?");
				showZoomed(obj, 1);
				wait(150);
			}
			if (flag){
				ojSetResult("Label", obj, newName);
				splitSpecAndStage(obj);
			}
		}			
	}
	ojSelectObject(0);
	ojSetItemVisible("*", true);
}

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

//shows previous cell zoomed (respects qualifying and sorting)
macro "<Navigate>Zoom to previous Object      [F1]"{
	navigate(-1, myZoom);
}

//shows next cell zoomed (respects qualifying and sorting)
macro "<Navigate>Zoom to next Object      [F2]"{
	navigate(1, myZoom);
}

//shows currrently selected cell zoomed
macro "<Navigate>Zoom to current Object      [F3]"{
	navigate(0, myZoom);
}

//shows cell # xx zoomed
macro "<Navigate>Zoom to Object#...     [F4]"{
	obj = getNumber("Show Object No:  ", maxOf(ojSelectedObject(), 1));
	showZoomed(obj, myZoom);	
}

macro "<Navigate>Orig Scale   [F5]"{
	run("Original Scale");
}

// used for cell browsing
function navigate(delta, zoomFactor){    
	obj = ojGetOpenObject();
	if (obj > 0) 
		ojCloseObject();
	else 
		obj = ojSelectedObject();
	if (obj==0) {
		if (lastShowed > 0 && lastShowed<= ojNObjects())
			suggest = lastShowed;
		else
			suggest = 1;
		showMessageWithCancel("No Object is selected, go to object #"+ suggest +" ?");
		obj = suggest;
		}
	else {
		row = ojIndexToRank(obj);
		row += delta;
		obj = ojRankToIndex(row);		
	}
	if (obj > ojNObjects() || obj <1 )
		beep;
	else {
		showZoomed(obj, zoomFactor);	
		lastShowed = obj;
	}
}

//shows object zoomed and temporarily highlighted by circle roi
function showZoomed(obj, zoomFactor){
	if(obj<1 || obj>ojNObjects()){
		beep;
		exit;
	}
	ojShowObject(obj);
 	xc = ojXPos(1);
 	yc = ojYPos(1);
 	if(ojNPoints() == shellPoints){
		yc = (ojYPos(1) + ojYPos(1+ shellPoints /2))/2;
	}

	ojZoom(zoomFactor, xc, yc);

	if(ojNPoints() == shellPoints){
		ojItemToRoi();
	}
	else
		makeOval(xc-50, yc - 50, 100, 100);
	run("Properties... ", "  stroke=Magenta width=8");
	wait(100);
	run("Select None");
}

//returns object number if user clicked inside a cell, otherwise zero
function findObject(x, y, shellOnly){
	owner = 0;
	minD = 20/getZoom; //pixels max click-distance
	img = ojImageLink();
	if (img == 0)
		return 0;
	for (obj = ojFirstObject(img); obj <= ojLastObject(img); obj++){
		ojSelectObject(obj);
		if(ojZPos(1) != getSliceNumber) 
			continue;
		n = ojNPoints();
		if(n == shellPoints){
			cx = ojXPos(1);
			cy = ojYPos(shellPoints/4 + 1);
			rad = cx - ojXPos(shellPoints/4 + 1);
			dx = x - cx; 
			dy = y - cy; 
			if (sqrt(dx * dx + dy * dy) < rad){
				owner = obj;
				return owner;
			}
		}
		if(n == 1 && !shellOnly){
			dx = ojXPos(1) - x;
			dy = ojYPos(1) - y;
			d = sqrt(dx * dx + dy * dy);
			if(d < minD){
				owner = obj;
				minD = d;
			}	
		}
	}
	ojSelectObject(0);
	return owner;
}

function doKey(key){
	checkTable();//if necessary
	if (ojImageLink()== 0)
		exit("front image is not linked");
	cmd = getInfo("command.name");
	if (!startsWith(cmd, "^"))
		exit("Command must be invoked via shortcut keyboard");
	if (getTime-keyTime > 1500) 
		prevChars = ""; //forget previous chars after 1.5 sec	
	keyTime = getTime;
	newCode = prevChars + key;
	index = -1;
	for(jj = 0; jj < keyCodes.length; jj++){
		if(newCode == keyCodes[jj]){
			index = jj;
			break;
		}
	}
	if (index < 0){//more keys needed
		prevChars = newCode;
		status = "" + key + "...          ";
		setMetadata("Label", status);
		return;//more keys needed
	}
	prevChars = "";
	currentIType = keyItems[index];
	currentLabel = Label[index];
	if(clickableKeyTable && isOpen("Key Table"))
		Table.setSelection(index, index, "Key Table");
	ojSetTool(-101);
	updateMetadata(index);
}

function updateMetadata(index){
	msg = "" + keyCodes[index] + ":  " + currentLabel +"     ";
	if( keyItems[index] == "Shell")
		msg = "*" + msg;
	if(ojImageLink() > 0)
		setMetadata("Label", msg);
}

//===== doMark ==========
function doMark(itemType, label){
	if(indexOf(getMetadata("Label"),"...") > 0)
		exit("No label is defined");

	getCursorLoc(x0, y0, z, flags);

	alt=8;
	if (flags & alt == 0)
		oilOwner = findObject(x0, y0, true);
	else
		oilOwner =0;

	if(!visibleResults)
		ojHideResults();
	if(itemType == ""){
		beep;
		return;
	}
	//Shell marking (egg or oil)
	if((itemType == "Shell") || (oilOwner > 0)){
		trackShell(itemType, label, oilOwner, x0, y0);
	}
	else{ //Spot marking:
		if(x0 <0 || x0 >= getWidth || y0 < 0 || y0 > getHeight)
			exit;
		keyTime = 1;
		ojSwitchToItem(itemType);
		ojSetMarker(x0, y0);
		ojCloseObject();
	}
	obj = ojNewestObject();
	if(obj == 0){
		showStatus("Newest Object not defined");
		exit;
	}
	oldLabel = ojResultString("Label", obj);
	if(oldLabel == "")
		ojSetResult("Label", obj, label);//18.06.2026, 00:31
	ojOrderObjectsInZ(ojImageLink());
	splitSpecAndStage(obj);
	ojSetTool(-101);
	if(nSlices > 1)
		setMetadata("label", "");
}

//===== trackShell ==========
function trackShell(itemType, label, oilOwner, x0, y0){
	eggDia = 0;
	if(oilOwner > 0)
		eggDia = ojResult("EggDia", oilOwner);
	
	getVoxelSize(pxSize, dummy,dummy,dummy);
	leftButton=16; rightButton=4; shift=1; ctrl=2; alt=8;	
	rad0 = 55;
	if (oilOwner > 0){
		rad0 = 7;
	}
	makeOval(x0 - rad0, y0, rad0*2, rad0*2);	// circular Roi can now be adjusted
	x1 =x0;
	y1=y0;
	offx = 0;
	rad = rad0;
	if(avoidFlicker)
		ojSetItemVisible("*", false);//avoid flickering
	done = false;
	nVisits = 0;

	while (!done){
		done = flags & leftButton == 0;
		wait(10);
		getCursorLoc(x2, y2, z, flags);
		if (x2 != x1 || y2 != y1 ){	
			nVisits++;
			dx = x2 - x0;
			dy = y2 - y0;
			rad = rad0 + 0.5 * dy;//speed increased to 0.5
			offx = dx * 0.25;
			makeOval(x0 + offx - rad, y0 , rad*2, rad*2);
			x1 = x2;
			y1 = y2;
			msg = "egg=" ;
			if(oilOwner > 0)
				msg += "" + eggDia + "  oil=";
			msg += "" + (rad*2*pxSize);
			showStatus(msg);
		}
	}

	ojSetItemVisible("*", true);
		
	if (nVisits >1){	
		if (oilOwner > 0){
			ojOpenObject(oilOwner);	
			ojCloseItem();	
			ojSwitchToItem("Oil");
			markCircle(6);
			addOilColumn(oilOwner);
		}
		else{	
			ojSwitchToItem(itemType);
			markCircle(shellPoints);
		}
	}
	ojSelectObject(0);
	run("Select None");
}

function addOilColumn(obj){
	ojSelectObject(obj);
	nOils = ojNItems("Oil");
	for(nn = 1; nn <= nOils; nn++){
		title = "OilDia" + nn;
		if(nOils > 0 && ojColumnNumber(title) == 0){
			ojHideResults();
			ojInitColumn(title);
			ojPutOperand("Oil", nn, 1);
			ojPutOperand("Oil", nn, 4);//1 and 4 are diagonal in hexagon
			ojPutAlgorithm("distance");
		}
	}	
}

//marks Circle and closes object
function 	markCircle(nPoints){
	getSelectionBounds(xx, yy, ww, hh);
	xc = xx + ww/2;
	yc = yy + hh/2;
	rad = ww/2;
	for (bb = 0; bb < nPoints; bb++ ){
		phi = -bb * 2*PI/nPoints - PI/2;
		xx =  xc + rad *cos(phi);
		yy =  yc - rad *sin(phi);
		ojSetMarker(xx, yy);
	}
	ojCloseObject();
}

//Error if dual digit starts with a blocked digit,
//e.g.  single digit '3'  blocks dual digits 30..39 
function checkDigits(){
	codeAr = Array.copy(keyCodes);
	for(jj = 0; jj < codeAr.length; jj++)
		codeAr[jj] = "" + codeAr[jj];
	
	for(jj = 0; jj < codeAr.length; jj++){
		for(kk = jj+1; kk < codeAr.length; kk++){
			if (startsWith(codeAr[jj], codeAr[kk] ) || startsWith(codeAr[kk], codeAr[jj] ))
		 		exit("Keys not unique: " + codeAr[jj] +  "  blocks  "  + codeAr[kk]);
		 }
	}
}

function lookUpLabel(code){

	code = replace(code, " ", "");
	for(jj = 0; jj < keyCodes.length; jj++){
		if(code==keyCodes[jj])
		return Label[jj];
	}
	return "";
}

function lookUpCode(lbl){
	lbl = replace(lbl, "-", "");
	for(jj = 0; jj < keyCodes.length; jj++){
		if(lbl==replace(Label[jj], "-", ""))
			return keyCodes[jj];
	}
	return -1;
}

macro "<Frequencies>Stage"{
	calcFrequencies("");
}
macro "<Frequencies>Species"{
	calcFrequencies("species");
}

function calcFrequencies(loop){

	title = "Freq_Stages";
	if(loop == "species")
		title = "Freq_Species";
	close(title);
	//wait(100);
	checkTable();
	pad = "^^^^^^^^^^^^^^^^^^^^^";
	stdLen = 21;//max 20 chars
	Freq = newArray(999);
	allLabels="";
	len = Label.length;
	for(jj= 0; jj<len; jj++){
		thisLabel = Label[jj];
		if(loop == "species"){
			parts = split(thisLabel, "-");
			thisLabel = parts[0];
		}
		s10 = substring(thisLabel + pad, 0, stdLen);
		if(indexOf(allLabels, s10) < 0)
			allLabels += s10;
	}
	for(obj = 1; obj <= ojNObjects(); obj++){
		thisLabel = ojResultString("Label", obj);
		if(loop == "species"){
			parts = split(thisLabel, "-");
			thisLabel = parts[0];
		}
		s10 = substring(thisLabel + pad, 0, stdLen);
		if(indexOf(allLabels, s10) < 0)
			allLabels += s10;
		index = indexOf(allLabels, s10)/stdLen; 
		Freq[index]++;
	}
	Label_ = split(allLabels, "^");
	len = Label_.length;
	Freq = Array.trim(Freq, len);
	Array.show(Label_, Freq);
	Table.rename(title);
}


macro "Create Stack from Marked Eggs"{
	nEggs = ojNObjects();
	if(nEggs == 0)
		exit("Zero eggs are marked");
	ojShowImage(1);
	if(nSlices > 1)
		exit("Cannot do this from a stack"); 
	sWidth = 300;//"Stack width and height in pixels

	Dialog.create("Stack from Marked Eggs");
	Dialog.addNumber("Window width [px]", sWidth);
	Dialog.addCheckbox("Show Names", false);
	Dialog.show();	
	sWidth = Dialog.getNumber();
	withNames = Dialog.getCheckbox();
	
	close("Test_Stack.tif");
	newImage("Test_Stack", "RGB black", sWidth, sWidth +30, nEggs);
	pxWidth = ojGetVoxelSize(1, "x");
	run("Properties...", "pixel_width=&pxWidth pixel_height=&pxWidth unit=um");

	stID = getImageID;
	setBatchMode("hide");
	setColor("#88ff88");
	setFont("SansSerif", 20);

	for(obj = 1; obj <= nEggs; obj++){
		ojShowObject(obj);
		xc = ojXPos(1);
		yc = ojYPos(1);
		if(ojNPoints() > 1){
			yc = (ojYPos(1) + ojYPos(1+ ojNPoints()/2))/2;
		}
		makeRectangle(xc - sWidth/2, yc - sWidth/2, sWidth, sWidth);
		run("Copy");
		run("Select None");
		selectImage(stID);
		setSlice(obj);
		makeRectangle(0, 0, sWidth, sWidth);
		run("Paste");
		msg = ""+ obj;
		if(withNames)
			msg += ":     " +ojResultString("Label", obj);
		drawString(msg, 5, sWidth+30-2);
	}
	run("Select None");
	setSlice(1);
	setBatchMode("exit and display");
	selectImage(stID);
	ojSelectObject(0);
}


macro "List Egg Labels of Test-Stack"{
	includeObjNum = false;
	if(ojNImages() != 1)
		exit("Exactly 1 stack must be linked");
	ojShowImage(1);
	if(nSlices <2)
		exit("Stack expected");
	tabName = "EggLabels.csv";
	Slc = newArray(nSlices);
	for(jj = 0; jj < Slc.length; jj++)
		Slc[jj] = jj+1;
	Table.create(tabName);
	Table.setColumn("Slc", Slc);
	if(includeObjNum)
		Table.setColumn("Obj", newArray(0));
	Table.setColumn("EggName", newArray(0));
	Table.setColumn("EggDia", newArray(0));
	Table.setColumn("OilDia1", newArray(0));
	
	for(obj = 1; obj <= ojNObjects(); obj++){
		ojSelectObject(obj);
		row = ojZPos(1) - 1;
		if(Table.getString("EggName", row, tabName) != "")
			exit("two objects in slice #" + (row+1));
		if(includeObjNum)
			Table.set("Obj", row, obj, tabName);
		label = ojResultString("Label", obj);
		Table.set("EggName", row, label, tabName);
		Table.set("EggDia", row, ojResult("EggDia", obj), tabName);
		Table.set("OilDia1", row, ojResult("OilDia1", obj), tabName);
		
	}
	ojSelectObject(0);
	Table.update(tabName);
}

macro "Avoid mousewheel stack-scrolling"{
	//requires("1.54j");
	msg= "Avoid stack scrolling:\n- by mousewheel," 
 	+ "\n- by two-finger-drag on a touchpad,"
 	+ "\n- by swiping on an Apple magic Mouse  "
 	+ "\n*\n This will be effective until Image is relaunched,"
 	+ "\n or make it permanent via StartUp Macro";//must be upper case M!
	Dialog.create("Stack scrolling");
	Dialog.addMessage(msg);
	Dialog.addCheckbox("Save to: File>Show Folder>Macros>RunAtStartup.ijm", false);
	Dialog.show();

	setOption("mouseWheelStackScrolling", false);
	if (Dialog.getCheckbox())
		run("Startup...", "text1=[setOption(\"mouseWheelStackScrolling\", false);\n\n]");
}
