Source code for neuroglancer_scripts.downscaling

#! /usr/bin/env python3
#
# Copyright (c) 2016, 2017, 2018 Forschungszentrum Juelich GmbH
# Author: Yann Leprince <y.leprince@fz-juelich.de>
#
# This software is made available under the MIT licence, see LICENCE.txt.

"""Downscaling is used to create a multi-resolution image pyramid.

The central component here is the :class:`Downscaler` base class. Use
:func:`get_downscaler` for instantiating a concrete downscaler object.
"""

import numpy as np

from neuroglancer_scripts.data_types import get_chunk_dtype_transformer
from neuroglancer_scripts.utils import ceil_div

__all__ = [
    "get_downscaler",
    "add_argparse_options",
    "Downscaler",
    "StridingDownscaler",
    "AveragingDownscaler",
    "MajorityDownscaler",
]


[docs] def get_downscaler(downscaling_method, info=None, options={}): """Create a downscaler object. :param str downscaling_method: one of ``"average"``, ``"majority"``, or ``"stride"`` :param dict options: options passed to the downscaler as kwargs. :returns: an instance of a sub-class of :class:`Downscaler` :rtype: Downscaler """ if downscaling_method == "auto": if info["type"] == "image": return get_downscaler("average", info=None, options=options) else: # info["type"] == "segmentation": return get_downscaler("stride", info=None, options=options) elif downscaling_method == "average": outside_value = options.get("outside_value") return AveragingDownscaler(outside_value) elif downscaling_method == "majority": return MajorityDownscaler() elif downscaling_method == "stride": return StridingDownscaler() else: raise NotImplementedError("invalid downscaling method " + downscaling_method)
[docs] def add_argparse_options(parser): """Add command-line options for downscaling. :param argparse.ArgumentParser parser: an argument parser The downscaling options can be obtained from command-line arguments with :func:`add_argparse_options` and passed to :func:`get_downscaler`:: import argparse parser = argparse.ArgumentParser() add_argparse_options(parser) args = parser.parse_args() get_downscaler(args.downscaling_method, vars(args)) """ group = parser.add_argument_group("Options for downscaling") group.add_argument("--downscaling-method", default="auto", choices=("auto", "average", "majority", "stride"), help='The default is "auto", which chooses ' '"average" or "stride" depending on the "type" ' 'attribute of the dataset (for "image" or ' '"segmentation", respectively). "average" is ' 'recommended for grey-level images. "majority" is a ' 'high-quality, but very slow method for segmentation ' 'images. "stride" is fastest, but provides no ' 'protection against aliasing artefacts.') group.add_argument("--outside-value", type=float, default=None, help='padding value used by the "average" downscaling ' "method for computing the voxels at the border. If " "omitted, the volume is padded with its edge values.")
[docs] class Downscaler: """Base class for downscaling algorithms."""
[docs] def check_factors(self, downscaling_factors): """Test support for given downscaling factors. Subclasses must override this method if they do not support any combination of integer downscaling factors. :param downscaling_factors: sequence of integer downscaling factors (Dx, Dy, Dz) :type downscaling_factors: :class:`tuple` of :class:`int` :returns: whether the provided downscaling factors are supported :rtype: bool """ return ( len(downscaling_factors) == 3 and all(isinstance(f, int) and 1 <= f for f in downscaling_factors) )
[docs] def downscale(self, chunk, downscaling_factors): """Downscale a chunk according to the provided factors. :param numpy.ndarray chunk: chunk with (C, Z, Y, X) indexing :param downscaling_factors: sequence of integer downscaling factors (Dx, Dy, Dz) :type downscaling_factors: tuple :returns: the downscaled chunk, with shape ``(C, ceil_div(Z, Dz), ceil_div(Y, Dy), ceil_div(X, Dx))`` :rtype: numpy.ndarray :raises NotImplementedError: if the downscaling factors are unsupported """ raise NotImplementedError
[docs] class StridingDownscaler(Downscaler): """Downscale using striding. This is a fast, low-quality downscaler that provides no protection against aliasing artefacts. It supports arbitrary downscaling factors. """
[docs] def downscale(self, chunk, downscaling_factors): if not self.check_factors(downscaling_factors): raise NotImplementedError return chunk[:, ::downscaling_factors[2], ::downscaling_factors[1], ::downscaling_factors[0]]
[docs] class AveragingDownscaler(Downscaler): """Downscale by a factor of two in any given direction, with averaging. This downscaler is suitable for grey-level images. .. todo:: Use code from the neuroglancer module to support arbitrary factors. """ def __init__(self, outside_value=None): if outside_value is None: self.padding_mode = "edge" self.pad_kwargs = {} else: self.padding_mode = "constant" self.pad_kwargs = {"constant_values": outside_value}
[docs] def check_factors(self, downscaling_factors): return ( len(downscaling_factors) == 3 and all(f in (1, 2) for f in downscaling_factors) )
[docs] def downscale(self, chunk, downscaling_factors): if not self.check_factors(downscaling_factors): raise NotImplementedError dtype = chunk.dtype # Use a floating-point type for arithmetic work_dtype = np.promote_types(chunk.dtype, np.float64) chunk = chunk.astype(work_dtype, casting="safe") half = work_dtype.type(0.5) if downscaling_factors[2] == 2: if chunk.shape[1] % 2 != 0: chunk = np.pad(chunk, ((0, 0), (0, 1), (0, 0), (0, 0)), self.padding_mode, **self.pad_kwargs) chunk = half * (chunk[:, ::2, :, :] + chunk[:, 1::2, :, :]) if downscaling_factors[1] == 2: if chunk.shape[2] % 2 != 0: chunk = np.pad(chunk, ((0, 0), (0, 0), (0, 1), (0, 0)), self.padding_mode, **self.pad_kwargs) chunk = half * (chunk[:, :, ::2, :] + chunk[:, :, 1::2, :]) if downscaling_factors[0] == 2: if chunk.shape[3] % 2 != 0: chunk = np.pad(chunk, ((0, 0), (0, 0), (0, 0), (0, 1)), self.padding_mode, **self.pad_kwargs) chunk = half * (chunk[:, :, :, ::2] + chunk[:, :, :, 1::2]) dtype_converter = get_chunk_dtype_transformer(work_dtype, dtype, warn=False) return dtype_converter(chunk)
[docs] class MajorityDownscaler(Downscaler): """Downscaler using majority voting. This downscaler is suitable for label images. .. todo:: The majority downscaler could be *really* optimized (clever iteration with nditer, Cython, countless for appropriate cases) """
[docs] def downscale(self, chunk, downscaling_factors): if not self.check_factors(downscaling_factors): raise NotImplementedError new_chunk = np.empty( (chunk.shape[0], ceil_div(chunk.shape[1], downscaling_factors[2]), ceil_div(chunk.shape[2], downscaling_factors[1]), ceil_div(chunk.shape[3], downscaling_factors[0])), dtype=chunk.dtype ) for t, z, y, x in np.ndindex(*new_chunk.shape): zd = z * downscaling_factors[2] yd = y * downscaling_factors[1] xd = x * downscaling_factors[0] block = chunk[t, zd:(zd + downscaling_factors[2]), yd:(yd + downscaling_factors[1]), xd:(xd + downscaling_factors[0])] labels, counts = np.unique(block.flat, return_counts=True) new_chunk[t, z, y, x] = labels[np.argmax(counts)] return new_chunk