Skip to content

Instantly share code, notes, and snippets.

@Yu-AnChen
Forked from jmuhlich/imagej_basic_ashlar.py
Last active March 19, 2020 18:28
Show Gist options
  • Save Yu-AnChen/1d3d1eef6b4c40e89f9de62c3b120ea2 to your computer and use it in GitHub Desktop.
Save Yu-AnChen/1d3d1eef6b4c40e89f9de62c3b120ea2 to your computer and use it in GitHub Desktop.
ImageJ BaSiC shading correction script for use with Ashlar
# @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()
# @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);
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 flat-field only (ignore 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()
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()
print "Done!"
main()
# @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()
# @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
# @Integer(label="Maximum number of images to use for estimation", value=300) max_num_images
# 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
import random
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()
# Limit number of images used for illumination profile estimation
# We are re-assigning the global variable max_num_images
global max_num_images
if max_num_images > num_images:
max_num_images = num_images
random.seed(0)
image_list = random.sample(list(range(num_images)), max_num_images)
image_list.sort()
# 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, max_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)
# 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 image_list:
# Only open a subset of series
options.setSeriesOn(i, True)
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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment