Mercurial > repos > perssond > basic_illumination
changeset 1:db20f09300bd draft
planemo upload for repository https://github.com/labsyspharm/basic-illumination commit d62977a02ee6e0e5100479d6d7b19eb4a8cf9761
author | goeckslab |
---|---|
date | Thu, 01 Sep 2022 22:46:21 +0000 |
parents | fd8dfd64f25e |
children | acc6f509968c |
files | basic_illumination.xml imagej_basic_ashlar.py imagej_basic_ashlar_filepattern.py macros.xml test-data/test.tiff |
diffstat | 5 files changed, 49 insertions(+), 346 deletions(-) [+] |
line wrap: on
line diff
--- a/basic_illumination.xml Fri Mar 12 00:13:46 2021 +0000 +++ b/basic_illumination.xml Thu Sep 01 22:46:21 2022 +0000 @@ -1,16 +1,16 @@ -<tool id="basic_illumination" name="BaSiC Illumination" version="@VERSION@.3" profile="17.09"> +<tool id="basic_illumination" name="BaSiC Illumination" version="@TOOL_VERSION@+galaxy@VERSION_SUFFIX@" profile="@PROFILE@"> <description>ImageJ BaSiC shading correction for use with Ashlar</description> <macros> <import>macros.xml</import> </macros> <expand macro="requirements"/> - @VERSION_CMD@ + <expand macro="version_cmd"/> <command detect_errors="exit_code"><![CDATA[ - ln -s $in_files "${in_files.name}" && + ln -s '$in_files' '${in_files.name}' && - #set $outname = str($in_files.name).replace('.ome.tiff','') + #set $outname = str('$in_files.name').replace('.ome.tiff','') @CMD_BEGIN@ @@ -26,25 +26,30 @@ <data format="tiff" name="output_dfp" label="${tool.name} on ${on_string}: DFP" from_work_dir="output-dfp.tif"/> <data format="tiff" name="output_ffp" label="${tool.name} on ${on_string}: FFP" from_work_dir="output-ffp.tif"/> </outputs> - + <tests> + <test> + <param name="in_files" value="test.tiff" /> + <output name="output_dfp" ftype="tiff"> + <assert_contents> + <has_size value="350000" delta="50000" /> + </assert_contents> + </output> + <output name="output_ffp" ftype="tiff"> + <assert_contents> + <has_size value="350000" delta="50000" /> + </assert_contents> + </output> + </test> + </tests> <help><![CDATA[ +------------------- basic-illumination -ImageJ BaSiC shading correction for use with Ashlar - -Running as a Docker container -Create a container: +------------------- -docker run -it -v /path/to/data:/data labsyspharm/basic-illumination bash -(where /path/to/data is the data directory on your local machine. Your data will be mapped to /data inside the container) - -Once inside the container, do ls /data to ensure your data is there, followed by - -ImageJ-linux64 --ij2 --headless --run imagej_basic_ashlar.py \ - "filename='/data/input.ome.tiff',output_dir='/data/',experiment_name='my_experiment'" -for each input.ome.tiff in your data directory. - -OHSU Wrapper Repo: https://github.com/ohsu-comp-bio/basic-illumination -Conda Package Available From: https://anaconda.org/ohsu-comp-bio/basic-illumination +**ImageJ BaSiC shading correction for use with Ashlar** +**basic-illumination** can read image data directly from BioFormats-supported microscope +vendor file formats as well as a directory of plain TIFF files. Output includes a flat field and +dark field image per input file. ]]></help> <expand macro="citations" /> </tool>
--- a/imagej_basic_ashlar.py Fri Mar 12 00:13:46 2021 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,135 +0,0 @@ -# @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, Prefs -from ij.macro import Interpreter -from loci.plugins import BF -from loci.plugins.in import ImporterOptions -from loci.formats import ImageReader -from loci.formats.in import DynamicMetadataOptions -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..." - - # For multi-scene .CZI files, we need raw tiles instead of the - # auto-stitched mosaic and we don't want labels or overview images. This - # only affects BF.openImagePlus, not direct use of the BioFormats reader - # classes which we also do (see below) - Prefs.set("bioformats.zeissczi.allow.autostitch", "false") - Prefs.set("bioformats.zeissczi.include.attachments", "false") - - # 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. - dyn_options = DynamicMetadataOptions() - # Directly calling a BioFormats reader will not use the IJ Prefs settings - # so we need to pass these options explicitly. - dyn_options.setBoolean("zeissczi.autostitch", False) - dyn_options.setBoolean("zeissczi.attachments", False) - bfreader = ImageReader() - bfreader.setMetadataOptions(dyn_options) - 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()
--- a/imagej_basic_ashlar_filepattern.py Fri Mar 12 00:13:46 2021 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,186 +0,0 @@ -# @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 - -# Takes a filename pattern describing a list of image files and generates flat- -# and dark-field correction profile images with BaSiC. The pattern must contain -# a "*" wildcard to indicate the part of the filename that varies with the image -# series number. If the images are stored with one channel per file then the -# pattern must also contain the placeholder {channel} in place of the channel -# name or number. If the image files are multi-channel then the {channel} -# placeholder must be omitted. The output format is two multi-channel TIFF files -# (one for flat and one for dark) which is the input format used by Ashlar. - -# Invocation for running from the commandline: -# (to match files like "s001_c1.tif", "s001_c2.tif", "s002_c1.tif", etc.) -# -# ImageJ --ij2 --headless --run imagej_basic_ashlar_filepattern.py "pattern='input/s*_c{channel}.tif',output_dir='output',experiment_name='my_experiment'" - -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 glob pattern containing an optional - {channel} placeholder). - - Returns a list of lists, where the top level is indexed by sorted channel - name/number and the bottom level is filenames for that channel. - - """ - (base, pattern) = os.path.split(pattern) - regex = re.sub(r'{([^:}]+)(?:[^}]*)}', r'(?P<\1>.*?)', - pattern.replace('.', '\.').replace('*', '.*?')) - channels = set() - num_images = 0 - # Dict[Union[int, str, None], List[str]] - filenames = collections.defaultdict(list) - for f in os.listdir(base): - match = re.match(regex, f) - if match: - gd = match.groupdict() - channel = gd.get('channel', None) - try: - channel = int(channel) - except (ValueError, TypeError): - pass - channels.add(channel) - filenames[channel].append(os.path.join(base, f)) - num_images += 1 - if num_images % len(channels) != 0: - print ( - "ERROR: Some image files seem to be missing --" - " image count (%d) is not a multiple of channel count (%d)" - % (num_images, len(channels)) - ) - return [] - channels = sorted(channels) - if len(channels) > 1: - print("Detected the following channel names/numbers from filenames:") - for channel in channels: - print(" %s" % channel) - filenames = [filenames[channel] for channel in channels] - 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" - - print "Loading images..." - filenames = enumerate_filenames(pattern) - if len(filenames) == 0: - return - # This is the number of channels inferred from the filenames. The number - # of channels in an individual image file will be determined below. - num_channels = len(filenames) - num_images = len(filenames[0]) - image = Opener().openImage(filenames[0][0]) - if image.getNDimensions() > 3: - print "ERROR: Can't handle images with more than 3 dimensions." - (width, height, channels, slices, frames) = image.getDimensions() - # The third dimension could be any of these three, but the other two are - # guaranteed to be equal to 1 since we know NDimensions is <= 3. - image_channels = max((channels, slices, frames)) - image.close() - if num_channels > 1 and image_channels > 1: - print ( - "ERROR: Can only handle single-channel images with {channel} in" - " the pattern, or multi-channel images without {channel}. The" - " filename patterns imply %d channels and the images themselves" - " have %d channels." % (num_channels, image_channels) - ) - return - if image_channels == 1: - multi_channel = False - else: - print ( - "Detected multi-channel image files with %d channels" - % image_channels - ) - multi_channel = True - num_channels = image_channels - # Clone the filename list across all channels. We will handle reading - # the individual image planes for each channel below. - filenames = filenames * num_channels - - # 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) - # For multi-channel images the channel determines the plane to read. - args = [channel + 1] if multi_channel else [] - image = opener.openImage(filename, *args) - 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()
--- a/macros.xml Fri Mar 12 00:13:46 2021 +0000 +++ b/macros.xml Thu Sep 01 22:46:21 2022 +0000 @@ -2,19 +2,38 @@ <macros> <xml name="requirements"> <requirements> - <requirement type="package" version="3.8">python</requirement> - <requirement type="package" version="1.0.2">basic-illumination</requirement> + <container type="docker">labsyspharm/basic-illumination:@TOOL_VERSION@</container> </requirements> </xml> <xml name="version_cmd"> - <version_command>echo @VERSION@</version_command> + <version_command>echo @TOOL_VERSION@</version_command> </xml> <xml name="citations"> <citations> + <citation type="doi">10.1038/ncomms14836</citation> </citations> </xml> - <token name="@VERSION@">1.0.2</token> - <token name="@CMD_BEGIN@">ImageJ --ij2 --headless --run ${__tool_directory__}/imagej_basic_ashlar.py</token> + <token name="@TOOL_VERSION@">1.0.3</token> + <token name="@VERSION_SUFFIX@">0</token> + <token name="@PROFILE@">19.01</token> + <token name="@CMD_BEGIN@"><![CDATA[ + ## if ImageJ isn't already present, this is the docker-dependency version + ## as such, ImageJ needs to be registered as a global executable on $PATH + + if ! [ -x "\$(which ImageJ)" ]; then + ln -sf "\$(which ImageJ-linux64)" "ImageJ"; + export PATH="\$PATH:\$(pwd)/"; + fi && + + BASIC_SCRIPT="" && + if [ -f "/opt/fiji/imagej_basic_ashlar.py" ]; then + export BASIC_SCRIPT="/opt/fiji/imagej_basic_ashlar.py"; + else + export BASIC_SCRIPT="${__tool_directory__}/imagej_basic_ashlar.py"; + fi && + + ImageJ --ij2 --headless --run \$BASIC_SCRIPT + ]]></token> </macros>