/** * Make montage stacks from MetaMorph 7 High Throughput Screening. * * MetaMorph 7's Screen Acquisition (screenacq) does not natively * support time and Z acquisitions from it's UI or journal functions. * Therefore it's easier to assemble the experiment with FIJI * including stitch TIF files together with overlap. It is * assumed the grid of all sites in a well are imaged, i.e. supports * Metamorph's "Well Selection" but not "Site Selection". * * Developer Notes: * - loci_tools does not index Metamorph's HTD TIF files correctly * (it reads all image leaves as index 1). The stitching plugin * uses loci_tools. Thus the files cannot be used directly and the * HTD file needs to be need to be renamed so that loci does not * recognize it. * - Written against Version 1.0 of HTSInfoFile * * Written by Pariksheet Nanda Oct, 2012 * License: Public Domain */ // Global variables (for use by functions) var datadir; // Path to HTS files containing HTD and TIF files. var htd_string; // HTD file fully read in as a string. macro "Unused Tool-2 - " {} // empty slot macro "Stitch Raw Data Action Tool - C000 R00ffL505fLa0afL05f5L0afa" { // Boilerplate setBatchMode(true); saveSettings(); datadir = getDirectory("Choose your raw 'Screen Acquisition' data folder"); Dialog.create("Hyperstack"); Dialog.addNumber("Z slices:", 1); Dialog.show(); slices = Dialog.getNumber(); outputdir = datadir + "montages/"; finaldir = datadir + "hyperstacks/"; if (! File.exists(outputdir)) File.makeDirectory(outputdir); if (! File.exists(finaldir)) File.makeDirectory(finaldir); if (unhide_htd_files(datadir)) exit("Exiting... Restored .HTD file naming from previous failed run."); htd_files = get_files(datadir, "HTD"); // make sure number of z slices is sane, otherwise the hyperstack // convertor will fail later. extra_frames = htd_files.length % slices; if (extra_frames != 0) { Dialog.create("Warning"); Dialog.addMessage("You have entered " + slices + " Z slices, but " + "there are " + htd_files.length + " timepoints. " + "This does not divide evenly."); Dialog.addMessage("Click OK to ignore the " + extra_frames + " extra time points " + "(if, say, you ended the acquisition prematurely) " + "or cancel, to restart the macro and enter the " + "correct number of z slices."); Dialog.show(); } // Hide the HTD file from Bioformats since it does not read in the // well metadata and repeatedly stitches the first frame only if (hide_htd_files(datadir) == 0) exit("Exiting... No HTD files found."); // Iterate over all HTD acquisition primitives to create stack // of experiment wells htd_string = File.openAsString(datadir + htd_files[0] + ".bak"); wells = get_well_coordinates(); x_sites = parseInt(get_htd_value("XSites")); y_sites = parseInt(get_htd_value("YSites")); total_sites = x_sites * y_sites; channels = get_wavelengths(); for (well_index = 0; well_index < wells.length; well_index++) { well = wells[well_index]; file_prefix = ""; // save individual files for (wavelength = 0; wavelength < channels.length; wavelength++) { for (acq = 0; acq < htd_files.length; acq++) { show_progress(1 + acq + wavelength * htd_files.length + well_index * channels.length * htd_files.length, wells.length * channels.length * htd_files.length); base = prefix(htd_files[acq]); file_prefix = base + "_" + well; if (channels.length == 1) channel_prefix = ""; else channel_prefix = "_w" + wavelength+1; tif_file_name = file_prefix + "_s{i}" + channel_prefix + ".TIF"; // Plugins > Stitching > Grid/Collection stitching run("Grid/Collection stitching", "type=[Grid: row-by-row] " + "order=[Right & Down ] " + "grid_size_x=&x_sites grid_size_y=&y_sites " + "tile_overlap=20 " + "first_file_index_i=1 " + "directory=&datadir " + "file_names=[&tif_file_name] " + "fusion_method=[Linear Blending] " + "regression_threshold=0.30 " + "max/avg_displacement_threshold=2.50 " + "absolute_displacement_threshold=3.50 " + "subpixel_accuracy " + "computation_parameters=[Save computation time " + "(but use more RAM)] " + "image_output=[Fuse and display]"); save_file_name = file_prefix + "_w" + wavelength+1; saveAs("tif", outputdir + save_file_name); IJ.log("Saved montage " + save_file_name); close(); } } // assemble hyperstack tif_file_name = outputdir + file_prefix + "_w" + 1 + ".tif"; // File > Import > Image Sequence run("Image Sequence...", "open=[&tif_file_name] " + "starting=1 " + "increment=1 " + "scale=100 " + "file=[&well] " + "or=[] " + "sort"); // strip extra frames if experiment ended prematurely frames = floor(htd_files.length / slices); total_channels = channels.length; extra_frames = htd_files.length % slices; if (extra_frames != 0) { IJ.log("Deleting last " + extra_frames * total_channels + " frames from stack of " + nSlices + "..."); Stack.setSlice(htd_files.length * total_channels); for (i = 0; i < extra_frames * total_channels; i++) run("Delete Slice"); IJ.log("...new stack size is " + nSlices); } // convert to hyperstack // Running "Stack to Hyperstack..." with batch mode on in // ImageJ 1.47c goves the following exception when turning off // batch mode, so as a workaround we turn off batch mode. // // java.lang.NullPointerException // at ij.CompositeImage.setupLuts(CompositeImage.java:139) // at ij.CompositeImage.updateImage(CompositeImage.java:201) // at ij.CompositeImage.getImage(CompositeImage.java:97) // at ij.ImagePlus.show(ImagePlus.java:364) // at ij.ImagePlus.show(ImagePlus.java:347) // at ij.macro.Functions.displayBatchModeImage(Functions.java:2605) // at ij.macro.Functions.setBatchMode(Functions.java:2588) // at ij.macro.Functions.doFunction(Functions.java:131) // at ij.macro.Interpreter.doStatement(Interpreter.java:216) // at ij.macro.Interpreter.doBlock(Interpreter.java:539) // at ij.macro.Interpreter.runMacro(Interpreter.java:131) // at ij.macro.MacroRunner.run(MacroRunner.java:143) // at java.lang.Thread.run(Thread.java:662) setBatchMode(false); run("Stack to HyperStack...", "order=xyczt(default) " + "channels=&total_channels " + "slices=&slices " + "frames=&frames " + "display=Color"); // using slices=1 above since one can always reslice the stack // to set Z planes, i.e. converting to a stack and then back // to hyperstack. // Image > Hyperstacks > Stack to Hyperstack setBatchMode(true); base = prefix(htd_files[0]); file_prefix = base + "_"; save_file_name = file_prefix + well; saveAs("tif", finaldir + save_file_name); close(); IJ.log("Saved hyperstack " + well); } IJ.log("Restoring .HTD files..."); unhide_htd_files(datadir); IJ.log("...restored"); // Boilerplate setBatchMode(false); restoreSettings(); IJ.log("Finished HTS Grid Stitching"); } //macro "Review Stitched Wells Action Tool - C111 O0966 O0066 O9966 O9066" {} // Easy to do this from ImageJ, but saves a few mouse clicks macro "Hyperstack Z Projection Action Tool - C000 P62f2a71762 Pc5f5aa1a47 Pc8f8ad1d4a" { setBatchMode(true); Stack.getDimensions(width, height, channels, slices, frames); if (slices == 1) exit("Image only has 1 Z slice"); // Reduce depth of hyperstack to a single MIP for each frame run("Z Project...", "start=1 " + "stop=&slices " + "projection=[Max Intensity] " + "all"); setBatchMode(false); } /** * Extract basename without extension. * * @param path files are located * @return string of file list */ function prefix(path) { file_split = split(File.getName(path), '.'); base_split = Array.trim(file_split, file_split.length - 1); base = ""; for (i = 0; i < base_split.length; i++) base = base + base_split[i]; return base; } /** * Find files with a specific extension. * * @param directory path where files are located * @param extension desired files * @return array file list */ function get_files(directory, extension) { all_file_list = getFileList(directory); match_file_list = newArray(); for (i = 0; i < all_file_list.length; i++) { file_split = split(File.getName(all_file_list[i]), '.'); file_extension = Array.slice(file_split, file_split.length-1, file_split.length); if (matches(file_extension[0], extension)) { match_file = Array.slice(all_file_list, i, i+1); match_file_list = Array.concat(match_file_list, match_file); } } return match_file_list; } /** * Read HTD metadata using key names. * * Lines in the HTD file format consist of "key_name", value * * @param key_name key contained in first column of HTD file * @return corresponding value read from HTD file */ function get_htd_value(key_name) { key_index = indexOf(htd_string, key_name); if (key_index == -1) return "Error: key '" + key_name + "' not found in HTD file"; line_end_index = indexOf(htd_string, '\n', key_index); KEY_VALUE_SEPARATOR = '", '; value_index = key_index + lengthOf(key_name) + lengthOf(KEY_VALUE_SEPARATOR); value = substring(htd_string, value_index, line_end_index); return value; } /** * Well coordinates of images from reading HTD metadata. * * @return array of coordinates like A01,A02,B03,... */ function get_well_coordinates() { x_wells = parseInt(get_htd_value("XWells")); y_wells = parseInt(get_htd_value("YWells")); wells = newArray(); for (i = 1; i <= y_wells; i++) { row = get_htd_value("WellsSelection" + i); row = replace(row, " ", ""); // strip spaces row = split(row, ","); for (j = 1; j <= x_wells; j++) { // Populate array with strings like A01, A02, etc if (row[j-1] == "TRUE") { coordinates = fromCharCode(64 + i) + IJ.pad(j,2); wells = Array.concat(wells, coordinates); } } } return wells; } /** * Wavelengths of images from reading HTD metadata. * * @return array of wavelengths like Confocal 488,Cam Trans... */ function get_wavelengths() { wavelengths = newArray(); total_wavelengths = parseInt(get_htd_value("NWavelengths")); for (i = 1; i <= total_wavelengths; i++) { wavelength = get_htd_value("WaveName" + i); wavelengths = Array.concat(wavelengths, wavelength); } return wavelengths; } /** * Strips .bak from all directory .HTD files. * * @return 1 if any file renamed, otherwise 0 */ function unhide_htd_files(directory) { // this can be fail if there is a non-HTD bak file because it will // only rename HTD bak files bak_files = get_files(directory, "bak"); for (bak = 0; bak < bak_files.length; bak++) { ret = File.rename(directory + bak_files[bak], directory + replace(bak_files[bak], ".HTD.bak", ".HTD")); } if (bak > 0) return 1; return 0; } /** * Adds .bak to all directory .HTD files. * * @return 1 if any file renamed, otherwise 0 */ function hide_htd_files(directory) { htd_files = get_files(directory, "HTD"); for (acq = 0; acq < htd_files.length; acq++) { ret = File.rename(directory + htd_files[acq], directory + htd_files[acq] + ".bak"); } if (acq > 0) return 1; return 0; } /** * Show progress string. * * @param current_value index data point * @param total total data points * @return string indicating progress, like ****--------------- */ function _get_bar(current_value, total) { n = 20; empty_bar = "--------------------"; complete_bar = "********************"; index = round(n * (current_value/total)); if (index < 1) index = 1; if (index > n-1) index = n-1; return substring(complete_bar, 0, index) + substring(empty_bar, index+1, n); } /** * Show progress in window. * * Adapted from http://imagej.nih.gov/ij/macros/ProgressBar.txt * * @param current_value index data point * @param total total data points */ function show_progress(current_value, total) { title = "[Progress]"; // create progress window for first run windows = getList("window.titles"); found_progress_window = 0; clean_title = replace(title, "\\[", ""); clean_title = replace(clean_title, "\\]", ""); for (i = 0; i < windows.length; i++) { if (matches(windows[i], clean_title)) found_progress_window = 1; } if (found_progress_window == 0) run("Text Window...", "name="+ title +" width=27 height=3 monospaced"); // show progress if (current_value >= total) { print(title, "\\Close"); } else { print(title, "\\Update:" + current_value + "/" + total + " (" + (current_value / total) * 100 + "%)\n" + _get_bar(current_value, total)); } }