# -*- 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__ = 'http://idisco.info'
__download__ = 'http://www.github.com/ChristophKirst/ClearMap2'
import numpy as np
import tempfile as tmpf
import gc
import scipy.ndimage as ndi
import skimage.filters as skif
import ClearMap.IO.IO as io
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
import ClearMap.Utils.HierarchicalDict as hdict
###############################################################################
### 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 = False),
#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 = False),
#median
median = dict(selem = ((3,)*3),
save = False),
#deconvolution
deconvolve = dict(sigma = 10,
threshold = 750,
save = False),
#equalization
equalize = dict(percentile = (0.4, 0.975),
selem = (200,200,5),
spacing = (50,50,5),
interpolate = 1,
threshold = 1.1,
save = False),
#adaptive threshold
adaptive = dict(selem = (250,250,3),
spacing = (50,50,3),
interpolate=1,
save = False),
#tubeness
vesselize = dict(background = dict(selem = ('disk', (30,30,1)),
percentile = 0.5),
tubeness = dict(sigma = 1.0,
gamma12 = 0.0),
threshold = 120,
save = False),
#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.
verbose : bool
If True, print progress output.
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 foregournd 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 lightshieet 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 lenght, 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 interpoltation 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 vascualture 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 Gaussina 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 biniarization.
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 interpoltation 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 interpoltation 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 biniarization.
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 biniarization.
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', None);
if binary_status:
ap.initialize_sink(binary_status, source=sink, shape=shape, order=order, dtype='uint16');
for key in binarization_parameter.keys():
par = binarization_parameter[key];
if isinstance(par, dict):
filename = par.get('save', None);
if filename:
ap.initialize_sink(filename, shape=shape, order=order, dtype='float');
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);
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', None);
if binary_status:
binary_status = io.as_source(binary_status);
binary_status = binary_status[base_slicing];
#clipping
parameter_clip = parameter.get('clip', None);
if parameter_clip:
parameter_clip = parameter_clip.copy();
if verbose:
timer = tmr.Timer(prefix);
hdict.pprint(parameter_clip, head = prefix + 'Clipping:')
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);
#high = low = np.zeros(source.shape, dtype=bool);
#low = np.zeros(source.shape, dtype=bool);
#not_low = np.logical_not(low);
#active arrays: clipped, mask, not_low
#lightsheet correction
parameter_lightsheet = parameter.get('lightsheet', None);
if parameter_lightsheet:
parameter_lightsheet = parameter_lightsheet.copy();
if verbose:
timer = tmr.Timer(prefix);
hdict.pprint(parameter_lightsheet, head = prefix + 'Lightsheet:')
#parameter_lightsheet.update(max_bin=max_bin);
save = parameter_lightsheet.pop('save', None);
corrected = lc.correct_lightsheet(clipped, mask=mask, max_bin=max_bin, **parameter_lightsheet);
if save:
save = io.as_source(save);
save[base_slicing] = corrected[valid_slicing];
if verbose:
timer.print_elapsed_time('Lightsheet');
else:
corrected = clipped;
del clipped
#active arrays: corrected, mask, not_low
#median filter
parameter_median = parameter.get('median', None);
if parameter_median:
parameter_median = parameter_median.copy();
if verbose:
timer = tmr.Timer(prefix);
hdict.pprint(parameter_median, head = prefix + 'Median:')
save = parameter_median.pop('save', None);
median = rnk.median(corrected, max_bin=max_bin, mask=not_low, **parameter_median);
if save:
save = io.as_source(save);
save[base_slicing] = median[valid_slicing];
if verbose:
timer.print_elapsed_time('Median');
else:
median = corrected;
del corrected, not_low;
#active arrays: median, mask
#pseudo deconvolution
parameter_deconvolution = parameter.get('deconvolve', None);
if parameter_deconvolution:
parameter_deconvolution = parameter_deconvolution.copy();
if verbose:
timer = tmr.Timer(prefix);
hdict.pprint(parameter_deconvolution, head = prefix + 'Deconvolution:')
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', None);
if parameter_adaptive:
parameter_adaptive = parameter_adaptive.copy();
if verbose:
timer = tmr.Timer(prefix);
hdict.pprint(parameter_adaptive, head = prefix + 'Adaptive:')
save = parameter_adaptive.pop('save', None);
adaptive = threshold_adaptive(deconvolved, **parameter_adaptive)
if save:
save = io.as_source(save);
save[base_slicing] = adaptive[valid_slicing];
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', None);
if parameter_equalize:
parameter_equalize = parameter_equalize.copy();
if verbose:
timer = tmr.Timer(prefix);
hdict.pprint(parameter_equalize, head = prefix + 'Equalization:')
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', None);
if parameter_vesselization and parameter_vesselization.get('background', None):
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 /capilarries
parameter_vesselization = parameter.get('vesselize', None);
if parameter_vesselization:
parameter_vesselization = parameter_vesselization.copy();
if verbose:
timer = tmr.Timer(prefix);
hdict.pprint(parameter_vesselization, head = prefix + 'Vesselization:')
parameter_background = parameter_vesselization.get('background', None)
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', None);
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', None);
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', None);
if parameter_fill:
parameter_fill = parameter_fill.copy();
if verbose:
timer = tmr.Timer(prefix);
#hdict.pprint(parameter_fill, head = 'Filling:')
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
parameter_smooth = parameter.get('smooth', None);
if parameter_smooth:
parameter_smooth = parameter_smooth.copy();
if verbose:
timer = tmr.Timer(prefix);
hdict.pprint(parameter_smooth, head = prefix + 'Smoothing:')
smoothed = bs.smooth_by_configuration(sink, sink=None, processes=1, **parameter_smooth);
sink[valid_slicing] = smoothed[valid_slicing];
del smoothed;
if verbose:
timer.print_elapsed_time('Smoothing');
if verbose:
total_time.print_elapsed_time('Binarization')
gc.collect()
return None;
###############################################################################
### 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 postprocesses result to.
If None, an array is returned.
postprocessing_parameter : dict
Parameter for the postprocessing.
processing_parameter : dict
Parameter for the parallel processing.
verbose : bool
If True, print progress output.
Returns
-------
sink : Source
The result of the binarization.
Notes
-----
* The postporcessing 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();
parameter_smooth = postprocessing_parameter.pop('smooth', None);
parameter_fill = postprocessing_parameter.pop('fill', None);
#print(parameter_smooth, parameter_fill)
#smoothing
save = None;
if parameter_smooth:
#intialize temporary files if needed
if parameter_fill:
save = parameter_smooth.pop('save', None);
temporary_filename = save;
if temporary_filename is None:
temporary_filename = postprocessing_parameter['temporary_filename'];
if temporary_filename is None:
temporary_filename = tmpf.mktemp(prefix='TubeMap_Vasculature_postprocessing', suffix='.npy');
sink_smooth = ap.initialize_sink(temporary_filename, shape=source.shape, dtype=source.dtype, order=source.order, return_buffer=False);
else:
sink_smooth = sink;
#run smoothing
source_fill = bs.smooth_by_configuration(source, sink=sink_smooth, processing_parameter=processing_parameter, processes=processes, verbose=verbose, **parameter_smooth);
else:
source_fill = source;
if parameter_fill:
sink = bf.fill(source_fill, sink=sink, processes=processes, verbose=verbose);
if parameter_smooth and save is None:
io.delete_file(temporary_filename);
else:
sink = source_fill;
if verbose:
timer.print_elapsed_time('Binary post processing');
gc.collect()
return None;
#return sink;
###############################################################################
### 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:
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])