# -*- coding: utf-8 -*-
"""
Vasculature
===========
Expert vasculature image processing pipeline.
This module provides the basic routines for processing vasculature data.
The routines are used in the :mod:`ClearMap.Scripts.TubeMap` pipeline.
"""
__author__ = 'Christoph Kirst <christoph.kirst.ck@gmail.com>'
__license__ = 'GPLv3 - GNU General Pulic License v3 (see LICENSE.txt)'
__copyright__ = 'Copyright © 2020 by Christoph Kirst'
__webpage__ = 'https://idisco.info'
__download__ = 'https://www.github.com/ChristophKirst/ClearMap2'
  
import gc
import tempfile as tmpf
import numpy as np
import scipy.ndimage as ndi
import skimage.filters as skif
import ClearMap.IO.IO as io
from ClearMap.Utils.exceptions import MissingRequirementException
import ClearMap.ParallelProcessing.BlockProcessing as bp
import ClearMap.ParallelProcessing.DataProcessing.ArrayProcessing as ap
import ClearMap.ImageProcessing.Filter.Rank as rnk
import ClearMap.ImageProcessing.LocalStatistics as ls
import ClearMap.ImageProcessing.LightsheetCorrection as lc
import ClearMap.ImageProcessing.Differentiation.Hessian as hes
import ClearMap.ImageProcessing.Binary.Filling as bf
import ClearMap.ImageProcessing.Binary.Smoothing as bs
import ClearMap.Utils.Timer as tmr
from ClearMap.ImageProcessing.Experts.utils import initialize_sinks, run_step, print_params
from ClearMap.Utils.utilities import check_enough_temp_space, get_free_temp_space
###############################################################################
# ## Generic parameter
###############################################################################
MAX_BIN = 2**12
"""Number of intensity levels to use for the data after preprocessing.
Note
----
      * Higher values will increase the intensity resolution but slow down
        processing. 
      * 2**12 is a good choice for the vasculature data.
"""
DTYPE = 'uint16'
"""Data type for the data after preprocessing.
Note
----
      * The data type should fit numbers as big as the :const:`MAX_BIN` parameter. 
      * 'uint16' is a good choice for the vasculature data.
"""
BINARY_NAMES = ['High', 'Equalized', 'Deconvolved', 'Adaptive', 'Tube', 'Fill', 'Tracing']
"""Names for the multi-path binarization steps."""
BINARY_STATUS = {n: 2**k for k, n in enumerate(BINARY_NAMES)}
"""Binary representation for the multi-path binarization steps."""
     
###############################################################################
# ## Default parameter
###############################################################################
default_binarization_parameter = dict(
    # initial clipping and mask generation
    clip=dict(clip_range=(400, 60000),
              save=None),
    # lightsheet correction
    lightsheet=dict(percentile=0.25,
                    lightsheet=dict(selem=(150, 1, 1)),
                    background=dict(selem=(200, 200, 1),
                                    spacing=(25, 25, 1),
                                    step=(2, 2, 1),
                                    interpolate=1),
                    lightsheet_vs_background=2,
                    save=None),
    # median
    median=dict(selem=((3,)*3),
                save=None),
    # deconvolution
    deconvolve=dict(sigma=10,
                    threshold=750,
                    save=None),
    # equalization
    equalize=dict(percentile=(0.4, 0.975),
                  selem=(200, 200, 5),
                  spacing=(50, 50, 5),
                  interpolate=1,
                  threshold=1.1,
                  save=None),
    # adaptive threshold
    adaptive=dict(selem=(250, 250, 3),
                  spacing=(50, 50, 3),
                  interpolate=1,
                  save=None),
    # tubeness
    vesselize=dict(background=dict(selem=('disk', (30, 30, 1)),
                                   percentile=0.5),
                   tubeness=dict(sigma=1.0,
                                 gamma12=0.0),
                   threshold=120,
                   save=None),
    # fill
    fill=None,
    # smooth
    smooth=None,
    # controls
    binary_status=None,
    max_bin=MAX_BIN
)
"""Parameter for the vasculature binarization pipeline. 
See :func:`binarize` for details."""
default_binarization_processing_parameter = dict(
    size_max=40,
    size_min=5,
    overlap=0,
    axes=[2],
    optimization=True,
    optimization_fix='all',
    verbose=None,
    processes=None
)
"""Parallel processing parameter for the vasculature binarization pipeline. 
See :func:`ClearMap.ParallelProcessing.BlockProcessing.process`. for details."""       
                   
default_postprocessing_parameter = dict(
    # binary smoothing
    smooth=dict(iterations=6),
    # binary filling
    fill=True,
    # temporary file
    temporary_filename=None
)                   
"""Parameter for the postprocessing step of the binarized data.
See :func:`postprocess` for details."""
default_postprocessing_processing_parameter = dict(
    overlap=None,
    size_min=None,
    optimization=True,
    optimization_fix='all',
    as_memory=True
)
"""Parallel processing parameter for the vasculature postprocessing pipeline. 
See :func:`ClearMap.ParallelProcessing.BlockProcessing.process`. for details."""     
###############################################################################
# ## Binarization
###############################################################################
                   
[docs]
def binarize(source, sink=None, binarization_parameter=default_binarization_parameter,
             processing_parameter=default_binarization_processing_parameter):
    """
    Multi-path binarization of iDISCO+ cleared vasculature data.
    Arguments
    ---------
    source : source specification
        The source of the stitched raw data.
    sink : sink specification or None
        The sink to write the result to. If None, an array is returned.
    binarization_parameter : dict
        Parameter for the binarization. See below for details.
    processing_parameter : dict
        Parameter for the parallel processing.
        See :func:`ClearMap.ParallelProcessing.BlockProcesing.process` for
        description of all the parameter.
    Returns
    -------
    sink : Source
        The result of the binarization.
    Notes
    -----
    * The binarization pipeline is composed of several steps. The parameters for
        each step are passed as sub-dictionaries to the binarization_parameter
        dictionary.
    * If None is passed for one of the steps this step is skipped.
    * Each step also has an additional parameter 'save' that enables saving of
        the result of that step to a file to inspect the pipeline.
    General parameter
    -----------------
    binary_status : str or None
        File name to save the information about which part of the multi-path
        binarization contributed to the final result.
    max_bin : int
        Number of intensity levels to use for the data after preprocessing.
        Higher values will increase the intensity resolution but slow down
        processing.
        For the vasculature a typical value is 2**12.
    Clipping
    --------
    clip : dict or None
        Clipping and mask generation step parameter.
        clip_range : tuple
            The range to clip the raw data as (lowest, highest)
            Voxels above lowest define the foreground mask used
            in the following steps.
            For the vasculature a typical value is (400,60000).
        save : str or None
            Save the result of this step to the specified file if not None.
    See also :mod:`ClearMap.ImageProcessing.Clipping.Clipping`
    Lightsheet correction
    ---------------------
    lightsheet : dict or None
        Lightsheet correction step parameter.
        percentile : float
            Percentile in [0,1] used to estimate the lightsheet artifact.
            For the vasculature a typical value is 0.25.
        lightsheet : dict
            Parameter for the ligthsheet artifact percentile estimation.
            See :func:`ClearMap.ImageProcessing.LightsheetCorrection.correct_lightsheet`
            for list of all parameters. The crucial parameter is
            selem : tuple
                The structural element shape used to estimate the stripe artifact.
                It should match the typical length, width, and depth of the artifact
                in the data.
                For the vasculature a typical value is (150,1,1).
        background : dict
            Parameter for the background estimation in the light sheet correction.
            See :func:`ClearMap.ImageProcessing.LightsheetCorrection.correct_lightsheet`
            for list of all parameters. The crucial parameters are
            selem : tuple
                The structural element shape used to estimate the background.
                It should be bigger than the largest vessels,
                For the vasculature a typical value is (200,200,1).
            spacing : tuple
                The spacing to use to estimate the background. Larger spacings speed up
                processing but become less local estimates.
                For the vasculature a typical value is (25,25,1)
            step : tuple
                This parameter enables to subsample from the entire array defined by
                the structural element using larger than single voxel steps.
                For the vasculature a typical value is (2,2,1).
            interpolate : int
                The order of the interpolation used in constructing the full
                background estimate in case a non-trivial spacing is used.
                For the vasculature a typical value is 1.
        lightsheet_vs_background : float
            The background is multiplied by this weight before comparing to the
            lightsheet artifact estimate.
            For the vasculature a typical value is 2.
        save : str or None
            Save the result of this step to the specified file if not None.
    Median filter
    -------------
    median : dict or None
        Median correction step parameter.
        See :func:`ClearMap.ImageProcessing.Filter.Rank.median` for all parameter.
        The important parameters are
        selem : tuple
            The structural element size for the median filter.
            For the vasculature a typical value is (3,3,3).
        save : str or None
            Save the result of this step to the specified file if not None.
    Pseudo Deconvolution
    --------------------
    deconvolve : dict
        The deconvolution step parameter.
        sigma : float
            The std of a Gaussian filter applied to the high intensity pixel image.
            The number should reflect the scale of the halo effect seen around high
            intensity structures.
            For the vasculature a typical value is 10.
        save : str or None
            Save the result of this step to the specified file if not None.
        threshold : float
            Voxels above this threshold will be added to the binarization result
            in the multi-path binarization.
            For the vasculature a typical value is 750.
    Adaptive Thresholding
    ---------------------
    adaptive : dict or None
        Adaptive thresholding step parameter.
        A local ISODATA threshold is estimated.
        See also :mod:`ClearMap.ImageProcessing.LocalStatistics`.
        selem : tuple
            The structural element size to estimate the percentiles.
            Should be larger than the larger vessels.
            For the vasculature a typical value is (200,200,5).
        spacing : tuple
            The spacing used to move the structural elements.
            Larger spacings speed up processing but become locally less precise.
            For the vasculature a typical value is (50,50,5)
        interpolate : int
            The order of the interpolation used in constructing the full
            background estimate in case a non-trivial spacing is used.
            For the vasculature a typical value is 1.
        save : str or None
            Save the result of this step to the specified file if not None.
    Equalization
    ------------
    equalize : dict or None
        Equalization step parameter.
        See also :func:`ClearMap.ImageProcessing.LocalStatistics.local_percentile`
        precentile : tuple
            The lower and upper percentiles used to estimate the equalization.
            The lower percentile is used for normalization, the upper to limit the
            maximal boost to a maximal intensity above this percentile.
            For the vasculature a typical value is (0.4, 0.975).
        max_value : float
            The maximal intensity value in the equalized image.
            For the vasculature a typical value is 1.5.
        selem : tuple
            The structural element size to estimate the percentiles.
            Should be larger than the larger vessels.
            For the vasculature a typical value is (200,200,5).
        spacing : tuple
            The spacing used to move the structural elements.
            Larger spacings speed up processing but become locally less precise.
            For the vasculature a typical value is (50,50,5)
        interpolate : int
            The order of the interpolation used in constructing the full
            background estimate in case a non-trivial spacing is used.
            For the vasculature a typical value is 1.
        save : str or None
            Save the result of this step to the specified file if not None.
        threshold : float
            Voxels above this threshold will be added to the binarization result
            in the multi-path binarization.
            For the vasculature a typical value is 1.1.
    Tube filter
    -----------
    vesselize : dict
        The tube filter step parameter.
        background : dict or None
            Parameters to correct for local background. See
            :func:`ClearMap.ImageProcessing.Filter.Rank.percentile`.
            If None, no background correction is done before the tube filter.
            selem : tuple
                The structural element specification to estimate the percentiles.
                Should be larger than the largest vessels intended to be
                boosted by the tube filter.
                For the vasculature a typical value is ('disk', (30,30,1)).
            percentile : float
                Percentile in [0,1] used to estimate the background.
                For the vasculature a typical value is 0.5.
        tubness : dict
            Parameters used for the tube filter. See
            :func:`ClearMap.ImageProcessing.Differentiation.Hessian.lambda123`.
            sigma : float
                The scale of the vessels to boos in the filter.
                For the vasculature a typical value is 1.0.
        save : str or None
            Save the result of this step to the specified file if not None.
        threshold : float
            Voxels above this threshold will be added to the binarization result
            in the multi-path binarization.
            For the vasculature a typical value is 120.
    Binary filling
    --------------
    fill : dict or None
        If not None, apply a binary filling the binarized result.
    For the vasculature this step is set to None and done globally
    in the postprocessing step.
    Binary smoothing
    ----------------
    smooth : dict or None
        The smoothing parameter passed to
        :func:`ClearMap.ImageProcessing.Binary.Smoothing.smooth_by_configuration`.
    For the vasculature this step is set to None and done globally
    in the postprocessing step.
    References
    ----------
    [1] C. Kirst et al., "Mapping the Fine-Scale Organization and Plasticity of the Brain Vasculature", Cell 180, 780 (2020)
    """
    # initialize sink
    shape = io.shape(source)
    order = io.order(source)
    sink, sink_buffer = ap.initialize_sink(sink=sink, shape=shape, order=order, dtype=bool)  # , memory='shared')
    # initialize addition output sinks
    binary_status = binarization_parameter.get('binary_status')
    if binary_status:
        ap.initialize_sink(binary_status, source=sink, shape=shape, order=order, dtype='uint16')
    initialize_sinks(binarization_parameter, shape, order)
    binarization_parameter.update(verbose=processing_parameter.get('verbose', False))
    bp.process(binarize_block, source, sink, function_type='block',
               parameter=binarization_parameter, **processing_parameter)
    return sink 
[docs]
def binarize_block(source, sink, parameter=default_binarization_parameter):
    """Binarize a Block."""
    # initialize parameter and slicings
    verbose = parameter.get('verbose', False)
    if verbose:
        prefix = 'Block %s: ' % (source.info(),)
        total_time = tmr.Timer(prefix)
    else:
        prefix = ''
    max_bin = parameter.get('max_bin', MAX_BIN)
    base_slicing = sink.valid.base_slicing
    valid_slicing = source.valid.slicing
    # initialize binary status for inspection
    binary_status = parameter.get('binary_status')
    if binary_status:
        binary_status = io.as_source(binary_status)
        binary_status = binary_status[base_slicing]
    default_step_params = {'parameter': parameter, 'steps_to_measure': {}, 'prefix': prefix,
                           'base_slicing': base_slicing, 'valid_slicing': valid_slicing}
    # clipping
    parameter_clip = parameter.get('clip')
    if parameter_clip:
        parameter_clip, timer = print_params(parameter_clip, 'clip', prefix, verbose)
        parameter_clip.update(norm=max_bin, dtype=DTYPE)
        save = parameter_clip.pop('save', None)
        clipped, mask, high, low = clip(source, **parameter_clip)
        not_low = np.logical_not(low)
        if save:
            save = io.as_source(save)
            save[base_slicing] = clipped[valid_slicing]
        if binary_status is not None:
            binary_status[high[valid_slicing]] += BINARY_STATUS['High']
        else:
            sink[valid_slicing] = high[valid_slicing]
        del high, low
        if verbose:
            timer.print_elapsed_time('Clipping')
    else:
        clipped = source
        mask = not_low = np.ones(source.shape, dtype=bool)
    # active arrays: clipped, mask, not_low
    # lightsheet correction
    corrected = run_step('lightsheet', clipped, lc.correct_lightsheet, remove_previous_result=True,
                         extra_kwargs={'mask': mask, 'max_bin': max_bin}, **default_step_params)
    # active arrays: corrected, mask, not_low
    # median filter
    median = run_step('median', corrected, rnk.median, remove_previous_result=True,
                      extra_kwargs={'max_bin': max_bin, 'mask': not_low}, **default_step_params)
    del not_low
    # active arrays: median, mask
    # pseudo deconvolution
    parameter_deconvolution = parameter.get('deconvolve')
    if parameter_deconvolution:
        parameter_deconvolution, timer = print_params(parameter_deconvolution, 'deconvolve', prefix, verbose)
        save = parameter_deconvolution.pop('save', None)
        threshold = parameter_deconvolution.pop('threshold', None)
        if binary_status is not None:
            binarized = binary_status > 0
        else:
            binarized = sink[:]
        deconvolved = deconvolve(median, binarized[:], **parameter_deconvolution)
        del binarized
        if save:
            save = io.as_source(save)
            save[base_slicing] = deconvolved[valid_slicing]
        if verbose:
            timer.print_elapsed_time('Deconvolution')
        if threshold:
            binary_deconvolved = deconvolved > threshold
            if binary_status is not None:
                binary_status[binary_deconvolved[valid_slicing]] += BINARY_STATUS['Deconvolved']
            else:
                sink[valid_slicing] += binary_deconvolved[valid_slicing]
            del binary_deconvolved
            if verbose:
                timer.print_elapsed_time('Deconvolution: binarization')
    else:
        deconvolved = median
    # active arrays: median, mask, deconvolved
    # adaptive
    parameter_adaptive = parameter.get('adaptive')
    adaptive = run_step('adaptive', deconvolved, threshold_adaptive, remove_previous_result=False,
                        **default_step_params)
    if parameter_adaptive:
        binary_adaptive = deconvolved > adaptive
        if binary_status is not None:
            binary_status[binary_adaptive[valid_slicing]] += BINARY_STATUS['Adaptive']
        else:
            sink[valid_slicing] += binary_adaptive[valid_slicing]
        del binary_adaptive, adaptive
        # if verbose:
        #     timer.print_elapsed_time('Adaptive')
    del deconvolved
    # active arrays: median, mask
    # equalize
    parameter_equalize = parameter.get('equalize')
    if parameter_equalize:
        parameter_equalize, timer = print_params(parameter_equalize, 'equalize', prefix, verbose)
        save = parameter_equalize.pop('save', None)
        threshold = parameter_equalize.pop('threshold', None)
        equalized = equalize(median, mask=mask, **parameter_equalize)
        if save:
            save = io.as_source(save)
            save[base_slicing] = equalized[valid_slicing]
        if verbose:
            timer.print_elapsed_time('Equalization')
        if threshold:
            binary_equalized = equalized > threshold
            if binary_status is not None:
                binary_status[binary_equalized[valid_slicing]] += BINARY_STATUS['Equalized']
            else:
                sink[valid_slicing] += binary_equalized[valid_slicing]
            # prepare equalized for use in vesselization
            parameter_vesselization = parameter.get('vesselize')
            if parameter_vesselization and parameter_vesselization.get('background'):
                equalized[binary_equalized] = threshold
                equalized = float(max_bin-1) / threshold * equalized
            del binary_equalized
            if verbose:
                timer.print_elapsed_time('Equalization: binarization')
    else:
        equalized = median
    del median
    # active arrays: mask, equalized
    # smaller vessels /capillaries
    parameter_vesselization = parameter.get('vesselize')
    if parameter_vesselization:
        parameter_vesselization, timer = print_params(parameter_vesselization, 'vesselize', prefix, verbose)
        parameter_background = parameter_vesselization.get('background')
        parameter_background = parameter_background.copy()
        if parameter_background:
            save = parameter_background.pop('save', None)
            equalized = np.array(equalized, dtype='uint16')
            background = rnk.percentile(equalized, max_bin=max_bin, mask=mask, **parameter_background)
            tubeness = equalized - np.minimum(equalized, background)
            del background
            if save:
                save = io.as_source(save)
                save[base_slicing] = tubeness[valid_slicing]
        else:
            tubeness = equalized
        parameter_tubeness = parameter_vesselization.get('tubeness', {})
        tubeness = tubify(tubeness, **parameter_tubeness)
        save = parameter_vesselization.get('save')
        if save:
            save = io.as_source(save)
            save[base_slicing] = tubeness[valid_slicing]
        if verbose:
            timer.print_elapsed_time('Vesselization')
        threshold = parameter_vesselization.get('threshold')
        if threshold:
            binary_vesselized = tubeness > threshold
            if binary_status is not None:
                binary_status[binary_vesselized[valid_slicing]] += BINARY_STATUS['Tube']
            else:
                sink[valid_slicing] += binary_vesselized[valid_slicing]
            del binary_vesselized
            if verbose:
                timer.print_elapsed_time('Vesselization: binarization')
        del tubeness
    del equalized, mask
    # active arrays: None
    # fill holes
    parameter_fill = parameter.get('fill')
    if parameter_fill:
        step_param, timer = print_params(parameter_fill, 'fill', prefix, verbose)
        if binary_status is not None:
            foreground = binary_status > 0
            filled = ndi.morphology.binary_fill_holes(foreground)
            binary_status[np.logical_and(filled, np.logical_not(foreground))] += BINARY_STATUS['Fill']
            del foreground, filled
        else:
            filled = ndi.morphology.binary_fill_holes(sink[:])
            sink[valid_slicing] += filled[valid_slicing]
            del filled
        if verbose:
            timer.print_elapsed_time('Filling')
    if binary_status is not None:
        sink[valid_slicing] = binary_status[valid_slicing] > 0
    # smooth binary
    if parameter.get('smooth'):  # WARNING: otherwise removes sink if no smoothing
        parameter['smooth']['save'] = False
        smoothed = run_step('smooth', sink, bs.smooth_by_configuration, remove_previous_result=False,
                            extra_kwargs={'sink': None, 'processes': 1}, **default_step_params)
        sink[valid_slicing] = smoothed[valid_slicing]
        del smoothed
    if verbose:
        total_time.print_elapsed_time('Binarization')
    gc.collect() 
