Project

General

Profile

Files ยป root_tools.txt

Volker Baecker, 09/18/2013 04:01 PM

 
/**
* root_tools
*
* The root tools help to efficiently measure the following characteristics of plant roots:
* - the angle of the opening of the whole root
* - the depth to which it goes down
* - the number of roots at 30 cm depth
* - the diameters of the roots at depth
*
* written 2013 by Volker Baecker (INSERM) at Montpellier RIO Imaging (www.mri.cnrs.fr)
*/

var helpURL = "http://dev.mri.cnrs.fr/wiki/imagej-macros/Root_Tools";

var NUMBER_OF_DEPTHS=3; // the number of possible depths at which the diameter of the roots can be measured
var BOX_WIDTH = 21.5; // the width of the rhizo-box (in reality 20)
var DELTA = 10; // half of the height of the selection used to auto-set the scale
var AUTO_SET_SCALE = true; // set the scale automatically
var KNOWN_DISTANCE = 4; // known distance for spatial calibration (there are 20mm between two nails)
var RADIUS=75; // radius of the circle drawn around the root point
var AUTO_SCALE_COLOR="green"; // color of the line that indicates the distance used to set the spacial scale of the image
var AUTO_SCALE_LINE_WIDTH= 3; // line width of the line that indicates the distance used to set the spacial scale of the image
var AUTO_SCALE_FONT_SIZE = 112; // size of the font indicating the length used to set the spacial scale
var ROOT_POINT_LINE_WIDTH = 15; // line width of the circle drawn around the root point
var COLOR_ROOT_POINT = "red"; // color of the circle drawn around the root point
var COUNT_ROOT_DEPTH = newArray(NUMBER_OF_DEPTHS) // depth from the root point at which the horizontal line is drawn. The number of roots will be counted at that depth
var HALF_LINE_WIDTH = 10; // half of the width of the line drawn at count root depth
var WIDTH_DEPTH = 8; // line width of the line drawn at count root depth
var COLOR_DEPTH = "cyan"; // color of the line drawn at count root depth
var ANGLE_COLOR = "red"; // the color for marking the measured angle
var ANGLE_LINE_WIDTH = 8; // the line width for marking the measured angle
var MEASURE_DEPTH_COLOR = "magenta"; // the color of the line indicating the max. depth to which the root goes down
var MEASURE_DEPTH_WIDTH = 8; // the line width of the line indicating the max. depth to which the root goes down
var ZOOM_RADIUS = 20; // radius of the region around the click that is copied and zoomed
var DIAMETER_COLOR = "yellow"; // color of the line indicating the measured root diameters
var DIAMETER_WIDTH = 2; // width of the line indicating the measured root diameters
var ON_IMAGE_OPEN_ON = false; // if true commands are run when an image is opened
var ON_IMAGE_OPEN_COMMANDS = newArray("Rotate 90 Degrees Left", "Enhance Contrast, saturated=0.35");
var ON_IMAGE_OPEN_COMMANDS_CHECKED = newArray(true, true);
var USE_GLOBAL_SCALE = true;
var rootX;
var rootY;
var deepX;
var deepY;
var UNIT = "cm";
var X1;
var X2;
var Y;
var MAX_ROOTS = 20;
var DIAMETERS = newArray(NUMBER_OF_DEPTHS * MAX_ROOTS);
var numberOfRootsAtDepth = newArray(NUMBER_OF_DEPTHS);
var DEPTH = 0;
var ID = 0;
var ANGLE = 0;
var TITLE;
var NUMBER = 0;
var FOLDER;
var currentDepthNumber = 0;

macro "AutoRun" {
COUNT_ROOT_DEPTH[0] = 30;
for (i=1;i<NUMBER_OF_DEPTHS;i++) {
COUNT_ROOT_DEPTH[i] = 0;
}

script = getJSRemoveAllImageListeners();
runJS(script);
ON_IMAGE_OPEN_ON = call("ij.Prefs.get", "roots.on_image_open_on", false);
if (ON_IMAGE_OPEN_ON) {
script = getJSAddListeners();
runJS(script);
}
}

macro "zoomIn [f1]" {
run("In [+]");
}

macro "zoomOut [f2]" {
run("Out [-]");
}

