diff --git a/setup.py b/setup.py index c3859050d9fc15c8215e3e376d93ff10eb1afc2e..d931be3256f3ecda443507168e36776230c01f4e 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,11 @@ setup_kwds = dict( 'wallMeshImageSignal = gnomon_package_tissueimagemesh.algorithm.cellImageQuantification.wallMeshImageSignal', 'wallMeshSignalPolarities = gnomon_package_tissueimagemesh.algorithm.cellImageQuantification.wallMeshSignalPolarities', ], + 'meshFilter': [ + 'surfaceCellMeshDecimation = gnomon_package_tissueimagemesh.algorithm.meshFilter.surfaceCellMeshDecimation', + 'surfaceCellMeshFlipOptimization = gnomon_package_tissueimagemesh.algorithm.meshFilter.surfaceCellMeshFlipOptimization', + 'surfaceMeshCellProjection = gnomon_package_tissueimagemesh.algorithm.meshFilter.surfaceMeshCellProjection' + ], 'meshFromImage': [ 'imageSurfaceMesh = gnomon_package_tissueimagemesh.algorithm.meshFromImage.imageSurfaceMesh', 'segmentationSurfaceMesh = gnomon_package_tissueimagemesh.algorithm.meshFromImage.segmentationSurfaceMesh', diff --git a/src/gnomon_package_tissueimagemesh/algorithm/cellImageQuantification/surfaceMeshCellProperty.py b/src/gnomon_package_tissueimagemesh/algorithm/cellImageQuantification/surfaceMeshCellProperty.py index 147c5d800d5e8fa37bb8a92f682f8c3cb92971cc..91e5fb406db58f88aa44225f814e8b1459cf8be6 100644 --- a/src/gnomon_package_tissueimagemesh/algorithm/cellImageQuantification/surfaceMeshCellProperty.py +++ b/src/gnomon_package_tissueimagemesh/algorithm/cellImageQuantification/surfaceMeshCellProperty.py @@ -66,8 +66,8 @@ class surfaceMeshCellProperty(gnomonAbstractCellImageQuantification): out_tissue.walls.image = out_tissue cell_labels = out_tissue.cell_ids() - if surface_topomesh.has_wisp_property('cell', 0, is_computed=True): - vertex_cells = surface_topomesh.wisp_property('cell', 0).values(list(surface_topomesh.wisps(0))) + if surface_topomesh.has_wisp_property('label', 0, is_computed=True): + vertex_cells = surface_topomesh.wisp_property('label', 0).values(list(surface_topomesh.wisps(0))) else: if 'surface_center' in out_tissue.cells.feature_names(): cell_surface_centers = out_tissue.cells.feature('surface_center') @@ -79,7 +79,7 @@ class surfaceMeshCellProperty(gnomonAbstractCellImageQuantification): vertex_cell_distances = np.linalg.norm(vertex_points[:, np.newaxis] - cell_centers[np.newaxis], axis=-1) vertex_cells = cell_labels[np.argmin(vertex_cell_distances, axis=-1)] - surface_topomesh.update_wisp_property('cell', 0, dict(zip(surface_topomesh.wisps(0), vertex_cells))) + surface_topomesh.update_wisp_property('label', 0, dict(zip(surface_topomesh.wisps(0), vertex_cells))) surface_topomesh.update_wisp_property('label', 0, dict(zip(surface_topomesh.wisps(0), vertex_cells%256))) for property_name in self['attribute_names']: diff --git a/src/gnomon_package_tissueimagemesh/algorithm/meshFilter/__init__.py b/src/gnomon_package_tissueimagemesh/algorithm/meshFilter/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/gnomon_package_tissueimagemesh/algorithm/meshFilter/surfaceCellMeshDecimation.py b/src/gnomon_package_tissueimagemesh/algorithm/meshFilter/surfaceCellMeshDecimation.py new file mode 100644 index 0000000000000000000000000000000000000000..46d972aebdd3c25f778c50384690eaa189ff9f58 --- /dev/null +++ b/src/gnomon_package_tissueimagemesh/algorithm/meshFilter/surfaceCellMeshDecimation.py @@ -0,0 +1,91 @@ +# {# gnomon, plugin.imports +# do not modify, any code after the gnomon tag will be overwritten +from dtkcore import d_bool, d_real + +import gnomon.core + +from gnomon.utils import algorithmPlugin +from gnomon.utils.decorators import meshInput, meshOutput +# #} +# add your imports before the next gnomon tag + +from copy import deepcopy + +import numpy as np +import scipy.ndimage as nd + +from cellcomplex.property_topomesh.analysis import compute_topomesh_property, compute_topomesh_vertex_property_from_faces +from cellcomplex.property_topomesh.topological_operations import topomesh_collapse_edge +from cellcomplex.property_topomesh.extraction import clean_topomesh_properties + +from cellcomplex.utils import array_dict + +# {# gnomon, plugin.class +# do not modify, any code after the gnomon tag will be overwritten +@algorithmPlugin(version='0.1.0', coreversion='0.81.0', name="Surface Cell Projection") +@meshInput(attr='in_surface_topomesh', data_plugin='gnomonMeshDataPropertyTopomesh') +@meshOutput(attr='surface_topomesh', data_plugin='gnomonMeshDataPropertyTopomesh') +class surfaceCellMeshDecimation(gnomon.core.gnomonAbstractMeshFilter): + """Decimate tissue mesh to keep one vertex per cell label + + """ + + def __init__(self): + super().__init__() + + self._parameters = {} + + # self.seg_img = {} + self.in_surface_topomesh = {} + self.surface_topomesh = {} + + def run(self): + self.surface_topomesh = {} + for time in self.in_surface_topomesh.keys(): + surface_topomesh = deepcopy(self.in_surface_topomesh[time]) + # #} + # implement the run method + + edges = np.array(list(surface_topomesh.wisps(1))) + edge_vertices = surface_topomesh.wisp_property('vertices', 1).values(edges) + edge_vertex_cells = surface_topomesh.wisp_property('label', 0).values(edge_vertices) + + inner_edges = edges[edge_vertex_cells[:, 1] == edge_vertex_cells[:, 0]] + inner_edge_lengths = surface_topomesh.wisp_property('length', 1).values(inner_edges) + + inner_edges = inner_edges[np.argsort(inner_edge_lengths)] + collapsed_edges = inner_edges + + iteration = 0 + while len(collapsed_edges)>0: + collapsed_edges = [] + affected_edges = set() + for e in inner_edges: + if not e in affected_edges: + neighbor_edges = set(surface_topomesh.border_neighbors(1, e)) + collapsed = topomesh_collapse_edge(surface_topomesh, e) + if collapsed: + collapsed_edges += [e] + affected_edges |= set(neighbor_edges) + iteration += 1 + print(f"Collapsed {len(collapsed_edges)} edges") + + clean_topomesh_properties(surface_topomesh) + compute_topomesh_property(surface_topomesh, 'vertices', 1) + compute_topomesh_property(surface_topomesh, 'length', 1) + + edges = np.array(list(surface_topomesh.wisps(1))) + edge_vertices = surface_topomesh.wisp_property('vertices', 1).values(edges) + edge_vertex_cells = surface_topomesh.wisp_property('label', 0).values(edge_vertices) + + inner_edges = edges[edge_vertex_cells[:, 1] == edge_vertex_cells[:, 0]] + inner_edge_lengths = surface_topomesh.wisp_property('length', 1).values(inner_edges) + + inner_edges = inner_edges[np.argsort(inner_edge_lengths)] + + compute_topomesh_property(surface_topomesh, 'borders', 2) + compute_topomesh_property(surface_topomesh, 'vertices', 2) + compute_topomesh_property(surface_topomesh, 'oriented_vertices', 2) + compute_topomesh_property(surface_topomesh, 'oriented_borders', 3) + + self.surface_topomesh[time] = surface_topomesh diff --git a/src/gnomon_package_tissueimagemesh/algorithm/meshFilter/surfaceCellMeshFlipOptimization.py b/src/gnomon_package_tissueimagemesh/algorithm/meshFilter/surfaceCellMeshFlipOptimization.py new file mode 100644 index 0000000000000000000000000000000000000000..0d27e5baad2157835e8609dbbe3a640843df8dd5 --- /dev/null +++ b/src/gnomon_package_tissueimagemesh/algorithm/meshFilter/surfaceCellMeshFlipOptimization.py @@ -0,0 +1,82 @@ +# {# gnomon, plugin.imports +# do not modify, any code after the gnomon tag will be overwritten +from dtkcore import d_int, d_real + +import gnomon.core + +from gnomon.utils import algorithmPlugin +from gnomon.utils.decorators import cellImageInput, meshInput, meshOutput +# #} +# add your imports before the next gnomon tag + +from copy import deepcopy + +import numpy as np + +from cellcomplex.property_topomesh.optimization import property_topomesh_edge_flip_optimization + + +# {# gnomon, plugin.class +# do not modify, any code after the gnomon tag will be overwritten +@algorithmPlugin(version='0.1.0', coreversion='0.72.0', name="Cell Mesh Flip Optimization") +@cellImageInput("seg_img", data_plugin="gnomonCellImageDataTissueImage") +@meshInput(attr='in_surface_topomesh', data_plugin='gnomonMeshDataPropertyTopomesh') +@meshOutput(attr='surface_topomesh', data_plugin='gnomonMeshDataPropertyTopomesh') +class surfaceCellMeshFlipOptimization(gnomon.core.gnomonAbstractMeshFilter): + """Perform edge flips to get closer to the image cell ajacencies + + """ + + def __init__(self): + super().__init__() + + self._parameters = {} + self._parameters['iterations'] = d_int("Iterations", 3, 0, 5, "The number of iterations to perform in the edge flip process") + self._parameters['regularization'] = d_real("Regularization Weight", 0, 0, 1, 2, "The weight associated to the regularization energy term") + + self.seg_img = {} + self.in_surface_topomesh = {} + self.surface_topomesh = {} + + def run(self): + self.surface_topomesh = {} + for time in self.in_surface_topomesh.keys(): + surface_topomesh = deepcopy(self.in_surface_topomesh[time]) + # #} + # implement the run method + + if self['iterations']>0: + vertex_cells = surface_topomesh.wisp_property('label', 0) + unique_cells = np.unique(vertex_cells.values()) + cell_vertices = {c: [] for c in unique_cells} + for v, c in vertex_cells.items(): + cell_vertices[c] += [v] + + seg_img = self.seg_img[time] + image_cell_edges = np.array([w for w in seg_img.wall_ids()]) + image_edges = [] + for c1, c2 in image_cell_edges: + if c1 in cell_vertices and c2 in cell_vertices: + image_edges += [ + (v1, v2) + for v1 in cell_vertices[c1] for v2 in cell_vertices[c2] + ] + image_edges += [ + (v2, v1) + for v1 in cell_vertices[c1] for v2 in cell_vertices[c2] + ] + image_edges = np.array(image_edges) + + omega_energies = {'image': 1} + if self['regularization'] > 0: + omega_energies.update({'regularization': self['regularization']}) + + property_topomesh_edge_flip_optimization( + surface_topomesh, + iterations=self['iterations'], + omega_energies=omega_energies, + simulated_annealing=False, + image_edges=image_edges + ) + + self.surface_topomesh[time] = surface_topomesh diff --git a/src/gnomon_package_tissueimagemesh/algorithm/meshFilter/surfaceMeshCellProjection.py b/src/gnomon_package_tissueimagemesh/algorithm/meshFilter/surfaceMeshCellProjection.py new file mode 100644 index 0000000000000000000000000000000000000000..dcb60ad84ebdf507dbc05b8931968669c30cdb3b --- /dev/null +++ b/src/gnomon_package_tissueimagemesh/algorithm/meshFilter/surfaceMeshCellProjection.py @@ -0,0 +1,59 @@ +# {# gnomon, plugin.imports +# do not modify, any code after the gnomon tag will be overwritten +from dtkcore import d_bool, d_real, d_inliststring + +import gnomon.core + +from gnomon.utils import algorithmPlugin +from gnomon.utils.decorators import cellImageInput, meshInput, meshOutput +# #} +# add your imports before the next gnomon tag + +from copy import deepcopy + +from timagetk_geometry.image_surface.tissue_image_mesh import surface_vertex_normal_sampling_label_projection, surface_vertex_remove_isolated_labels + + +# {# gnomon, plugin.class +# do not modify, any code after the gnomon tag will be overwritten +@algorithmPlugin(version='0.1.0', coreversion='0.72.0', name="Surface Cell Projection") +@cellImageInput("seg_img", data_plugin="gnomonCellImageDataTissueImage") +@meshInput(attr='in_surface_topomesh', data_plugin='gnomonMeshDataPropertyTopomesh') +@meshOutput(attr='surface_topomesh', data_plugin='gnomonMeshDataPropertyTopomesh') +class surfaceMeshCellProjection(gnomon.core.gnomonAbstractMeshFilter): + """Project cell labels on surface mesh using normals + + """ + + def __init__(self): + super().__init__() + + self._parameters = {} + self._parameters['sampling_depth'] = d_real("Sampling depth", 3., 0., 10., 1, "Depth up to which to sample the segmented image") + self._parameters['method'] = d_inliststring("Method", "median", ["most", "first", "median"], "Method used to choose the selected label") + self._parameters['remove_isolated_labels'] = d_bool("Remove isolated labels",True, "Whether to remove labels that have no identical neighbor") + + self.seg_img = {} + self.in_surface_topomesh = {} + self.surface_topomesh = {} + + def run(self): + self.surface_topomesh = {} + for time in self.in_surface_topomesh.keys(): + surface_topomesh = deepcopy(self.in_surface_topomesh[time]) + # #} + # implement the run method + + seg_img = self.seg_img[time] + + surface_vertex_normal_sampling_label_projection( + surface_topomesh, + seg_img, + sampling_depth=self['sampling_depth'], + method=self['method'] + ) + + if self['remove_isolated_labels']: + surface_vertex_remove_isolated_labels(surface_topomesh) + + self.surface_topomesh[time] = surface_topomesh diff --git a/src/gnomon_package_tissueimagemesh/algorithm/pointCloudQuantification/surfaceMeshProperty.py b/src/gnomon_package_tissueimagemesh/algorithm/pointCloudQuantification/surfaceMeshProperty.py index 343c692c0be1a65b498c0059b3d7d87b572d4ed6..6b11aa64912e8df474d9744c16d89b2128e62dd9 100644 --- a/src/gnomon_package_tissueimagemesh/algorithm/pointCloudQuantification/surfaceMeshProperty.py +++ b/src/gnomon_package_tissueimagemesh/algorithm/pointCloudQuantification/surfaceMeshProperty.py @@ -58,8 +58,8 @@ class surfaceMeshProperty(gnomonAbstractPointCloudQuantification): out_df = deepcopy(df) cell_labels = df['label'].values - if surface_topomesh.has_wisp_property('cell', 0, is_computed=True): - vertex_cells = surface_topomesh.wisp_property('cell', 0).values(list(surface_topomesh.wisps(0))) + if surface_topomesh.has_wisp_property('label', 0, is_computed=True): + vertex_cells = surface_topomesh.wisp_property('label', 0).values(list(surface_topomesh.wisps(0))) else: cell_centers = df[['center_'+dim for dim in 'xyz']].values @@ -67,7 +67,7 @@ class surfaceMeshProperty(gnomonAbstractPointCloudQuantification): vertex_cell_distances = np.linalg.norm(vertex_points[:, np.newaxis] - cell_centers[np.newaxis], axis=-1) vertex_cells = cell_labels[np.argmin(vertex_cell_distances, axis=-1)] - surface_topomesh.update_wisp_property('cell', 0, dict(zip(surface_topomesh.wisps(0), vertex_cells))) + surface_topomesh.update_wisp_property('label', 0, dict(zip(surface_topomesh.wisps(0), vertex_cells))) surface_topomesh.update_wisp_property('label', 0, dict(zip(surface_topomesh.wisps(0), vertex_cells%256))) for property_name in self['attribute_names']: