changeset 0:0729a4b20e67 draft default tip

Uploaded
author greg
date Wed, 24 Jul 2019 08:30:37 -0400
parents
children
files .shed.yml imagej2_adjust_threshold_binary.py imagej2_adjust_threshold_binary.xml imagej2_adjust_threshold_binary_jython_script.py imagej2_base_utils.py imagej2_base_utils.pyc imagej2_macros.xml jython_utils.py test-data/blobs.gif test-data/blobs_threshold_default.gif test-data/blobs_threshold_huang_dark.gif test-data/blobs_threshold_ijiso.gif
diffstat 12 files changed, 569 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.shed.yml	Wed Jul 24 08:30:37 2019 -0400
@@ -0,0 +1,19 @@
+name: imagej2_adjust_threshold_binary
+owner: greg
+homepage_url: http://fiji.sc
+long_description: |
+  ImageJ2 is a new version of ImageJ for the next generation of multidimensional
+  image data, with a focus on scientific imaging.  Its central goal is to broaden
+  the paradigm of ImageJ beyond the limitations of ImageJ 1.x, to support the next
+  generation of multidimensional scientific imaging.
+
+  ImageJ2 is also a collection of reusable software libraries built on the SciJava
+  software stack, using a powerful plugin framework to facilitate rapid development
+  and painless user customization.
+
+  The Fiji distribution of ImageJ has shipped with beta versions of ImageJ2 for
+  quite some time.
+remote_repository_url: https://github.com/bgruening/galaxytools/tree/master/tools/image_processing/imagej2
+type: unrestricted
+categories:
+  - Imaging
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/imagej2_adjust_threshold_binary.py	Wed Jul 24 08:30:37 2019 -0400
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+import argparse
+import os
+import shutil
+import subprocess
+import tempfile
+import imagej2_base_utils
+
+parser = argparse.ArgumentParser()
+parser.add_argument( '--input', dest='input', help='Path to the input file' )
+parser.add_argument( '--input_datatype', dest='input_datatype', help='Datatype of the input image' )
+parser.add_argument( '--threshold_min', dest='threshold_min', type=float, help='Minimum threshold value' )
+parser.add_argument( '--threshold_max', dest='threshold_max', type=float, help='Maximum threshold value' )
+parser.add_argument( '--method', dest='method', help='Threshold method' )
+parser.add_argument( '--display', dest='display', help='Display mode' )
+parser.add_argument( '--black_background', dest='black_background', help='Black background' )
+parser.add_argument( '--stack_histogram', dest='stack_histogram', help='Stack histogram' )
+parser.add_argument( '--jython_script', dest='jython_script', help='Path to the Jython script' )
+parser.add_argument( '--output', dest='output', help='Path to the output file' )
+parser.add_argument( '--output_datatype', dest='output_datatype', help='Datatype of the output image' )
+args = parser.parse_args()
+
+tmp_dir = imagej2_base_utils.get_temp_dir()
+# ImageJ expects valid image file extensions, so the Galaxy .dat extension does not
+# work for some features.  The following creates a symlink with an appropriate file
+# extension that points to the Galaxy dataset.  This symlink is used by ImageJ.
+tmp_input_path = imagej2_base_utils.get_input_image_path( tmp_dir, args.input, args.input_datatype )
+tmp_output_path = imagej2_base_utils.get_temporary_image_path( tmp_dir, args.output_datatype )
+# Define command response buffers.
+tmp_out = tempfile.NamedTemporaryFile().name
+tmp_stdout = open( tmp_out, 'wb' )
+tmp_err = tempfile.NamedTemporaryFile().name
+tmp_stderr = open( tmp_err, 'wb' )
+# Java writes a lot of stuff to stderr, so we'll specify a file for handling actual errors.
+error_log = tempfile.NamedTemporaryFile( delete=False ).name
+# Build the command line.
+cmd = imagej2_base_utils.get_base_command_imagej2( None, jython_script=args.jython_script )
+if cmd is None:
+    imagej2_base_utils.stop_err( "ImageJ not found!" )
+cmd += ' %s' % error_log
+cmd += ' %s' % tmp_input_path
+cmd += ' %.3f' % args.threshold_min
+cmd += ' %.3f' % args.threshold_max
+cmd += ' %s' % args.method
+cmd += ' %s' % args.display
+cmd += ' %s' % args.black_background
+cmd += ' %s' % args.stack_histogram
+cmd += ' %s' % tmp_output_path
+cmd += ' %s' % args.output_datatype
+# Run the command.
+proc = subprocess.Popen( args=cmd, stderr=tmp_stderr, stdout=tmp_stdout, shell=True )
+rc = proc.wait()
+# Handle execution errors.
+if rc != 0:
+    error_message = imagej2_base_utils.get_stderr_exception( tmp_err, tmp_stderr, tmp_out, tmp_stdout )
+    imagej2_base_utils.stop_err( error_message )
+# Handle processing errors.
+if os.path.getsize( error_log ) > 0:
+    error_message = open( error_log, 'r' ).read()
+    imagej2_base_utils.stop_err( error_message )
+# Save the output image.
+shutil.move( tmp_output_path, args.output )
+imagej2_base_utils.cleanup_before_exit( tmp_dir )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/imagej2_adjust_threshold_binary.xml	Wed Jul 24 08:30:37 2019 -0400
@@ -0,0 +1,115 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<tool id="imagej2_adjust_threshold_binary" name="Adjust threshold" version="@WRAPPER_VERSION@.0">
+    <description>of binary image</description>
+    <macros>
+        <import>imagej2_macros.xml</import>
+    </macros>
+    <expand macro="fiji_requirements" />
+    <command detect_errors="exit_code"><![CDATA[
+    python $__tool_directory__/imagej2_adjust_threshold_binary.py
+    --input "$input"
+    --input_datatype $input.ext
+    --threshold_min $threshold_min
+    --threshold_max $threshold_max
+    --method $method
+    --display $display
+    --black_background $black_background
+    --stack_histogram $stack_histogram
+    --jython_script $__tool_directory__/imagej2_adjust_threshold_binary_jython_script.py
+    --output_datatype $output.ext
+    --output "$output"
+]]></command>
+    <inputs>
+        <param format="bmp,eps,gif,jpg,pcx,pgm,png,psd,tiff" name="input" type="data" label="Select image"/>
+        <param name="threshold_min" type="float" value="0" min="0" max="255" label="Minimum threshold value" />
+        <param name="threshold_max" type="float" value="0" min="0" max="255" label="Maximum threshold value" />
+        <param name="method" type="select" label="Method" help="The Default method is the modified IsoData algorithm.">
+            <option value="Default" selected="True">Default</option>
+            <option value="Huang">Huang</option>
+            <option value="Intermodes">Intermodes</option>
+            <option value="IsoData">IsoData</option>
+            <option value="IJ_IsoData">IJ_IsoData</option>
+            <option value="Li">Li</option>
+            <option value="MaxEntropy">MaxEntropy</option>
+            <option value="Mean">Mean</option>
+            <option value="MinError">MinError</option>
+            <option value="Minimum">Minimum</option>
+            <option value="Moments">Moments</option>
+            <option value="Otsu">Otsu</option>
+            <option value="RenyiEntropy">RenyiEntropy</option>
+            <option value="Shanbhag">Shanbhag</option>
+            <option value="Triangle">Triangle</option>
+            <option value="Yen">Yen</option>
+        </param>
+        <param name="display" type="select" label="Display">
+            <option value="red" selected="True">Red</option>
+            <option value="bw">Black and White</option>
+            <option value="over_under">Over/Under</option>
+        </param>
+        <param name="black_background" type="select" label="Black background" help="Select yes if features are lighter than the background.">
+            <option value="no" selected="True">No</option>
+            <option value="yes">Yes</option>
+        </param>
+        <param name="stack_histogram" type="select" label="Stack histogram" help="Select yes to first compute the histogram of the whole stack (or hyperstack) and then compute the threshold based on that histogram.">
+            <option value="no" selected="True">No</option>
+            <option value="yes">Yes</option>
+        </param>
+    </inputs>
+    <outputs>
+        <data name="output" format_source="input" label="${tool.name} on ${on_string}"/>
+    </outputs>
+    <tests>
+        <test>
+            <param name="input" value="blobs.gif" />
+            <param name="output_datatype" value="gif" />
+            <param name="threshold_min" value="0.0" />
+            <param name="threshold_max" value="129.0" />
+            <param name="method" value="Default" />
+            <param name="display" value="red" />
+            <param name="black_background" value="no" />
+            <param name="stack_histogram" value="no" />
+            <output name="output" file="blobs_threshold_default.gif" compare="sim_size" />
+        </test>
+        <test>
+            <param name="input" value="blobs.gif" />
+            <param name="output_datatype" value="gif" />
+            <param name="threshold_min" value="118.0" />
+            <param name="threshold_max" value="255.0" />
+            <param name="method" value="IJ_IsoData" />
+            <param name="display" value="over_under" />
+            <param name="black_background" value="no" />
+            <param name="stack_histogram" value="no" />
+            <output name="output" file="blobs_threshold_ijiso.gif" compare="sim_size" />
+        </test>
+        <test>
+            <param name="input" value="blobs.gif" />
+            <param name="output_datatype" value="gif" />
+            <param name="threshold_min" value="72.0" />
+            <param name="threshold_max" value="255.0" />
+            <param name="method" value="Huang" />
+            <param name="display" value="bw" />
+            <param name="black_background" value="yes" />
+            <param name="stack_histogram" value="no" />
+            <output name="output" file="blobs_threshold_huang_dark.gif" compare="sim_size" />
+        </test>
+    </tests>
+    <help>
+
+@requires_binary_input@
+
+**What it does**
+
+<![CDATA[
+Sets lower and upper threshold values, segmenting grayscale images into features of interest and background
+
+- **Minimum threshold value** - Adjusts the minimum threshold value.
+- **Maximum threshold value** - Adjusts the maximum threshold value.
+- **Method** - Allows any of the 16 different automatic thresholding methods to be selected.  These are global thresholding methods that typically cannot deal with unevenly illuminated images (such as in brightfield microscopy)."
+- **Display** - Selects one of three display mode:  **Red** displays the thresholded values in red, **Black and White** features are displayed in black and background in white, **Over/Under** displays pixels below the lower threshold value in blue, thresholded pixels in grayscale, and pixels above the upper threshold value in green.
+- **Black background** - Select **yes** when features are lighter than the background.
+- **Stack histogram**  Select **yes** to first compute the histogram of the whole stack (or hyperstack) and then compute the threshold based on that histogram.  As such, all slices are binarized using the single computed value. If unchecked, the threshold of each slice is computed separately.
+]]>
+
+    </help>
+    <expand macro="fiji_headless_citations" />
+</tool>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/imagej2_adjust_threshold_binary_jython_script.py	Wed Jul 24 08:30:37 2019 -0400
@@ -0,0 +1,49 @@
+import jython_utils
+import sys
+from ij import IJ
+
+# Fiji Jython interpreter implements Python 2.5 which does not
+# provide support for argparse.
+error_log = sys.argv[ -10 ]
+input = sys.argv[ -9 ]
+threshold_min = float( sys.argv[ -8 ] )
+threshold_max = float( sys.argv[ -7 ] )
+method = sys.argv[ -6 ]
+display = sys.argv[ -5 ]
+black_background = jython_utils.asbool( sys.argv[ -4 ] )
+# TODO: this is not being used.
+stack_histogram = jython_utils.asbool( sys.argv[ -3 ] )
+tmp_output_path = sys.argv[ -2 ]
+output_datatype = sys.argv[ -1 ]
+
+# Open the input image file.
+input_image_plus = IJ.openImage( input )
+
+# Create a copy of the image.
+input_image_plus_copy = input_image_plus.duplicate()
+image_processor_copy = input_image_plus_copy.getProcessor()
+
+try:
+    # Convert image to binary if necessary.
+    if not image_processor_copy.isBinary():
+        # Convert the image to binary grayscale.
+        IJ.run( input_image_plus_copy, "Make Binary","iterations=1 count=1 edm=Overwrite do=Nothing" )
+    # Set the options.
+    if black_background:
+        method_str = "%s dark" % method
+    else:
+        method_str = method
+    IJ.setAutoThreshold( input_image_plus_copy, method_str )
+    if display == "red":
+        display_mode = "Red"
+    elif display == "bw":
+        display_mode = "Black & White"
+    elif display == "over_under":
+        display_mode = "Over/Under"
+    IJ.setThreshold( input_image_plus_copy, threshold_min, threshold_max, display_mode )
+    # Run the command.
+    IJ.run( input_image_plus_copy, "threshold", "" )
+    # Save the ImagePlus object as a new image.
+    IJ.saveAs( input_image_plus_copy, output_datatype, tmp_output_path )
+except Exception, e:
+    jython_utils.handle_error( error_log, str( e ) )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/imagej2_base_utils.py	Wed Jul 24 08:30:37 2019 -0400
@@ -0,0 +1,169 @@
+import os
+import shutil
+import sys
+import tempfile
+
+BUFF_SIZE = 1048576
+
+
+def cleanup_before_exit(tmp_dir):
+    """
+    Remove temporary files and directories prior to tool exit.
+    """
+    if tmp_dir and os.path.exists(tmp_dir):
+        shutil.rmtree(tmp_dir)
+
+
+def get_base_cmd_bunwarpj(jvm_memory):
+    if jvm_memory in [None, 'None']:
+        jvm_memory_str = ''
+    else:
+        jvm_memory_str = '-Xmx%s' % jvm_memory
+    # The following bunwarpj_base_cmd string will look something like this:
+    # "java %s -cp $JAR_DIR/ij-1.49k.jar:$PLUGINS_DIR/bUnwarpJ_-2.6.1.jar \
+    # bunwarpj.bUnwarpJ_" % (jvm_memory_str)
+    # See the bunwarpj.sh script for the fiji 20151222
+    # bioconda recipe in github.
+    bunwarpj_base_cmd = "bunwarpj %s" % jvm_memory_str
+    return bunwarpj_base_cmd
+
+
+def get_base_command_imagej2(memory_size=None, macro=None, jython_script=None):
+    imagej2_executable = get_imagej2_executable()
+    if imagej2_executable is None:
+        return None
+    cmd = '%s --ij2 --headless --debug' % imagej2_executable
+    if memory_size is not None:
+        memory_size_cmd = ' -DXms=%s -DXmx=%s' % (memory_size, memory_size)
+        cmd += memory_size_cmd
+    if macro is not None:
+        cmd += ' --macro %s' % os.path.abspath(macro)
+    if jython_script is not None:
+        cmd += ' --jython %s' % os.path.abspath(jython_script)
+    return cmd
+
+
+def get_file_extension(image_format):
+    """
+    Return a valid bioformats file extension based on the received
+    value of image_format(e.g., "gif" is returned as ".gif".
+    """
+    return '.%s' % image_format
+
+
+def get_file_name_without_extension(file_path):
+    """
+    Eliminate the .ext from the received file name, assuming that
+    the file name consists of only a single '.'.
+    """
+    if os.path.exists(file_path):
+        path, name = os.path.split(file_path)
+        name_items = name.split('.')
+        return name_items[0]
+    return None
+
+
+def get_imagej2_executable():
+    """
+    Fiji names the ImageJ executable different names for different
+    architectures, but our bioconda recipe allows us to do this.
+    """
+    return 'ImageJ'
+
+
+def get_input_image_path(tmp_dir, input_file, image_format):
+    """
+    Bioformats uses file extensions (e.g., .job, .gif, etc)
+    when reading and writing image files, so the Galaxy dataset
+    naming convention of setting all file extensions as .dat
+    must be handled.
+    """
+    image_path = get_temporary_image_path(tmp_dir, image_format)
+    # Remove the file so we can create a symlink.
+    os.remove(image_path)
+    os.symlink(input_file, image_path)
+    return image_path
+
+
+def get_platform_info_dict():
+    '''Return a dict with information about the current platform.'''
+    platform_dict = {}
+    sysname, nodename, release, version, machine = os.uname()
+    platform_dict['os'] = sysname.lower()
+    platform_dict['architecture'] = machine.lower()
+    return platform_dict
+
+
+def get_stderr_exception(tmp_err, tmp_stderr, tmp_out, tmp_stdout, include_stdout=False):
+    tmp_stderr.close()
+    """
+    Return a stderr string of reasonable size.
+    """
+    # Get stderr, allowing for case where it's very large.
+    tmp_stderr = open(tmp_err, 'rb')
+    stderr_str = ''
+    buffsize = BUFF_SIZE
+    try:
+        while True:
+            stderr_str += tmp_stderr.read(buffsize)
+            if not stderr_str or len(stderr_str) % buffsize != 0:
+                break
+    except OverflowError:
+        pass
+    tmp_stderr.close()
+    if include_stdout:
+        tmp_stdout = open(tmp_out, 'rb')
+        stdout_str = ''
+        buffsize = BUFF_SIZE
+        try:
+            while True:
+                stdout_str += tmp_stdout.read(buffsize)
+                if not stdout_str or len(stdout_str) % buffsize != 0:
+                    break
+        except OverflowError:
+            pass
+    tmp_stdout.close()
+    if include_stdout:
+        return 'STDOUT\n%s\n\nSTDERR\n%s\n' % (stdout_str, stderr_str)
+    return stderr_str
+
+
+def get_temp_dir(prefix='tmp-imagej-', dir=None):
+    """
+    Return a temporary directory.
+    """
+    return tempfile.mkdtemp(prefix=prefix, dir=dir)
+
+
+def get_tempfilename(dir=None, suffix=None):
+    """
+    Return a temporary file name.
+    """
+    fd, name = tempfile.mkstemp(suffix=suffix, dir=dir)
+    os.close(fd)
+    return name
+
+
+def get_temporary_image_path(tmp_dir, image_format):
+    """
+    Return the path to a temporary file with a valid image format
+    file extension that can be used with bioformats.
+    """
+    file_extension = get_file_extension(image_format)
+    return get_tempfilename(tmp_dir, file_extension)
+
+
+def handle_none_type(val, val_type='float'):
+    if val is None:
+        return ' None'
+    else:
+        if val_type == 'float':
+            return ' %.3f' % val
+        elif val_type == 'int':
+            return ' %d' % val
+    return ' %s' % val
+
+
+def stop_err(msg):
+    sys.stderr.write(msg)
+    sys.exit(1)
Binary file imagej2_base_utils.pyc has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/imagej2_macros.xml	Wed Jul 24 08:30:37 2019 -0400
@@ -0,0 +1,106 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<macros>
+    <token name="@WRAPPER_VERSION@">20170530</token>
+    <xml name="fiji_requirements">
+        <requirements>
+            <requirement type="package" version="20170530">fiji</requirement>
+        </requirements>
+    </xml>
+    <xml name="stdio">
+        <stdio>
+            <exit_code range="1:"/>
+            <exit_code range=":-1"/>
+            <regex match="Error:"/>
+            <regex match="Exception:"/>
+        </stdio>
+    </xml>
+    <xml name="image_type">
+        <param name="image_type" type="select" label="Image type">
+            <option value="8-bit_white" selected="True">8-bit white</option>
+            <option value="8-bit_black">8-bit black</option>
+            <option value="8-bit_random">8-bit random</option>
+            <option value="8-bit_ramp">8-bit ramp</option>
+            <option value="16-bit_white">16-bit white</option>
+            <option value="16-bit_black">16-bit black</option>
+            <option value="16-bit_random">16-bit random</option>
+            <option value="16-bit_ramp">16-bit ramp</option>
+            <option value="32-bit_white">32-bit white</option>
+            <option value="32-bit_black">32-bit black</option>
+            <option value="32-bit_random">32-bit random</option>
+            <option value="32-bit_ramp">32-bit ramp</option>
+            <option value="RGB_white">RGB white</option>
+            <option value="RGB_black">RGB black</option>
+            <option value="RGB_random">RGB random</option>
+            <option value="RGB_ramp">RGB ramp</option>
+        </param>
+    </xml>
+    <xml name="make_binary_params">
+        <param name="iterations" type="integer" value="1" min="1" max="100" label="Iterations" help="The number of times (1-100) erosion, dilation, opening, and closing are performed."/>
+        <param name="count" type="integer" value="1" min="1" max="8" label="Count" help="The number of adjacent background pixels necessary (1-8) for erosion or dilation."/>
+        <param name="black_background" type="select" label="Black background" help="If Yes, the background is black and the foreground is white (no implies the opposite).">
+            <option value="no" selected="True">No</option>
+            <option value="yes">Yes</option>
+        </param>
+        <param name="pad_edges_when_eroding" type="select" label="Pad edges when eroding" help="If Yes, eroding does not erode from the edges of the image.">
+            <option value="no" selected="True">No</option>
+            <option value="yes">Yes</option>
+        </param>
+    </xml>
+    <xml name="black_background_param">
+        <param name="black_background" type="select" label="Black background" help="If Yes, the background is black and the foreground is white (no implies the opposite).">
+            <option value="no" selected="True">No</option>
+            <option value="yes">Yes</option>
+        </param>
+    </xml>
+    <token name="@make_binary_args@">
+        --iterations $iterations
+        --count $count
+        --black_background $black_background
+        --pad_edges_when_eroding $pad_edges_when_eroding
+    </token>
+    <token name="@requires_binary_input@">
+.. class:: warningmark
+
+This tool works on binary images, so other image types will automatically be converted to binary
+before they are analyzed.  This step is performed using the ImageJ2 **Make Binary** command with
+the following settings: **Iterations:** 1, **Count:** 1, **Pad edges when eroding:** No.  The tool
+allows you to choose the **Black background** setting.  If these settings are not appropriate,
+first manually convert the image to binary using the **Convert to binary (black and white)**
+tool, which allows you to change them.
+    </token>
+    <xml name="image_datatypes">
+        <option value="bmp">bmp</option>
+        <option value="gif">gif</option>
+        <option value="jpg">jpg</option>
+        <option value="png" selected="true">png</option>
+        <option value="tiff">tiff</option>
+    </xml>
+    <xml name="bunwarpj_citations">
+        <citations>
+            <citation type="bibtex">
+                @InProceedings(Arganda-Carreras2006,
+                    author =     "Ignacio Arganda-Carreras and
+                                        Carlos Oscar S{\'a}nchez Sorzano and
+                                        Roberto Marabini and
+                                        Jos{\'e} Mar\'{\i}a Carazo and
+                                        Carlos Ortiz-de-Solorzano and
+                                        Jan Kybic",
+                    title =          "Consistent and Elastic Registration of Histological Sections Using Vector-Spline Regularization",    
+                    publisher =  "Springer Berlin / Heidelberg",    
+                    booktitle =   "Computer Vision Approaches to Medical Image Analysis",
+                    series =       "Lecture Notes in Computer Science",
+                    year =          "2006",
+                    volume =      "4241",
+                    pages =       "85-95",
+                    month =       "May",
+                    city =            "Graz, Austria")
+            </citation>
+            <citation type="doi">10.1038/nmeth.2019</citation>
+        </citations>
+    </xml>
+    <xml name="fiji_headless_citations">
+        <citations>
+            <citation type="doi">10.1038/nmeth.2102</citation>
+        </citations>
+    </xml>
+</macros>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jython_utils.py	Wed Jul 24 08:30:37 2019 -0400
@@ -0,0 +1,48 @@
+import imagej2_base_utils
+from ij import IJ
+
+IMAGE_PLUS_IMAGE_TYPE_FIELD_VALUES = { '0':'GRAY8', '1':'GRAY16', '2':'GRAY32',
+                                       '3':'COLOR_256', '4':'COLOR_RGB' }
+
+def asbool( val ):
+    return str( val ).lower() in [ 'yes', 'true' ]
+
+def convert_before_saving_as_tiff( image_plus ):
+    # The bUnwarpJ plug-in produces TIFF image stacks consisting of 3
+    # slices which can be viewed in ImageJ.  The 3 slices are: 1) the
+    # registered image, 2) the target image and 3) the black/white warp
+    # image.  When running bUnwarpJ from the command line (as these
+    # Galaxy wrappers do) the initial call to IJ.openImage() (to open the
+    # registered source and target images produced by bUnwarpJ) in the
+    # tool's jython_script.py returns an ImagePlus object with a single
+    # slice which is the "generally undesired" slice 3 discussed above.
+    # However, a call to IJ.saveAs() will convert the single-slice TIFF
+    # into a 3-slice TIFF image stack (as described above) if the selected
+    # format for saving is TIFF.  Galaxy supports only single-layered
+    # images, so to work around this behavior, we have to convert the
+    # image to something other than TIFF so that slices are eliminated.
+    # We can then convert back to TIFF for saving.  There might be a way
+    # to do this without converting twice, but I spent a lot of time looking
+    # and I have yet to discover it.
+    tmp_dir = imagej2_base_utils.get_temp_dir()
+    tmp_out_png_path = imagej2_base_utils.get_temporary_image_path( tmp_dir, 'png' )
+    IJ.saveAs( image_plus, 'png', tmp_out_png_path )
+    return IJ.openImage( tmp_out_png_path )
+
+def get_binary_options( black_background, iterations=1, count=1, pad_edges_when_eroding='no' ):
+    options = [ 'edm=Overwrite', 'iterations=%d' % iterations, 'count=%d' % count ]
+    if asbool( pad_edges_when_eroding ):
+        options.append( 'pad' )
+    if asbool( black_background ):
+        options.append( "black" )
+    return " ".join( options )
+
+def get_display_image_type( image_type ):
+    return IMAGE_PLUS_IMAGE_TYPE_FIELD_VALUES.get( str( image_type ), None )
+
+def handle_error( error_log, msg ):
+    # Java writes a lot of stuff to stderr, so the received error_log 
+    # will log actual errors.
+    elh = open( error_log, 'wb' )
+    elh.write( msg )
+    elh.close()
Binary file test-data/blobs.gif has changed
Binary file test-data/blobs_threshold_default.gif has changed
Binary file test-data/blobs_threshold_huang_dark.gif has changed
Binary file test-data/blobs_threshold_ijiso.gif has changed