macro "Root Tools Help Action Tool- C134D01D04D06D11D1fD20D2fD3fDcfDd4Dd7De0De2De3De4De5De6De7Df2Df3Df4Df5Df6Df7Df9DfdC345D08D09D18D25D27D32D34D3aD42D43D5fD6dD92Da3Da5Dc5Dc8DdbC334D0fD12D19D30D3bD59D5aD69D6aDa2Da4DadDaeDafDb2Db3DbdDbeDc6Dd8DdeC666D53D54D62D77D7bD7cD7dD7eD91D94D95D9aD9bD9cD9fDa9DbaDcaDcbC234D02D03D05D07D0aD0bD10D2eD4aDb4DbfDceDd0Dd1Dd5Dd6DdfDe8De9DeaDebDecDeeDf8DfbC555D0eD1bD35D36D45D50D51D52D56D6fD7aD90D93D9dDa1Da6Db1Db7DbcDddC345D0cD15D1aD37D3dD40D5cDb5Db6Dc9DfeC777D1cD57D66D71D72D73D74D76D80D82D83D88D89D8aD8eD8fD97D98C234D00Dc4Dd3De1Df0Df1DfaDfcC445D17D29D2bD38D41D46D55D58D65D6bD6eD78D79D9eDa0DacDcdC335D13D14D16D1eD23D24D28D33D3eD4cD5eD68D6cDc7Dd2DefC666D1dD48D61D63D67D70D7fD96D99Da7Da8Db9DbbDccC234D21D2dD3cD4bD4fDc0Dc3Dd9DdaDedC556D0dD26D2aD39D44D47D60D64D75DaaDabDb8DdcC345D22D2cD31D49D4dD4eD5bD5dDb0Dc1Dc2C777D81D84D85D86D87D8bD8cD8dDff" {
run('URL...', 'url='+helpURL);
}

macro "Root Tools Help Action Tool Options" {
ON_IMAGE_OPEN_ON = call("ij.Prefs.get", "roots.on_image_open_on", false);
Dialog.create("Root Tools - Options");
Dialog.addCheckbox("auto run commands when image opened", ON_IMAGE_OPEN_ON);
for (i=0; i<lengthOf(ON_IMAGE_OPEN_COMMANDS_CHECKED); i++) {
Dialog.setInsets(0, 40, 0);
Dialog.addCheckbox(ON_IMAGE_OPEN_COMMANDS[i], ON_IMAGE_OPEN_COMMANDS_CHECKED[i]);
}
Dialog.show();
ON_IMAGE_OPEN_ON = Dialog.getCheckbox();
for (i=0; i<lengthOf(ON_IMAGE_OPEN_COMMANDS_CHECKED); i++) {
ON_IMAGE_OPEN_COMMANDS_CHECKED[i] = Dialog.getCheckbox();
}
script = getJSRemoveAllImageListeners();
runJS(script);
if (ON_IMAGE_OPEN_ON) {
call("ij.Prefs.set", "roots.on_image_open_on", true);
script = getJSAddListeners();
runJS(script);
} else {
call("ij.Prefs.set", "roots.on_image_open_on", false);
}
}

macro "Set Scale [f5]" {
if (AUTO_SET_SCALE)
autoSetScale();
else
setScale();
}

macro "Define Root Point [f6]" {
setTool("Define Root Point Tool");
}

macro "Angle [f7]" {
measureAngle();
}

macro "Measure Depth [f8]" {
setTool("Measure Depth Tool");
}

macro "Zoom Region [f9]" {
setTool("Zoom region Tool");
}

macro "Measure Diameter [f10]" {
measureDiameter();
}

macro "Write Report [f11]" {
writeReport();
}

macro "Set Scale Action Tool- C000T4b12s"{
if (AUTO_SET_SCALE)
autoSetScale();
else
setScale();
}

macro "Set Scale Action Tool Options" {
Dialog.create("Root Tools - Set Scale - Options");
Dialog.addCheckbox("auto-set scale", AUTO_SET_SCALE)
Dialog.addNumber("known distance ["+UNIT+"]: ", KNOWN_DISTANCE);
Dialog.addCheckbox("use global scale", USE_GLOBAL_SCALE);
Dialog.show();
AUTO_SET_SCALE = Dialog.getCheckbox();
KNOWN_DISTANCE = Dialog.getNumber();
USE_GLOBAL_SCALE = Dialog.getCheckbox();
}

