changeset 0:d065ec177dcd draft

planemo upload commit 18df9e67efd4adafcde4eb9b62cd815e4afe9733-dirty
author iuc
date Wed, 26 Aug 2015 14:36:17 -0400
parents
children dd9041dc3c27
files imagej2_analyze_particles_binary.py imagej2_analyze_particles_binary.xml imagej2_base_utils.py imagej2_macros.xml jython_script.py jython_utils.py test-data/analyze_particles_masks.gif test-data/analyze_particles_nothing.tabular test-data/analyze_particles_outlines.gif test-data/blobs_watershed_binary.gif tool_dependencies.xml
diffstat 11 files changed, 672 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/imagej2_analyze_particles_binary.py	Wed Aug 26 14:36:17 2015 -0400
@@ -0,0 +1,81 @@
+#!/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( '--black_background', dest='black_background', help='Black background' )
+parser.add_argument( '--size', dest='size', help='Size (pixel^2)' )
+parser.add_argument( '--circularity_min', dest='circularity_min', type=float, help='Circularity minimum' )
+parser.add_argument( '--circularity_max', dest='circularity_max', type=float, help='Circularity maximum' )
+parser.add_argument( '--show', dest='show', help='Show' )
+parser.add_argument( '--display_results', dest='display_results', help='Display results' )
+parser.add_argument( '--all_results', dest='all_results', help='All results' )
+parser.add_argument( '--exclude_edges', dest='exclude_edges', help='Exclude edges' )
+parser.add_argument( '--include_holes', dest='include_holes', help='Include holes' )
+parser.add_argument( '--jython_script', dest='jython_script', help='Path to the Jython script' )
+parser.add_argument( '--results', dest='results', default=None, help='Path to the output results file' )
+parser.add_argument( '--output', dest='output', default=None, help='Path to the output image file' )
+parser.add_argument( '--output_datatype', dest='output_datatype', default='data',  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 )
+if args.output is None:
+    tmp_output_path = None
+else:
+    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 += ' %s' % args.black_background
+cmd += ' %s' % args.size
+cmd += ' %.3f' % args.circularity_min
+cmd += ' %.3f' % args.circularity_max
+cmd += ' %s' % args.show
+cmd += ' %s' % args.display_results
+cmd += '%s' % imagej2_base_utils.handle_none_type( args.all_results, val_type='str' )
+cmd += ' %s' % args.exclude_edges
+cmd += ' %s' % args.include_holes
+cmd += '%s' % imagej2_base_utils.handle_none_type( tmp_output_path, val_type='str' )
+cmd += ' %s' % args.output_datatype
+cmd += '%s' % imagej2_base_utils.handle_none_type( args.results, val_type='str' )
+
+# 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 )
+
+if tmp_output_path is not None:
+    # 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_analyze_particles_binary.xml	Wed Aug 26 14:36:17 2015 -0400
@@ -0,0 +1,132 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<tool id="imagej2_analyze_particles_binary" name="Analyze particles" version="@WRAPPER_VERSION@.0">
+    <description>of binary image with ImageJ2</description>
+    <macros>
+        <import>imagej2_macros.xml</import>
+    </macros>
+    <expand macro="fiji_requirements" />
+    <command>
+<![CDATA[
+    python $__tool_directory__/imagej2_analyze_particles_binary.py
+    --input "$input"
+    --input_datatype $input.ext
+    --black_background $black_background
+    --size "$size"
+    --circularity_min $circularity_min
+    --circularity_max $circularity_max
+    --show $show
+    --exclude_edges $exclude_edges
+    --include_holes $include_holes
+    --jython_script $__tool_directory__/jython_script.py
+    #if str($show) == 'Nothing':
+        --display_results "yes"
+        --all_results "yes"
+    #else:
+        --display_results $display_results_cond.display_results
+        --all_results $display_results_cond.all_results
+        --output_datatype $output.ext
+        --output "$output"
+    #end if
+    --results "$results"
+]]>
+    </command>
+    <inputs>
+        <param format="bmp,eps,gif,jpg,pcx,pgm,png,psd,tiff" name="input" type="data" label="Select image"/>
+        <expand macro="black_background_param" />
+        <param name="size" type="text" value="0-Infinity" label="Size (pixel^2)" help="Enter a single value and particles smaller than that value will be ignored."/>
+        <param name="circularity_min" type="float" value="0.0" label="Circularity minimum" help="Values from 0.0 to 1.0, but less than maximum">
+            <validator type="in_range" min="0" max="1"/>
+        </param>
+        <param name="circularity_max" type="float" value="1.0" label="Circularity maximum" help="Values from 0.0 to 1.0, but greater than minimum">
+            <validator type="in_range" min="0" max="1"/>
+        </param>
+        <param name="show" type="select" label="Show" help="Selecting Nothing will display results, but no image.">
+            <option value="Outlines" selected="True">Outlines</option>
+            <option value="Bare_Outlines">Bare Outlines</option>
+            <option value="Ellipses">Ellipses</option>
+            <option value="Masks">Masks</option>
+            <option value="Count_Masks">Count Masks</option>
+            <option value="Nothing">Nothing</option>
+        </param>
+        <conditional name="display_results_cond">
+            <param name="display_results" type="select" label="Display results">
+                <option value="no" selected="True">No</option>
+                <option value="yes">Yes</option>
+            </param>
+            <when value="no">
+                <param name="all_results" type="hidden" value="no"/>
+            </when>
+            <when value="yes">
+                <param name="all_results" type="select" label="All results?" help="If no, only summary displayed.">
+                    <option value="yes" selected="True">Yes</option>
+                    <option value="no">No</option>
+                </param>
+            </when>
+        </conditional>
+        <param name="exclude_edges" type="select" label="Exclude on edges?">
+            <option value="no" selected="True">No</option>
+            <option value="yes">Yes</option>
+        </param>
+        <param name="include_holes" type="select" label="Include holes?">
+            <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}">
+            <filter>show != "Nothing"</filter>
+       </data>
+        <data format="tabular" name="results" label="${tool.name} on ${on_string}: Results">
+            <filter>show == "Nothing" or display_results_cond['display_results'] == "yes"</filter>
+        </data>
+    </outputs>
+    <tests>
+        <test>
+            <param name="input" value="blobs_watershed_binary.gif" />
+            <param name="show" value="Nothing" />
+            <output name="output" file="analyze_particles_nothing.tabular" />
+        </test>
+        <test>
+            <param name="input" value="blobs_watershed_binary.gif" />
+            <param name="show" value="Outlines" />
+            <output name="output" file="analyze_particles_outlines.gif" compare="sim_size" />
+        </test>
+        <test>
+            <param name="input" value="blobs_watershed_binary.gif" />
+            <param name="show" value="Masks" />
+            <param name="exclude_edges" value="yes" />
+            <param name="include_holes" value="yes" />
+            <output name="output" file="analyze_particles_masks.gif" compare="sim_size" />
+        </test>
+    </tests>
+    <help>
+.. class:: warningmark
+
+@requires_binary_input@
+
+**What it does**
+
+<![CDATA[
+Counts and measures objects in binary or thresholded images.  It works by scanning the image until
+it finds the edge of an object.  It then outlines the object, measures it, fills it to make it
+invisible, then resumes scanning until it reaches the end of the image.  Features of thresholded
+images can be extracted by specifying suitable **Size** and **Circularity** ranges and/or by choosing
+if particles should be traced by their outer edge or by flood filling.
+
+- **Size** - Particles with size (area) outside the range specified in this field are ignored. Values may range between 0 and ‘Infinity’.  Enter a single value and particles smaller than that value will be ignored.
+- **Cicularity** - Particles with size circularity values outside the range specified in the min/max fields are also ignored.
+- **Show: Nothing** - No image will be generated, but **Display Results** will be set to Yes.  Note that the particle analyzer will display a blank image when the count of detected particles is zero and **Show** is not **Nothing**.
+- **Show: Outlines** - Generates an 8-bit image containing numbered outlines of the measured particles (gray levels: Outlines: 0; Labels: 1; Background: 255).
+- **Show: Bare Outlines** - Generates an 8-bit image containing simple outlines of the measured particles without labels (graylevels: Outlines: 0; Background: 255).
+- **Show: Ellipses** - Generates an 8-bit image containing the best fit ellipse of each measured particle (gray levels: Ellipses: 0; Background: 255).
+- **Show: Masks** - Generates an 8-bit image containing filled outlines of the measured particles (gray levels: Masks: 0; Background: 255).
+- **Show: Count Masks** - Generates a 16-bit image containing filled outlines of the measured particles painted with a grayscale value corresponding to the particle number.
+- **Display Results** - If Yes, the measurements for each particle will be generated.
+- **All results** - If No, results will be restricted to the particle count, total particle area, average particle size, and area fraction.
+- **Exclude on Edges** - If Yes, particles touching the edge of the image will be ignored.
+- **Include Holes** - If Yes, interior holes will be included (set to No to exclude interior holes and to measure particles enclosed by other particles).
+]]>
+
+    </help>
+    <expand macro="fiji_headless_citations" />
+</tool>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/imagej2_base_utils.py	Wed Aug 26 14:36:17 2015 -0400
@@ -0,0 +1,164 @@
+import os
+import shutil
+import sys
+import tempfile
+
+FIJI_JAR_DIR = os.environ.get( 'FIJI_JAR_DIR', None )
+FIJI_OSX_JAVA3D_DIR = os.environ.get( 'FIJI_OSX_JAVA3D_DIR', None )
+FIJI_PLUGIN_DIR = os.environ.get( 'FIJI_PLUGIN_DIR', None )
+FIJI_ROOT_DIR = os.environ.get( 'FIJI_ROOT_DIR', None )
+
+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 FIJI_JAR_DIR is not None and FIJI_PLUGIN_DIR is not None:
+        if jvm_memory in [ None, 'None' ]:
+            jvm_memory_str = ''
+        else:
+            jvm_memory_str = '-Xmx%s' % jvm_memory
+        bunwarpj_base_cmd = "java %s -cp %s/ij-1.49k.jar:%s/bUnwarpJ_-2.6.1.jar bunwarpj.bUnwarpJ_" % \
+            ( jvm_memory_str, FIJI_JAR_DIR, FIJI_PLUGIN_DIR )
+        return bunwarpj_base_cmd
+    return None
+
+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, so figure out which name we need.
+    """
+    platform_dict = get_platform_info_dict()
+    if platform_dict.get( 'architecture', None ) in [ 'x86_64' ]:
+        if platform_dict.get( 'os', None ) in [ 'darwin' ]:
+            return 'ImageJ-macosx'
+        if platform_dict.get( 'os', None ) in [ 'linux' ]:
+            return 'ImageJ-linux64'
+    return None
+    
+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 ' %.1f' % val
+        elif val_type == 'int':
+            return ' %d' % val
+    return ' %s' % val
+
+def stop_err( msg ):
+    sys.stderr.write( msg )
+    sys.exit( 1 )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/imagej2_macros.xml	Wed Aug 26 14:36:17 2015 -0400
@@ -0,0 +1,121 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<macros>
+    <token name="@WRAPPER_VERSION@">1.0</token>
+    <xml name="fiji_requirements">
+        <requirements>
+            <requirement type="package" version="20141125">fiji</requirement>
+        </requirements>
+    </xml>
+    <xml name="python_bioformats_requirements">
+        <requirements>
+            <requirement type="package" version="20141125">fiji</requirement>
+            <requirement type="package" version="1.0.11">javabridge</requirement>
+            <requirement type="package" version="1.9">numpy</requirement>
+            <requirement type="package" version="1.0.4">python_bioformats</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) with ImageJ2**
+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>
+    <xml name="bioformats_fiji_javabridge_citations">
+        <citations>
+            <citation type="doi">10.1038/nmeth.2102</citation>
+            <citation type="doi">10.1038/nmeth.2019</citation>
+            <citation type="doi">10.1083/jcb.201004104</citation>
+        </citations>
+    </xml>
+</macros>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jython_script.py	Wed Aug 26 14:36:17 2015 -0400
@@ -0,0 +1,72 @@
+import jython_utils
+import sys
+from ij import IJ
+from ij.plugin.filter import Analyzer
+
+# Fiji Jython interpreter implements Python 2.5 which does not
+# provide support for argparse.
+error_log = sys.argv[ -14 ]
+input = sys.argv[ -13 ]
+black_background = jython_utils.asbool( sys.argv[ -12 ] )
+size = sys.argv[ -11 ]
+circularity_min = float( sys.argv[ -10 ] )
+circularity_max = float( sys.argv[ -9 ] )
+show = sys.argv[ -8 ]
+display_results = jython_utils.asbool( sys.argv[ -7 ] )
+all_results = jython_utils.asbool( sys.argv[ -6 ] )
+exclude_edges = jython_utils.asbool( sys.argv[ -5 ] )
+include_holes = jython_utils.asbool( sys.argv[ -4 ] )
+tmp_output_path = sys.argv[ -3 ]
+output_datatype = sys.argv[ -2 ]
+results_path = 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()
+analyzer = Analyzer( input_image_plus_copy )
+
+try:
+    # Set binary options.
+    options = jython_utils.get_binary_options( black_background=black_background )
+    IJ.run( input_image_plus_copy, "Options...", options )
+
+    # 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", "" )
+
+    # Set the options.
+    options = [ 'size=%s' % size ]
+    circularity_str = '%.3f-%.3f' % ( circularity_min, circularity_max )
+    options.append( 'circularity=%s' % circularity_str )
+    if show.find( '_' ) >= 0:
+        show_str = '[%s]' % show.replace( '_', ' ' )
+    else:
+        show_str = show
+    options.append( 'show=%s' % show_str )
+    if display_results:
+        options.append( 'display' )
+        if not all_results:
+            options.append( 'summarize' )
+    if exclude_edges:
+        options.append( 'exclude' )
+    if include_holes:
+        options.append( 'include' )
+    # Always run "in_situ".
+    options.append( 'in_situ' )
+
+    # Run the command.
+    IJ.run( input_image_plus_copy, "Analyze Particles...", " ".join( options ) )
+
+    # Save outputs.
+    if tmp_output_path not in [ None, 'None' ]:
+        # Save the ImagePlus object as a new image.
+        IJ.saveAs( input_image_plus_copy, output_datatype, tmp_output_path )
+    if display_results and results_path not in [ None, 'None' ]:
+        results_table = analyzer.getResultsTable()
+        results_table.saveAs( results_path )
+except Exception, e:
+    jython_utils.handle_error( error_log, str( e ) )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jython_utils.py	Wed Aug 26 14:36:17 2015 -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/analyze_particles_masks.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/analyze_particles_nothing.tabular	Wed Aug 26 14:36:17 2015 -0400
@@ -0,0 +1,48 @@
+ 	Area	Mean	Min	Max
+1	86	0	0	0
+2	72	0	0	0
+3	25	0	0	0
+4	85	0	0	0
+5	157	0	0	0
+6	207	0	0	0
+7	29	0	0	0
+8	73	0	0	0
+9	143	0	0	0
+10	125	0	0	0
+11	159	0	0	0
+12	133	0	0	0
+13	85	0	0	0
+14	51	0	0	0
+15	133	0	0	0
+16	133	0	0	0
+17	81	0	0	0
+18	88	0	0	0
+19	212	0	0	0
+20	116	0	0	0
+21	172	0	0	0
+22	103	0	0	0
+23	4	0	0	0
+24	60	0	0	0
+25	198	0	0	0
+26	187	0	0	0
+27	80	0	0	0
+28	75	0	0	0
+29	103	0	0	0
+30	52	0	0	0
+31	122	0	0	0
+32	129	0	0	0
+33	77	0	0	0
+34	171	0	0	0
+35	117	0	0	0
+36	207	0	0	0
+37	119	0	0	0
+38	181	0	0	0
+39	49	0	0	0
+40	150	0	0	0
+41	191	0	0	0
+42	170	0	0	0
+43	174	0	0	0
+44	270	0	0	0
+45	87	0	0	0
+46	69	0	0	0
+47	1	0	0	0
Binary file test-data/analyze_particles_outlines.gif has changed
Binary file test-data/blobs_watershed_binary.gif has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tool_dependencies.xml	Wed Aug 26 14:36:17 2015 -0400
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<tool_dependency>
+    <package name="fiji" version="20141125">
+        <repository changeset_revision="9dbe89105a25" name="package_fiji_20141125" owner="iuc" toolshed="https://testtoolshed.g2.bx.psu.edu" />
+    </package>
+</tool_dependency>