Source code for ClearMap.Analysis.Measurements.radius_measurements

# -*- coding: utf-8 -*-
"""
MeasureRadius
=============

Measures intensity decays at specified points in the data.
"""
__author__    = 'Christoph Kirst <christoph.kirst.ck@gmail.com>, Charly Rousseau <charly.rousseau@icm-institute.org>'
__license__   = 'GPLv3 - GNU General Public License v3 (see LICENSE.txt)'
__copyright__ = 'Copyright © 2020 by Christoph Kirst'
__webpage__   = 'https://idisco.info'
__download__  = 'https://www.github.com/ChristophKirst/ClearMap2'

import warnings

import numpy as np

import ClearMap.IO.IO as io
import ClearMap.ParallelProcessing.DataProcessing.MeasurePointList as mpl
import ClearMap.Utils.Timer as tmr

###############################################################################
### Measure Radius
###############################################################################

[docs] def measure_radius(source, points, fraction=None, value=None, max_radius=100, method='sphere', default=np.inf, scale=None, return_radii=True, return_radii_as_scalar=True, return_indices=False, processes=None, verbose=False) -> np.ndarray | tuple[np.ndarray, np.ndarray]: """ Measures a radius via decay of intensity values for a list of points. Arguments --------- source : array Source for measurement. points : array List of indices to measure radis for. fraction : float or None Fraction of center intensity that needs to be reached to determine the radius. If None, value needs to be given. value : array or float or None: The value below which the intensity has to fall to measure the radius from the center pixel. For array, it has to be the same size as the points. If None, fraction has to be given. max_radius : int or tuple of ints The maximal pixel radius to consider in each dimension. The larger, the slower the measurement. default : number or None Default value to use if no radius was detected. scale: tuple or float An optional scale in each direction to determine the distance. return_radii : bool If True, return the radii measured. return_radii_as_scalar : bool If True, return the radii as single floats, otherwise a radius for each dimension. return_indices : bool If True, return the indices of the search which allows identifying the pixel at which the radius condition was met. processes : int or None Number of processes to use. verbose : bool If True, print progress info. Returns ------- radii : array Array of measured radii if return_radii is True. indices : array Array of measured indices at which the radius detection condition is met. """ if scale is None: warnings.warn(f'No scale given, using 1 for all dimensions. ' f'Please note this behavior is deprecated. Future versions will require a scale.', DeprecationWarning, stacklevel=2) source = io.read(source) if verbose: timer = tmr.Timer() print(f'Measuring radii of {points.shape[0]} points in array of shape {source.shape}.') ndim = source.ndim if not hasattr(max_radius, '__len__'): max_radius = [max_radius] * ndim if len(max_radius) != ndim: raise ValueError(f'The maximal search radius {max_radius} has wrong dimension!') if method == 'sphere': search = search_indices_sphere(max_radius) elif method == 'rectangle': search = search_indices_rectangle(max_radius) else: raise ValueError(f'The method is not "sphere" or "rectangle" but {method}!') worker_args = [source, points, search] + [value if value is not None else fraction] worker_kwargs = {'sink': None, 'processes': processes, 'verbose': verbose} if value is not None: if hasattr(value, '__len__'): measured = mpl.find_smaller_than_values(*worker_args, **worker_kwargs) else: measured = mpl.find_smaller_than_value(*worker_args, **worker_kwargs) elif fraction is not None: measured = mpl.find_smaller_than_fraction(*worker_args, **worker_kwargs) else: raise ValueError('fraction or value cannot both be None!') if verbose: timer.print_elapsed_time('Measuring radii done') result = () if return_radii: if scale is None: scale = 1 if not hasattr(scale, '__len__'): # scalar provided -> broadcast scale = np.full(ndim, scale, dtype=float) distance_table = np.abs(search) * scale if return_radii_as_scalar: distance_table = np.sqrt(np.sum(distance_table * distance_table, axis=1)) distance_table = np.hstack([distance_table, default]) else: distance_table = np.vstack([distance_table, [default] * ndim]) radii = distance_table[measured] result += (radii,) if return_indices: search = np.vstack([search, [np.max(search, axis=0) + 1]]) indices = search[measured] result += (indices,) if len(result) == 1: result = result[0] return result
# ############################################################################## # ## Search indices # ##############################################################################
[docs] def search_indices_sphere(radius): """ Creates all relative indices within a sphere of specified radius in an array with specified strides. Arguments --------- radius : tuple of int Radius of the sphere of the search index list. Returns ------- indices : np.ndarray Array of ints of relative indices for the search area voxels. """ grid = [np.arange(-r,r+1, dtype=float)/np.maximum(1,r) for r in radius] grid = np.array(np.meshgrid(*grid, indexing = 'ij')) #sort indices by radius dist = np.sum(grid*grid, axis = 0) dist_shape = dist.shape dist = dist.reshape(-1) dist_index = np.argsort(dist) dist_sorted = dist[dist_index] keep = dist_sorted <= 1 dist_index = dist_index[keep] # convert to relative coordinates indices = np.array(np.unravel_index(dist_index, dist_shape)).T indices -= radius return indices
[docs] def search_indices_rectangle(radius): """ Creates all relative indices within a rectangle. Arguments --------- radius : tuple or float Radius of the sphere of the search index list. Returns ------- indices : np.ndarray Array of ints of relative indices for the search area voxels. """ # create coordinate grid grid = [np.arange(-r,r+1, dtype=int) for r in radius] grid = np.array(np.meshgrid(*grid, indexing = 'ij')) # sort indices by radius dist = np.sum(grid*grid, axis = 0) dist_shape = dist.shape dist = dist.reshape(-1) dist_index = np.argsort(dist) # convert to relative coordinates indices = np.array(np.unravel_index(dist_index, dist_shape)).T indices -= radius return indices
############################################################################### ### Tests ###############################################################################
[docs] def test(): import numpy as np import ClearMap.IO.IO as io # noinspection PyUnresolvedReferences import ClearMap.Analysis.Measurements.radius_measurements as mr data = 10-np.abs(10-np.arange(0,21)) search = mr.search_indices_sphere(radius=[10,10,10]) print(search) points = np.array([10]) d,i = mr.measure_radius(data, points, fraction=0.75, max_radius=10, scale=2, verbose=True, processes=4, return_indices=True) data = np.random.rand(*(30,40,50)) io.write('data.npy', data) points = np.array([np.random.randint(0,s, size=10) for s in data.shape]).T d,i = mr.measure_radius(data, points, value=0.5, max_radius=10, scale=2, verbose=True, processes=4, return_indices=True) data = np.zeros((30,40,50), dtype=int) data[10:20, 15:25,10:20] = 1 data[15,20,15] = 2 import ClearMap.Visualization.Plot3d as p3d p3d.plot(data) points = np.array([[15, 20, 15],[4,4,4]]) d,i = mr.measure_radius(data, points, value=0.0, max_radius=10, scale=None, verbose=True, processes=None, return_indices=True)