Last active
August 4, 2021 00:31
-
-
Save jmuhlich/96417108a2cf7d95ba9258a96904ba5a to your computer and use it in GitHub Desktop.
ImageJ BaSiC shading correction script for use with Ashlar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# @File(label="Select a slide to process") filename | |
# @File(label="Select the output location", style="directory") output_dir | |
# @String(label="Experiment name (base name for output files)") experiment_name | |
# @Float(label="Flat field smoothing parameter (0 for automatic)", value=0.1) lambda_flat | |
# @Float(label="Dark field smoothing parameter (0 for automatic)", value=0.01) lambda_dark | |
# Takes a slide (or other multi-series BioFormats-compatible file set) and | |
# generates flat- and dark-field correction profile images with BaSiC. The | |
# output format is two multi-series TIFF files (one for flat and one for dark) | |
# which is the input format used by Ashlar. | |
# Invocation for running from the commandline: | |
# | |
# ImageJ --ij2 --headless --run imagej_basic_ashlar.py "filename='input.ext',output_dir='output',experiment_name='my_experiment'" | |
import sys | |
from ij import IJ, WindowManager | |
from ij.macro import Interpreter | |
from loci.plugins import BF | |
from loci.plugins.in import ImporterOptions | |
from loci.formats import ImageReader | |
import BaSiC_ as Basic | |
import pdb | |
def main(): | |
Interpreter.batchMode = True | |
if (lambda_flat == 0) ^ (lambda_dark == 0): | |
print ("ERROR: Both of lambda_flat and lambda_dark must be zero," | |
" or both non-zero.") | |
return | |
lambda_estimate = "Automatic" if lambda_flat == 0 else "Manual" | |
print "Loading images..." | |
# Use BioFormats reader directly to determine dataset dimensions without | |
# reading every single image. The series count (num_images) is the one value | |
# we can't easily get any other way, but we might as well grab the others | |
# while we have the reader available. | |
bfreader = ImageReader() | |
bfreader.id = str(filename) | |
num_images = bfreader.seriesCount | |
num_channels = bfreader.sizeC | |
width = bfreader.sizeX | |
height = bfreader.sizeY | |
bfreader.close() | |
# The internal initialization of the BaSiC code fails when we invoke it via | |
# scripting, unless we explicitly set a the private 'noOfSlices' field. | |
# Since it's private, we need to use Java reflection to access it. | |
Basic_noOfSlices = Basic.getDeclaredField('noOfSlices') | |
Basic_noOfSlices.setAccessible(True) | |
basic = Basic() | |
Basic_noOfSlices.setInt(basic, num_images) | |
# Pre-allocate the output profile images, since we have all the dimensions. | |
ff_image = IJ.createImage("Flat-field", width, height, num_channels, 32); | |
df_image = IJ.createImage("Dark-field", width, height, num_channels, 32); | |
print("\n\n") | |
# BaSiC works on one channel at a time, so we only read the images from one | |
# channel at a time to limit memory usage. | |
for channel in range(num_channels): | |
print "Processing channel %d/%d..." % (channel + 1, num_channels) | |
print "===========================" | |
options = ImporterOptions() | |
options.id = str(filename) | |
options.setOpenAllSeries(True) | |
# concatenate=True gives us a single stack rather than a list of | |
# separate images. | |
options.setConcatenate(True) | |
# Limit the reader to the channel we're currently working on. This loop | |
# is mainly why we need to know num_images before opening anything. | |
for i in range(num_images): | |
options.setCBegin(i, channel) | |
options.setCEnd(i, channel) | |
# openImagePlus returns a list of images, but we expect just one (a | |
# stack). | |
input_image = BF.openImagePlus(options)[0] | |
# BaSiC seems to require the input image is actually the ImageJ | |
# "current" image, otherwise it prints an error and aborts. | |
WindowManager.setTempCurrentImage(input_image) | |
basic.exec( | |
input_image, None, None, | |
"Estimate shading profiles", "Estimate both flat-field and dark-field", | |
lambda_estimate, lambda_flat, lambda_dark, | |
"Ignore", "Compute shading only" | |
) | |
input_image.close() | |
# Copy the pixels from the BaSiC-generated profile images to the | |
# corresponding channel of our output images. | |
ff_channel = WindowManager.getImage("Flat-field:%s" % input_image.title) | |
ff_image.slice = channel + 1 | |
ff_image.getProcessor().insert(ff_channel.getProcessor(), 0, 0) | |
ff_channel.close() | |
df_channel = WindowManager.getImage("Dark-field:%s" % input_image.title) | |
df_image.slice = channel + 1 | |
df_image.getProcessor().insert(df_channel.getProcessor(), 0, 0) | |
df_channel.close() | |
print("\n\n") | |
template = '%s/%s-%%s.tif' % (output_dir, experiment_name) | |
ff_filename = template % 'ffp' | |
IJ.saveAsTiff(ff_image, ff_filename) | |
ff_image.close() | |
df_filename = template % 'dfp' | |
IJ.saveAsTiff(df_image, df_filename) | |
df_image.close() | |
print "Done!" | |
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# @String(label="Enter a filename pattern describing the TIFFs to process") pattern | |
# @File(label="Select the output location", style="directory") output_dir | |
# @String(label="Experiment name (base name for output files)") experiment_name | |
# @Float(label="Flat field smoothing parameter (0 for automatic)", value=0.1) lambda_flat | |
# @Float(label="Dark field smoothing parameter (0 for automatic)", value=0.01) lambda_dark | |
import sys | |
import os | |
import re | |
import collections | |
from ij import IJ, WindowManager, ImagePlus, ImageStack | |
from ij.io import Opener | |
from ij.macro import Interpreter | |
import BaSiC_ as Basic | |
def enumerate_filenames(pattern): | |
"""Return filenames matching pattern (a str.format pattern containing | |
{channel} and {tile} placeholders). | |
Returns a list of lists, where the top level is indexed by channel number | |
and the bottom level is sorted filenames for that channel. | |
""" | |
(base, pattern) = os.path.split(pattern) | |
regex = re.sub(r'{([^:}]+)(?:[^}]*)}', r'(?P<\1>.*?)', | |
pattern.replace('.', '\.')) | |
tiles = set() | |
channels = set() | |
num_images = 0 | |
# Dict[channel: int, List[filename: str]] | |
filenames = collections.defaultdict(list) | |
for f in os.listdir(base): | |
match = re.match(regex, f) | |
if match: | |
gd = match.groupdict() | |
tile = int(gd['tile']) | |
channel = int(gd['channel']) | |
tiles.add(tile) | |
channels.add(channel) | |
filenames[channel].append(os.path.join(base, f)) | |
num_images += 1 | |
if len(tiles) * len(channels) != num_images: | |
raise Exception("Missing some image files") | |
filenames = [ | |
sorted(filenames[channel]) | |
for channel in sorted(filenames.keys()) | |
] | |
return filenames | |
def main(): | |
Interpreter.batchMode = True | |
if (lambda_flat == 0) ^ (lambda_dark == 0): | |
print ("ERROR: Both of lambda_flat and lambda_dark must be zero," | |
" or both non-zero.") | |
return | |
lambda_estimate = "Automatic" if lambda_flat == 0 else "Manual" | |
#import pdb; pdb.set_trace() | |
print "Loading images..." | |
filenames = enumerate_filenames(pattern) | |
num_channels = len(filenames) | |
num_images = len(filenames[0]) | |
image = Opener().openImage(filenames[0][0]) | |
width = image.width | |
height = image.height | |
image.close() | |
# The internal initialization of the BaSiC code fails when we invoke it via | |
# scripting, unless we explicitly set a the private 'noOfSlices' field. | |
# Since it's private, we need to use Java reflection to access it. | |
Basic_noOfSlices = Basic.getDeclaredField('noOfSlices') | |
Basic_noOfSlices.setAccessible(True) | |
basic = Basic() | |
Basic_noOfSlices.setInt(basic, num_images) | |
# Pre-allocate the output profile images, since we have all the dimensions. | |
ff_image = IJ.createImage("Flat-field", width, height, num_channels, 32); | |
df_image = IJ.createImage("Dark-field", width, height, num_channels, 32); | |
print("\n\n") | |
# BaSiC works on one channel at a time, so we only read the images from one | |
# channel at a time to limit memory usage. | |
for channel in range(num_channels): | |
print "Processing channel %d/%d..." % (channel + 1, num_channels) | |
print "===========================" | |
stack = ImageStack(width, height, num_images) | |
opener = Opener() | |
for i, filename in enumerate(filenames[channel]): | |
print "Loading image %d/%d" % (i + 1, num_images) | |
image = opener.openImage(filename) | |
stack.setProcessor(image.getProcessor(), i + 1) | |
input_image = ImagePlus("input", stack) | |
# BaSiC seems to require the input image is actually the ImageJ | |
# "current" image, otherwise it prints an error and aborts. | |
WindowManager.setTempCurrentImage(input_image) | |
basic.exec( | |
input_image, None, None, | |
"Estimate shading profiles", "Estimate both flat-field and dark-field", | |
lambda_estimate, lambda_flat, lambda_dark, | |
"Ignore", "Compute shading only" | |
) | |
input_image.close() | |
# Copy the pixels from the BaSiC-generated profile images to the | |
# corresponding channel of our output images. | |
ff_channel = WindowManager.getImage("Flat-field:%s" % input_image.title) | |
ff_image.slice = channel + 1 | |
ff_image.getProcessor().insert(ff_channel.getProcessor(), 0, 0) | |
ff_channel.close() | |
df_channel = WindowManager.getImage("Dark-field:%s" % input_image.title) | |
df_image.slice = channel + 1 | |
df_image.getProcessor().insert(df_channel.getProcessor(), 0, 0) | |
df_channel.close() | |
print("\n\n") | |
template = '%s/%s-%%s.tif' % (output_dir, experiment_name) | |
ff_filename = template % 'ffp' | |
IJ.saveAsTiff(ff_image, ff_filename) | |
ff_image.close() | |
df_filename = template % 'dfp' | |
IJ.saveAsTiff(df_image, df_filename) | |
df_image.close() | |
print "Done!" | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
When running on less powerful computers ImageJ was leaking memory on each iteration. Adding
WindowManager.setTempCurrentImage(None)
afterimp.close()
(line 80) solved it for me.