###############################################################################
# ## Postprocessing
###############################################################################
[docs]
def postprocess(source, sink=None, postprocessing_parameter=default_postprocessing_parameter,
                processing_parameter=default_postprocessing_processing_parameter, processes=None, verbose=True):
    """
    Postprocess a binarized image.
    Arguments
    ---------
    source : source specification
        The binary  source.
    sink : sink specification or None
        The sink to write the postprocessed result to.
        If None, an array is returned.
    postprocessing_parameter : dict
        Parameter for the postprocessing.
    processing_parameter : dict
        Parameter for the parallel processing.
    processes: int or None
        Number of parallel processes to run. Defaults to max if None
    verbose : bool
        If True, print progress output.
    Returns
    -------
    sink : Source
        The result of the binarization.
    Notes
    -----
    * The postprocessing pipeline is composed of several steps. The parameters
        for each step are passed as sub-dictionaries to the
        postprocessing_parameter dictionary.
    * If None is passed for one of the steps the step is skipped.
    Smoothing
    ---------
    smooth : dict or None
        Smoothing step parameter. See
        :func:`ClearMap.ImageProcessing.Binary.Smoothing.smooth_by_configuration`
        iterations : int
            Number of smoothing iterations.
            For the vasculature a typical value is 6.
    Filling
    -------
    fill : bool or None
        If True, fill holes in the binary data.
    """
    source = io.as_source(source)
    sink = ap.initialize_sink(sink, shape=source.shape, dtype=source.dtype, order=source.order, return_buffer=False)
    if verbose:
        timer = tmr.Timer()
        print('Binary post processing: initialized.')
    postprocessing_parameter = postprocessing_parameter.copy()
    run_binary_filling = postprocessing_parameter.get('fill', False)
    parameter_smooth = postprocessing_parameter.get('smooth')
    # smoothing
    if parameter_smooth:
        fill_source, tmp_f_path, save = apply_smoothing(source, sink, processing_parameter, postprocessing_parameter,
                                                        processes, verbose)
    else:
        fill_source = source
        save = False
    if run_binary_filling:
        bf.fill(fill_source, sink=sink, processes=processes, verbose=verbose)
        if parameter_smooth and not save:
            io.delete_file(tmp_f_path)
    if verbose:
        timer.print_elapsed_time('Binary post processing')
    gc.collect() 
[docs]
def apply_smoothing(source, sink, processing_parameter, postprocessing_parameter, processes=None, verbose=True):
    parameter_smooth = postprocessing_parameter.pop('smooth', None)
    if postprocessing_parameter.get('fill'):
        save = parameter_smooth.pop('save', False)
        # initialize temporary files if needed
        if not check_enough_temp_space():
            raise MissingRequirementException(f'Free space in temporary directory is insufficient, required 200 GB, '
                                              f'got {get_free_temp_space() // (2**30)} GB'
                                              f'Please free some space or use a different temporary directory.'
                                              f'You can set the "TMP" environment variable to a directory of your choice')
        tmp_f_path = save if save else postprocessing_parameter['temporary_filename']
        tmp_f_path = tmp_f_path if tmp_f_path else tmpf.mktemp(prefix='TubeMap_Vasculature_postprocessing',
                                                               suffix='.npy')
        sink_smooth = ap.initialize_sink(tmp_f_path, shape=source.shape, dtype=source.dtype,
                                         order=source.order, return_buffer=False)
    else:
        sink_smooth = sink
        save = False
        tmp_f_path = ''
    # run smoothing
    fill_source = bs.smooth_by_configuration(source, sink=sink_smooth, processing_parameter=processing_parameter,
                                             processes=processes, verbose=verbose, **parameter_smooth)
    return fill_source, tmp_f_path, save 
