Skip to content

Instantly share code, notes, and snippets.

@omsai
Last active October 11, 2015 09:18
Show Gist options
  • Save omsai/3836862 to your computer and use it in GitHub Desktop.
Save omsai/3836862 to your computer and use it in GitHub Desktop.
ImageJ macro - import MetaMorph 7 Screen Acquisition data
/**
* 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 <omsai@member.fsf.org> Oct, 2012
* License: Public Domain
* Version: 0.7a
*/
// 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');
if (unhide_htd_files(datadir))
exit('Exiting... Restored .HTD file naming from previous failed run.');
if (is_filenaming_invalid(datadir)) {
ok_to_rename =
getBoolean('Extra "." characters found in filenames which will ' +
'confuse the Stitching / Bioformats plugins.\n' +
'Ok to replace them with "_" characters?');
if (ok_to_rename == 0)
exit('Exiting... filenames contain invalid characters, ' +
'which will crash the macro');
else {
IJ.log('Renaming files...');
IJ.log('... ' + fix_filenaming(datadir) + ' files renamed.');
}
}
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);
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();
}
}
if (htd_files.length > 1 || channels.length > 1) {
// 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);
}
if (nSlices > 1) {
// 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);
IJ.log('Saved hyperstack ' + well);
}
close();
}
}
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 directory path where files are located
* @return string of file list
*/
function prefix(directory) {
file_name = File.getName(directory);
extension_index = lastIndexOf(file_name, '.');
base_name = substring(file_name, 0, extension_index);
return base_name;
}
/**
* 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;
}
/**
* Check for filename characters that upset the Stitching plugin.
*
* @param directory path where files are located
* @return 1 if any filename invalid, otherwise 0
*/
function is_filenaming_invalid(directory) {
htd_files = get_files(directory, 'HTD');
for (i = 0; i < htd_files.length; i++)
if (indexOf(prefix(htd_files[i]), '.') != -1)
return 1;
return 0;
}
/**
* Replace invalid TIF filename characters with underscores in directory.
*
* @param directory path where files are located
* @return number of files renamed
*/
function fix_filenaming(directory) {
types_to_rename = newArray('HTD', 'TIF');
files_renamed = 0;
for (t = 0; t < types_to_rename.length; t++) {
extension = types_to_rename[t];
files = get_files(directory, extension);
for (i = 0; i < files.length; i++) {
file = files[i];
base = prefix(file);
if (indexOf(base, '.') != -1) {
new_name = replace(base, '.', '_') + '.' + extension;
File.rename(directory + File.separator + file,
directory + File.separator + new_name);
files_renamed++;
}
}
}
return files_renamed;
}
/**
* 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));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment