Source code for ClearMap.ImageProcessing.Experts.Cells

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

Expert cell image processing pipeline.

This module provides the basic routines for processing immmediate eaerely
gene data. 

The routines are used in the :mod:`ClearMap.Scripts.CellMap` 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 cv2
import scipy.ndimage as ndi
import scipy.ndimage.filters as ndf
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.IlluminationCorrection as ic
import ClearMap.ImageProcessing.Filter.StructureElement as se
import ClearMap.ImageProcessing.Filter.FilterKernel as fk
import ClearMap.ImageProcessing.LocalStatistics as ls

import ClearMap.Analysis.Measurements.MaximaDetection as md
import ClearMap.Analysis.Measurements.ShapeDetection as sd
import ClearMap.Analysis.Measurements.MeasureExpression as me

#import ClearMap.ImageProcessing.Filter.Rank as rnk

#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


###############################################################################
### Default parameter
###############################################################################

default_cell_detection_parameter = dict( 
  #flatfield
  iullumination_correction = dict(flatfield = None,
                                  scaling = 'mean'),
                       
  #background removal
  background_correction = dict(shape = (7,7),
                               form = 'Disk',
                    save = False),
  
  #equalization
  equalization = None,
  
  #difference of gaussians filter
  dog_filter = dict(shape = None,
                    sigma = None,
                    sigma2 = None),
  
  #extended maxima detection
  maxima_detection = dict(h_max = None,
                          shape = 5,
                          threshold = 0,
                          valid = True,
                          save = False),

  #cell shape detection                                  
  shape_detection = dict(threshold = 700,
                         save = False),
  
  #cell intenisty detection                   
  intensity_detection = dict(method = 'max',
                             shape = 3,
                             measure = ['source', 'background']), 
)
"""Parameter for the cell detectrion pipeline. 
See :func:`detect_cells` for details."""


default_cell_detection_processing_parameter = dict(
  size_max = 100,
  size_min = 50,
  overlap = 32,
  axes = [2],
  optimization = True,
  optimization_fix = 'all',
  verbose = None,
  processes = None
)
"""Parallel processing parameter for the cell detection pipeline. 
See :func:`ClearMap.ParallelProcessing.BlockProcessing.process` for details."""       


###############################################################################
### Cell detection
###############################################################################
                   
[docs]def detect_cells(source, sink = None, cell_detection_parameter = default_cell_detection_parameter, processing_parameter = default_cell_detection_processing_parameter): """Cell detection pipeline. 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. cell_detection_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 cell detection. Notes ----- Effectively this function performs the following steps: * illumination correction via :func:`~ClearMap.ImageProcessing.IlluminationCorrection.correct_illumination` * background removal * difference of Gaussians (DoG) filter * maxima detection via :func:`~ClearMap.Analysis.Measurements.MaximaDetection.find_extended_maxima` * cell shape detection via :func:`~ClearMap.Analysis.Measurements.ShapeDetection.detect_shape` * cell intensity and size measurements via: :func:`~ClearMap.ImageProcessing.Measurements.ShapeDetection.find_intensity`, :func:`~ClearMap.ImageProcessing.Measurements.ShapeDetection.find_size`. The parameters for each step are passed as sub-dictionaries to the cell_detection_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. Illumination correction ----------------------- illumination_correction : dict or None Illumination correction step parameter. flatfield : array or str The flat field estimate for the image planes. background : array or None A background level to assume for the flatfield correction. scaling : float, 'max', 'mean' or None Optional scaling after the flat field correction. save : str or None Save the result of this step to the specified file if not None. See also :func:`ClearMap.ImageProcessing.IlluminationCorrection.correct_illumination` Background removal ------------------ background_correction : dict or None Background removal step parameter. shape : tuple The shape of the structure lement to estimate the background. This should be larger than the typical cell size. form : str The form of the structur element (e.g. 'Disk') save : str or None Save the result of this step to the specified file if not None. Equalization ------------ equalization : 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. max_value : float The maximal intensity value in the equalized image. selem : tuple The structural element size to estimate the percentiles. Should be larger than the larger vessels. spacing : tuple The spacing used to move the structural elements. Larger spacings speed up processing but become locally less precise. interpolate : int The order of the interpoltation used in constructing the full background estimate in case a non-trivial spacing is used. save : str or None Save the result of this step to the specified file if not None. DoG Filter ---------- dog_filter : dict or None Difference of Gaussian filter step parameter. shape : tuple The shape of the filter. This should be near the typical cell size. sigma : tuple or None The std of the inner Gaussian. If None, detemined automatically from shape. sigma2 : tuple or None The std of the outer Gaussian. If None, detemined automatically from shape. save : str or None Save the result of this step to the specified file if not None. Maxima detection ---------------- maxima_detection : dict or None Extended maxima detection step parameter. h_max : float or None The 'height'for the extended maxima. If None, simple local maxima detection isused. shape : tuple The shape of the structural element for extended maxima detection. This should be near the typical cell size. threshold : float or None Only maxima above this threshold are detected. If None, all maxima are detected. valid : bool If True, only detect cell centers in the valid range of the blocks with overlap. save : str or None Save the result of this step to the specified file if not None. Shape detection --------------- shape_detection : dict or None Shape detection step parameter. threshold : float Cell shape is expanded from maxima if pixles are above this threshold and not closer to another maxima. save : str or None Save the result of this step to the specified file if not None. Intensity detection ------------------- intensity_detection : dict or None Intensity detection step parameter. method : {'max'|'min','mean'|'sum'} The method to use to measure the intensity of a cell. shape : tuple or None If no cell shapes are detected a disk of this shape is used to measure the cell intensity. save : str or None Save the result of this step to the specified file if not None. References ---------- [1] Renier, Adams, Kirst, Wu et al., "Mapping of Brain Activity by Automated Volume Analysis of Immediate Early Genes.", Cell 165, 1789 (2016) [1] 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); for key in cell_detection_parameter.keys(): par = cell_detection_parameter[key]; if isinstance(par, dict): filename = par.get('save', None); if filename: ap.initialize_sink(filename, shape=shape, order=order, dtype='float'); cell_detection_parameter.update(verbose=processing_parameter.get('verbose', False)); results, blocks = bp.process(detect_cells_block, source, sink=None, function_type='block', return_result=True, return_blocks=True, parameter=cell_detection_parameter, **processing_parameter) #merge results results = np.vstack([np.hstack(r) for r in results]) #create column headers header = ['x','y','z']; dtypes = [int, int, int]; if cell_detection_parameter['shape_detection'] is not None: header += ['size']; dtypes += [int]; measures = cell_detection_parameter['intensity_detection']['measure']; header += measures dtypes += [float] * len(measures) dt = {'names' : header, 'formats' : dtypes}; cells = np.zeros(len(results), dtype=dt); for i,h in enumerate(header): cells[h] = results[:,i]; #save results return io.write(sink, cells);
[docs]def detect_cells_block(source, parameter = default_cell_detection_parameter): """Detect cells in a Block.""" #initialize parameter and slicings verbose = parameter.get('verbose', False); if verbose: prefix = 'Block %s: ' % (source.info(),); total_time = tmr.Timer(prefix); base_slicing = source.valid.base_slicing; valid_slicing = source.valid.slicing; valid_lower = source.valid.lower; valid_upper = source.valid.upper; lower = source.lower; parameter_intensity = parameter.get('intensity_detection', None); measure_to_array = dict(); if parameter_intensity: parameter_intensity = parameter_intensity.copy(); measure = parameter_intensity.pop('measure', []); if measure is None: measure = []; for m in measure: measure_to_array[m] = None; if 'source' in measure_to_array: measure_to_array['source'] = source; # correct illumination parameter_illumination = parameter.get('illumination_correction', None); if parameter_illumination: parameter_illumination = parameter_illumination.copy(); if verbose: timer = tmr.Timer(prefix); hdict.pprint(parameter_illumination, head=prefix + 'Illumination correction') save = parameter_illumination.pop('save', None); corrected = ic.correct_illumination(source, **parameter_illumination) if save: save = io.as_source(save); save[base_slicing] = corrected[valid_slicing]; if verbose: timer.print_elapsed_time('Illumination correction'); else: corrected = np.array(source.array); if 'illumination' in measure_to_array: measure_to_array['illumination'] = corrected; #background subtraction parameter_background = parameter.get('background_correction', None); if parameter_background: parameter_background = parameter_background.copy(); if verbose: timer = tmr.Timer(prefix); hdict.pprint(parameter_background, head = prefix + 'Background removal') save = parameter_background.pop('save', None); background = remove_background(corrected, **parameter_background) if save: save = io.as_source(save); save[base_slicing] = corrected[valid_slicing]; if verbose: timer.print_elapsed_time('Illumination correction'); else: background = corrected; del corrected; if 'background' in measure_to_array: measure_to_array['background'] = background; # equalize parameter_equalize = parameter.get('equalization', 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); equalized = equalize(background, mask=None, **parameter_equalize); if save: save = io.as_source(save); save[base_slicing] = equalized[valid_slicing]; if verbose: timer.print_elapsed_time('Equalization'); else: equalized = background; del background; if 'equalized' in measure_to_array: measure_to_array['equalized'] = equalized; #DoG filter parameter_dog_filter = parameter.get('dog_filter', None); if parameter_dog_filter: parameter_dog_filter = parameter_dog_filter.copy(); if verbose: timer = tmr.Timer(prefix); hdict.pprint(parameter_dog_filter, head = prefix + 'DoG filter:') save = parameter_dog_filter.pop('save', None); dog = dog_filter(equalized, **parameter_dog_filter); if save: save = io.as_source(save); save[base_slicing] = dog[valid_slicing]; if verbose: timer.print_elapsed_time('DoG filter'); else: dog = equalized; del equalized; if 'dog' in measure_to_array: measure_to_array['dog'] = dog; #Maxima detection parameter_maxima = parameter.get('maxima_detection', None); parameter_shape = parameter.get('shape_detection', None); if parameter_shape or parameter_intensity: if not parameter_maxima: print(prefix + 'Warning: maxima detection needed for shape and intensity detection!'); parameter_maxima = dict(); if parameter_maxima: parameter_maxima = parameter_maxima.copy(); if verbose: timer = tmr.Timer(prefix); hdict.pprint(parameter_maxima, head = prefix + 'Maxima detection:') save = parameter_maxima.pop('save', None); valid = parameter_maxima.pop('valid', None); # extended maxima maxima = md.find_maxima(source.array, **parameter_maxima, verbose=verbose); if save: save = io.as_source(save); save[base_slicing] = maxima[valid_slicing]; #center of maxima if parameter_maxima['h_max']: centers = md.find_center_of_maxima(source, maxima=maxima, verbose=verbose); else: centers = ap.where(maxima).array; if verbose: timer.print_elapsed_time('Maxima detection'); #correct for valid region if valid: ids = np.ones(len(centers), dtype=bool); for c,l,u in zip(centers.T, valid_lower, valid_upper): ids = np.logical_and(ids, np.logical_and(l <= c, c < u)); centers = centers[ids]; del ids; del dog, maxima; results = (centers,); #cell shape detection if parameter_shape: parameter_shape = parameter_shape.copy(); if verbose: timer = tmr.Timer(prefix); hdict.pprint(parameter_shape, head = prefix + 'Shape detection:') save = parameter_shape.pop('save', None); # shape detection shape = sd.detect_shape(source, centers, **parameter_shape, verbose=verbose); if save: save = io.as_source(save); save[base_slicing] = shape[valid_slicing]; #size detection max_label = centers.shape[0]; sizes = sd.find_size(shape, max_label=max_label); valid = sizes > 0; if verbose: timer.print_elapsed_time('Shape detection'); results += (sizes,); else: valid = None; shape = None; #cell intensity detection if parameter_intensity: parameter_intensity = parameter_intensity.copy(); if verbose: timer = tmr.Timer(prefix); hdict.pprint(parameter_intensity, head = prefix + 'Intensity detection:') if not shape is None: r = parameter_intensity.pop('shape', 3); if isinstance(r, tuple): r = r[0]; for m in measure: if shape is not None: intensity = sd.find_intensity(measure_to_array[m], label=shape, max_label=max_label, **parameter_intensity); else: intensity = me.measure_expression(measure_to_array[m], centers, search_radius=r, **parameter_intensity, processes=1, verbose=False) results += (intensity,) if verbose: timer.print_elapsed_time('Shape detection'); if valid is not None: results = tuple(r[valid] for r in results); #correct coordinate offsets of blocks results = (results[0] + lower,) + results[1:]; #correct shapes for merging results = tuple(r[:,None] if r.ndim == 1 else r for r in results); if verbose: total_time.print_elapsed_time('Cell detection') gc.collect() return results;
############################################################################### ### Cell detection processing steps ###############################################################################
[docs]def remove_background(source, shape, form = 'Disk'): selem = se.structure_element(shape, form=form, ndim=2).astype('uint8'); removed = np.empty(source.shape, dtype=source.dtype); for z in range(source.shape[2]): #img[:,:,z] = img[:,:,z] - grey_opening(img[:,:,z], structure = structureElement('Disk', (30,30))); #img[:,:,z] = img[:,:,z] - morph.grey_opening(img[:,:,z], structure = self.structureELement('Disk', (150,150))); removed[:,:,z] = source[:,:,z] - cv2.morphologyEx(source[:,:,z], cv2.MORPH_OPEN, selem) return removed;
[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 dog_filter(source, shape, sigma = None, sigma2 = None): if not shape is None: fdog = fk.filter_kernel(ftype='dog', shape=shape, sigma=sigma, sigma2=sigma2); fdog = fdog.astype('float32'); filtered = ndf.correlate(source, fdog); filtered[filtered < 0] = 0; return filtered else: return source;
[docs]def detect_maxima(source, h_max = None, shape = 5, threshold = None, verbose = False): # extended maxima maxima = md.find_maxima(source, h_max=h_max, shape=shape, threshold=threshold, verbose=verbose); #center of maxima if h_max: centers = md.find_center_of_maxima(source, maxima=maxima, verbose=verbose); else: centers = ap.where(maxima).array; return centers;
############################################################################### ### Cell filtering ###############################################################################
[docs]def filter_cells(source, sink, thresholds): """Filter a array of detected cells according to the thresholds. Arguments --------- source : str, array or Source The source for the cell data. sink : str, array or Source The sink for the results. thresholds : dict Dictionary of the form {name : threshold} where name refers to the column in the cell data and threshold can be None, a float indicating a minimal threshold or a tuple (min,max) where min,max can be None or a minimal and maximal threshold value. Returns ------- sink : str, array or Source The thresholded cell data. """ source = io.as_source(source); ids = np.ones(source.shape[0], dtype=bool); for k,t in thresholds.items(): if t: if not isinstance(t, (tuple, list)): t = (t, None); if t[0] is not None: ids = np.logical_and(ids, t[0] <= source[k]) if t[1] is not None: ids = np.logical_and(ids, t[1] > source[k]); cells_filtered = source[ids]; return io.write(sink, cells_filtered)
############################################################################### ### Tests ############################################################################### def _test(): """Tests.""" import numpy as np import ClearMap.Visualization.Plot3d as p3d import ClearMap.Tests.Files as tsf import ClearMap.ImageProcessing.Experts.Cells as cells