macro "Define Root Point Tool- C000T4b12r" {
run("Remove Overlay");
width = getWidth();
getCursorLoc(x, y, z, flags);
ID = getImageID();
TITLE = getTitle();
FOLDER = getDirectory("image");
run("Remove Overlay");
rootX = x;
rootY = y;
setColor(COLOR_ROOT_POINT);
setLineWidth(ROOT_POINT_LINE_WIDTH);
Overlay.drawLine(0, rootY, width, rootY);
Overlay.show();
run("Select None");
for (i=0; i<NUMBER_OF_DEPTHS; i++) {
if (COUNT_ROOT_DEPTH[i]>0) {
drawLineAtDepth(COUNT_ROOT_DEPTH[i]);
}
}
setTool("Straight Line");
}

macro "Define Root Point Tool Options" {
Dialog.create("Root Tools - Define Root Point - Options");
Dialog.addNumber("radius: ", RADIUS);
Dialog.addNumber("line width: ", ROOT_POINT_LINE_WIDTH);
Dialog.addString("root point color: ", COLOR_ROOT_POINT);
for (i=0; i<NUMBER_OF_DEPTHS; i++) {
Dialog.addNumber("depth " +(i+1)+" [cm]: ", COUNT_ROOT_DEPTH[i]);
}
Dialog.addNumber("half of length [cm]: ", HALF_LINE_WIDTH);
Dialog.addNumber("line width for depth: ", WIDTH_DEPTH);
Dialog.addString("depth line color: ", COLOR_DEPTH);
Dialog.show();
RADIUS = Dialog.getNumber();
ROOT_POINT_LINE_WIDTH = Dialog.getNumber();
COLOR_ROOT_POINT = Dialog.getString();
for (i=0; i<NUMBER_OF_DEPTHS; i++) {
COUNT_ROOT_DEPTH[i] = Dialog.getNumber();
}
HALF_LINE_WIDTH = Dialog.getNumber();
WIDTH_DEPTH = Dialog.getNumber();
COLOR_DEPTH = Dialog.getString();
}

macro "Angle Action Tool- C000T4b12a" {
measureAngle();
}

macro "Angle Action Tool Options" {
Dialog.create("Root Tools - Angle - Options");
Dialog.addString("angle color: ", ANGLE_COLOR);
Dialog.addNumber("line width: ", ANGLE_LINE_WIDTH);
Dialog.show();
ANGLE_COLOR = Dialog.getString();
ANGLE_LINE_WIDTH = Dialog.getNumber();
}

macro "Measure Depth Tool- C000T4b12d" {
getCursorLoc(x, y, z, flags);
deepX = x;
deepY = y;
DEPTH = deepY - rootY;
toScaled(DEPTH);
setColor(MEASURE_DEPTH_COLOR);
setLineWidth(MEASURE_DEPTH_WIDTH);
Overlay.drawLine(rootX, rootY, rootX, deepY);
Overlay.drawLine(rootX, deepY, deepX, deepY);
Overlay.show();
run("Select None");
setTool("Zoom region Tool");
}

macro "Measure Depth Tool Options" {
Dialog.create("Root Tools - Measure Depth - Options");
Dialog.addString("line color: ", MEASURE_DEPTH_COLOR);
Dialog.addNumber("line width: ", MEASURE_DEPTH_WIDTH);
Dialog.show();
MEASURE_DEPTH_COLOR = Dialog.getString();
MEASURE_DEPTH_WIDTH = Dialog.getNumber();
}

macro "Zoom region Tool- C000T4b12z" {
title = getTitle();
getCursorLoc(x, y, z, flags);
yScaled = y;
toScaled(yScaled);
rootYScaled = rootY;
toScaled(rootYScaled);
currentDepthNumber = findCurrentDepthFor(yScaled-rootYScaled);
makeRectangle(x-ZOOM_RADIUS, y-ZOOM_RADIUS, 2*ZOOM_RADIUS+1, 2*ZOOM_RADIUS+1);

script = getJSRemoveAllImageListeners();
runJS(script);
run("Duplicate...", "title="+title+"box");
ON_IMAGE_OPEN_ON = call("ij.Prefs.get", "roots.on_image_open_on", false);
if (ON_IMAGE_OPEN_ON) {
script = getJSAddListeners();
runJS(script);
}
run("Remove Overlay");
run("In [+]");
run("In [+]");
run("In [+]");
run("In [+]");
run("In [+]");
run("In [+]");
setTool("line");
}

