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']: