# Copyright CNRS/Inria/UNS # Contributor(s): Eric Debreuve (since 2019), Morgane Nadal (2020) # # eric.debreuve@cnrs.fr # # This software is governed by the CeCILL license under French law and # abiding by the rules of distribution of free software. You can use, # modify and/ or redistribute the software under the terms of the CeCILL # license as circulated by CEA, CNRS and INRIA at the following URL # "http://www.cecill.info". # # As a counterpart to the access to the source code and rights to copy, # modify and redistribute granted by the license, users are provided only # with a limited warranty and the software's author, the holder of the # economic rights, and the successive licensors have only limited # liability. # # In this respect, the user's attention is drawn to the risks associated # with loading, using, modifying and/or developing or reproducing the # software by the user in light of its specific status of free software, # that may mean that it is complicated to manipulate, and that also # therefore means that it is reserved for developers and experienced # professionals having in-depth computer knowledge. Users are therefore # encouraged to load and test the software's suitability as regards their # requirements in conditions enabling the security of their systems and/or # data to be ensured and, more generally, to use and operate it in the # same conditions as regards security. # # The fact that you are presently reading this means that you have had # knowledge of the CeCILL license and that you accept its terms. from __future__ import annotations from brick.component.glial_cmp import glial_cmp_t import brick.processing.map_labeling as ml_ from brick.general.type import array_t, py_array_picker_h, site_h from collections import namedtuple as namedtuple_t import sys as sy_ from typing import Optional, Sequence, Tuple import numpy as np_ import scipy.ndimage as im_ import skimage.filters as fl_ import skimage.measure as ms_ import skimage.morphology as mp_ som_ext_path_t = namedtuple_t("som_ext_path_t", "extension length path idx") class soma_t(glial_cmp_t): # __slots__ = ("contour_points", "centroid", "skl_graph", "ext_roots", "graph_roots") def __init__(self): # super().__init__() for slot in self.__class__.__slots__: setattr(self, slot, None) self.ext_roots = [] @classmethod def FromMap(cls, lmp: array_t, uid: int) -> soma_t: # instance = cls() bmp = lmp == uid instance.InitializeFromMap(bmp, uid) contour_map = cls.ContourMap(bmp) instance.contour_points = tuple(zip(*contour_map.nonzero())) instance.centroid = np_.array([np_.median(instance.sites[i]) for i in range(3)]) return instance def Extensions(self, max_level: int = sy_.maxsize) -> Tuple[glial_cmp_t]: # # max_level=1: directly connected extensions # ... soma_ext = self.extensions.copy() current_level_idx = 0 for level in range(2, max_level + 1): next_level_idx = soma_ext.__len__() if next_level_idx == current_level_idx: break for extension in soma_ext[current_level_idx:]: soma_ext.extend(extension.extensions) current_level_idx = next_level_idx return tuple(soma_ext) def ContourPointsCloseTo( self, point: site_h, max_distance: float ) -> Tuple[Optional[Tuple[site_h, ...]], Optional[py_array_picker_h]]: # points = tuple( contour_point for contour_point in self.contour_points if (np_.subtract(point, contour_point) ** 2).sum() <= max_distance ) return __PointsCloseTo__(points) def ExtensionPointsCloseTo( self, point: site_h, max_distance: float ) -> Tuple[Optional[Tuple[site_h, ...]], Optional[py_array_picker_h]]: # # Leave connection paths apart because only detected extension pieces # (as opposed to invented=connection paths) are considered valid connection targets # points = [] for extension in self.Extensions(): ext_sites = tuple(zip(*extension.sites)) points.extend( ext_point for ext_point in ext_sites if (np_.subtract(point, ext_point) ** 2).sum() <= max_distance ) return __PointsCloseTo__(tuple(points)) def BackReferenceSoma(self, glial_cmp: glial_cmp_t) -> None: # raise NotImplementedError("Soma do not need to back-reference a soma") def __str__(self) -> str: # if self.extensions is None: n_extensions = 0 else: n_extensions = self.extensions.__len__() return ( f"Soma.{self.uid}, " f"area={self.sites[0].__len__()}, " f"contour points={self.contour_points.__len__()}, " f"extensions={n_extensions}" ) @staticmethod def Map(image: array_t, low: float, high: float, selem: array_t) -> array_t: # # low = 10 #0.15 # high = 67.4 # 0.7126 # max_image = image.max() nonzero_sites = image.nonzero() nonzero_values = image[nonzero_sites] min_image = nonzero_values.min() low = low * (max_image - min_image) + min_image high = high * (max_image - min_image) + min_image result = fl_.apply_hysteresis_threshold(image, low, high) result = result.astype(np_.int8, copy=False) for dep in range(image.shape[0]): result[dep, :, :] = mp_.closing(result[dep, :, :], selem) result[dep, :, :] = mp_.opening(result[dep, :, :], selem) return result @staticmethod def FilteredMap(map_: array_t, min_area: int) -> array_t: result = map_.copy() lmp = ms_.label(map_) # label the image region by measuring the connectivity for region in ms_.regionprops(lmp): # Measure properties of labeled image regions if region.area <= min_area: region_sites = (lmp == region.label).nonzero() result[region_sites] = 0 lmp[region_sites] = 0 return result, lmp @staticmethod def ContourMap(map_: array_t) -> array_t: # part_map = ml_.PartLMap(map_) # Works because the background is labeled with 27 result = part_map < 26 return result.astype(np_.int8) @staticmethod def InfluenceMaps(map_: array_t) -> Tuple[array_t, array_t]: background = (map_ == 0).astype(np_.int8) dist_map, idx_map = im_.morphology.distance_transform_edt( background, return_indices=True ) # obj_map = np_.empty_like(map_) # for row in range(0, obj_map.shape[0]): # for col in range(0, obj_map.shape[1]): # for dep in range(0, obj_map.shape[2]): # obj_map[row, col, dep] = map_[ # idx_map[0, row, col, dep], # idx_map[1, row, col, dep], # idx_map[2, row, col, dep], # ] return dist_map, np_.array(map_[tuple(idx_map)]) @staticmethod def SomasLMap(somas: Sequence[soma_t], with_extensions: bool = True) -> array_t: # shape = somas[0].img_shape result = np_.zeros(shape, dtype=np_.int64) for soma in somas: result[soma.sites] = soma.uid if with_extensions: for connection_path in filter( lambda path: path is not None, soma.connection_path.values() ): result[connection_path] = soma.uid for extension in soma.Extensions(): for connection_path in filter( lambda path: path is not None, extension.connection_path.values(), ): result[connection_path] = soma.uid result[extension.sites] = soma.uid return result def __PointsCloseTo__( points: Tuple[site_h, ...] ) -> Tuple[Optional[Tuple[site_h, ...]], Optional[py_array_picker_h]]: # if points.__len__() > 0: points_as_picker = tuple(zip(*points)) else: points = None points_as_picker = None return points, points_as_picker