macro "Zoom region Tool Options" {
Dialog.create("Root Tools - Zoom region - Options");
Dialog.addNumber("radius ", ZOOM_RADIUS);
Dialog.show();
ZOOM_RADIUS = Dialog.getNumber();
}

macro "Measure diameter Action Tool- C000T4b12m" {
measureDiameter();
}

macro "Measure diameter Action Tool Options" {
Dialog.create("Root Tools - Measure diameter - Options");
Dialog.addString("line color: ", DIAMETER_COLOR);
Dialog.addNumber("line width", DIAMETER_WIDTH);
Dialog.show();
DIAMETER_COLOR = Dialog.getString();
DIAMETER_WIDTH = Dialog.getNumber();
}

macro "Write report Action Tool- C000T4b12w" {
writeReport();
}

function drawLineAtDepth(depth) {
setColor(COLOR_DEPTH);
setLineWidth(WIDTH_DEPTH);
tenUnscaled = HALF_LINE_WIDTH;
toUnscaled(tenUnscaled);
depthUnscaled = depth;
toUnscaled(depthUnscaled);
X1 = rootX - tenUnscaled;
X2 = rootX + tenUnscaled;
Y = rootY + depthUnscaled;
Overlay.drawLine(X1, Y, X2, Y);
Overlay.show();
}

function setScale() {
getLine(x1, y1, x2, y2, lineWidth);
dx = x2-x1;
dy = y2-y1;
length = sqrt(dx*dx+dy*dy);
options = "distance=" + length+" known="+KNOWN_DISTANCE+" pixel=1 unit="+UNIT;
if (USE_GLOBAL_SCALE) options = options + " global";
run("Set Scale...", options);
run("Select None");
setTool("Define Root Point Tool");
}

function measureAngle() {
if (roiManager("count")!=2) {
waitForUser("Please add two lines to the roi-manager first.");
return;
}
roiManager("select", 0);
getSelectionCoordinates(xCoordinates1, yCoordinates1);
roiManager("select", 1);
getSelectionCoordinates(xCoordinates2, yCoordinates2);
roiManager("Deselect");
run("Set Measurements...", " redirect=None decimal=3");
run("Clear Results");
roiManager("Measure");
roiManager("Delete");
a1 = 180 + getResult("Angle", 0);
a2 = -1 * getResult("Angle", 1);
ANGLE = 180 - a1 - a2;
selectWindow("Results");
run("Close");
run("Select None");
setColor(ANGLE_COLOR);
setLineWidth(ANGLE_LINE_WIDTH);
Overlay.drawLine(xCoordinates1[0], yCoordinates1[0], xCoordinates1[1], yCoordinates1[1]);
Overlay.drawLine(xCoordinates2[0], yCoordinates2[0], xCoordinates2[1], yCoordinates2[1]);
Overlay.show();
setTool("Measure Depth Tool");
}

function measureDiameter() {
title = getTitle();
if (indexOf(title, "box")==-1) return;
getLine(x1, y1, x2, y2, lineWidth);
dx = x2-x1;
dy = y2-y1;
length = sqrt(dx*dx+dy*dy);
toScaled(length);
DIAMETERS[currentDepthNumber + numberOfRootsAtDepth[currentDepthNumber]*NUMBER_OF_DEPTHS] = length;
numberOfRootsAtDepth[currentDepthNumber] = numberOfRootsAtDepth[currentDepthNumber] + 1;
close();
selectImage(ID);
getSelectionBounds(x, y, width, height);
run("Select None");
setColor(DIAMETER_COLOR);
setLineWidth(DIAMETER_WIDTH);
Overlay.drawLine(x+x1, y+y1, x+x2, y+y2);
Overlay.show();
setTool("Zoom region Tool");
}

