Newer
Older
# 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 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 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")
NADAL Morgane
committed
__slots__ = ("contour_points", "centroid", "skl_graph", "ext_roots", "graph_roots")
for slot in self.__class__.__slots__:
setattr(self, slot, None)
NADAL Morgane
committed
self.ext_roots = []
def FromMap(cls, lmp: array_t, uid: int) -> soma_t:
bmp = lmp == uid
instance.InitializeFromMap(bmp, uid)
instance.contour_points = tuple(zip(*contour_map.nonzero()))
instance.centroid = np_.array([np_.median(instance.sites[i]) for i in range(3)])
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
)
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
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"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:
lmp = ms_.label(map_) # label the image region by measuring the connectivity
for region in ms_.regionprops(lmp): # Measure properties of labeled image regions
region_sites = (lmp == region.label).nonzero()
result[region_sites] = 0
lmp[region_sites] = 0
return result, lmp
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
@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)
lambda path: path is not None, soma.connection_path.values()
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
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