Mentions légales du service

Skip to content
Snippets Groups Projects
soma.py 8.3 KiB
Newer Older
DEBREUVE Eric's avatar
DEBREUVE Eric committed
# Copyright CNRS/Inria/UNS
# Contributor(s): Eric Debreuve (since 2019), Morgane Nadal (2020)
DEBREUVE Eric's avatar
DEBREUVE Eric committed
#
# 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.

DEBREUVE Eric's avatar
DEBREUVE Eric committed
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
DEBREUVE Eric's avatar
DEBREUVE Eric committed

from collections import namedtuple as namedtuple_t
DEBREUVE Eric's avatar
DEBREUVE Eric committed
import sys as sy_
from typing import Optional, Sequence, Tuple
DEBREUVE Eric's avatar
DEBREUVE Eric committed

import numpy as np_
import scipy.ndimage as im_
import skimage.filters as fl_
import skimage.measure as ms_
import skimage.morphology as mp_


DEBREUVE Eric's avatar
DEBREUVE Eric committed
som_ext_path_t = namedtuple_t("som_ext_path_t", "extension length path idx")
DEBREUVE Eric's avatar
DEBREUVE Eric committed
class soma_t(glial_cmp_t):
DEBREUVE Eric's avatar
DEBREUVE Eric committed
    #
    __slots__ = ("contour_points", "centroid", "skl_graph", "ext_roots", "graph_roots")
DEBREUVE Eric's avatar
DEBREUVE Eric committed

    def __init__(self):
DEBREUVE Eric's avatar
DEBREUVE Eric committed
        #
DEBREUVE Eric's avatar
DEBREUVE Eric committed
        super().__init__()
        for slot in self.__class__.__slots__:
            setattr(self, slot, None)
DEBREUVE Eric's avatar
DEBREUVE Eric committed

    @classmethod
    def FromMap(cls, lmp: array_t, uid: int) -> soma_t:
DEBREUVE Eric's avatar
DEBREUVE Eric committed
        #
        instance = cls()

        bmp = lmp == uid
        instance.InitializeFromMap(bmp, uid)
DEBREUVE Eric's avatar
DEBREUVE Eric committed
        contour_map = cls.ContourMap(bmp)
DEBREUVE Eric's avatar
DEBREUVE Eric committed
        instance.contour_points = tuple(zip(*contour_map.nonzero()))
        instance.centroid = np_.array([np_.median(instance.sites[i]) for i in range(3)])
DEBREUVE Eric's avatar
DEBREUVE Eric committed

        return instance

    def Extensions(self, max_level: int = sy_.maxsize) -> Tuple[glial_cmp_t]:
DEBREUVE Eric's avatar
DEBREUVE Eric committed
        #
        # 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)
DEBREUVE Eric's avatar
DEBREUVE Eric committed

    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
        )

DEBREUVE Eric's avatar
DEBREUVE Eric committed
        return __PointsCloseTo__(points)
DEBREUVE Eric's avatar
DEBREUVE Eric committed

DEBREUVE Eric's avatar
DEBREUVE Eric committed
    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
DEBREUVE Eric's avatar
DEBREUVE Eric committed
        #
DEBREUVE Eric's avatar
DEBREUVE Eric committed
        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
DEBREUVE Eric's avatar
DEBREUVE Eric committed
            )
DEBREUVE Eric's avatar
DEBREUVE Eric committed

DEBREUVE Eric's avatar
DEBREUVE Eric committed
        return __PointsCloseTo__(tuple(points))
DEBREUVE Eric's avatar
DEBREUVE Eric committed

    def BackReferenceSoma(self, glial_cmp: glial_cmp_t) -> None:
DEBREUVE Eric's avatar
DEBREUVE Eric committed
        #
        raise NotImplementedError("Soma do not need to back-reference a soma")
DEBREUVE Eric's avatar
DEBREUVE Eric committed

    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__()}, "
DEBREUVE Eric's avatar
DEBREUVE Eric committed
            f"contour points={self.contour_points.__len__()}, "
            f"extensions={n_extensions}"
        )
DEBREUVE Eric's avatar
DEBREUVE Eric committed

    @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)
DEBREUVE Eric's avatar
DEBREUVE Eric committed

        for dep in range(image.shape[0]):
            result[dep, :, :] = mp_.closing(result[dep, :, :], selem)
            result[dep, :, :] = mp_.opening(result[dep, :, :], selem)

        return result

    @staticmethod
DEBREUVE Eric's avatar
DEBREUVE Eric committed
    def FilteredMap(map_: array_t, min_area: int) -> array_t:
DEBREUVE Eric's avatar
DEBREUVE Eric committed

        result = map_.copy()
DEBREUVE Eric's avatar
DEBREUVE Eric committed
        lmp = ms_.label(map_)  # label the image region by measuring the connectivity
DEBREUVE Eric's avatar
DEBREUVE Eric committed

DEBREUVE Eric's avatar
DEBREUVE Eric committed
        for region in ms_.regionprops(lmp):  # Measure properties of labeled image regions
DEBREUVE Eric's avatar
DEBREUVE Eric committed
            if region.area <= min_area:
DEBREUVE Eric's avatar
DEBREUVE Eric committed
                region_sites = (lmp == region.label).nonzero()
                result[region_sites] = 0
DEBREUVE Eric's avatar
DEBREUVE Eric committed

DEBREUVE Eric's avatar
DEBREUVE Eric committed

    @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:
DEBREUVE Eric's avatar
DEBREUVE Eric committed
        #
DEBREUVE Eric's avatar
DEBREUVE Eric committed
        shape = somas[0].img_shape
        result = np_.zeros(shape, dtype=np_.int64)
DEBREUVE Eric's avatar
DEBREUVE Eric committed

        for soma in somas:
DEBREUVE Eric's avatar
DEBREUVE Eric committed
            result[soma.sites] = soma.uid

            if with_extensions:
DEBREUVE Eric's avatar
DEBREUVE Eric committed
                for connection_path in filter(
                    lambda path: path is not None, soma.connection_path.values()
DEBREUVE Eric's avatar
DEBREUVE Eric committed
                ):
DEBREUVE Eric's avatar
DEBREUVE Eric committed
                    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
DEBREUVE Eric's avatar
DEBREUVE Eric committed

        return result


DEBREUVE Eric's avatar
DEBREUVE Eric committed
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