###############################################################################
# ## Binarization processing steps
###############################################################################
[docs]
def clip(source, clip_range=(300, 60000), norm=MAX_BIN, dtype=DTYPE):
    clip_low, clip_high = clip_range
    clipped = np.array(source[:], dtype=float)
    low = clipped < clip_low
    clipped[low] = clip_low
    high = clipped >= clip_high
    clipped[high] = clip_high
    mask = np.logical_not(np.logical_or(low, high))
    clipped -= clip_low
    clipped *= float(norm-1) / (clip_high - clip_low)
    clipped = np.asarray(clipped, dtype=dtype)
    return clipped, mask, high, low 
[docs]
def deconvolve(source, binarized, sigma=10):
    convolved = np.zeros(source.shape, dtype=float)
    convolved[binarized] = source[binarized]
    for z in range(convolved.shape[2]):
        convolved[:, :, z] = ndi.gaussian_filter(convolved[:, :, z], sigma=sigma)
    deconvolved = source - np.minimum(source, convolved)
    deconvolved[binarized] = source[binarized]
    return deconvolved 
[docs]
def threshold_isodata(source):
    try:
        thresholds = skif.threshold_isodata(source, return_all=True)
        if len(thresholds) > 0:
            return thresholds[-1]
        else:
            return 1
    except:  # FIXME: too broad
        return 1 
