//Inspired by the FASTMAP plugin by Dylan Terstege from the Epp Lab, University of Calgary published on 12-07-2019
//
// Created 2025-09-15 by Julian Rodefeld
// Ciernia Lab, University of British Columbia, Vancouver
var map_to_control_channel = false;
var is_czi = newArray();
var one_roi_for_all = false;
var automatic_bounding_box = false;
var output_path = "";
var combined_output_path = "";
var control_channel = "";
var temp = "";
var atlas_directory = "";
var text_file = "";
var mapping_index_path = "";
var atlas_name = "";
var atlas_path = "";
var skip_choice = "Continue";
var xvertex_array = 0;
var yvertex_array = 0;
var utilities_directory = 0;
var xbounding = 0;
var ybounding = 0;
var widthbounding = 0;
var heightbounding = 0;
var image_name = "";
var do_blinding = false;
showMessage("ROIMAPer", "
+"
ROIMAPer
"
+"Version: 2.4.0 (Feb 2026)"
+"Created by Julian Rodefeld, Ciernia Lab, University of British Columbia
"
+"Inspired by the FASTMAP plugin by Dylan Terstege from the Epp Lab
"
+" "
+"
"
+" "
+"");
//directory setup
default_directory = File.getDefaultDir;//to restore in the end
//find the ROIMAPer plugin
plugin_list = getFileList(getDirectory("imagej") + "scripts/Plugins/");
found_roimapper = false;
for (i = 0; i < plugin_list.length; i++) {
if (startsWith(plugin_list[i], "ROIMAPer")) {
plugin_name = plugin_list[i];
found_roimapper = true;
break;
}
}
if (!found_roimapper) {
exit("Please save the ROIMAPer folder under \"scripts/Plugins/\" in the FIJI folder.");
}
home_directory = replace(getDirectory("imagej"), "\\", "/") + "images/ROIMAPer/atlases/";
utilities_directory = replace(getDirectory("imagej"), "\\", "/") + "images/ROIMAPer/ROIMAPerUtilities/";
File.setDefaultDir(home_directory);
//get atlas specification
atlas_path = replace(File.openDialog("Please select which atlas (saved in the FIJI folder in \"scripts/Plugins/ROIMAPer/atlases\") you would like to work with (select a .tif file)."), "\\", "/"); //replace backslash with forwardslash
atlas_name = File.getNameWithoutExtension(atlas_path);
atlas_directory = home_directory + atlas_name + "_ROIs/";
File.setDefaultDir(default_directory);
//the atlas id to brain region information
text_file = substring(atlas_name, 0, indexOf(atlas_name, "-")) + "-brain_region_mapping.csv";
mapping_index_path = utilities_directory + "mapping_index.csv";
getDateAndTime(year, month, dayOfWeek, dayOfMonth, hour, minute, second, msec);
month = month + 1;//because month is zero-based index
//make folder in temporary directory
temp = getDirectory("temp");
temp = replace(temp, "\\", "/");
temp = temp + "ROIMAPer_results_" + year + "_" + month + "_" + dayOfMonth + "_" + hour + "_" + minute + "/";
File.makeDirectory(temp);
//get the directory of the analysis
image_directory = getDirectory("Please choose the directory that contains your images.");
image_list = getFileList(image_directory);
image_list = Array.sort(image_list);
total_images = lengthOf(image_list);
directory_name = File.getNameWithoutExtension(image_directory);
higher_directory = File.getDirectory(image_directory);
Dialog.create("Image selection");
Dialog.addMessage("Which subrange of files would you like to analyze?");
Dialog.addNumber("Start of selection", 1);
Dialog.addNumber("End of selection", total_images);
Dialog.show();
image_selection_start = Dialog.getNumber() - 1; // minus one because arrays start counting at 0
image_selection_end = Dialog.getNumber();
image_selection_size = image_selection_end-image_selection_start;
Dialog.createNonBlocking("File confirmation");
length_limit = 20;
columns = Math.ceil(image_selection_size/length_limit);
Dialog.addMessage("These are the files you have chosen, please confirm.");
for (i = 0; i < image_selection_size; i++) {//creating a grid-like dialog window of the files provided. Grid likeness enables the display of more file titles
Dialog.addCheckbox(image_list[i + image_selection_start], true);
for (j = 1; j < columns; j++) {
if (i + 1 < image_selection_size) {
Dialog.addToSameRow();
i++;
Dialog.addCheckbox(image_list[i + image_selection_start], true);
}
}
}//making checkbox system to select/deselect individual files from the file-range
//displaying in columns, so the window doesn't get bigger than the screen
Dialog.show();
file_chosen = newArray(image_selection_size);
for (i = 0; i < image_selection_size; i++) { //retrieving the file confirmation info
file_chosen[i] = Dialog.getCheckbox();
}
image_path = newArray();
image_name = newArray();
image_name_without_extension = newArray();
for (i = 0; i < image_selection_size; i++) {
if (file_chosen[i]) {
image_path = Array.concat(image_path, replace(image_directory + image_list[i + image_selection_start], "\\", "/"));
image_name = Array.concat(image_name, File.getName(image_directory + image_list[i + image_selection_start]));
image_name_without_extension = Array.concat(image_name_without_extension, File.getNameWithoutExtension(image_directory + image_list[i + image_selection_start]));
}
}
File.setDefaultDir(higher_directory);
output_home_path = getDirectory("Where should the output folder be created? Do not pick your input directory.");
output_path = output_home_path + "/" + directory_name + "_ROIMAPer_results_" + year + "_" + month + "_" + dayOfMonth + "_" + hour + "_" + minute + "/";
File.setDefaultDir(default_directory);
//restore default directory
//make empty array of roi-set names
xpixelnumber = newArray();//could re-sort the image_path index (and all others belonging to it) to start with smallest image or smthg
ypixelnumber = newArray();
slicenumber = newArray();
channelnumber = newArray();
setBatchMode(true);
roiManager("reset");
for (i = 0; i < image_path.length; i++) {
is_czi = Array.concat(is_czi, endsWith(image_name[i], ".czi"));
//get metadata, so we do not have to open big stacks of images
run("Bio-Formats Importer", "open=[" + image_path[i] + "] color_mode=Default display_metadata rois_import=[ROI manager] view=[Metadata only] stack_order=Default");
//selectWindow("Original Metadata - " + image_name[i]); //not sure if this is needed
//get the metadata as a string
metadata = getInfo("window.contents");
//close metadata table immediately
table_name = getInfo("window.title");
//make a text search for dimension-specific identifiers
metadata_length = lengthOf(metadata);
metadata_queries = newArray("SizeX", "SizeY", "SizeZ", "SizeC");
metadata_answer = newArray();
for (j = 0; j < metadata_queries.length; j++) {
metadata_location = indexOf(metadata, metadata_queries[j]) + 5;
metadata_line_separator_location = indexOf(metadata, "\n", metadata_location);
//using the line separator as a delimiter after the value atributed to the query
if (metadata_line_separator_location < 0) {
metadata_line_separator_location = metadata_length;
}
local_metadata_answer = String.trim(substring(metadata, metadata_location, metadata_line_separator_location));
metadata_answer = Array.concat(metadata_answer,parseInt(local_metadata_answer));
}
//these are the image dimensions (useful, because we can now select, which channels and slices to use, without opening the image itself (saves time/processing power)
xpixelnumber = Array.concat(xpixelnumber, metadata_answer[0]);
ypixelnumber = Array.concat(ypixelnumber, metadata_answer[1]);
if (!is_czi[i]) {
slicenumber = Array.concat(slicenumber, metadata_answer[2]);
}
if (is_czi[i]) { //because .czi files have different metadata layout than .tif
//is not perfect yet
selectWindow("Original Metadata - " + image_name[i]);
for (j = 0; indexOf(metadata, "Series " + j + " Name") > 0; j++) {
} //count how many entries with "Series 0 Name" there are, where 0 counts up until it doesn't find this expression any more
if (indexOf(metadata, "label image") > 0) {//removing additional images in the czi file
j = j-1;
}
if (indexOf(metadata, "macro image") > 0) {
j = j-1;//this only works, if the macro image and the label image are the last in the series
}
slicenumber = Array.concat(slicenumber, j); //divide by six does not always work
}
channelnumber = Array.concat(channelnumber, metadata_answer[3]);
close(table_name);
}
setBatchMode(false);
length_limit = 20;
columns = Math.ceil(image_path.length/length_limit);
Dialog.create("Settings");
//Dialog.addCheckbox("Images have consistent channel order", true);
Dialog.addCheckbox("Use the same atlas position for all images?", false);
Dialog.addCheckbox("Automatically create bounding box", false);
Dialog.addCheckbox("Save between images?", false);
Dialog.addChoice("Output channels individually or combined?", newArray("individual", "combined", "both"), "both");
Dialog.addCheckbox("Specify slices on import. If no it uses the first slice in every image", false);
Dialog.addCheckbox("Blind images? No image names will be displayed and image order is randomized.", false);
Dialog.show();
//one_channel_for_all = Dialog.getCheckbox();
one_roi_for_all = Dialog.getCheckbox();
automatic_bounding_box = Dialog.getCheckbox();
autosave = Dialog.getCheckbox();
combined_results = Dialog.getChoice();
do_slice_selection = Dialog.getCheckbox();
do_blinding = Dialog.getCheckbox();
if (do_slice_selection) {
Dialog.create("Slice selection");
Dialog.addMessage("Which slices would you like to use for each image?");
for (i = 0; i < image_path.length; i++) {//Grid likeness enables the display of more file titles
checkboxitems = Array.deleteValue(Array.getSequence(slicenumber[i] + 1), 0);
//making an array of the numbers from 1 to slicenumber
Dialog.addChoice(image_name_without_extension[i], checkboxitems);
for (j = 1; j < columns; j++) {
if (i + 1 < image_path.length) {
Dialog.addToSameRow();
i++;
checkboxitems = Array.deleteValue(Array.getSequence(slicenumber[i] + 1), 0);
//making an array of the numbers from 1 to slicenumber
Dialog.addChoice(image_name_without_extension[i], checkboxitems);
}
}
}
Dialog.show();
/*
if(one_channel_for_all == false) {
exit("Differing channels have not been implemented yet. Please analyze these images seperately.");
//fix this at some point
}
*/
selected_slices = newArray();
for (i = 0; i < image_path.length; i++) {
selected_slices = Array.concat(selected_slices, parseInt(Dialog.getChoice()));
//choice puts out decimal figures as characters
}
} else { //if no slice selection
selected_slices = newArray(image_path.length);
for (i = 0; i < selected_slices.length; i++) {
selected_slices[i] = 1;
}
}
Table.open(utilities_directory + text_file);
defaultchannels = "DAPI, Iba1, GFAP, mOC87, Temp";
if(one_roi_for_all) {
//get roi set from user
screen_height = screenHeight;
screen_width = screenWidth;
call("ij.gui.ImageWindow.setNextLocation", round(screen_height*0.4), round(screen_height*0.01));
open(utilities_directory + atlas_name + "_overview.tif");
Dialog.createNonBlocking("Template selection");
Dialog.addMessage("What slice of the Allen Brain do you want to map to?");
Dialog.addNumber("Slice", 1, 0, 3, "");
Dialog.addMessage("Which brain regions do you want to map? Please add the region acronyms separated by a comma, like this: \"HY, BLA, CA1\".");
Dialog.addString("Brain regions:", "", 35);
Dialog.addCheckbox("Only save part of these regions?\nThis can be used if you want to use the other regions as context clues.", false);
Dialog.addMessage("These are the default channels, please add a channel, separated by a comma, if you use a custom one");
Dialog.addString("Default channels:", defaultchannels, 50);
Dialog.show();
template_slice_number = Array.concat(newArray(),Dialog.getNumber());
regions_text = Dialog.getString();
regions = split(regions_text, ",");
map_part = Dialog.getCheckbox();
channeloptions_return = Dialog.getString();
for (i = 0; i < regions.length; i++) {
regions[i] = trim(regions[i]); //deal with whitespace in the brain region submission
}
if (map_part) {
Dialog.create("Save ROIs");
Dialog.addMessage("Which brain regions do you want to save? Please add the region acronyms separated by a comma, like this: \"HY, BLA, CA1\".");
Dialog.addString("Brain regions:", regions_text, 35);
Dialog.addMessage("If you picked a combined output, all regions will be saved in the tif file.");
Dialog.show();
save_regions = split(Dialog.getString(), ",");
for (i = 0; i < save_regions.length; i++) {
save_regions[i] = trim(save_regions[i]); //deal with whitespace in the brain region submission
}
} else {
save_regions = regions;
}
for (i = 0; i < template_slice_number.length; i++) {
template_slice_number[i] = parseInt(template_slice_number[i]); //because otherwise they get a decimal point, which messes up the folder system
}
close(atlas_name + "_overview.tif");
} else {
//get roi set from user
Dialog.createNonBlocking("Template selection");
Dialog.addMessage("Which brain regions do you want to map? Please add the region acronyms separated by a comma, like this: \"HY, BLA, CA1\".");
Dialog.addMessage("Please refer to the opened table for correct acronyms. They need to first be created with the atlas_to_roi.ijm script.");
Dialog.addString("Brain regions:", "", 35);
Dialog.addCheckbox("Only save part of these regions?\nThis can be used if you want to use the other regions as context clues.", false);
Dialog.addMessage("These are the default channels, please add a channel, separated by a comma, if you use a custom one");
Dialog.addString("Default channels:", defaultchannels, 50);
Dialog.show();
regions_text = Dialog.getString();
regions = split(regions_text, ",");
map_part = Dialog.getCheckbox();
channeloptions_return = Dialog.getString();
for (i = 0; i < regions.length; i++) {
regions[i] = trim(regions[i]); //deal with whitespace in the brain region submission
}
if (map_part) {
Dialog.create("Save ROIs");
Dialog.addMessage("Which brain regions do you want to save? Please add the region acronyms separated by a comma, like this: \"HY, BLA, CA1\".");
Dialog.addString("Brain regions:", regions_text, 35);
Dialog.addMessage("If you picked a combined output, all regions will be saved in the tif file.");
Dialog.show();
save_regions = split(Dialog.getString(), ",");
for (i = 0; i < save_regions.length; i++) {
save_regions[i] = trim(save_regions[i]); //deal with whitespace in the brain region submission
}
} else {
save_regions = regions;
}
}
close(text_file);
//run through the ROIs requested, create those that have not been created yet
createROIs(atlas_name, mapping_index_path, regions);
//if there are channels, select which belongs to the respective fluorophor and only open those channels
//make the user create custom channels
//channeloptions_array is the array of all possible channels, but without the "do not use"
channeloptions_array = split(channeloptions_return, ", ");
//channeloptions is the finished possible options for the cannel menue
channeloptions = Array.concat(channeloptions_array, "do not use");
//now select labels for the individual channels
Dialog.create("Channels");
Dialog.addMessage("Your image has " + channelnumber[0] + " channels. Please select, which ones to use.");
Dialog.addMessage("All images have to have the same channel order.");
for (i = 1; i <= channelnumber[0]; i++) {
Dialog.addChoice("Channel " + i, channeloptions, "do not use");
}
Dialog.addChoice("Which is the control channel?", channeloptions_array, channeloptions_array[0]);
Dialog.addCheckbox("Also map ROIs to control channel?", true);
Dialog.show();
//get the selected labels and make them into an array (this is an array of the length of channels, with the channel-label in each entry)
channelchoices = newArray();
for (i = 1; i <= channelnumber[0]; i++) {
channelchoices = Array.concat(channelchoices,Dialog.getChoice());
}
control_channel = Dialog.getChoice();
map_to_control_channel = Dialog.getCheckbox();
//go through this array
control_channel_id = 0; //default, so things do not break if this is forgotten
for (i = 1; i <= channelchoices.length; i++) {
if (channelchoices[i-1] == control_channel) {
control_channel_id = i;
}
}
if (combined_results == "individual" || combined_results == "both") {
File.makeDirectory(output_path);
}
if (combined_results == "combined" || combined_results == "both") {
combined_output_path = output_home_path + "/" + directory_name + "_ROIMAPer_results_" + year + "_" + month + "_" + dayOfMonth + "_" + hour + "_" + minute + "_combined/";
File.makeDirectory(combined_output_path);
}
if (do_blinding) {
random_array = newArray(image_path.length);
for (i = 0; i < image_path.length; i++) {
random_array[i] = random;
}
sequence_array = Array.rankPositions(random_array);
} else {
sequence_array = Array.getSequence(image_path.length);
}
//then run the roi adjusting function for each image
for (current_image = 0; current_image < image_path.length; current_image++) {
if (one_roi_for_all) {
atlas_slice = template_slice_number[0];
} else {
atlas_slice = 1; //gets changed in the image_processing function
}
if (do_blinding) {
print("Now working on " + image_name_without_extension[current_image] + " which is " + (current_image + 1) + " out of " + image_path.length);
} else {
print("Now working on image " + (current_image + 1) + " out of " + image_path.length);
}
image_processing(current_image, image_path[sequence_array[current_image]], image_name_without_extension[sequence_array[current_image]], control_channel_id, selected_slices[sequence_array[current_image]], atlas_slice, regions, home_directory);
if (autosave) {//if we want to save after every image
print("Saving image " + (current_image + 1) + " out of " + image_path.length);
saving(current_image, image_path[sequence_array[current_image]], image_name_without_extension[sequence_array[current_image]], channelchoices, channeloptions_array, selected_slices[sequence_array[current_image]], home_directory, save_regions);
}
}
//then save the rois on tifs
if (!autosave) {
for (current_image = 0; current_image < image_path.length; current_image++) {
print("Saving image " + (current_image + 1) + " out of " + image_path.length);
saving(current_image, image_path[current_image], image_name_without_extension[current_image], channelchoices, channeloptions_array, selected_slices[current_image], home_directory, save_regions);
}
}
close("ROI Manager");
Dialog.create("Command finished");
Dialog.addMessage("Your ROIMAPing has been completed. You can find your results in: " + output_path);
Dialog.show();
//all the previous stuff is done on the first image; now open each image individually
function image_processing(image_number, local_image_path, local_image_name_without_extension, control_channel_id, selectedslice, atlas_slice, regions, home_directory) {
skip_choice = "Continue";//reset this, in case the last image was skipped
proceed = false;
if (one_roi_for_all) {
roi_path = check_roi_availability(atlas_slice, regions, local_image_name_without_extension);
if (roi_path.length > 0) {
proceed = true;
}
} else {
proceed = true;
}
if (proceed) { //only do this, if there are saved ROIs for this slice of the ABA
//rename the image, because otherwise its title is visible in the channel name
if (do_blinding) {
local_image_path_split = split(local_image_path, "/");
image_extension_split = split(local_image_path, ".");
image_extension = image_extension_split[image_extension_split.length - 1];
pseudopath_split = Array.deleteIndex(local_image_path_split, local_image_path_split.length - 1);
pseudopath = String.join(pseudopath_split, "/") + "/pseudoname." + image_extension;
File.rename(local_image_path, pseudopath);
original_path = local_image_path;
local_image_path = pseudopath;
}
if (!is_czi[image_number]) {
run("Bio-Formats Importer", "open=[" + local_image_path + "] color_mode=Default specify_range view=Hyperstack stack_order=XYCZT c_begin=" + control_channel_id + " c_end=" + control_channel_id + " c_step=1 z_begin=" + selectedslice + " z_end=" + selectedslice + " z_step=1");
} else {
selectedslice = selectedslice;
run("Bio-Formats Importer", "open=[" + local_image_path + "] color_mode=Default specify_range view=Hyperstack stack_order=XYCZT series_" + selectedslice + " c_begin_" + selectedslice + "=" + control_channel_id + " c_end_" + selectedslice + "=" + control_channel_id + " c_step_" + selectedslice + "=1");
}
if (do_blinding) {
rename(control_channel);
} else {
rename(local_image_name_without_extension);
}
image_name = getTitle();
getDimensions(width, height, channels, slices, frames);
selectWindow(image_name);
/*
//get feret diameters for rotation???? - this would be an autofitting process
Roi.getFeretPoints(x,y);
bigferetangle = atan2(y[1] - y[0], x[1] - x[0]) * 180 / PI;
smallferetangle = atan2(y[2] - y[3], x[2] - x[3]) * 180 / PI;
makeSelection(5, newArray(x[0], x[1]), newArray(y[0], y[1]));
roiManager("add");
roiManager("select",roi_id_brain + 1);
roiManager("rename", "bigferet");
makeSelection(5, newArray(x[3], x[2]), newArray(y[3], y[2]));
roiManager("add");
roiManager("select",roi_id_brain + 2);
roiManager("rename", "smallferet");
*/
//select the original background image again, for the user
selectWindow(image_name);
run("Enhance Contrast", "saturated=0.35"); //better visibility
if (automatic_bounding_box) {
setAutoThreshold();
run("Create Selection");
run("Fit Rectangle");
resetThreshold;
setTool("rotatedrect");
Dialog.createNonBlocking("Automatic selection");
Dialog.addMessage("Please adjust the created bounding rectangle if necessary.");
if (!one_roi_for_all) {
screen_height = screenHeight;
screen_width = screenWidth;
call("ij.gui.ImageWindow.setNextLocation", round(screen_height*0.7), round(screen_height*0.2));
open(utilities_directory + atlas_name + "_overview.tif");
selectWindow(image_name);
Dialog.addMessage("Which slice of the atlas does this brain slice correspond to?");
Dialog.addNumber("Slice", 1, 0, 3, "");
}
Dialog.addChoice("Continue or skip this image:", newArray("Continue", "Skip"), "Continue");
Dialog.show();
if (!one_roi_for_all) {
close(atlas_name + "_overview.tif");
atlas_slice = Dialog.getNumber();
atlas_slice = parseInt(atlas_slice);//removing decimal points
}
skip_choice = Dialog.getChoice();
if (skip_choice == "Continue") {//only get this if user wants to continue
if (selectionType() == 3 || selectionType() == 0 || selectionType() == 2) {
getSelectionCoordinates(xbounding, ybounding);
} else {// if no valid bounding box was created default to manual box creation
print("Bounding box was removed. Please add a bounding box manually.");
atlas_slice = user_bounding_box(atlas_slice);
if (skip_choice == "Continue") {//only get this if user wants to continue
getSelectionCoordinates(xbounding, ybounding);
run("Select None");
}
}
}
} else {//if not automatic bounding box
atlas_slice = user_bounding_box(atlas_slice);
if (skip_choice == "Continue") {//only get this if user wants to continue
getSelectionCoordinates(xbounding, ybounding);
run("Select None");
}
}
if (skip_choice == "Continue") {//only do this if user wants to continue
//because rotated rectangles start in a different corner than normal rectangles
xbounding = Array.rotate(xbounding, 1);
ybounding = Array.rotate(ybounding, 1);
//angle of the bounding box
angle = atan((ybounding[1]-ybounding[0])/(xbounding[1]-xbounding[0]))*180/PI;
//roll-over of the angle
if (xbounding[1]-xbounding[0] < 0) {
angle = angle + 180;
} else {
if (ybounding[1]-ybounding[0] < 0) {
angle = angle + 360;
}
}
widthbounding = sqrt(Math.pow(xbounding[1]-xbounding[0], 2) + Math.pow(ybounding[1]-ybounding[0], 2)); //pythagoras
heightbounding = sqrt(Math.pow(xbounding[2]-xbounding[1], 2) + Math.pow(ybounding[2]-ybounding[1], 2));
//have to check for availability of ROIs separately because these are per-image and not for the whole analysis
if (!one_roi_for_all) {
roi_path = check_roi_availability(atlas_slice, regions, local_image_name_without_extension);
}
atlas_combined_ids = open_rois(roi_path);
atlas_start_id = atlas_combined_ids[0];
atlas_end_id = atlas_combined_ids[1];
if (atlas_start_id >= atlas_end_id) {//if no ROIs were opened
print("Not found any of the specified regions in image " + local_image_name_without_extension);
give_user_choice = false;
} else {//only proceed, if ROIs were opened - could put this in a loop with a retry
//go through the atlas, whichever entry is a rectangle is the bounding box
brain_region_roi_ids = newArray();
for (i = atlas_start_id; i <= atlas_end_id; i++) {
roiManager("select", i);
roitype = Roi.getType;
if (Roi.getName == "atlas_bounding_box") {
atlas_bounding_box_id = i;
} else {
brain_region_roi_ids = Array.concat(brain_region_roi_ids, i);
}
}
full_atlas_ids = Array.concat(brain_region_roi_ids, atlas_bounding_box_id);
//this moves the ROIs in the right scaling and orientation inside the bounding box
scaling(atlas_bounding_box_id, full_atlas_ids, angle, widthbounding, heightbounding, xbounding, ybounding);
give_user_choice = true;
}
modifying_options = newArray("Do not modify" , "flip x", "flip y", "rotate by 90 degrees", "change one roi", "mesh transform", "redo bounding box");
modifying = true;
while (modifying) {
if (give_user_choice) {//only allow this if there were no problems in the previous round
Dialog.createNonBlocking("Modifying.\nAre the ROIs oriented correctly?\nUsing both the mesh transform and \"change one roi\" in the same image will result in a crash.");
Dialog.addChoice("Modify orientation:", modifying_options, modifying_options[0]);
Dialog.show();
modifyer = Dialog.getChoice();
} else {
modifyer = "redo bounding box";
Dialog.createNonBlocking("Error in ROI search");
Dialog.addMessage("Not found any of the specified regions in image " + local_image_name_without_extension + ". Please select a different atlas slice");
Dialog.addChoice("Retry mapping of this image with a new atlas slice?", newArray("Yes", "No, skip this image"), "Yes");
Dialog.show();
error_action = Dialog.getChoice();
give_user_choice = true; //so the user can choose again in the next round
if (error_action == "No, skip this image") {
proceed = false;
close(atlas_name + "_overview.tif");
break;
}
}
//the normal actions that can be performed on this image
if (modifyer == "flip x") {
flip_roi_x(full_atlas_ids, angle, atlas_bounding_box_id);
}
if (modifyer == "flip y") {
flip_roi_y(full_atlas_ids, angle, atlas_bounding_box_id);
}
if (modifyer == "Do not modify") {
modifying = false;
}
if (modifyer == "rotate by 90 degrees") {
rotate90(widthbounding, heightbounding, full_atlas_ids, angle, atlas_bounding_box_id);
}
if (modifyer == "change one roi") {
brain_region_roi_ids = to_downsampled_selection(brain_region_roi_ids);
}
if (modifyer == "mesh transform") {
brain_region_roi_ids = mesh_transform(brain_region_roi_ids);
}
if (modifyer == "redo bounding box") {
roiManager("reset");
//restore the previous bounding rectangle as a selection, to make for easier editing
makeRotatedRectangle((xbounding[0]+xbounding[1])/2, (ybounding[0]+ybounding[1])/2, (xbounding[2]+xbounding[3])/2, (ybounding[2]+ybounding[3])/2, widthbounding);
atlas_slice = user_bounding_box(atlas_slice);
if (skip_choice == "Skip") {
proceed = false;
break;//exits the "modifying" while loop
}
getSelectionCoordinates(xbounding, ybounding);
run("Select None");
//because rotated rectangles start in a different corner than normal rectangles
xbounding = Array.rotate(xbounding, 1);
ybounding = Array.rotate(ybounding, 1);
angle = atan((ybounding[1]-ybounding[0])/(xbounding[1]-xbounding[0]))*180/PI;
//roll-over of the angle
if (xbounding[1]-xbounding[0] < 0) {
angle = angle + 180;
} else {
if (ybounding[1]-ybounding[0] < 0) {
angle = angle + 360;
}
}
widthbounding = sqrt(Math.pow(xbounding[1]-xbounding[0], 2) + Math.pow(ybounding[1]-ybounding[0], 2)); //pythagoras
heightbounding = sqrt(Math.pow(xbounding[2]-xbounding[1], 2) + Math.pow(ybounding[2]-ybounding[1], 2));
if (!one_roi_for_all) {//get the roi_paths of the possibly new slice
roi_path = check_roi_availability(atlas_slice, regions, local_image_name_without_extension);
}
atlas_combined_ids = open_rois(roi_path);
atlas_start_id = atlas_combined_ids[0];
atlas_end_id = atlas_combined_ids[1];
if (atlas_start_id >= atlas_end_id) {//if no ROIs were opened
give_user_choice = false;
} else {//allow scaling when there were ROIs opened
//go through the atlas, whichever entry is a rectangle is the bounding box
brain_region_roi_ids = newArray();
for (i = atlas_start_id; i <= atlas_end_id; i++) {
roiManager("select", i);
roitype = Roi.getType;
if (Roi.getName == "atlas_bounding_box") {
atlas_bounding_box_id = i;
} else {
brain_region_roi_ids = Array.concat(brain_region_roi_ids, i);
}
}
full_atlas_ids = Array.concat(brain_region_roi_ids, atlas_bounding_box_id);
scaling(atlas_bounding_box_id, full_atlas_ids, angle, widthbounding, heightbounding, xbounding, ybounding);
}
}
}
if (proceed) {
//save the rois to the temp directory, named after the images
if (brain_region_roi_ids.length > 0) {
roiManager("select", brain_region_roi_ids);
roiManager("save selected", temp + local_image_name_without_extension + "roi.zip"); //change the [0] to image_number later
}
}
}//all this is skipped if user decided to skip this image
//delete these rois
//roiManager("select", full);
//roiManager("delete");
roiManager("reset"); //not as elegant, but selection of atlas_bounding box after the downsampling gets tricky
close(image_name);
if (do_blinding) {
File.rename(pseudopath, original_path);
}
} else {
print("Not found any of the specified regions in image " + local_image_name_without_extension); }
}
function check_roi_availability(atlas_slice, regions, local_image_name_without_extension) {
roi_path = newArray();
for (i = 0; i < regions.length; i++) {
if(File.exists(atlas_directory + atlas_slice + "/" + regions[i] + ".zip")) {
roi_path = Array.concat(roi_path, atlas_directory + atlas_slice + "/" + regions[i] + ".zip");
} else {
print("Not found the region " + regions[i] + " in image " + local_image_name_without_extension);
}
} //get the paths of individual ROIs
return roi_path;
}
function user_bounding_box(atlas_slice) {
//get bounding box from user
bounding_box_text = "Please create a bounding box around the tissue and click \"OK\" once you are satisfied with the selection.";
before_bounding_box = roiManager("count");
waiting_for_bounding_box = true;
while (waiting_for_bounding_box) {//so there is no chance to procede without providing a bounding box
setTool("rotatedrect");
Dialog.createNonBlocking("Brain selection");
Dialog.addMessage("Please create a rectangle that sits flush with the brain.");
screen_height = screenHeight;
screen_width = screenWidth;
call("ij.gui.ImageWindow.setNextLocation", round(screen_height*0.7), round(screen_height*0.2));
open(utilities_directory + atlas_name + "_overview.tif");
selectWindow(image_name);
Dialog.addMessage("Which slice of the atlas does this brain slice correspond to?");
Dialog.addNumber("Slice", atlas_slice, 0, 3, "");
Dialog.addChoice("Continue or skip this image:", newArray("Continue", "Skip"), "Continue");
Dialog.show();
//even when same ROI set for all is specified, allow for change of atlas slice here
close(atlas_name + "_overview.tif");
atlas_slice = Dialog.getNumber();
atlas_slice = parseInt(atlas_slice);//removing decimal points
skip_choice = Dialog.getChoice();
if (selectionType() == 3 || selectionType() == 0 || selectionType() == 2 || skip_choice == "Skip") {
//break the loop when correct bounding box or user wants to skip
waiting_for_bounding_box = false;
} else {
bounding_box_text = "No rectangular selection provided, please try again.";
}
}
return atlas_slice;
}
function open_rois(roi_path) {
//open the atlas and save the indices of the first and the last entry
atlas_start_id = roiManager("count");
for (i = 0; i < roi_path.length; i++) {
roi_number_opening = roiManager("count");
roiManager("open", roi_path[i]); //open all the rois in the specified folder
roi_number_after_opening = roiManager("count");
if (i < roi_path.length - 1) {//for all but the last roi
if (roi_number_after_opening > roi_number_opening + 1) { //this checks, if the roi.zip was not empty
for (j = roi_number_opening; j < roi_number_after_opening; j++) {//go through all newly opened ROIs and delete the bounding box (for all but the las ROI
roiManager("select", j);
roitype = Roi.getType;
if (Roi.getName == "atlas_bounding_box") {//could add type requirement
roiManager("delete");
}
}
}
}
}
atlas_end_id = roiManager("count") - 1;
return newArray(atlas_start_id, atlas_end_id);
}
function scaling(atlas_bounding_box_id, full_atlas_ids, angle, widthbounding, heightbounding, xbounding, ybounding) {
//get coordinates of unscaled atlas
roiManager("select", atlas_bounding_box_id);
getSelectionBounds(xatlas, yatlas, widthatlas, heightatlas);
//calculate scaling factor
xscale = widthbounding / widthatlas;
yscale = heightbounding / heightatlas;
//select all of the atlas
roiManager("select", full_atlas_ids);
//the actual scaling
RoiManager.scale(xscale, yscale, false);
//now do the same thing with translation (measure the coordinates again, because I do not want to bother with maths (maybe they do not even change, when scaling non-centered)
roiManager("select", atlas_bounding_box_id);
getSelectionCoordinates(xatlas, yatlas);
xtrans = xbounding[0] - xatlas[0];
ytrans = ybounding[0] - yatlas[0];
roiManager("select", full_atlas_ids);
roiManager("translate", xtrans, ytrans);
roiManager("select", full_atlas_ids);
RoiManager.rotate(angle, xbounding[0], ybounding[0]);
roiManager("show all without labels");
}
function rotate90(widthbounding, heightbounding, full_atlas_ids, angle, atlas_bounding_box_id) {
roiManager("select", atlas_bounding_box_id);
getSelectionCoordinates(xatlas, yatlas);
xcenter = (xatlas[2] - xatlas[0]) / 2 + xatlas[0]; //get center of the bounding box
ycenter = (yatlas[2] - yatlas[0]) / 2 + yatlas[0];
roi_indices_new = newArray();
roi_indices_new = Array.concat(roi_indices_new,full_atlas_ids); //so it is an array if there is only one ID to modify
roiManager("select", roi_indices_new);
//normalize rotational axis so scaling is not skewed, rotate around the bounding box center
RoiManager.rotate(-angle, xcenter, ycenter);
roiManager("select", atlas_bounding_box_id);
getSelectionCoordinates(xatlas_trans, yatlas_trans); //get atlas coordinates after translation
roiManager("select", roi_indices_new);
getSelectionBounds(xroiold, yroiold, widthroiold, heightroiold);
RoiManager.scale(heightbounding/widthbounding, widthbounding/heightbounding, false); //invert length and height scaling of all ROIs
roiManager("select", roi_indices_new);
getSelectionBounds(xroinew, yroinew, widthroinew, heightroinew);
RoiManager.translate(xatlas_trans[0] - xroinew, yatlas_trans[0] - yroinew); //move to top right corner to allow for 90 degree turn around that corner
roiManager("select", full_atlas_ids);
RoiManager.rotate(90, xatlas_trans[0], yatlas_trans[0]); //rotate by 90 degrees
//now rotate again to restore the original tilt
RoiManager.rotate(angle, xcenter, ycenter);
//get distance of new center from old center - move back to old center
roiManager("select", atlas_bounding_box_id);
getSelectionCoordinates(xatlas_trans_rot, yatlas_trans_rot);
xcenter_trans_rot = (xatlas_trans_rot[2] - xatlas_trans_rot[0]) / 2 + xatlas_trans_rot[0]; //get center of the bounding box
ycenter_trans_rot = (yatlas_trans_rot[2] - yatlas_trans_rot[0]) / 2 + yatlas_trans_rot[0];
roiManager("select", roi_indices_new);
RoiManager.translate(xcenter - xcenter_trans_rot, ycenter - ycenter_trans_rot); //move to top right corner to allow for 90 degree turn around that corner
roiManager("show all without labels");
}
function flip_roi_x(roi_indices, angle, atlas_bounding_box_id) {
roiManager("select", atlas_bounding_box_id);
getSelectionCoordinates(xatlas, yatlas);
xcenter = (xatlas[2] - xatlas[0]) / 2 + xatlas[0]; //get center of the bounding box
ycenter = (yatlas[2] - yatlas[0]) / 2 + yatlas[0];
roi_indices_new = newArray();
roi_indices_new = Array.concat(roi_indices_new,roi_indices);
roiManager("select", roi_indices_new);
RoiManager.rotate(-angle, xcenter, ycenter); //bring into neutral orientation
roiManager("select", atlas_bounding_box_id);
getSelectionBounds(x, y, width, height);
roiManager("select", roi_indices_new);
RoiManager.scale(-1, 1, false);//this is flipping the selection
roiManager("select", atlas_bounding_box_id);
getSelectionBounds(x_scale, y_scale, width_scale, height_scale); //get bounds after scale
roiManager("select", roi_indices_new);
RoiManager.translate(x - x_scale , y - y_scale); // move to original spot
RoiManager.rotate(angle, xcenter, ycenter); //restore original orientation
roiManager("show all without labels");
}
function flip_roi_y(roi_indices, angle, atlas_bounding_box_id) {
roiManager("select", atlas_bounding_box_id);
getSelectionCoordinates(xatlas, yatlas);
xcenter = (xatlas[2] - xatlas[0]) / 2 + xatlas[0]; //get center of the bounding box
ycenter = (yatlas[2] - yatlas[0]) / 2 + yatlas[0];
roi_indices_new = newArray();
roi_indices_new = Array.concat(roi_indices_new,roi_indices);
roiManager("select", roi_indices_new);
RoiManager.rotate(-angle, xcenter, ycenter); //bring into neutral orientation
roiManager("select", atlas_bounding_box_id);
getSelectionBounds(x, y, width, height);
roiManager("select", roi_indices_new);
RoiManager.scale(1, -1, false);//this is flipping the selection
roiManager("select", atlas_bounding_box_id);
getSelectionBounds(x_scale, y_scale, width_scale, height_scale); //get bounds after scale
roiManager("select", roi_indices_new);
RoiManager.translate(x - x_scale , y - y_scale); // move to original spot
RoiManager.rotate(angle, xcenter, ycenter); //restore original orientation
roiManager("show all without labels");
}
//prompts user, if they want to manually adjust any ROI. If yes:
//turns an roi into a set of points, reduces the amount of points and makes it an editable polygon selection
//then updates the array that stores the brain region ROIs
function to_downsampled_selection(roi_ids) {
roiManager("show all without labels");
Dialog.createNonBlocking("Brain region selection");
Dialog.addMessage("Is the brain region selection okay? If yes, click \"OK\", you will be redirected to the \"Modifying\" window, where you can proceed");
Dialog.addMessage("Otherwise please select an roi to adjust, uncheck the following box, and enter a downsampling factor for the amount of points in the ROI");
Dialog.addCheckbox("Change an ROI", false);
Dialog.addToSameRow();
Dialog.addNumber("Downsampling", 10, 0, 2, "");
Dialog.show();
do_downsampling = Dialog.getCheckbox();
downsample_factor = Dialog.getNumber();
if (do_downsampling) {
selectiontype = selectionType();
if (selectiontype != -1) { //test, if an roi was selected
roi_type = Roi.getType;
changing_roi = roiManager("index");
old_name = Roi.getName;//transfer this to the new, downsampled ROI
//if ROI is composite, split it and handle every ROI individually
if (roi_type == "composite") {
first_split = roiManager("count");
roiManager("split");
last_split = roiManager("count");
intermediate_rois = newArray();
for (i = first_split; i < last_split; i++) {
roiManager("select", i);
getSelectionCoordinates(xpoints, ypoints);
new_xpoints = newArray();
new_ypoints = newArray();
for (j = 0; j < maxOf(Math.floor(xpoints.length / downsample_factor), 1); j++) {//only adding downsample_factor few points to the new selection (this is the downsampling
new_xpoints = Array.concat(new_xpoints,xpoints[j*downsample_factor]);
new_ypoints = Array.concat(new_ypoints,ypoints[j*downsample_factor]);
}
makeSelection("polygon", new_xpoints, new_ypoints);
roiManager("add");//this is the new version of the roi
roiManager("select", roiManager("count") - 1);
new_name = old_name + "_" + (i - first_split);
roiManager("rename", new_name);
intermediate_rois = Array.concat(intermediate_rois, i - 1); //has to be one smaller because we will delete the original ROI, which is before the newly added ROIs
}
roi_ids = Array.deleteValue(roi_ids, changing_roi);//so we have to delete the former one from the archive of ROIs that will be saved in the end
roiManager("select", changing_roi);
roiManager("delete");
for (i = 0; i < roi_ids.length; i++) {//after deletion, the index of all higher ROIs will change, so have to adjust all higher ones
if (roi_ids[i] > changing_roi) {
roi_ids[i] = roi_ids[i]-1;
}
}
//delete the intermediate, split ROIs
roiManager("select", intermediate_rois);
roiManager("delete");
//after deletion, the new, downsampled ROIs are the same indices
downsampled_rois = intermediate_rois;
roiManager("select", downsampled_rois);
waitForUser("Please adjust the ROIs you have selected. They are at the bottom of the ROI Manager list");
roiManager("show all without labels");
//make composite ROI again
roiManager("select", downsampled_rois);
roiManager("combine");
roiManager("add");
roiManager("rename", old_name);
roiManager("select", downsampled_rois);
roiManager("delete");
//latest index is of the newly added ROI
new_roi_index = roiManager("count") - 1;
roi_ids = Array.concat(roi_ids, new_roi_index);//and update the brain_region selection that will be saved to reflect this change
} else { //if ROI is not composite
getSelectionCoordinates(xpoints, ypoints);
new_xpoints = newArray();
new_ypoints = newArray();
for (i = 0; i < maxOf(Math.floor(xpoints.length / downsample_factor), 1); i++) {//only adding downsample_factor few points to the new selection (this is the downsampling
new_xpoints = Array.concat(new_xpoints,xpoints[i*downsample_factor]);
new_ypoints = Array.concat(new_ypoints,ypoints[i*downsample_factor]);
}
makeSelection("polygon", new_xpoints, new_ypoints);
roiManager("add");//this is the new version of the roi
roiManager("select", roiManager("count")-1);//select the newly added roi
roiManager("rename", old_name);
roi_ids = Array.deleteValue(roi_ids, changing_roi);//so we have to delete the former one from the archive of ROIs that will be saved in the end
roiManager("select", changing_roi);
roiManager("delete");
for (i = 0; i < roi_ids.length; i++) {//after deletion, the index of all higher ROIs will change, so have to adjust all higher ones
if (roi_ids[i] > changing_roi) {
roi_ids[i] = roi_ids[i]-1;
}
}
new_roi_index = roiManager("index");
roi_ids = Array.concat(roi_ids, new_roi_index);//and update the brain_region selection that will be saved to reflect this change
waitForUser("Please adjust the ROI you have selected.");
roiManager("show all without labels");
}
roi_ids = to_downsampled_selection(roi_ids);
} else {
do_downsampling = false;
waitForUser("No ROI selected. Please do so. Press OK to try again");
to_downsampled_selection(roi_ids);
}
}
return roi_ids;
}
function mesh_transform(roi_ids) {
current_tool = IJ.getToolName();
//first split composites into individual rois
split_composite_rois = newArray();
split_composite_names = newArray();
skip_composites = newArray();
for (i = 0; i < roi_ids.length; i++) {//roi_ids.length
roiManager("select", roi_ids[i]);
roi_type = Roi.getType;
old_name = Roi.getName;
//if ROI is composite, split it and handle every ROI individually
if (roi_type == "composite") {
skip_composites = Array.concat(skip_composites, roi_ids[i]);
changing_roi = roi_ids[i];
first_split = roiManager("count");
roiManager("split");
last_split = roiManager("count");
for (j = first_split; j < last_split; j++) {
split_composite_names = Array.concat(split_composite_names, old_name);
split_composite_rois = Array.concat(split_composite_rois, j);
new_name = old_name + "_" + (j - first_split);
roiManager("select", j);
roiManager("rename", new_name);
}
}
} //done with spliting
full_and_composite_roi_ids = Array.concat(roi_ids, split_composite_rois);
xcenter = (xbounding[2] - xbounding[0]) / 2 + xbounding[0]; //get center of the bounding box
ycenter = (ybounding[2] - ybounding[0]) / 2 + ybounding[0];
min_x_bounding = minOf(xbounding[0], minOf(xbounding[1], minOf(xbounding[2], xbounding[3])));
max_x_bounding = maxOf(xbounding[0], maxOf(xbounding[1], maxOf(xbounding[2], xbounding[3])));
min_y_bounding = minOf(ybounding[0], minOf(ybounding[1], minOf(ybounding[2], ybounding[3])));
max_y_bounding = maxOf(ybounding[0], maxOf(ybounding[1], maxOf(ybounding[2], ybounding[3])));
start_mesh_points_x = newArray(min_x_bounding - 100, min_x_bounding - 100, max_x_bounding + 100, max_x_bounding + 100);
start_mesh_points_y = newArray(min_y_bounding - 100, max_y_bounding + 100, min_y_bounding - 100, max_y_bounding + 100);
makeSelection("multipoint", start_mesh_points_x, start_mesh_points_y);
roiManager("add");
mesh_id = roiManager("count") - 1;
roiManager("select", mesh_id);
roiManager("rename", "Transform_mesh");
//let user create a mesh
roiManager("show all without labels");
setTool("multipoint");
roiManager("select", mesh_id);
waitForUser("Please left-click to create points that define the transformation mesh.\nIf some of your regions border the bounding box, there has to be a mesh point outside of the bounding box on that side.\nPlace at leas one point.");
//so the macro does not break
if (selectionType() == -1) {
makePoint((xbounding[0] + xbounding[1]) / 2, (ybounding[0] + ybounding[1]) / 2);
}
roiManager("select", mesh_id);
getSelectionCoordinates(x_mesh, y_mesh);
//all vortices
//bounding values are global variables
xvertex_array = Array.concat(x_mesh, xbounding);
yvertex_array = Array.concat(y_mesh, ybounding);
//every ROI gets three vertices of the delauney triangle
all_delauney_vertex_positions = newArray();
for (i = 0; i < full_and_composite_roi_ids.length; i++) {//full_and_composite_roi_ids.length
//only if this is not one of the original composites
if (!value_is_in_array(skip_composites, full_and_composite_roi_ids[i])) {
roiManager("select", full_and_composite_roi_ids[i]);
getSelectionCoordinates(xroi, yroi);
//run through every probed point and find closest comparison vertex
for (j = 0; j < xroi.length; j++) {//xroi.length
xcandidate = xroi[j];
ycandidate = yroi[j];
if (!check_if_in_bounding(xcandidate, ycandidate)) {
moved_coords = move_into_bounding(xcandidate, ycandidate);
xcandidate = moved_coords[0];
ycandidate = moved_coords[1];
}
delauney_vertex_positions = get_delauney_triangle(xcandidate, ycandidate);
all_delauney_vertex_positions = Array.concat(all_delauney_vertex_positions, delauney_vertex_positions);
}
}
}
roiManager("select", mesh_id);
waitForUser("Please modify the points of the mesh.\nBe carefull not to add or remove any points.");
roiManager("select", mesh_id);
getSelectionCoordinates(newx_mesh, newy_mesh);
Overlay.remove;
roiManager("show all without labels");
newxvertex_array = Array.concat(newx_mesh, xbounding);
newyvertex_array = Array.concat(newy_mesh, ybounding);
//do not need the mesh anymore
roiManager("select", mesh_id);
roiManager("delete");
delauney_position_counter = 0;
//this will be the output
transformed_roi_ids = newArray();
//index of the split composites after transform
transformed_split_composite_rois = Array.concat(newArray(), split_composite_rois);
for (i = 0; i < full_and_composite_roi_ids.length; i++) { //full_and_composite_roi_ids.length
//have to skip the value if it is an original composite
if (!value_is_in_array(skip_composites, full_and_composite_roi_ids[i])) {
roiManager("select", full_and_composite_roi_ids[i]);
getSelectionCoordinates(xroi, yroi);
roiname = Roi.getName;
new_xroi = newArray(xroi.length);
new_yroi = newArray(yroi.length);
for (j = 0; j < new_xroi.length; j++) { //xroi.length
//because every point has three associated vertices
triangle1 = all_delauney_vertex_positions[delauney_position_counter];
delauney_position_counter++;
triangle2 = all_delauney_vertex_positions[delauney_position_counter];
delauney_position_counter++;
triangle3 = all_delauney_vertex_positions[delauney_position_counter];
delauney_position_counter++;
//use barycentric coordinates
//first get coordinates of the triangle that this point is in
x1 = xvertex_array[triangle1];
x2 = xvertex_array[triangle2];
x3 = xvertex_array[triangle3];
y1 = yvertex_array[triangle1];
y2 = yvertex_array[triangle2];
y3 = yvertex_array[triangle3];
barycentric_weights = barycentric(x1, y1, x2, y2, x3, y3, xroi[j], yroi[j]);
w1 = barycentric_weights[0];
w2 = barycentric_weights[1];
w3 = barycentric_weights[2];
//print(w1 + ", " + w2 + ", " + w3);
x1new = newxvertex_array[triangle1];
x2new = newxvertex_array[triangle2];
x3new = newxvertex_array[triangle3];
y1new = newyvertex_array[triangle1];
y2new = newyvertex_array[triangle2];
y3new = newyvertex_array[triangle3];
//makePolygon(x1new, y1new, x2new, y2new, x3new, y3new);
//run("Draw");
new_xroi[j] = w1 * x1new + w2 * x2new + w3 * x3new;
new_yroi[j] = w1 * y1new + w2 * y2new + w3 * y3new;
if (!(0 <= w1 && w1 <= 1 && 0 <= w2 && w2 <= 1 && 0 <= w3 && w3 <= 1)) {
print("An error in the affine transform occured. This might result in deformed regions. You can restart within this image by following with \"reo bounding box\".");
new_xroi[j] = xroi[j];
new_yroi[j] = yroi[j];
}
}
//Array.print(new_xroi);
//Array.print(new_yroi);
makeSelection("polygon", new_xroi, new_yroi);
roiManager("add");
roiManager("select", roiManager("count") - 1);
roiManager("rename", roiname);
not_composite = true;
//if this was a composite, update the array of composite ROIs with the id of the new ROI
for (j = 0; j < transformed_split_composite_rois.length; j++) {
if (transformed_split_composite_rois[j] == full_and_composite_roi_ids[i]) {
transformed_split_composite_rois[j] = roiManager("count") - 1;
not_composite = false;
}
}
//if this was not a composite, add the ID to the output
if (not_composite) {
transformed_roi_ids = Array.concat(transformed_roi_ids, roiManager("count") - 1);
}
}
}
//fuse the ROIs that were split from composites
split_rois_delete_array = newArray();
for (i = 0; i < transformed_split_composite_rois.length; i++) {
split_roi_search_name = split_composite_names[i];
fuse_array = Array.concat(newArray(), transformed_split_composite_rois[i]);
//find those with the same name and take all ids of the ones with the same name together
for (j = i + 1; j < split_composite_names.length; j++) {
if (split_roi_search_name == split_composite_names[j]) {
//add this id to an array of all the IDs of ROIs with the same parent name
fuse_array = Array.concat(fuse_array, transformed_split_composite_rois[j]);
//advance i, because we already compared this j
i = j;
}
}
//creates the composite again
roiManager("select", fuse_array);
roiManager("combine");
roiManager("add");
roiManager("select", roiManager("count") - 1);
roiManager("rename", split_roi_search_name);
//because the composites will be deleted in the end
// so we substract their number from the ROI index now
//with additional one because roimanager index is zero based but count not
//I think the following line contains a mistake
transformed_roi_ids = Array.concat(transformed_roi_ids, roiManager("count") - 1);
//add all those that were fused to an array of ROIs that need to be deleted
split_rois_delete_array = Array.concat(split_rois_delete_array, fuse_array);
}
setTool(current_tool);
print("done");
all_delete_array = Array.concat(roi_ids, Array.concat(split_composite_rois, split_rois_delete_array));
roiManager("select", all_delete_array);
roiManager("delete");
//adjust output roi ids, because other ROIs were deleted
for (i = 0; i < transformed_roi_ids.length; i++) {
value = transformed_roi_ids[i];
for (j = 0; j < all_delete_array.length; j++) {
if (value > all_delete_array[j]) {
transformed_roi_ids[i] = transformed_roi_ids[i] - 1;
}
}
}
//reset view to only show the new ROIs
roiManager("show none");
roiManager("show all");
return transformed_roi_ids;
}
function get_delauney_triangle(xcandidate, ycandidate) {
distances = newArray(xvertex_array.length);
for (i = 0; i < distances.length; i++) {
distances[i] = distance(xcandidate, ycandidate, xvertex_array[i], yvertex_array[i]);
}
rank_distances = Array.rankPositions(distances);
//iteratively go through the closest vertices, if no delauney triangle is found, add another vertex and try again
for (i = 2; i < rank_distances.length; i++) {
x3 = xvertex_array[rank_distances[i]];
y3 = yvertex_array[rank_distances[i]];
for (j = 1; j < i; j++) {
x2 = xvertex_array[rank_distances[j]];
y2 = yvertex_array[rank_distances[j]];
for (k = 0; k < j; k++) {
x1 = xvertex_array[rank_distances[k]];
y1 = yvertex_array[rank_distances[k]];
//check if the three corners of the trianle are on the same line, if that is the case search for other candidate
check = (y1 - y2)*(x1 - x3) != (y1 - y3)*(x1 - x2);
//if it is confirmed that the three points are not one one line
if (check) {
barycentric_weights = barycentric(x1, y1, x2, y2, x3, y3, xcandidate, ycandidate);
w1 = barycentric_weights[0];
w2 = barycentric_weights[1];
w3 = barycentric_weights[2];
//if all barycentric weights are in 0-1 then the point is in the triangle
if (0 <= w1 && w1 <= 1 && 0 <= w2 && w2 <= 1 && 0 <= w3 && w3 <= 1) {
check = true;
} else {
check = false;
}
}
//now test if there is any other vertex in the circumcircle
if (check) {
circumcircle_dim = circumcircle(x1, y1, x2, y2, x3, y3);
for (l = 0; l < rank_distances.length; l++) {
if (l != i && l != j && l != k) {
circum_distance = distance(circumcircle_dim[0], circumcircle_dim[1], xvertex_array[rank_distances[l]], yvertex_array[rank_distances[l]]);
//if the distance from this candidate to the circumcenter is smaller than the circle radius, look for another candidate
if (circum_distance < circumcircle_dim[2]) {
check = false;
break;
}
}
}
}
//now check if those three conditions were fullfilled
if (check) {
break;
}
}
if (check) {
break;
}
}
if (check) {
break;
}
}
if (!check) {
i = rank_distances.length - 1;
}
return newArray(rank_distances[k], rank_distances[j], rank_distances[i]);
}
function barycentric(x1, y1, x2, y2, x3, y3, xpoint, ypoint) {
//find barycentric weights
//https://codeplea.com/triangular-interpolation
//a is a helper-value so the term does not become too long
a = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3);
w1 = ((y2 - y3) * (xpoint - x3) + (x3 - x2) * (ypoint - y3)) / a;
w2 = ((y3 - y1) * (xpoint - x3) + (x1 - x3) * (ypoint - y3)) / a;
w3 = 1 - w1 - w2;
return newArray(w1, w2, w3);
}
function distance(x1, y1, x2, y2) {
return Math.sqrt(Math.sqr(x1 - x2) + Math.sqr(y1 - y2));
}
function circumcircle(x1, y1, x2, y2, x3, y3) {
//from https://en.wikipedia.org/wiki/Circumcircle
bx = x2 - x1;
cx = x3 - x1;
by = y2 - y1;
cy = y3 - y1;
D = 2 * (bx * cy - by * cx);
xcircumcenter = 1 / D * (cy * (Math.sqr(bx) + Math.sqr(by)) - by * (Math.sqr(cx) + Math.sqr(cy)));
ycircumcenter = 1 / D * (bx * (Math.sqr(cx) + Math.sqr(cy)) - cx * (Math.sqr(bx) + Math.sqr(by)));
//circumradius is distance of circumcenter to any point
circumradius = Math.sqrt(Math.sqr(xcircumcenter) + Math.sqr(ycircumcenter));
xcircumcenter = xcircumcenter + x1;
ycircumcenter = ycircumcenter + y1;
return newArray(xcircumcenter, ycircumcenter, circumradius);
}
function value_is_in_array(array, value) {
for (i = 0; i < array.length; i++) {
if (value == array[i]) {
return true;
}
}
return false;
}
function move_into_bounding(xcandidate, ycandidate) {
//get distance of all corners
first_distance = distance(xcandidate, ycandidate, xbounding[0], ybounding[0]);
second_distance = distance(xcandidate, ycandidate, xbounding[1], ybounding[1]);
third_distance = distance(xcandidate, ycandidate, xbounding[2], ybounding[2]);
fourth_distance = distance(xcandidate, ycandidate, xbounding[3], ybounding[3]);
first_distance_comp = (first_distance <= second_distance) + (first_distance <= third_distance) + (first_distance <= fourth_distance);
second_distance_comp = (second_distance <= first_distance) + (second_distance <= third_distance) + (second_distance <= fourth_distance);
third_distance_comp = (third_distance <= first_distance) + (third_distance <= second_distance) + (third_distance <= fourth_distance);
fourth_distance_comp = (fourth_distance <= first_distance) + (fourth_distance <= second_distance) + (fourth_distance <= third_distance);
corner_distance_array = newArray();
if (first_distance_comp >=2) {
corner_distance_array = Array.concat(corner_distance_array, 0);
}
if (second_distance_comp >=2) {
corner_distance_array = Array.concat(corner_distance_array, 1);
}
if (third_distance_comp >=2) {
corner_distance_array = Array.concat(corner_distance_array, 2);
}
if (fourth_distance_comp >=2) {
corner_distance_array = Array.concat(corner_distance_array, 3);
}
a_x = xbounding[corner_distance_array[0]];
a_y = ybounding[corner_distance_array[0]];
b_x = xbounding[corner_distance_array[1]];
b_y = ybounding[corner_distance_array[1]];
a_b_x = b_x - a_x;
a_b_y = b_y - a_y;
a_c_x = xcandidate - a_x;
a_c_y = ycandidate - a_y;
ab_ac = scalar_product(a_b_x, a_b_y, a_c_x, a_c_y);
ab_ab = scalar_product(a_b_x, a_b_y, a_b_x, a_b_y);
a_d_x = a_b_x * ab_ac / ab_ab;
a_d_y = a_b_y * ab_ac / ab_ab;
return newArray(a_x + a_d_x, a_y + a_d_y);
}
function check_if_in_bounding(xcandidate, ycandidate) {
a_m_x = xcandidate - xbounding[0];
a_m_y = ycandidate - ybounding[0];
a_b_x = xbounding[1] - xbounding[0];
a_b_y = ybounding[1] - ybounding[0];
a_d_x = xbounding[3] - xbounding[0];
a_d_y = ybounding[3] - ybounding[0];
first_term = 0 < scalar_product(a_m_x, a_m_y, a_b_x, a_b_y);
second_term = scalar_product(a_m_x, a_m_y, a_b_x, a_b_y) < scalar_product(a_b_x, a_b_y, a_b_x, a_b_y);
third_term = 0 < scalar_product(a_m_x, a_m_y, a_d_x, a_d_y);
fourth_term = scalar_product(a_m_x, a_m_y, a_d_x, a_d_y) < scalar_product(a_d_x, a_d_y, a_d_x, a_d_y);
return (first_term && second_term && third_term && fourth_term);
}
function scalar_product(x1, y1, x2, y2) {
return x1 * x2 + y1 * y2;
}
//saving the results
//make this a seperate function that runs on all images after all the adjustment is done
function saving(image_number, local_image_path, local_image_name_without_extension, channelchoices, channeloptions_array, selectedslice, home_directory, save_regions) {
//open the saved scaled rois of the respective image
setBatchMode(true);
save_roi_ids_start = roiManager("count");
if (File.exists(temp + local_image_name_without_extension + "roi.zip")) {
//only do this if ROIs were actually saved
roiManager("open", temp + local_image_name_without_extension + "roi.zip");
save_roi_ids_end = roiManager("count") -1;
//get the array of selected channels to go through
only_channelchoices = Array.deleteValue(channelchoices, "do not use");
if (combined_results == "individual" || combined_results == "both") {
for (i = 1; i <= channelchoices.length; i++) {
//open the channels specified for analysis (so everything in channeloptions, which does not include "do not use", other than the background image)
for (j = 0; j < channeloptions_array.length; j++) {
if (channelchoices[i-1] == channeloptions_array[j]) {
if ((channelchoices[i-1] != control_channel && !map_to_control_channel) || only_channelchoices.length <= 1 || map_to_control_channel) {
if (!is_czi[image_number]) {
run("Bio-Formats Importer", "open=[" + local_image_path + "] color_mode=Default specify_range view=Hyperstack stack_order=XYCZT c_begin=" + i + " c_end=" + i + " c_step=1 z_begin=" + selectedslice + " z_end=" + selectedslice + " z_step=1");
} else {
run("Bio-Formats Importer", "open=[" + local_image_path + "] color_mode=Default specify_range view=Hyperstack stack_order=XYCZT series_" + selectedslice + " c_begin_" + selectedslice + "=" + i + " c_end_" + selectedslice + "=" + i + " c_step_" + selectedslice + "=1");
}
rename(channeloptions_array[j]);
//go through all the individual rois of the atlas that are not the bounding box and save them as individual images on each of the selected channels
for (k = save_roi_ids_start; k <= save_roi_ids_end; k++) {
roiManager("select", k);
roi_name = Roi.getName;
if (value_is_in_array(save_regions, roi_name)) {
save(output_path + local_image_name_without_extension + "_" + channeloptions[j] + "_" + getInfo("selection.name") + ".tif");
}
}
close(channeloptions_array[j]);
}
}
}
}
}
roi_closing_array = newArray();
for (i = save_roi_ids_start; i <= save_roi_ids_end; i++) {
roiManager("select", i);
roi_closing_array = Array.concat(roi_closing_array, i);
}
if (combined_results == "combined" || combined_results == "both") {//opens the whole image and saves all ROIs combined
if (!is_czi[image_number]) {
run("Bio-Formats Importer", "open=[" + local_image_path + "] color_mode=Default specify_range view=Hyperstack stack_order=XYCZT z_begin=" + selectedslice + " z_end=" + selectedslice + " z_step=1");
} else {
run("Bio-Formats Importer", "open=[" + local_image_path + "] color_mode=Default specify_range view=Hyperstack stack_order=XYCZT series_" + selectedslice);
}
rename("current_image");
roi_saving_array = newArray();
for (i = save_roi_ids_start; i <= save_roi_ids_end; i++) {
roiManager("select", i);
roi_name = Roi.getName;
if (value_is_in_array(save_regions, roi_name)) {
roi_saving_array = Array.concat(roi_saving_array, i);
}
}
if (roi_saving_array.length > 0) {
roiManager("select", roi_saving_array);
roiManager("show all without labels");
save(combined_output_path + local_image_name_without_extension + "_combined.tif");
roiManager("save selected", combined_output_path + local_image_name_without_extension + "_combined_roi.zip");
}
close("current_image");
}
roiManager("select", roi_closing_array);
roiManager("delete");
}
setBatchMode(false);
}
//functions to create ROIs:
function createROIs(atlas_name, mapping_index_path, searchTerm) {
Table.open(utilities_directory + text_file);
for (i = 0; i < searchTerm.length; i++) {
searchTerm[i] = trim(searchTerm[i]); //deal with whitespace in the brain region submission
}
open(atlas_path);
title = getTitle();
getDimensions(width, height, channels, slices, frames);
//creates structure for the ROIs to be saved in, if it is run for the first time on this atlas
if (!File.exists(atlas_directory)) {
File.makeDirectory(atlas_directory);
for (i = 1; i <= slices; i++) {
File.makeDirectory(atlas_directory + i + "/");
}
}
print("Creating ROIs");
setBatchMode(true);
//open record of which ROIs have been saved already
already_saved = newArray();
if (File.exists(mapping_index_path)) {
Table.open(mapping_index_path);
//go through the table and get the regions corresponding to this specific atlas
for (i = 0; i < Table.size; i++) {
if (trim(Table.getString("Atlas", i)) == trim(atlas_name)) {
already_saved = Array.concat(already_saved, Table.getString("Region", i));
}
}
} else {
Table.create("mapping_index.csv");
}
//delete values from the searchTerm array, if they have been saved as ROIs already
for (i = 0; i < already_saved.length; i++) {
searchTerm = Array.deleteValue(searchTerm, trim(already_saved[i]));
}
for (i = 0; i < searchTerm.length; i++) {
print("Working on: " + searchTerm[i]);
roiManager("reset");
run("Select None");
//get the ID of the searchterm
search_id = "none";
selectWindow(text_file);
nrows = Table.size;
for (j = 0; j < nrows; j++) {
if (searchTerm[i] == Table.getString("acronym", j)) {
search_id = Table.getString("id", j);
}
}
children = getRecursiveChildren(search_id);
rows = getTableRowFromSearch(children);
if (rows.length > 0) { //only do the stuff, when region was found
thresholdfromtable(rows, title, search_id);
savingRoi(title, atlas_directory, search_id, searchTerm[i]);
close(search_id);
//add, which region has just been mapped to the register
selectWindow("mapping_index.csv");
rownumber_mapping_index_table = Table.size;
Table.set("Atlas", rownumber_mapping_index_table, atlas_name);
Table.set("Region", rownumber_mapping_index_table, searchTerm[i]);
} else {
print(searchTerm[i] + " was not found");
}
}
//save, which ROIs have been successfully mapped
selectWindow("mapping_index.csv");
saveAs("results", mapping_index_path);
setBatchMode(false);
close(title);
close(text_file);
File.setDefaultDir(default_directory);//restore default directory
close("mapping_index.csv");
print("ROIs created");
}
function getRecursiveChildren(parents) {
//search the table to find children of the searchterm, add them to the list of all the regions to combine
print("Getting sub-terms");
parents = Array.concat(newArray(), parents);
selectWindow(text_file);
nrows = Table.size;
children = newArray();
for (i = 0; i < parents.length; i++) {
for (j = 0; j < nrows; j++) {
if (parents[i] == Table.getString("parent", j)) {
children = Array.concat(children, Table.getString("id", j));
}
}
}
if (children.length > 0) {
children = getRecursiveChildren(children);//calls itself when it finds children
}
return Array.concat(parents, children);
}
function getTableRowFromSearch(searchTerm) {
//retrieve the rows of all entries in searchterm
//not really necessary anymore, but was useful for rgb coded images
print("Getting rows from table");
selectWindow(text_file);
searchTerm = Array.concat(newArray(), searchTerm);//to make sure that searchTerm is in array-form
rows = newArray();
for (i = 0; i < Table.size; i++) {
for (j = 0; j < searchTerm.length; j++) {
if (searchTerm[j] == Table.getString("id", i)) {
rows = Array.concat(rows, i);//get the row of that entry of searchterm
}
}
}
return rows;
}
function thresholdfromtable(rows, image, searchTerm) {
rows = Array.concat(newArray(), rows); //if rows is a single number, still make it an array
selectWindow(text_file);
//every row is one child-term
for (i = 0; i < rows.length; i++) {
selectWindow(image);
run("Duplicate...", "title=threshold" + i + " duplicate"); //duplicate the stack to threshold for one region
print("Processing subregions " + (i + 1) + " out of " + rows.length + ".");
threshold = Table.get("id", rows[i]);
setThreshold(threshold, threshold);
run("Convert to Mask", "background=Light");
if (i > 0) {
imageCalculator("OR stack", "threshold0", "threshold" + i); //calculate all subregions/children together
close("threshold" + i);
}
}
selectWindow("threshold0");
rename(searchTerm);
}
function savingRoi(image, atlas_directory, searchID, searchTerm) {
print("Saving ROIs.");
selectWindow(image);
run("Duplicate...", "title=bw duplicate");
run("8-bit");
selectWindow(searchID);
getDimensions(width, height, channels, slices, frames);
for (i = 1; i <= nSlices; i++) {
selectWindow(searchID);
setSlice(i);
setThreshold(1, 255);
run("Create Selection");
if(selectionType() != -1) {
roiManager("add");
roiManager("select", roiManager("count") - 1);
roiManager("rename", searchTerm);
}
selectWindow("bw");
setSlice(i);
//make bounding box to register where the brain was
getStatistics(area, mean, min, max, std, histogram);
//this is the background
setThreshold(0,0);
run("Create Selection");
//select everything but the background
run("Make Inverse");
//only run the following, if something was actually selected
if(selectionType() != -1) {
run("To Bounding Box");
roiManager("add");
roiManager("select", roiManager("count") - 1);
roiManager("rename", "atlas_bounding_box");
}
if (roiManager("count") > 1) {
//only save ROIs if both a bounding box and a region was created
roiManager("select", newArray(roiManager("count")-1, roiManager("count")-2));
roiManager("save selected", atlas_directory + i + "/" + searchTerm + ".zip");
//print(atlas_directory + i + "/" + searchTerm + ".zip");
roiManager("select", newArray(roiManager("count")-1, roiManager("count")-2));
roiManager("delete");
}
else {//if only the bounding box was created, delete it again
if (roiManager("count") == 1) {
roiManager("delete");
}
}
run("Select None");
}
close("bw");
}