Пример #1
0
import scyjava_config
scyjava_config.add_options('-Xmx8g')

import imagej
import multiprocessing as mp
import os
from pathlib import Path

from importlib.resources import open_text

if __package__:
    with open_text(__package__, "Fuse.ijm") as macro:
        STITCHING_MACRO = macro.read()
else:
    with open("Fuse.ijm") as macro:
        STITCHING_MACRO = macro.read()


def stitch(input_directory, input_filename, output_directory, imagej_instance):
    """
    Use the BigStitcher plugin to stitch together the series at input_filepath.

    See Fuse.ijm for the ImageJ macro code.

    Args:
      input_directory (Union[str, pathlib.Path]):
        Directory in which input microscopy file is located.
      input_filename (str):
        Name of microscopy file (excluding file extension).
      output_directory (Union[str, pathlib.Path]):
        Directory in which to output stitched images,
Пример #2
0
def init(ij_dir_or_version_or_endpoint=None,
         headless=True,
         new_instance=False):
    """
    Initialize the ImageJ environment.

    :param ij_dir_or_version_or_endpoint:
        Path to a local ImageJ installation (e.g. /Applications/Fiji.app),
        OR version of net.imagej:imagej artifact to launch (e.g. 2.0.0-rc-67),
        OR endpoint of another artifact (e.g. sc.fiji:fiji) that uses imagej.
        OR list of Maven artifacts to include (e.g. ['net.imagej:imagej-legacy', 'net.preibisch:BigStitcher'])
    :param headless: Whether to start the JVM in headless or gui mode.
    :param new_instance: If JVM is already running, setting this parameter to
        True will create a new ImageJ instance.
    :return: an instance of the net.imagej.ImageJ gateway
    """

    global ij

    if jnius_config.vm_running and not new_instance:
        _logger.warning('The JVM is already running.')
        return ij

    if not jnius_config.vm_running:

        if headless:
            scyjava_config.add_options('-Djava.awt.headless=true')

        if ij_dir_or_version_or_endpoint is None:
            # Use latest release of ImageJ.
            _logger.debug('Using newest ImageJ release')
            scyjava_config.add_endpoints('net.imagej:imagej')

        elif isinstance(ij_dir_or_version_or_endpoint, list):
            # Assume that this is a list of Maven endpoints
            endpoint = '+'.join(ij_dir_or_version_or_endpoint)
            _logger.debug('List of Maven coordinates given: %s',
                          ij_dir_or_version_or_endpoint)
            scyjava_config.add_endpoints(endpoint)

        elif os.path.isdir(ij_dir_or_version_or_endpoint):
            # Assume path to local ImageJ installation.
            path = ij_dir_or_version_or_endpoint
            _logger.debug('Local path to ImageJ installation given: %s', path)
            num_jars = set_ij_env(path)
            _logger.info("Added " + str(num_jars + 1) +
                         " JARs to the Java classpath.")
            plugins_dir = str(Path(path, 'plugins'))
            scyjava_config.add_options('-Dplugins.dir=' + plugins_dir)

        elif re.match('^(/|[A-Za-z]:)', ij_dir_or_version_or_endpoint):
            # Looks like a file path was intended, but it's not a folder.
            path = ij_dir_or_version_or_endpoint
            _logger.error('Local path given is not a directory: %s', path)
            return False

        elif ':' in ij_dir_or_version_or_endpoint:
            # Assume endpoint of an artifact.
            # Strip out white spaces
            endpoint = ij_dir_or_version_or_endpoint.replace(" ", "")
            _logger.debug('Maven coordinate given: %s', endpoint)
            scyjava_config.add_endpoints(endpoint)

        else:
            # Assume version of net.imagej:imagej.
            version = ij_dir_or_version_or_endpoint
            _logger.debug('ImageJ version given: %s', version)
            scyjava_config.add_endpoints('net.imagej:imagej:' + version)

    # Must import imglyb (not scyjava) to spin up the JVM now.
    import imglyb
    from jnius import autoclass, JavaException, cast
    import scyjava

    # Initialize ImageJ.
    ImageJ = autoclass('net.imagej.ImageJ')
    ij = ImageJ()

    # Append some useful utility functions to the ImageJ gateway.

    from scyjava import jclass, isjava, to_java, to_python

    Dataset = autoclass('net.imagej.Dataset')
    ImgPlus = autoclass('net.imagej.ImgPlus')
    Img = autoclass('net.imglib2.img.Img')
    RandomAccessibleInterval = autoclass(
        'net.imglib2.RandomAccessibleInterval')
    Axes = autoclass('net.imagej.axis.Axes')
    Double = autoclass('java.lang.Double')

    # EnumeratedAxis is a new axis made for xarray, so is only present in ImageJ versions that are released
    # later than March 2020.  This check defaults to LinearAxis instead if Enumerated does not work.
    try:
        EnumeratedAxis = autoclass('net.imagej.axis.EnumeratedAxis')
    except JavaException:
        DefaultLinearAxis = autoclass('net.imagej.axis.DefaultLinearAxis')

        def EnumeratedAxis(axis_type, values):
            origin = values[0]
            scale = values[1] - values[0]
            axis = DefaultLinearAxis(axis_type, scale, origin)
            return axis

    # Try to define the legacy service, and create a dummy method if it doesn't exist.
    try:
        LegacyService = autoclass('net.imagej.legacy.LegacyService')
        legacyService = cast(LegacyService,
                             ij.get("net.imagej.legacy.LegacyService"))
    except JavaException:

        class LegacyService:
            def isActive(self):
                return False

        legacyService = LegacyService()

    # Create a method to get the legacy service that is similar to other ImageJ services
    def legacy():
        try:
            legacyService = cast(LegacyService,
                                 ij.get('net.imagej.legacy.LegacyService'))
        except JavaException:
            legacyService = LegacyService()
        return legacyService

    setattr(ij, 'legacy', legacy)

    if legacyService.isActive():
        WindowManager = autoclass('ij.WindowManager')
    else:

        class WindowManager:
            def getCurrentImage(self):
                """
                Throw an error saying IJ1 is not available
                :return:
                """
                raise ImportError(
                    "Your ImageJ installation does not support IJ1.  This function does not work."
                )

        WindowManager = WindowManager()

    class ImageJPython:
        def __init__(self, ij):
            self._ij = ij

        def dims(self, image):
            """
            Return the dimensions of the equivalent numpy array for the image.  Reverse dimension order from Java.
            """
            if self._is_arraylike(image):
                return image.shape
            if not isjava(image):
                raise TypeError('Unsupported type: ' + str(type(image)))
            if jclass('net.imglib2.Dimensions').isInstance(image):
                return [
                    image.dimension(d)
                    for d in range(image.numDimensions() - 1, -1, -1)
                ]
            if jclass('ij.ImagePlus').isInstance(image):
                dims = image.getDimensions()
                dims.reverse()
                dims = [dim for dim in dims if dim > 1]
                return dims
            raise TypeError('Unsupported Java type: ' +
                            str(jclass(image).getName()))

        def dtype(self, image_or_type):
            """
            Return the dtype of the equivalent numpy array for the given image or type.
            """
            if type(image_or_type) == numpy.dtype:
                return image_or_type
            if self._is_arraylike(image_or_type):
                return image_or_type.dtype
            if not isjava(image_or_type):
                raise TypeError('Unsupported type: ' +
                                str(type(image_or_type)))

            # -- ImgLib2 types --
            if jclass('net.imglib2.type.Type').isInstance(image_or_type):
                ij2_types = {
                    'net.imglib2.type.logic.BitType': 'bool',
                    'net.imglib2.type.numeric.integer.ByteType': 'int8',
                    'net.imglib2.type.numeric.integer.ShortType': 'int16',
                    'net.imglib2.type.numeric.integer.IntType': 'int32',
                    'net.imglib2.type.numeric.integer.LongType': 'int64',
                    'net.imglib2.type.numeric.integer.UnsignedByteType':
                    'uint8',
                    'net.imglib2.type.numeric.integer.UnsignedShortType':
                    'uint16',
                    'net.imglib2.type.numeric.integer.UnsignedIntType':
                    'uint32',
                    'net.imglib2.type.numeric.integer.UnsignedLongType':
                    'uint64',
                    'net.imglib2.type.numeric.real.FloatType': 'float32',
                    'net.imglib2.type.numeric.real.DoubleType': 'float64',
                }
                for c in ij2_types:
                    if jclass(c).isInstance(image_or_type):
                        return numpy.dtype(ij2_types[c])
                raise TypeError(
                    'Unsupported ImgLib2 type: {}'.format(image_or_type))

            # -- ImgLib2 images --
            if jclass('net.imglib2.IterableInterval').isInstance(
                    image_or_type):
                ij2_type = image_or_type.firstElement()
                return self.dtype(ij2_type)
            if jclass('net.imglib2.RandomAccessibleInterval').isInstance(
                    image_or_type):
                Util = autoclass('net.imglib2.util.Util')
                ij2_type = Util.getTypeFromInterval(image_or_type)
                return self.dtype(ij2_type)

            # -- ImageJ1 images --
            if jclass('ij.ImagePlus').isInstance(image_or_type):
                ij1_type = image_or_type.getType()
                ImagePlus = autoclass('ij.ImagePlus')
                ij1_types = {
                    ImagePlus.GRAY8: 'uint8',
                    ImagePlus.GRAY16: 'uint16',
                    ImagePlus.GRAY32:
                    'float32',  # NB: ImageJ1's 32-bit type is float32, not uint32.
                }
                for t in ij1_types:
                    if ij1_type == t:
                        return numpy.dtype(ij1_types[t])
                raise TypeError(
                    'Unsupported ImageJ1 type: {}'.format(ij1_type))

            raise TypeError('Unsupported Java type: ' +
                            str(jclass(image_or_type).getName()))

        def new_numpy_image(self, image):
            """
            Creates a numpy image (NOT a Java image) dimensioned the same as
            the given image, and with the same pixel type as the given image.
            """
            try:
                dtype_to_use = self.dtype(image)
            except TypeError:
                dtype_to_use = numpy.dtype('float64')
            return numpy.zeros(self.dims(image), dtype=dtype_to_use)

        def rai_to_numpy(self, rai):
            """
            Convert a RandomAccessibleInterval into a numpy array
            """
            result = self.new_numpy_image(rai)
            self._ij.op().run("copy.rai", self.to_java(result), rai)
            return result

        def run_plugin(self, plugin, args=None, ij1_style=True):
            """
            Run an ImageJ plugin
            :param plugin: The string name for the plugin command
            :param args: A dict of macro arguments in key/value pairs
            :param ij1_style: Whether to use implicit booleans in IJ1 style or explicit booleans in IJ2 style
            :return: The plugin output
            """
            macro = self._assemble_plugin_macro(plugin,
                                                args=args,
                                                ij1_style=ij1_style)
            return self.run_macro(macro)

        def run_macro(self, macro, args=None):
            """
            Run an ImageJ1 style macro script
            :param macro: The macro code
            :param args: Arguments for the script as a dictionary of key/value pairs
            :return:
            """
            if not ij.legacy().isActive():
                raise ImportError(
                    "Your IJ endpoint does not support IJ1, and thus cannot use IJ1 macros."
                )

            try:
                if args is None:
                    return self._ij.script().run("macro.ijm", macro,
                                                 True).get()
                else:
                    return self._ij.script().run("macro.ijm", macro, True,
                                                 to_java(args)).get()
            except Exception as exc:
                _dump_exception(exc)
                raise exc

        def run_script(self, language, script, args=None):
            """
            Run a script in an IJ scripting language
            :param language: The file extension for the scripting language
            :param script: A string containing the script code
            :param args: Arguments for the script as a dictionary of key/value pairs
            :return:
            """
            script_lang = self._ij.script().getLanguageByName(language)
            if script_lang is None:
                script_lang = self._ij.script().getLanguageByExtension(
                    language)
            if script_lang is None:
                raise ValueError("Unknown script language: " + language)
            exts = script_lang.getExtensions()
            if exts.isEmpty():
                raise ValueError("Script language '" +
                                 script_lang.getLanguageName() +
                                 "' has no extensions")
            ext = exts.get(0)
            try:
                if args is None:
                    return self._ij.script().run("script." + ext, script,
                                                 True).get()
                return self._ij.script().run("script." + ext, script, True,
                                             to_java(args)).get()
            except Exception as exc:
                _dump_exception(exc)
                raise exc

        def to_java(self, data):
            """
            Converts the data into a java equivalent.  For numpy arrays, the java image points to the python array.

            In addition to the scyjava types, we allow ndarray-like and xarray-like variables
            """
            if self._is_memoryarraylike(data):
                return imglyb.to_imglib(data)
            if self._is_xarraylike(data):
                return self.to_dataset(data)
            return to_java(data)

        def to_dataset(self, data):
            """Converts the data into an ImageJ dataset"""
            if self._is_xarraylike(data):
                return self._xarray_to_dataset(data)
            if self._is_arraylike(data):
                return self._numpy_to_dataset(data)
            if scyjava.isjava(data):
                return self._java_to_dataset(data)

            raise TypeError(f'Type not supported: {type(data)}')

        def _numpy_to_dataset(self, data):
            rai = imglyb.to_imglib(data)
            return self._java_to_dataset(rai)

        def _ends_with_channel_axis(self, xarr):
            ends_with_axis = xarr.dims[len(xarr.dims) -
                                       1].lower() in ['c', 'channel']
            return ends_with_axis

        def _xarray_to_dataset(self, xarr):
            """
            Converts a xarray dataarray to a dataset, inverting C-style (slow axis first) to F-style (slow-axis last)
            :param xarr: Pass an xarray dataarray and turn into a dataset.
            :return: The dataset
            """
            if self._ends_with_channel_axis(xarr):
                vals = numpy.moveaxis(xarr.values, -1, 0)
                dataset = self._numpy_to_dataset(vals)
            else:
                dataset = self._numpy_to_dataset(xarr.values)
            axes = self._assign_axes(xarr)
            dataset.setAxes(axes)

            self._assign_dataset_metadata(dataset, xarr.attrs)

            return dataset

        def _assign_axes(self, xarr):
            """
            Obtain xarray axes names, origin, and scale and convert into ImageJ Axis; currently supports EnumeratedAxis
            :param xarr: xarray that holds the units
            :return: A list of ImageJ Axis with the specified origin and scale
            """
            axes = [''] * len(xarr.dims)

            for axis in xarr.dims:
                axis_str = self._pydim_to_ijdim(axis)

                ax_type = Axes.get(axis_str)
                ax_num = self._get_axis_num(xarr, axis)

                scale = self._get_scale(xarr.coords[axis])
                if scale is None:
                    logging.warning(
                        f"The {ax_type.label} axis is non-numeric and is translated to a linear index."
                    )
                    doub_coords = [
                        Double(numpy.double(x))
                        for x in numpy.arange(len(xarr.coords[axis]))
                    ]
                else:
                    doub_coords = [
                        Double(numpy.double(x)) for x in xarr.coords[axis]
                    ]

                # EnumeratedAxis is a new axis made for xarray, so is only present in ImageJ versions that are released
                # later than March 2020.  This actually returns a LinearAxis if using an earlier version.
                java_axis = EnumeratedAxis(ax_type, ij.py.to_java(doub_coords))

                axes[ax_num] = java_axis

            return axes

        def _pydim_to_ijdim(self, axis):
            """Convert between the lowercase Python convention (x, y, z, c, t) to IJ (X, Y, Z, C, T)"""
            if str(axis) in ['x', 'y', 'z', 'c', 't']:
                return str(axis).upper()
            return str(axis)

        def _ijdim_to_pydim(self, axis):
            """Convert the IJ uppercase dimension convention (X, Y, Z C, T) to lowercase python (x, y, z, c, t) """
            if str(axis) in ['X', 'Y', 'Z', 'C', 'T']:
                return str(axis).lower()
            return str(axis)

        def _get_axis_num(self, xarr, axis):
            """
            Get the xarray -> java axis number due to inverted axis order for C style numpy arrays (default)
            :param xarr: Xarray to convert
            :param axis: Axis number to convert
            :return: Axis idx in java
            """
            py_axnum = xarr.get_axis_num(axis)
            if numpy.isfortran(xarr.values):
                return py_axnum

            if self._ends_with_channel_axis(xarr):
                if axis == len(xarr.dims) - 1:
                    return axis
                else:
                    return len(xarr.dims) - py_axnum - 2
            else:
                return len(xarr.dims) - py_axnum - 1

        def _assign_dataset_metadata(self, dataset, attrs):
            """
            :param dataset: ImageJ Java dataset
            :param attrs: Dictionary containing metadata
            """
            dataset.getProperties().putAll(self.to_java(attrs))

        def _get_origin(self, axis):
            """
            Get the coordinate origin of an axis, assuming it is the first entry.
            :param axis: A 1D list like entry accessible with indexing, which contains the axis coordinates
            :return: The origin for this axis.
            """
            return axis.values[0]

        def _get_scale(self, axis):
            """
            Get the scale of an axis, assuming it is linear and so the scale is simply second - first coordinate.
            :param axis: A 1D list like entry accessible with indexing, which contains the axis coordinates
            :return: The scale for this axis or None if it is a non-numeric scale.
            """
            try:
                return axis.values[1] - axis.values[0]
            except TypeError:
                return None

        def _java_to_dataset(self, data):
            """
            Converts the data into a ImageJ Dataset
            """
            # This try checking is necessary because the set of ImageJ converters is not complete.  E.g., here is no way
            # to directly go from Img to Dataset, instead you need to chain the Img->ImgPlus->Dataset converters.
            try:
                if self._ij.convert().supports(data, Dataset):
                    return self._ij.convert().convert(data, Dataset)
                if self._ij.convert().supports(data, ImgPlus):
                    imgPlus = self._ij.convert().convert(data, ImgPlus)
                    return self._ij.dataset().create(imgPlus)
                if self._ij.convert().supports(data, Img):
                    img = self._ij.convert().convert(data, Img)
                    return self._ij.dataset().create(ImgPlus(img))
                if self._ij.convert().supports(data, RandomAccessibleInterval):
                    rai = self._ij.convert().convert(data,
                                                     RandomAccessibleInterval)
                    return self._ij.dataset().create(rai)
            except Exception as exc:
                _dump_exception(exc)
                raise exc
            raise TypeError('Cannot convert to dataset: ' + str(type(data)))

        def from_java(self, data):
            """
            Converts the data into a python equivalent
            """
            # todo: convert a datset to xarray

            if not isjava(data): return data
            try:
                if self._ij.convert().supports(data, Dataset):
                    # HACK: Converter exists for ImagePlus -> Dataset, but not ImagePlus -> RAI.
                    data = self._ij.convert().convert(data, Dataset)
                    return self._dataset_to_xarray(data)
                if self._ij.convert().supports(data, RandomAccessibleInterval):
                    rai = self._ij.convert().convert(data,
                                                     RandomAccessibleInterval)
                    return self.rai_to_numpy(rai)
            except Exception as exc:
                _dump_exception(exc)
                raise exc
            return to_python(data)

        def _dataset_to_xarray(self, dataset):
            """
            Converts an ImageJ dataset into an xarray, inverting F-style (slow idx last) to C-style (slow idx first)
            :param dataset: ImageJ dataset
            :return: xarray with reversed (C-style) dims and coords as labeled by the dataset
            """
            attrs = self._ij.py.from_java(dataset.getProperties())
            axes = [(cast('net.imagej.axis.CalibratedAxis', dataset.axis(idx)))
                    for idx in range(dataset.numDimensions())]

            dims = [
                self._ijdim_to_pydim(axes[idx].type().getLabel())
                for idx in range(len(axes))
            ]
            values = self.rai_to_numpy(dataset)
            coords = self._get_axes_coords(
                axes, dims, numpy.shape(numpy.transpose(values)))

            if dims[len(dims) - 1].lower() in ['c', 'channel']:
                xarr_dims = self._invert_except_last_element(dims)
                values = numpy.moveaxis(values, 0, -1)
            else:
                xarr_dims = list(reversed(dims))

            xarr = xr.DataArray(values,
                                dims=xarr_dims,
                                coords=coords,
                                attrs=attrs)
            return xarr

        def _invert_except_last_element(self, lst):
            """
            Invert a list except for the last element.
            :param lst:
            :return:
            """
            cut_list = lst[0:-1]
            reverse_cut = list(reversed(cut_list))
            reverse_cut.append(lst[-1])
            return reverse_cut

        def _get_axes_coords(self, axes, dims, shape):
            """
            Get xarray style coordinate list dictionary from a dataset
            :param axes: List of ImageJ axes
            :param dims: List of axes labels for each dataset axis
            :param shape: F-style, or reversed C-style, shape of axes numpy array.
            :return: Dictionary of coordinates for each axis.
            """
            coords = {
                dims[idx]: [
                    axes[idx].calibratedValue(position)
                    for position in range(shape[idx])
                ]
                for idx in range(len(dims))
            }
            return coords

        def show(self, image, cmap=None):
            """
            Display a java or python 2D image.
            :param image: A java or python image that can be converted to a numpy array
            :param cmap: The colormap of the image, if it is not RGB
            :return:
            """
            if image is None:
                raise TypeError('Image must not be None')

            # NB: Import this only here on demand, rather than above.
            # Otherwise, some headless systems may experience errors
            # like "ImportError: Failed to import any qt binding".
            from matplotlib import pyplot

            pyplot.imshow(self.from_java(image),
                          interpolation='nearest',
                          cmap=cmap)
            pyplot.show()

        def _is_arraylike(self, arr):
            return hasattr(arr, 'shape') and \
                hasattr(arr, 'dtype') and \
                hasattr(arr, '__array__') and \
                hasattr(arr, 'ndim')

        def _is_memoryarraylike(self, arr):
            return self._is_arraylike(arr) and \
                hasattr(arr, 'data') and \
                type(arr.data).__name__ == 'memoryview'

        def _is_xarraylike(self, xarr):
            return hasattr(xarr, 'values') and \
                hasattr(xarr, 'dims') and \
                hasattr(xarr, 'coords') and \
                self._is_arraylike(xarr.values)

        def _assemble_plugin_macro(self,
                                   plugin: str,
                                   args=None,
                                   ij1_style=True):
            """
            Assemble an ImageJ macro string given a plugin to run and optional arguments in a dict
            :param plugin: The string call for the function to run
            :param args: A dict of macro arguments in key/value pairs
            :param ij1_style: Whether to use implicit booleans in IJ1 style or explicit booleans in IJ2 style
            :return: A string version of the macro run
            """
            if args is None:
                macro = "run(\"{}\");".format(plugin)
                return macro
            macro = """run("{0}", \"""".format(plugin)
            for key, value in args.items():
                argument = self._format_argument(key, value, ij1_style)
                if argument is not None:
                    macro = macro + ' {}'.format(argument)
            macro = macro + """\");"""
            return macro

        def _format_argument(self, key, value, ij1_style):
            if value is True:
                argument = '{}'.format(key)
                if not ij1_style:
                    argument = argument + '=true'
            elif value is False:
                argument = None
                if not ij1_style:
                    argument = '{0}=false'.format(key)
            elif value is None:
                raise NotImplementedError(
                    'Conversion for None is not yet implemented')
            else:
                val_str = self._format_value(value)
                argument = '{0}={1}'.format(key, val_str)
            return argument

        def _format_value(self, value):
            temp_value = str(value).replace('\\', '/')
            if temp_value.startswith('[') and temp_value.endswith(']'):
                return temp_value
            final_value = '[' + temp_value + ']'
            return final_value

        def window_manager(self):
            """
            Get the ImageJ1 window manager if legacy mode is enabled.  It may not work properly if in headless mode.
            :return: WindowManager
            """
            if not ij.legacy_enabled:
                raise ImportError(
                    "Your ImageJ installation does not support IJ1.  This function does not work."
                )
            elif ij.ui().isHeadless():
                logging.warning(
                    "Operating in headless mode - The WindowManager will not be fully funtional."
                )
            else:
                return WindowManager

        def active_xarray(self, sync=True):
            """
            Convert the active image to a xarray.DataArray, synchronizing from IJ1 -> IJ2
            :param sync: Manually synchronize the current IJ1 slice if True
            :return: numpy array containing the image data
            """
            # todo: make the behavior use pure IJ2 if legacy is not active

            if ij.legacy().isActive():
                imp = self.active_image_plus(sync=sync)
                return self._ij.py.from_java(imp)
            else:
                dataset = self.active_dataset()
                return self._ij.py.from_java(dataset)

        def active_dataset(self):
            """Get the currently active Dataset from the Dataset service"""
            return self._ij.imageDisplay().getActiveDataset()

        def active_image_plus(self, sync=True):
            """
            Get the currently active IJ1 image, optionally synchronizing from IJ1 -> IJ2
            :param sync: Manually synchronize the current IJ1 slice if True
            :return: The ImagePlus corresponding to the active image
            """
            imp = WindowManager.getCurrentImage()
            if sync:
                self.synchronize_ij1_to_ij2(imp)
            return imp

        def synchronize_ij1_to_ij2(self, imp):
            """
            Synchronize between a Dataset or ImageDisplay linked to an ImagePlus by accepting the ImagePlus data as true
            :param imp: The IJ1 ImagePlus that needs to be synchronized
            """
            # This code is necessary because an ImagePlus can sometimes be modified without modifying the
            # linked Dataset/ImageDisplay.  This happens when someone uses the ImageProcessor of the ImagePlus to change
            # values on a slice.  The imagej-legacy layer does not synchronize when this happens to prevent
            # significant overhead, as otherwise changing a single pixel would mean syncing a whole slice.  The
            # ImagePlus also has a stack, which in the legacy case links to the Dataset/ImageDisplay.  This stack is
            # updated by the legacy layer when you change slices, using ImageJVirtualStack.setPixelsZeroBasedIndex().
            # As such, we only need to make sure that the current 2D image slice is up to date.  We do this by manually
            # setting the stack to be the same as the imageprocessor.
            stack = imp.getStack()
            pixels = imp.getProcessor().getPixels()
            # Don't sync if the ImagePlus is not linked back to a corresponding dataset
            if str(type(pixels)) == '<class \'jnius.ByteArray\'>':
                return

            stack.setPixels(pixels, imp.getCurrentSlice())

    ij.py = ImageJPython(ij)

    # Forward stdout and stderr from Java to Python.

    from jnius import PythonJavaClass, java_method

    class JavaOutputListener(PythonJavaClass):
        __javainterfaces__ = ['org/scijava/console/OutputListener']

        @java_method('(Lorg/scijava/console/OutputEvent;)V')
        def outputOccurred(self, e):
            source = e.getSource().toString()
            output = e.getOutput()
            if source == 'STDOUT':
                sys.stdout.write(output)
            elif source == 'STDERR':
                sys.stderr.write(output)
            else:
                sys.stderr.write('[{}] {}'.format(source, output))

    ij.py._outputMapper = JavaOutputListener()
    ij.console().addOutputListener(ij.py._outputMapper)

    return ij
Пример #3
0
def init(ij_dir_or_version_or_endpoint=None,
         headless=True,
         new_instance=False):
    """
    Initialize the ImageJ environment.

    :param ij_dir_or_version_or_endpoint:
        Path to a local ImageJ installation (e.g. /Applications/Fiji.app),
        OR version of net.imagej:imagej artifact to launch (e.g. 2.0.0-rc-67),
        OR endpoint of another artifact (e.g. sc.fiji:fiji) that uses imagej.
    :param headless: Whether to start the JVM in headless or gui mode.
    :param new_instance: If JVM is already running, setting this parameter to
        True will create a new ImageJ instance.
    :return: an instance of the net.imagej.ImageJ gateway
    """

    global ij

    if jnius_config.vm_running and not new_instance:
        logging.warning('The JVM is already running.')
        return ij

    if not jnius_config.vm_running:

        if headless:
            scyjava_config.add_options('-Djava.awt.headless=true')

        if ij_dir_or_version_or_endpoint is None:
            # Use latest release of ImageJ.
            _logger.debug('Using newest ImageJ release')
            scyjava_config.add_endpoints('net.imagej:imagej')

        elif os.path.isdir(ij_dir_or_version_or_endpoint):
            # Assume path to local ImageJ installation.
            path = ij_dir_or_version_or_endpoint
            _logger.debug('Local path to ImageJ installation given: %s', path)
            num_jars = set_ij_env(path)
            _logger.info("Added " + str(num_jars + 1) +
                         " JARs to the Java classpath.")
            plugins_dir = str(Path(path, 'plugins'))
            scyjava_config.add_options('-Dplugins.dir=' + plugins_dir)

        elif ':' in ij_dir_or_version_or_endpoint:
            # Assume endpoint of an artifact.
            endpoint = ij_dir_or_version_or_endpoint
            _logger.debug('Maven coordinate given: %s', endpoint)
            scyjava_config.add_endpoints(endpoint)

        else:
            # Assume version of net.imagej:imagej.
            version = ij_dir_or_version_or_endpoint
            _logger.debug('ImageJ version given: %s', version)
            scyjava_config.add_endpoints('net.imagej:imagej:' + version)

    # Must import imglyb (not scyjava) to spin up the JVM now.
    import imglyb
    from jnius import autoclass

    # Initialize ImageJ.
    ImageJ = autoclass('net.imagej.ImageJ')
    ij = ImageJ()

    # Append some useful utility functions to the ImageJ gateway.

    from scyjava import jclass, isjava, to_java, to_python

    Dataset = autoclass('net.imagej.Dataset')
    RandomAccessibleInterval = autoclass(
        'net.imglib2.RandomAccessibleInterval')

    class ImageJPython:
        def __init__(self, ij):
            self._ij = ij

        def dims(self, image):
            """
            Return the dimensions of the equivalent numpy array for the image.  Reverse dimension order from Java.
            """
            if self._is_arraylike(image):
                return image.shape
            if not isjava(image):
                raise TypeError('Unsupported type: ' + str(type(image)))
            if jclass('net.imglib2.Dimensions').isInstance(image):
                return [
                    image.dimension(d)
                    for d in range(image.numDimensions() - 1, -1, -1)
                ]
            if jclass('ij.ImagePlus').isInstance(image):
                dims = image.getDimensions()
                dims.reverse()
                dims = [dim for dim in dims if dim > 1]
                return dims
            raise TypeError('Unsupported Java type: ' +
                            str(jclass(image).getName()))

        def new_numpy_image(self, image):
            """
            Creates a numpy image (NOT a Java image)
            dimensioned the same as the given image.
            """
            return numpy.zeros(self.dims(image))

        def rai_to_numpy(self, rai):
            """
            Convert a RandomAccessibleInterval into a numpy array
            """
            result = self.new_numpy_image(rai)
            self._ij.op().run("copy.rai", self.to_java(result), rai)
            return result

        def run_plugin(self, plugin, args=None, ij1_style=True):
            """
            Run an ImageJ plugin
            :param plugin: The string name for the plugin command
            :param args: A dict of macro arguments in key/value pairs
            :param ij1_style: Whether to use implicit booleans in IJ1 style or explicit booleans in IJ2 style
            :return: The plugin output
            """
            macro = self._assemble_plugin_macro(plugin,
                                                args=args,
                                                ij1_style=ij1_style)
            return self.run_macro(macro)

        def run_macro(self, macro, args=None):
            """
            Run an ImageJ1 style macro script
            :param macro: The macro code
            :param args: Arguments for the script as a dictionary of key/value pairs
            :return:
            """
            try:
                if args is None:
                    return self._ij.script().run("macro.ijm", macro,
                                                 True).get()
                else:
                    return self._ij.script().run("macro.ijm", macro, True,
                                                 to_java(args)).get()
            except Exception as exc:
                _dump_exception(exc)
                raise exc

        def run_script(self, language, script, args=None):
            """
            Run a script in an IJ scripting language
            :param language: The file extension for the scripting language
            :param script: A string containing the script code
            :param args: Arguments for the script as a dictionary of key/value pairs
            :return:
            """
            script_lang = self._ij.script().getLanguageByName(language)
            if script_lang is None:
                script_lang = self._ij.script().getLanguageByExtension(
                    language)
            if script_lang is None:
                raise ValueError("Unknown script language: " + language)
            exts = script_lang.getExtensions()
            if exts.isEmpty():
                raise ValueError("Script language '" +
                                 script_lang.getLanguageName() +
                                 "' has no extensions")
            ext = exts.get(0)
            try:
                if args is None:
                    return self._ij.script().run("script." + ext, script,
                                                 True).get()
                return self._ij.script().run("script." + ext, script, True,
                                             to_java(args)).get()
            except Exception as exc:
                _dump_exception(exc)
                raise exc

        def to_java(self, data):
            """
            Converts the data into a java equivalent.  For numpy arrays, the java image points to the python array
            """
            if self._is_memoryarraylike(data):
                return imglyb.to_imglib(data)
            return to_java(data)

        def to_dataset(self, data):
            """
            Converts the data into a ImageJ Dataset
            """
            try:
                if self._ij.convert().supports(data, Dataset):
                    return self._ij.convert().convert(data, Dataset)
            except Exception as exc:
                _dump_exception(exc)
                raise exc
            raise TypeError('Cannot convert to dataset: ' + str(type(data)))

        def from_java(self, data):
            """
            Converts the data into a python equivalent
            """
            if not isjava(data): return data
            try:
                if self._ij.convert().supports(data, Dataset):
                    # HACK: Converter exists for ImagePlus -> Dataset, but not ImagePlus -> RAI.
                    data = self._ij.convert().convert(data, Dataset)
                if (self._ij.convert().supports(data,
                                                RandomAccessibleInterval)):
                    rai = self._ij.convert().convert(data,
                                                     RandomAccessibleInterval)
                    return self.rai_to_numpy(rai)
            except Exception as exc:
                _dump_exception(exc)
                raise exc
            return to_python(data)

        def show(self, image, cmap=None):
            """
            Display a java or python 2D image.
            :param image: A java or python image that can be converted to a numpy array
            :param cmap: The colormap of the image, if it is not RGB
            :return:
            """
            if image is None:
                raise TypeError('Image must not be None')

            # NB: Import this only here on demand, rather than above.
            # Otherwise, some headless systems may experience errors
            # like "ImportError: Failed to import any qt binding".
            from matplotlib import pyplot

            pyplot.imshow(self.from_java(image),
                          interpolation='nearest',
                          cmap=cmap)
            pyplot.show()

        def _is_arraylike(self, arr):
            return hasattr(arr, 'shape') and \
                hasattr(arr, 'dtype') and \
                hasattr(arr, '__array__') and \
                hasattr(arr, 'ndim')

        def _is_memoryarraylike(self, arr):
            return self._is_arraylike(arr) and \
                hasattr(arr, 'data') and \
                type(arr.data).__name__ == 'memoryview'

        def _assemble_plugin_macro(self,
                                   plugin: str,
                                   args=None,
                                   ij1_style=True):
            """
            Assemble an ImageJ macro string given a plugin to run and optional arguments in a dict
            :param plugin: The string call for the function to run
            :param args: A dict of macro arguments in key/value pairs
            :param ij1_style: Whether to use implicit booleans in IJ1 style or explicit booleans in IJ2 style
            :return: A string version of the macro run
            """
            if args is None:
                macro = "run(\"{}\");".format(plugin)
                return macro
            macro = """run("{0}", \"""".format(plugin)
            for key, value in args.items():
                argument = self._format_argument(key, value, ij1_style)
                if argument is not None:
                    macro = macro + ' {}'.format(argument)
            macro = macro + """\");"""
            return macro

        def _format_argument(self, key, value, ij1_style):
            if value is True:
                argument = '{}'.format(key)
                if not ij1_style:
                    argument = argument + '=true'
            elif value is False:
                argument = None
                if not ij1_style:
                    argument = '{0}=false'.format(key)
            elif value is None:
                raise NotImplementedError(
                    'Conversion for None is not yet implemented')
            else:
                val_str = self._format_value(value)
                argument = '{0}={1}'.format(key, val_str)
            return argument

        def _format_value(self, value):
            temp_value = str(value).replace('\\', '/')
            if temp_value.startswith('[') and temp_value.endswith(']'):
                return temp_value
            final_value = '[' + temp_value + ']'
            return final_value

    ij.py = ImageJPython(ij)
    return ij