[docs]
def threshold_adaptive(source, function=threshold_isodata, selem=(100, 100, 3), spacing=(25, 25, 3),
                       interpolate=1, mask=None, step=None):
    source = io.as_source(source)[:]
    threshold = ls.apply_local_function(source, function=function, mask=mask, dtype=float,
                                        selem=selem, spacing=spacing, interpolate=interpolate, step=step)
    return threshold 
[docs]
def equalize(source, percentile=(0.5, 0.95), max_value=1.5, selem=(200, 200, 5), spacing=(50, 50, 5),
             interpolate=1, mask=None):
    equalized = ls.local_percentile(source, percentile=percentile, mask=mask, dtype=float,
                                    selem=selem, spacing=spacing, interpolate=interpolate)
    normalize = 1/np.maximum(equalized[..., 0], 1)
    maxima = equalized[..., 1]
    ids = maxima * normalize > max_value
    normalize[ids] = max_value / maxima[ids]
    equalized = np.array(source, dtype=float) * normalize
    return equalized 
[docs]
def tubify(source, sigma=1.0, gamma12=1.0, gamma23=1.0, alpha=0.25):
    return hes.lambda123(source=source, sink=None, sigma=sigma, gamma12=gamma12, gamma23=gamma23, alpha=alpha) 
###############################################################################
# ## Helper
###############################################################################
[docs]
def status_to_description(status):
    """
    Converts a status int to its description.
    Arguments
    ---------
    status : int
        The status.
    Returns
    -------
    description : str
        The description corresponding to the status.
    """
    description = ''
    for k in range(len(BINARY_NAMES)-1, -1, -1):
        if status / 2**k == 1:
            description = BINARY_NAMES[k] + ',' + description
            status -= 2**k
    if len(description) == 0:
        description = 'Background'
    else:
        description = description[:-1]
    return description 
[docs]
def binary_statistics(source):
    """
    Counts the binarization types.
    Arguments
    ---------
    source : array
        The status array of the binarization process.
    Returns
    -------
    statistics : dict
       A dict with entires {description : count}.
    """
    status, counts = np.unique(io.as_source(source)[:], return_counts=True)
    return {status_to_description(s): c for s, c in zip(status, counts)} 
###############################################################################
# ## Tests
###############################################################################
def _test():
    """Tests."""
    import numpy as np
    import ClearMap.Visualization.Plot3d as p3d
    import ClearMap.Tests.Files as tsf
    import ClearMap.ImageProcessing.Experts.Vasculature as vasc
    source = np.array(tsf.source('vls')[:300, :300, 80:120])
    source[:, :, [0, -1]] = 0
    source[:, [0, -1], :] = 0
    source[[0, -1], :, :] = 0
    bpar = vasc.default_binarization_parameter.copy()
    bpar['clip']['clip_range'] = (150, 7000)
    bpar['as_memory'] = True
    # bpar['binary_status'] = 'binary_status.npy'
    ppar = vasc.default_processing_parameter.copy()
    ppar['processes'] = 10
    ppar['size_max'] = 10
    sink = 'binary.npy'
    # sink=None;
    binary = vasc.binarize(source, sink=sink, binarization_parameter=bpar, processing_parameter=ppar)
    p3d.plot([source, binary])
    import ClearMap.IO.IO as io
    io.delete_file(sink)
    pppar = vasc.default_postprocessing_parameter.copy()
    pppar['smooth']['iterations'] = 3
    smoothed = vasc.postprocess(binary, postprocessing_parameter=pppar)
    p3d.plot([binary, smoothed])