|
/**
|
|
* 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 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" {
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|