function writeReport() {
NUMBER++;
getDateAndTime(year, month, dayOfWeek, dayOfMonth, hour, minute, second, msec);
title = "root measurements - " + year + "-" + month + "-" + dayOfMonth + ".txt";
ref = "[" + title + "]";
if (!isOpen(title)) {
run("Table...", "name="+ref+" width=250 height=600");
header = "\\Headings:" + "nr." + "\t" + "image" + "\t" + "root x" + "\t" + "root y" + "\t" + "angle" + "\t" + "depth" + "\t" + "depth of measurements" + "\t" + "number at depth";
for (i=0; i<MAX_ROOTS; i++) {
header = header + "\t" + "d" + (i+1);
}
print(ref, header);
}
for (i=0; i<NUMBER_OF_DEPTHS; i++) {
if (COUNT_ROOT_DEPTH[i]>0) {
row = "" + NUMBER + "\t" + TITLE + "\t" + rootX + "\t" + rootY + "\t" + ANGLE + "\t" + DEPTH + "\t" + COUNT_ROOT_DEPTH[i] + " \t" + numberOfRootsAtDepth[i];
for (j=0; j<numberOfRootsAtDepth[i]; j++) {
row = row + "\t" + DIAMETERS[i + j*NUMBER_OF_DEPTHS];
}
print(ref, row);
}
}
resetMeasurements();
if (!File.exists(FOLDER + "/" + "control/")) File.makeDirectory(FOLDER + "/" + "control/");
path = FOLDER + "/" + "control/" + TITLE;
saveAs("tiff", path);
Overlay.remove;
setTool("line");
}

function resetMeasurements() {
numberOfRootsAtDepth = newArray(NUMBER_OF_DEPTHS);
DIAMETERS = newArray(NUMBER_OF_DEPTHS * MAX_ROOTS);
}

function getJSAddListeners() {
events = newArray("imageOpened", "imageUpdated", "imageClosed");
event = "imageOpened";
commands = ON_IMAGE_OPEN_COMMANDS;
commandFlags = ON_IMAGE_OPEN_COMMANDS_CHECKED;
script = "listenerImpl = {";
for (i=0; i<lengthOf(events); i++) {
script = script + events[i] + ": function(imp){";
if (events[i]==event) {
for (j=0; j<lengthOf(commands); j++) {
if (commandFlags[j]) {
components = split(commands[j],",");
if (lengthOf(components)==1)
script = script + "IJ.run(\"" + commands[j] + "\");";
else
script = script + "IJ.run(\"" + components[0] + "\",\""+components[1] +"\");";
}
}
}
script = script + "}";
if (i<lengthOf(events)-1) script = script + ",";
}
script = script + "}; listener = new ImageListener(listenerImpl); ImagePlus.addImageListener(listener);";
return script;
}

function runJS(script) {
eval("script", script);
}

function getJSRemoveAllImageListeners() {
script = "cl = new ImagePlus().getClass(); df = cl.getDeclaredField(\"listeners\"); df.setAccessible(true); df.get(null).removeAllElements();";
return script;
}

function autoSetScale() {
setBatchMode(true);
height = getHeight();
width = getWidth();
run("Duplicate...", "title=scale_tmp");
run("8-bit");
setAutoThreshold("Huang dark");
doWand(width/2, height/2);
setKeyDown("alt");
makeRectangle(0, 0, width, (height /2)-DELTA);
setKeyDown("alt");
makeRectangle(0, (height /2)+DELTA, width, height);
setKeyDown("none");
getSelectionBounds(x, y, selectionWidth, selectionHeight);
selectWindow("scale_tmp");
close();
setBatchMode(false);
run("Set Scale...", "distance="+selectionWidth+" known="+BOX_WIDTH+" pixel=1 unit="+UNIT);
Overlay.remove;
setColor(AUTO_SCALE_COLOR);
setLineWidth(AUTO_SCALE_LINE_WIDTH);
Overlay.drawLine(x,y+selectionHeight/2, x+selectionWidth, y+selectionHeight/2);
setFont("SansSerif" , AUTO_SCALE_FONT_SIZE, "antialiased");
Overlay.drawString(BOX_WIDTH + "" + UNIT, x+selectionWidth/2-1.5*AUTO_SCALE_FONT_SIZE, y+selectionHeight/2-AUTO_SCALE_FONT_SIZE);
Overlay.show;
}

function findCurrentDepthFor(y) {
indexOfResult = 0;
for(i=0; i<NUMBER_OF_DEPTHS; i++) {
if (COUNT_ROOT_DEPTH[i]>0) {
if (i==0) lastDiff = 100000000;
if (abs(y-COUNT_ROOT_DEPTH[i])<lastDiff) {
result = COUNT_ROOT_DEPTH[i];
indexOfResult = i;
}
lastDiff = abs(y-COUNT_ROOT_DEPTH[i]);
}
}
return indexOfResult;
}
    (1-1/1)