diff --git a/setup.cfg b/setup.cfg
index 039071f2b3eb0e8b90ee12c81df9d90a8768405a..4aff539ee4f8b45e96c59fc9c13139e170f7cbba 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = epicure
-version = 0.2.2
+version = 0.2.5
 description = Napari plugin to manually correct epithelia segmentation in movies
 long_description = file: README.md
 long_description_content_type = text/markdown
diff --git a/src/epicure/Utils.py b/src/epicure/Utils.py
index f82ae114fbdaed9f77d5266030c3820d7a015eea..597725aff9ba0bbae5a7cf92b5ab79e6f25498f2 100644
--- a/src/epicure/Utils.py
+++ b/src/epicure/Utils.py
@@ -7,7 +7,7 @@ from skimage.segmentation import find_boundaries, expand_labels
 from napari.utils.translations import trans
 from napari.utils.notifications import show_info
 from napari.utils import notifications as nt
-from skimage.morphology import binary_dilation, disk
+from skimage.morphology import skeletonize, binary_dilation, disk
 import pandas as pd
 from epicure.laptrack_centroids import LaptrackCentroids
 
@@ -130,10 +130,7 @@ def clear_bindings(layer):
 
 def is_binary( img ):
     """ Test if more than 2 values (skeleton or labelled image) """
-    for frame in img:
-        if len(np.unique(frame)) > 2:
-            return False
-    return True
+    return all(len(np.unique(frame)) <= 2 for frame in img)
 
 def set_frame(viewer, frame):
     """ Set current frame """
@@ -308,7 +305,7 @@ def setNewLabel(label_layer, indices, newvalue, add_frame=None):
     if isinstance(newvalue, list):
         newvalue = np.array(newvalue)[np.where(changed_indices)[0]]
     label_layer.data_setitem( inds, newvalue )
-    return inds, newvalue, oldvalues
+    return inds, newvalue, oldvalues 
 
 def convert_coords( coord ):
     """ Get the time frame, and the 2D coordinates as int """
@@ -335,21 +332,25 @@ def isInsideBBox( bbox, obbox ):
     return False
 
 def setBBox(position, extend, imshape):
-    bbox = [int(position[0]-extend), int(position[1]-extend), int(position[2]-extend), int(position[0]+extend), int(position[1]+extend), int(position[2]+extend)]
-    for i in range(3):
-        if bbox[i] < 0:
-            bbox[i] = 0
-        if bbox[i+3] > imshape[i]:
-            bbox[i+3] = imshape[i]
+    bbox = [
+        max(int(position[0] - extend), 0),
+        max(int(position[1] - extend), 0),
+        max(int(position[2] - extend), 0),
+        min(int(position[0] + extend), imshape[0]),
+        min(int(position[1] + extend), imshape[1]),
+        min(int(position[2] + extend), imshape[2])
+    ]
     return bbox
 
 def setBBoxXY(position, extend, imshape):
-    bbox = [int(position[0]), int(position[1]-extend), int(position[2]-extend), int(position[0]+1), int(position[1]+extend), int(position[2]+extend)]
-    for i in range(3):
-        if bbox[i] < 0:
-            bbox[i] = 0
-        if bbox[i+3] > imshape[i]:
-            bbox[i+3] = imshape[i]
+    bbox = [
+        max(int(position[0]), 0),
+        max(int(position[1] - extend), 0),
+        max(int(position[2] - extend), 0),
+        min(int(position[0] + 1), imshape[0]),
+        min(int(position[1] + extend), imshape[1]),
+        min(int(position[2] + extend), imshape[2])
+    ]
     return bbox
 
 def getBBox2DFromPts(pts, extend, imshape):
@@ -357,17 +358,20 @@ def getBBox2DFromPts(pts, extend, imshape):
     arr = np.array(pts)
     ptsdim = arr.shape[1]
     if ptsdim == 2:
-        bbox = [int(np.min(arr[:,0])), int(np.min(arr[:,1])), int(np.max(arr[:,0]))+1, int(np.max(arr[:,1]))+1]
+        bbox = [
+            max( int(np.min(arr[:,0])) - extend, 0), 
+            max( int(np.min(arr[:,1])) - extend, 0), 
+            min( int(np.max(arr[:,0]))+1+extend, imshape[0]), 
+            min( int(np.max(arr[:,1]))+1+extend, imshape[1] )
+            ]
     if ptsdim == 3:
-        bbox = [int(np.min(arr[:,1])), int(np.min(arr[:,2])), int(np.max(arr[:,1]))+1, int(np.max(arr[:,2]))+1]
-    if extend > 0:
-        for i in range(2):
-            bbox[i] = bbox[i] - extend
-            if bbox[i] < 0:
-                bbox[i] = 0
-            bbox[i+2] = bbox[i+2] + extend
-            if bbox[i+2] > imshape[i]:
-                bbox[i+2] = imshape[i]
+        bbox = [
+            max( int(np.min(arr[:,1])) -extend, 0), 
+            max( int(np.min(arr[:,2])) - extend, 0),
+            min( int(np.max(arr[:,1]))+1 + extend, imshape[0]), 
+            min( int(np.max(arr[:,2]))+1 + extend, imshape[1] )
+            ]
+
     return bbox
 
 def getBBoxFromPts(pts, extend, imshape, outdim=None, frame=None):
@@ -395,19 +399,15 @@ def getBBoxFromPts(pts, extend, imshape, outdim=None, frame=None):
     return bbox
 
 def inside_bounds( pt, imshape ):
-    """ Chaque if given point is inside image limits """
-    for i in range( len(pt) ):
-        if (pt[i] < 0) or (pt[i] >= imshape[i]):
-            return False
-    return True
+    """ Check if given point is inside image limits """
+    return all(0 <= pt[i] < imshape[i] for i in range(len(pt)))
 
 def extendBBox2D( bbox, extend_factor, imshape ):
     """ Extend bounding box with given margin """
-    extend = max( bbox[2]-bbox[0], bbox[3]-bbox[1] )* extend_factor
+    extend = max(bbox[2] - bbox[0], bbox[3] - bbox[1]) * extend_factor
     bbox = np.array(bbox)
-    for i in range(2):
-        bbox[i] = max( bbox[i] - extend, 0 )
-        bbox[i+2] = min( bbox[i+2] + extend, imshape[i] )
+    bbox[:2] = np.maximum(bbox[:2] - extend, 0)
+    bbox[2:] = np.minimum(bbox[2:] + extend, imshape[:2])
     return bbox
 
 def getBBox2D(img, label):
@@ -420,8 +420,8 @@ def getBBox2D(img, label):
 
 def getPropLabel(img, label):
     """ Get the properties of label """
-    props = regionprops(np.uint8(img==label))
-    #print(props)
+    mask = np.uint8(img == label)
+    props = regionprops(mask)
     return props[0]
 
 def getBBoxLabel(img, label):
@@ -442,6 +442,22 @@ def getBBox2DMerge(img, label, second_label): #, checkTouching=False):
     props = regionprops(mask*1)
     return props[0].bbox, mask 
 
+
+def frame_to_skeleton(frame, connectivity=1):
+    """ convert labels frame to skeleton (thin boundaries) """
+    return skeletonize( find_boundaries(frame, connectivity=connectivity, mode="outer") )
+
+def remove_boundaries(img):
+    """ Put the boundaries pixels between labels as 0 """
+    bound = frame_to_skeleton( img, connectivity=1 )
+    img[bound>0] = 0
+    return img
+
+def ind_boundaries(img):
+    """ Get indices of the boundaries pixels between two labels """
+    bound = frame_to_skeleton( img, connectivity=1 )
+    return np.argwhere(bound>0)
+
 def checkTouchingLabels(img, label, second_label):
     """ Returns if labels are in contact (1-2 pixel away) """
     disk_one = disk(radius=1)
@@ -465,8 +481,9 @@ def position2DIn2DBBox(position, bbox):
     """ Returns the position shifted to its position inside the 2D bounding box """
     return (int(position[0]-bbox[0]), int(position[1]-bbox[1]))
 
-def toFullImagePos( indices, bbox ):
-    return [ [ind[0]+bbox[0], ind[1]+bbox[1]] for ind in indices ]
+def toFullImagePos(indices, bbox):
+    indices = np.array(indices)
+    return np.column_stack((indices[:, 0] + bbox[0], indices[:, 1] + bbox[1])).tolist()
 
 def addFrameIndices( indices, frame ):
     return [ [frame, ind[0], ind[1]] for ind in indices ]
@@ -476,19 +493,15 @@ def shiftFrameIndices( indices, add_frame ):
 
 def toFullMoviePos( indices, bbox, frame=None ):
     """ Replace indexes inside bounding box to full movie indexes """
-    if frame is not None:
-        indices = np.array(indices)
-        frame_arr = np.repeat(frame, len(indices[:,0]))
-        return np.stack([frame_arr, indices[:,0]+bbox[0], indices[:,1]+bbox[1]], axis=1)
-    ## frame is None if indices contains already the frame position
     indices = np.array(indices)
-    return np.stack([indices[:,0], indices[:,1]+bbox[0], indices[:,2]+bbox[1]], axis=1)
+    if frame is not None:
+        frame_arr = np.full(len(indices), frame)
+        return np.column_stack((frame_arr, indices[:, 0] + bbox[0], indices[:, 1] + bbox[1]))
+    return np.column_stack((indices[:, 0], indices[:, 1] + bbox[0], indices[:, 2] + bbox[1]))
 
 def cropBBox(img, bbox):
-    if len(bbox) == 6:
-        return img[bbox[0]:bbox[3], bbox[1]:bbox[4], bbox[2]:bbox[5]]
-    return img[bbox[0]:bbox[2], bbox[1]:bbox[3]]
-
+    slices = tuple(slice(bbox[i], bbox[i + len(bbox) // 2]) for i in range(len(bbox) // 2))
+    return img[slices]
 
 def crop_twoframes( img, bbox, frame ):
     """ Crop bounding box with two frames """
@@ -611,12 +624,27 @@ def labels_table( labimg, intensity_image=None, properties=None, extra_propertie
         return regionprops_table( labimg, intensity_image=intensity_image, properties=properties, extra_properties=extra_properties )
     return regionprops_table( labimg, properties=properties, extra_properties=extra_properties )
 
+def labels_properties( labimg ):
+    """ Returns basic label properties """
+    return regionprops( labimg )
+
+def labels_bbox( labimg ):
+    """ Returns for each label its bounding box """
+    return regionprops_table( labimg, properties=('label', 'bbox') )
+
 def tuple_int(pos):
     if len(pos) == 3:
         return ( (int(pos[0]), int(pos[1]), int(pos[2])) )
     if len(pos) == 2:
         return ( (int(pos[0]), int(pos[1])) )
 
+def get_consecutives( ordered ):
+    """ Returns the list of consecutives integers (already sorted) """
+    gaps = [ [start, end] for start, end in zip( ordered, ordered[1:] ) if start+1 < end ]
+    edges = iter( ordered[:1] + sum(gaps, []) + ordered[-1:] )
+    return list( zip(edges, edges) )
+
+
 def prop_to_pos(prop, frame):
     return np.array( (frame, int(prop.centroid[0]), int(prop.centroid[1])) )
 
diff --git a/src/epicure/editing.py b/src/epicure/editing.py
index 6d40953bd9e8a532e1efaedd1ce8d2425e2e9ef9..977b472d5de7991850b7fc90c3dc62c4725f22bf 100644
--- a/src/epicure/editing.py
+++ b/src/epicure/editing.py
@@ -1,10 +1,9 @@
 import numpy as np
-import time
 import edt
-from skimage.segmentation import watershed, expand_labels, clear_border, find_boundaries, random_walker
-from skimage.measure import regionprops, label, points_in_poly
+from skimage.segmentation import watershed, clear_border, find_boundaries, random_walker
+from skimage.measure import label, points_in_poly
 from skimage.morphology import binary_closing, binary_opening, binary_dilation, binary_erosion, disk
-from qtpy.QtWidgets import QVBoxLayout, QWidget, QGroupBox 
+from qtpy.QtWidgets import QVBoxLayout, QWidget
 from napari.layers.labels._labels_utils import interpolate_coordinates
 from scipy.ndimage import binary_fill_holes, distance_transform_edt, generate_binary_structure
 from scipy.ndimage import label as ndlabel 
@@ -152,7 +151,7 @@ class Editing( QWidget ):
         
         ## if try to fill an empty zone, ensure that it doesn't fill the skeletons
         if prev_label == 0:
-            skel = self.epicure.frame_to_skeleton( segdata )
+            skel = ut.frame_to_skeleton( segdata )
             skel_fill = max(np.max(segdata)+2, new_label+1)
             segdata[skel] = skel_fill
             skel = None
@@ -181,7 +180,7 @@ class Editing( QWidget ):
             self.napari_fill(coord, new_label, refresh=True)
             if prev_label == 0:
                 segdata[segdata==skel_fill] = 0  ## put skeleton back to 0
-                self.remove_boundaries(segdata)
+                ut.remove_boundaries(segdata)
             self.epicure.add_label(new_label, tframe)
         
         ## Finish filling step to ensure everything's fine
@@ -265,7 +264,7 @@ class Editing( QWidget ):
         new_labels = np.repeat(new_label, len(mask_indices)).tolist()
 
         ## Update label boundaries if necessary
-        cind_bound = self.ind_boundaries( painted )
+        cind_bound = ut.ind_boundaries( painted )
         if self.epicure.seglayer.preserve_labels:
             ind_bound = [ ind for ind in cind_bound if (cropdata[tuple(ind)] == new_label) ]
         else:
@@ -371,7 +370,7 @@ class Editing( QWidget ):
             new_labels = new_labels + ([free_labels[i]]*curindices.shape[0])    
         
         ## add the label boundary
-        indbound = self.ind_boundaries( new_cells )
+        indbound = ut.ind_boundaries( new_cells )
         indices = np.vstack( (indices, indbound) )
         new_labels = new_labels + np.repeat( 0, len(indbound) ).tolist()
         indices = ut.toFullMoviePos( indices, bbox, tframe )
@@ -714,23 +713,13 @@ class Editing( QWidget ):
                         curframe[splitted==splitlab] = i+1
                         labels.append(i+1)
 
-                curframe = self.remove_boundaries(curframe)
+                curframe = ut.remove_boundaries(curframe)
                 ## apply the split and propagate the label to descendant label
                 self.propagate_label_change( curframe, labels, labelBB, tframe, [startlab] )
             else:
                 if self.epicure.verbose > 0:
                     print("Split failed, no boundary in pixel intensities found")
 
-    def remove_boundaries(self, img):
-        """ Put the boundaries pixels between two labels as 0 """
-        bound = self.epicure.frame_to_skeleton( img, connectivity=1 )
-        img[bound>0] = 0
-        return img
-    
-    def ind_boundaries(self, img):
-        """ Get indices of the boundaries pixels between two labels """
-        bound = self.epicure.frame_to_skeleton( img, connectivity=1 )
-        return np.argwhere(bound>0)
 
     def redraw_along_line(self, tframe, positions):
         """ Redraw the two labels separated by a line drawn manually """
@@ -806,7 +795,7 @@ class Editing( QWidget ):
         labels = label(clab, background=0, connectivity=1)
         if (np.max(labels) == 2) & (np.sum(labels==1)>self.epicure.minsize) & (np.sum(labels==2)>self.epicure.minsize):
             ## get new image with the 2 cells to retrack
-            labels = expand_labels(labels, distance=dis+1)
+            labels = ut.touching_labels(labels, expand=dis+1)
             indmodif = []
             newlabels = []
             for i in range(2):
@@ -820,7 +809,7 @@ class Editing( QWidget ):
             indmodif = ut.toFullMoviePos( indmodif, bbox, frame )
             
             # remove the boundary between the two updated labels only
-            cind_bound = self.ind_boundaries( labels )
+            cind_bound = ut.ind_boundaries( labels )
             ind_bound = [ ind for ind in cind_bound if cropt[tuple(ind)]==curlabel ]
             ind_bound = ut.toFullMoviePos( ind_bound, bbox, frame )
             indmodif = np.vstack((indmodif, ind_bound))
@@ -898,12 +887,12 @@ class Editing( QWidget ):
         labels = label(clab, background=0, connectivity=1)
         if (np.max(labels) == 2) & (np.sum(labels==1)>self.epicure.minsize) & (np.sum(labels==2)>self.epicure.minsize):
             ## get new image with the 2 cells to retrack
-            labels = expand_labels(labels, distance=dis+1)
+            labels = ut.touching_labels(labels, expand=dis+1)
             curframe = np.zeros( cropt.shape, dtype="uint8" )
             for i in range(2):
                 curframe[ (labels==(i+1)) & (cropt==curlabel) ] = i+1
             
-            curframe = self.remove_boundaries(curframe)
+            curframe = ut.remove_boundaries(curframe)
             self.propagate_label_change( curframe, [1,2], bbox, frame, [curlabel] )
 
         else:
@@ -916,7 +905,7 @@ class Editing( QWidget ):
 
     def merge_labels(self, tframe, startlab, endlab, extend_factor=1.25):
         """ Merge the two given labels """
-        start_time = time.time()
+        start_time = ut.start_time()
         segt = self.epicure.seglayer.data[tframe]
         
         ## Crop around labels to work on smaller field of view
@@ -972,7 +961,7 @@ class Editing( QWidget ):
     ## Erase border cells
     def remove_border(self):
         """ Remove all cells that touch the border """
-        start_time = time.time()
+        start_time = ut.start_time()
         self.viewer.window._status_bar._toggle_activity_dock(True)
         size = int(self.border_size.text())
         if size == 0:
@@ -1006,7 +995,7 @@ class Editing( QWidget ):
 
     def remove_smalls( self ):
         """ Remove all cells smaller than given area (in nb pixels) """
-        start_time = time.time()
+        start_time = ut.start_time()
         self.viewer.window._status_bar._toggle_activity_dock(True)
         for i in progress(range(0, self.epicure.nframes)):
             self.remove_small_cells( np.copy(self.epicure.seglayer.data[i]), i)
@@ -1018,18 +1007,18 @@ class Editing( QWidget ):
         """ Remove if few the cell is only few pixels """
         #init_labels = set(np.unique(img))
         minarea = int(self.small_size.text())
-        props = regionprops( img )
+        props = ut.labels_properties( img )
         resimg = np.copy( img )
         for prop in props:
             if prop.area < minarea:
-                (resimg[prop.bbox[0]:prop.bbox[2], prop.bbox[1]:prop.bbox[3]])[prop.image] = 0
+                (resimg[prop.slice])[prop.image] = 0
         ## update the tracks after the potential disappearance of some cells
         self.epicure.seglayer.data[frame] = resimg
         self.epicure.removed_labels( img, resimg, frame )
     
     def merge_inside_cells( self ):
         """ Merge cell that falls inside another cell with ut """
-        start_time = time.time()
+        start_time = ut.start_time()
         self.viewer.window._status_bar._toggle_activity_dock(True)
         for i in progress(range(0, self.epicure.nframes)):
             self.merge_inside_cell(self.epicure.seglayer.data[i], i)
@@ -1039,12 +1028,7 @@ class Editing( QWidget ):
 
     def merge_inside_cell( self, img, frame ):
         """ Merge cells that fits inside the convex hull of a cell with it """
-        try:
-            from skimage.graph import RAG
-        except:
-            from skimage.future.graph import RAG  ## older version of scikit-image
-        touchlab = expand_labels(img, distance=3)  ## be sure that labels touch
-        graph = RAG( touchlab, connectivity=2)
+        graph = ut.connectivity_graph( img, distance=3)
         adj_bg = []
         
         nodes = list(graph.nodes)
@@ -1065,7 +1049,7 @@ class Editing( QWidget ):
     def create_shapelayer( self ):
         """ Create the layer that handle temporary drawings """
         shapes = []
-        shap = self.viewer.add_shapes( shapes, name=self.shapelayer_name, blending="additive", opacity=1, edge_width=2 )
+        shap = self.viewer.add_shapes( shapes, name=self.shapelayer_name, ndim=3, blending="additive", opacity=1, edge_width=2 )
         shap.text.visible = False
         shap.visible = False
 
@@ -1085,8 +1069,7 @@ class Editing( QWidget ):
         seed_layout.addWidget(seed_loadbtn)
         
         ## choose method and segment from seeds
-        gseg = QGroupBox("Seed based segmentation")
-        gseg_layout = QVBoxLayout()
+        gseg, gseg_layout = wid.group_layout( "Seed based segmentation" )
         seed_btn = wid.add_button( btn="Segment cells from seeds", btn_func=self.segment_from_points, descr="Segment new cells from placed seeds" )
         gseg_layout.addWidget(seed_btn)
         method_line, self.seed_method = wid.list_line( label="Method", descr="Seed based segmentation method to segment some cells" )
@@ -1118,7 +1101,7 @@ class Editing( QWidget ):
         if tframe > 0:
             pts = self.viewer.layers["Seeds"].data
             segp = self.epicure.seglayer.data[tframe-1]
-            props = regionprops(segp)
+            props = ut.labels_properties(segp)
             for prop in props:
                 cent = prop.centroid
                 ## create a seed in the centroid only in empty spaces
@@ -1172,7 +1155,7 @@ class Editing( QWidget ):
             self.diffusion_from_points( tframe, segBB, markers, maskBB, labelBB )
 
         ## finish segmentation: thin to have one pixel boundaries, update all
-        skelBB = self.epicure.frame_to_skeleton( segBB, connectivity=1 )
+        skelBB = ut.frame_to_skeleton( segBB, connectivity=1 )
         segBB[ skelBB>0 ] = 0
         self.reset_seeds()
         ## update the list of tracks with the potential new cells
@@ -1198,7 +1181,6 @@ class Editing( QWidget ):
         pos = ut.positions2DIn2DBBox( seeds, labelBB )
         markers = np.zeros(maskBB.shape, dtype="int32")
         freelabs = self.epicure.get_free_labels( len(pos) )
-        slab = freelabs[0]
         for freelab, p in zip(freelabs, pos):
             markers[p] = freelab
         return segBB, markers, maskBB, labelBB
@@ -1235,7 +1217,7 @@ class Editing( QWidget ):
         maxdist = float(self.max_distance.text())
         dist = 0
         while dist <= maxdist:
-            markers = expand_labels( markers, distance=1 )
+            markers = ut.touching_labels( markers, expand=1 )
             markers[maskBB==0] = 0
             dist = dist + 1
         segBB[(maskBB>0) * (markers>0)] = markers[(maskBB>0) * (markers>0)]
@@ -1427,12 +1409,11 @@ class Editing( QWidget ):
         if current_shape is None:
             return None
         self.current_bbox = ut.getBBox2DFromPts(current_shape, 30, self.epicure.imgshape2D)
-        self.current_cropshape = ut.positions2DIn2DBBox(current_shape, self.current_bbox )
-
+        self.current_cropshape = ut.positionsIn2DBBox(current_shape, self.current_bbox )
         tframe = ut.current_frame(self.viewer)
         segt = self.epicure.seglayer.data[tframe]
         croped = ut.cropBBox2D(segt, self.current_bbox)
-        labprops = regionprops(croped)
+        labprops = ut.labels_properties(croped)
         inside = points_in_poly( [lab.centroid for lab in labprops], self.current_cropshape )
         toedit = [lab.label for i, lab in enumerate(labprops) if inside[i] ]
         return toedit
@@ -1596,10 +1577,7 @@ class Editing( QWidget ):
 
     ######### overlay message
     def add_overlay_message(self):
-        over = self.epicure.text
-        text = over + "\n"
-        #for txt in self.epicure.overtext.values():
-        #    text += txt
+        text = self.epicure.text + "\n"
         ut.setOverlayText(self.viewer, text, size=10)
 
     ################## Track editing functions
@@ -1715,10 +1693,7 @@ class Editing( QWidget ):
                     if ut.shortcut_click_match( strack["split track"], event ):
                         start_frame = int(event.position[0])
                         label = ut.getCellValue(self.epicure.seglayer, event) 
-                        new_label = self.epicure.get_free_label()
-                        self.epicure.replace_label( label, new_label, start_frame )
-                        if self.epicure.verbose > 0:
-                            ut.show_info("Split track "+str(label)+" from frame "+str(start_frame))
+                        self.epicure.split_track( label, start_frame )
                         self.end_track_edit()
                         return
                         
@@ -1865,7 +1840,7 @@ class Editing( QWidget ):
             joinlab = new_label * binary_closing(joinlab, footprint)
            
             ## get the index and new values to change
-            indmodif = self.ind_boundaries( joinlab )
+            indmodif = ut.ind_boundaries( joinlab )
             indmodif = ut.toFullMoviePos( indmodif, bbox, frame )
             if ind_tomodif is None:
                 ind_tomodif = indmodif
@@ -1888,12 +1863,14 @@ class Editing( QWidget ):
         Merge track with label a with track of label b if consecutives frames. 
         It does not check if label are close in distance, assume it is.
         """
-        if abs(int(posb[0]) - int(posa[0])) != 1:
-            if self.epicure.verbose > 0:
-                print("Frames to merge are not consecutives, refused")
-            return
 
-        ## Frames are consecutives, swap so that a is first if necessary
+        if self.epicure.forbid_gaps:
+            if abs(int(posb[0]) - int(posa[0])) != 1:
+                if self.epicure.verbose > 0:
+                    print("Frames to merge are not consecutives, refused")
+                return
+
+        ## If frame b is before frame a, swap so that a is first 
         if posa[0] > posb[0]:
             posc = np.copy(posa)
             posa = posb
@@ -1905,13 +1882,13 @@ class Editing( QWidget ):
         ## Check that posa is last frame of label a and pos b first frame of label b
         if int(posa[0]) != self.epicure.tracking.get_last_frame( labela ):
             if self.epicure.verbose > 0:
-                print("Clicked label "+str(labela)+" at frame "+str(posa[0])+" is not the last frame of the track, refused")
-            return
+                print("Clicked label "+str(labela)+" at frame "+str(posa[0])+" was not the last frame of the track -> splitting it")
+            self.epicure.split_track( labela, int(posa[0])+1 )
 
         if posb[0] != self.epicure.tracking.get_first_frame( labelb ):
             if self.epicure.verbose > 0:
-                print("Clicked label "+str(labelb)+" at frame "+str(posb[0])+" is not the first frame of the track, refused")
-            return
+                print("Clicked label "+str(labelb)+" at frame "+str(posb[0])+" is not the first frame of the track -> splitting it")
+            labelb = self.epicure.split_track( labelb, int(posb[0]) )
 
         self.epicure.replace_label( labelb, labela, int(posb[0]) )
         
@@ -1922,7 +1899,6 @@ class Editing( QWidget ):
     
     def get_position_label_2D(self, img, labels, parent_labels):
         """ Get position of each label to update with parent label """
-        #start_time = time.time()
         indmodif = None
         new_labels = []
         ## get possible free labels, to be sure that it will not take the same ones
@@ -1938,7 +1914,6 @@ class Editing( QWidget ):
             else:
                 indmodif = np.vstack((indmodif, curmodif))
             new_labels = new_labels + ([parent_label]*curmodif.shape[0])
-        #ut.show_info("Pos label in "+"{:.3f}".format((time.time()-start_time)/60)+" min")
         return indmodif, new_labels, parent_labels
 
     def inherit_parent_labels(self, myframe, labels, bbox, frame):
@@ -1964,7 +1939,6 @@ class Editing( QWidget ):
     
     def inherit_child_labels(self, myframe, labels, bbox, frame, parent_labels, keep_labels):
         """ Get child labels if any and indices to modify with it """
-        #start_time = time.time()
         if (self.epicure.tracked == 0 ) or (frame>=self.epicure.nframes-1):
             return [], []
         else:
@@ -2000,8 +1974,8 @@ class Editing( QWidget ):
 
     def propagate_label_change(self, myframe, labels, bbox, frame, keep_labels):
         """ Propagate the new labelling to match parent/child labels """
-        start_time = time.time()
-        indmodif = self.ind_boundaries( myframe )
+        start_time = ut.start_time()
+        indmodif = ut.ind_boundaries( myframe )
         indmodif = ut.toFullMoviePos( indmodif, bbox, frame )
         #ut.show_info("Boundaries in "+"{:.3f}".format((time.time()-start_time)/60)+" min")
         new_labels = np.repeat(0, len(indmodif)).tolist()
@@ -2034,7 +2008,7 @@ class Editing( QWidget ):
         if self.epicure.verbose > 1:
             print("Interpolating between "+str(labela)+" and "+str(labelb))
             print("From frame "+str(framea)+" to frame "+str(frameb))
-            start_time = time.time()
+            start_time = ut.start_time()
         
         sega = self.epicure.seglayer.data[framea]
         maska = np.isin( sega, [labela] )
@@ -2043,7 +2017,7 @@ class Editing( QWidget ):
 
         ## get merged bounding box, and crop around it
         mask = maska | maskb
-        props = regionprops(mask*1)
+        props = ut.labels_properties(mask*1)
         bbox = ut.extendBBox2D( props[0].bbox, extend_factor=1.2, imshape=mask.shape )
 
         maska = ut.cropBBox2D( maska, bbox )
@@ -2072,7 +2046,7 @@ class Editing( QWidget ):
             new_labels = new_labels + [labela]*len(indmodif)
 
             ## be sure to remove the boundaries with neighbor labels
-            bound_ind = self.ind_boundaries( tochange )
+            bound_ind = ut.ind_boundaries( tochange )
             new_labels = new_labels + [0]*len(bound_ind)
             bound_ind = ut.toFullMoviePos( bound_ind, bbox, frame )
             inds = np.vstack( (inds, bound_ind) )
diff --git a/src/epicure/epicuring.py b/src/epicure/epicuring.py
index 8514c669d5f9823ca8791c4d47aa2e816de48935..9fa9c87e4e353781d71c1df0e017f2a742d2506f 100644
--- a/src/epicure/epicuring.py
+++ b/src/epicure/epicuring.py
@@ -33,6 +33,7 @@ class EpiCure():
         if self.viewer is None:
             self.viewer = napari.Viewer(show=False)
         self.viewer.title = "Napari - EpiCure"
+        self.init_epicure_metadata()   ## initialize metadata variables (scalings, channels)
         self.img = None
         self.inspecting = None
         self.others = None
@@ -41,6 +42,7 @@ class EpiCure():
         self.thickness = 4 ## thickness of junctions, wider
         self.minsize = 4   ## smallest number of pixels in a cell
         self.verbose = 1  ## level of printing messages (None/few, normal, debug mode)
+        self.event_class = ["division", "extrusion", "suspect"] ## list of possible events
         
         self.overtext = dict()
         self.help_index = 1   ## current display index of help overlay
@@ -62,8 +64,6 @@ class EpiCure():
         if "Display" in self.settings:
             if "Colors" in self.settings["Display"]:
                 self.display_colors = self.settings["Display"]["Colors"]
-        
-        self.init_epicure_metadata()   ## initialize metadata variables (scalings, channels)
 
     def init_epicure_metadata( self ):
         """ Returns metadata to save """
@@ -74,6 +74,8 @@ class EpiCure():
         self.epi_metadata["ScaleT"] = 1
         self.epi_metadata["UnitT"] = "min"
         self.epi_metadata["MainChannel"] = 0
+        self.epi_metadata["Allow gaps"] = True
+        self.epi_metadata["Verbose"] = 1
 
     def get_resetbtn_color( self ):
         """ Returns the color of Reset buttons if defined """
@@ -122,6 +124,16 @@ class EpiCure():
     def quantiles(self):
         return tuple(np.quantile(self.img, [0.01, 0.9999]))
 
+    def set_verbose( self, verbose ):
+        """ Set verbose level """
+        self.verbose = verbose
+        self.epi_metadata["Verbose"] = verbose
+
+    def set_gaps_option( self, allow_gap ):
+        """ Set the mode for gap allowing/forbid in tracks """
+        self.epi_metadata["Allow gaps"] = allow_gap
+        self.forbid_gaps = not allow_gap
+
     def set_scales( self, scalexy, scalet, unitxy, unitt ):
         """ Set the scaling units for outputs """
         self.epi_metadata["ScaleXY"] = scalexy
@@ -182,7 +194,13 @@ class EpiCure():
         if np.max(self.seg) < 50000:
             self.dtype = np.uint16
             self.seg = np.uint16(self.seg)
-        
+
+        ## define a reference size of the movie to scale default parameters
+        self.reference_size = np.max( self.imgshape2D )
+        # on cell area ut.summary_labels( self.seg[0] )
+        if self.verbose > 1:
+            print("Reference size of the movie: "+str(self.reference_size))
+
         # display the segmentation file movie
         if self.viewer is not None:
             self.seglayer = self.viewer.add_labels( self.seg, name="Segmentation", blending="additive", opacity=0.5 )
@@ -494,7 +512,8 @@ class EpiCure():
             try:
                 epidata = pickle.load( infile )
                 if "EpiMetaData" in epidata.keys():
-                    self.epi_metadata = epidata["EpiMetaData"]
+                    for key, vals in epidata["EpiMetaData"].items():
+                        self.epi_metadata[key] = vals
                 infile.close()
             except:
                 ut.show_warning( "Could not read EpiCure data file "+epiname )
@@ -700,10 +719,8 @@ class EpiCure():
 
     def copy_border( self, skel, bin ):
         """ Copy the pixel border onto skeleton image """
-        skel[0,:] = bin[0,:] ## borders
-        skel[:,0] = bin[:,0]
-        skel[skel.shape[0]-1,:] = bin[skel.shape[0]-1,:]
-        skel[:,skel.shape[1]-1] = bin[:,skel.shape[1]-1]
+        skel[[0, -1], :] = bin[[0, -1], :]  # top and bottom borders
+        skel[:, [0, -1]] = bin[:, [0, -1]]  # left and right borders
         return skel
         
     def thin_boundaries(self):
@@ -723,9 +740,9 @@ class EpiCure():
         newlab = np.zeros( newlab.shape, np.uint32 )   
         for prop in props:
             if prop.label != 0:
-                labvals, counts = np.unique(labelled[prop.bbox[0]:prop.bbox[2],prop.bbox[1]:prop.bbox[3]][prop.image], return_counts=True )
+                labvals, counts = np.unique(labelled[prop.slice][prop.image], return_counts=True )
                 labval = labvals[ np.argmax(counts) ]
-                newlab[prop.bbox[0]:prop.bbox[2],prop.bbox[1]:prop.bbox[3]][prop.image] = labval
+                newlab[prop.slice][prop.image] = labval
         return newlab
 
     def thin_seg_one_frame(self, tframe):
@@ -735,9 +752,6 @@ class EpiCure():
         skel = self.copy_border( skel, bin_img )
         self.seg[tframe] = self.skeleton_to_label( skel, self.seg[tframe] )
 
-    def frame_to_skeleton(self, frame, connectivity=1):
-        """ convert labels frame to skeleton (thin boundaries) """
-        return skeletonize( find_boundaries(frame, connectivity=connectivity, mode="outer") )
 
     def add_skeleton(self):
         """ add a layer containing the skeleton movie of the segmentation """
@@ -763,7 +777,7 @@ class EpiCure():
             start_time = ut.start_time()
             skel = np.zeros(self.seg.shape, dtype="uint8")
             for z in range(self.seg.shape[0]):
-                skel[z,] = apply_parallel( self.frame_to_skeleton, self.seg[z,], depth=5, compute=True )
+                skel[z,] = apply_parallel( ut.frame_to_skeleton, self.seg[z,], depth=5, compute=True )
         if self.verbose > 0:
             ut.show_duration(start_time)
         return skel
@@ -896,6 +910,7 @@ class EpiCure():
             movie[ i, xminshift:xmaxshift, yminshift:ymaxshift ] = self.img[ frame, xmin:xmax, ymin:ymax ]
         return movie 
 
+
     ### Check individual cell features
     def cell_radius( self, label, frame ):
         """ Approximate the cell radius at given frame """
@@ -1004,7 +1019,14 @@ class EpiCure():
         ## replace the two initial labels, in inversed order
         self.replace_label( tmp_labels[0], olab, start_frame )
         self.replace_label( tmp_labels[1], lab, start_frame )
-        
+
+    def split_track( self, label, frame ):
+        """ Split a track at given frame """
+        new_label = self.get_free_label()
+        self.replace_label( label, new_label, frame )
+        if self.verbose > 0:
+            ut.show_info("Split track "+str(label)+" from frame "+str(frame))
+        return new_label
 
     def update_changed_labels_img( self, img_before, img_after, added=True, removed=True ):
         """ Update tracks from changes between the two labelled images """
@@ -1091,9 +1113,11 @@ class EpiCure():
         if numeric:
             groups = [0]*len(labels)
         else:
-            groups = ["None"]*len(labels)
+            groups = ["Ungrouped"]*len(labels)
         for lab in np.unique(labels):
             gr = self.find_group( lab )
+            if gr is None:
+                continue
             if numeric:
                 gr = self.groups.keys().index() + 1
             indexes = (np.argwhere(labels==lab)).flatten()
@@ -1113,7 +1137,15 @@ class EpiCure():
         ## add only non present label(s)
         grlabels = self.groups[ group ]
         self.groups[ group ] = list( set( grlabels + labels.tolist()) )
-        
+
+    def group_of_labels( self ):
+        """ List the group of each label """
+        res = {}
+        for group, labels in self.groups.items():
+            for label in labels:
+                res[label] = group
+        return res
+
     def find_group(self, label):
         """ Find in which group the label is """
         for gr, labs in self.groups.items():
diff --git a/src/epicure/epiwidgets.py b/src/epicure/epiwidgets.py
index e9cd9c23376024b8792ea78302cd8150adfb5499..0ff8bc8c5840cf667dc0dc70ff86b430091a1c0c 100644
--- a/src/epicure/epiwidgets.py
+++ b/src/epicure/epiwidgets.py
@@ -1,5 +1,5 @@
 import epicure.Utils as ut
-from qtpy.QtWidgets import QPushButton, QCheckBox, QHBoxLayout, QLabel, QLineEdit, QComboBox, QSpinBox, QSlider, QGroupBox
+from qtpy.QtWidgets import QPushButton, QCheckBox, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit, QComboBox, QSpinBox, QSlider, QGroupBox
 from qtpy.QtCore import Qt
 
 def help_button( link, description="", display_settings=None ):
@@ -22,6 +22,12 @@ def help_button( link, description="", display_settings=None ):
             help_btn.setStyleSheet( 'QPushButton {background-color: '+color+'}' )
     return help_btn
 
+def group_layout( grname ):
+    """ Create a group box with a vertical layout """
+    group = QGroupBox( grname )
+    layout = QVBoxLayout()
+    return group, layout
+
 def checkgroup_help( name, checked, descr, help_link, display_settings=None, groupnb=None ):
     """ Create a group that can be show/hide with checkbox and an help button """
     group = QGroupBox( name )
@@ -73,6 +79,21 @@ def add_check( check, checked, check_func=None, descr="" ):
     cbox.setChecked( checked )
     return cbox
 
+def label_line( label ):
+    """ Returns a label line """
+    lab = QLabel( label )
+    return lab
+
+def hlayout():
+    """ Return a horizontal layout """
+    return QHBoxLayout()
+
+def add_check_tolayout( layout, check, checked, check_func=None, descr="" ):
+    """ Add a checkbox with set parameters """
+    cbox = add_check( check, checked, check_func, descr )
+    layout.addWidget( cbox )
+    return cbox
+
 def double_check( checka, checkeda, funca, descra, checkb, checkedb, funcb, descrb ):
     """ Line with two customized checkboxes """
     line = QHBoxLayout()
@@ -244,6 +265,36 @@ def list_line( label, descr="", func=None ):
         value.currentIndexChanged.connect( func )
     return line, value
 
+def listbox( func=None ):
+    """ Create a choice list to edit """
+    ## Value editable part
+    value = QComboBox()
+    if func is not None:
+        value.currentIndexChanged.connect( func )
+    return value
+
+def slider_line( name, minval, maxval, step, value, slidefunc=None, descr="" ):
+    """ Line with a text and a slider """
+    line = QHBoxLayout()
+    ## add name if any
+    if name is not None:
+        lab = QLabel()
+        lab.setText( name )
+        line.addWidget( lab )
+    ## add slider
+    slider =  QSlider( Qt.Horizontal )
+    slider.setMinimum( minval )
+    slider.setMaximum( maxval )
+    slider.setSingleStep( step )
+    slider.setValue( value )
+    if slidefunc is not None:
+        slider.valueChanged.connect( slidefunc )
+    if descr != "":
+        slider.setToolTip( descr )
+    line.addWidget( slider )
+    return line, slider
+
+
 def slider_line( name, minval, maxval, step, value, slidefunc=None, descr="" ):
     """ Line with a text and a slider """
     line = QHBoxLayout()
diff --git a/src/epicure/inspecting.py b/src/epicure/inspecting.py
index 7696473249ad92bca2c6ff322bd79b046a22b611..2c8ea1caee7389b869c198dd63b75e2f23af1fb3 100644
--- a/src/epicure/inspecting.py
+++ b/src/epicure/inspecting.py
@@ -1,8 +1,8 @@
 import numpy as np
 from skimage import filters
-from skimage.measure import regionprops, label
+from skimage.measure import regionprops
 from skimage.morphology import binary_erosion, binary_dilation, disk
-from qtpy.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QLabel, QComboBox
+from qtpy.QtWidgets import QVBoxLayout, QWidget, QLabel
 from napari.utils import progress
 import epicure.Utils as ut
 import epicure.epiwidgets as wid
@@ -24,6 +24,7 @@ class Inspecting(QWidget):
         self.eventlayer_name = "Events"
         self.events = None
         self.win_size = 10
+        self.event_class = self.epicure.event_class
 
         ## Print the current number of events
         self.nevents_print = QLabel("")
@@ -33,21 +34,29 @@ class Inspecting(QWidget):
         layout = QVBoxLayout()
         layout.addWidget( self.nevents_print )
         
-        show_line, self.show_divisions, self.show_suspects = wid.double_check( "Show divisions", True, None, "Show/hide the division events", "Show suspects", True, None, "Show/hide suspect events" )
+        show_label = wid.label_line( "Show events:" )
+        layout.addWidget( show_label )
+        show_line = wid.hlayout()
+        self.show_class = []
+        for i, eclass in enumerate(self.event_class) :
+            self.show_class.append( wid.add_check_tolayout( show_line, eclass, True, None, "Show/hide the "+eclass ) )
+            self.show_class[i].stateChanged.connect( self.show_hide_events )
         layout.addLayout( show_line )
-        self.show_divisions.stateChanged.connect( self.show_hide_divisions )
-        self.show_suspects.stateChanged.connect( self.show_hide_suspects )
-        
+
         ## Handle division events
-        #div_line, self.show_divisions = wid.button_check_line( btn="Update divisions", btn_func=self.get_divisions, check="Show divisions", checked=True, #checkfunc=None, descr_btn="Update the list of division events from the track graph", descr_check="Show/hide the division events", leftbtn=False )
         update_div_btn = wid.add_button( btn="Update divisions from graph", btn_func=self.get_divisions, descr="Update the list of division events from the track graph" )
         layout.addWidget(update_div_btn)
-        #self.show_divisions.stateChanged.connect( self.show_hide_divisions )
-        
+
         ### Reset: delete all events
         reset_color = self.epicure.get_resetbtn_color()
         reset_event_btn = wid.add_button( btn="Reset all events", btn_func=self.reset_all_events, descr="Delete all current events", color=reset_color )
         layout.addWidget(reset_event_btn)
+
+        ## Visualisation options
+        disp_line, self.event_disp, self.displayevent = wid.checkgroup_help( "Display options", False, "Show/hide event display options panel", "event#visualisation", self.epicure.display_colors, "group3" )
+        self.create_displayeventBlock() 
+        layout.addLayout( disp_line )
+        layout.addWidget(self.displayevent)
         
         ## Error suggestions based on cell features
         outlier_line, self.outlier_vis, self.featOutliers = wid.checkgroup_help( "Outlier options", False, "Show/Hide outlier options panel", "event#frame-based-events", self.epicure.display_colors, "group" )
@@ -61,12 +70,6 @@ class Inspecting(QWidget):
         layout.addLayout( track_line )
         layout.addWidget(self.eventTrack)
         
-        ## Visualisation options
-        disp_line, self.event_disp, self.displayevent = wid.checkgroup_help( "Display options", False, "Show/hide event display options panel", "event#visualisation", self.epicure.display_colors, "group3" )
-        self.create_displayeventBlock() 
-        layout.addLayout( disp_line )
-        layout.addWidget(self.displayevent)
-        
         self.setLayout(layout)
         self.key_binding()
 
@@ -115,7 +118,7 @@ class Inspecting(QWidget):
             num_event = int(self.event_num.value())
             nevents = self.nb_events()
             if num_event < 0:
-                if self.event_sizenb_events( only_suspect=True ) == 0:
+                if self.nb_events( only_suspect=True ) == 0:
                     if self.epicure.verbose > 0:
                         print("No more suspect event")
                     return  
@@ -123,18 +126,35 @@ class Inspecting(QWidget):
                     self.event_num.setValue(0)
             else:
                 self.event_num.setValue( (num_event+1)%nevents )
-            self.skip_nonsuspect_event( nevents, nevents )
+            self.skip_nonselected_event( nevents, nevents )
             self.go_to_event()       
 
-    def skip_nonsuspect_event( self, nevents, left ):
-        """ Skip next event if not a suspect one (eg division) """
+    def skip_nonselected_event( self, nevents, left ):
+        """ Skip next event if not a selected one (show event is not checked) """
+        if left < 0:
+            return 0
+        
         index = int(self.event_num.value())
-        if self.is_division( index ):
-            index = (index + 1)%nevents
+        nothing_showed = True
+        for i, curclass in enumerate(self.show_class):
+            if curclass.isChecked():
+                nothing_showed = False
+                break
+        if nothing_showed:
+            ## nothing is shown, then go through all events
             self.event_num.setValue( index )
-            self.skip_nonsuspect_event( nevents, left-1 )
-        if left < 0:
-            return
+            return index
+        
+        event_class = self.get_event_class( index )
+        ## Show only if show event class is selected
+        if self.show_class[ event_class ].isChecked():
+            self.event_num.setValue( index )
+            return index
+        ## else go to next event
+        index = (index + 1)%nevents
+        self.event_num.setValue( index )
+        return self.skip_nonselected_event( nevents, left-1 )
+    
 
     def create_eventlayer(self):
         """ Create a point layer that contains the events """
@@ -166,8 +186,9 @@ class Inspecting(QWidget):
 
     def update_nevents_display( self ):
         """ Update the display of number of event"""
-        text = str(self.nb_events(only_suspect=True))+" suspects \n" 
-        text += str(self.nb_type("division"))+" divisions" 
+        text = str(self.nb_events(only_suspect=True))+" suspects | " 
+        text += str(self.nb_type("division"))+" divisions | "
+        text += str(self.nb_type("extrusion"))+" extrusions"  
         self.nevents_print.setText( text )
 
     def nb_events( self, only_suspect=False ):
@@ -180,7 +201,7 @@ class Inspecting(QWidget):
             return 0
         if not only_suspect:
             return len(self.events.properties["score"])
-        return ( len(self.events.properties["score"]) - self.nb_type("division") )
+        return ( len(self.events.properties["score"]) - self.nb_type("division") - self.nb_type("extrusion") )
 
     def get_events_from_type( self, feature ):
         """ Return the list of events of a given type """
@@ -207,26 +228,26 @@ class Inspecting(QWidget):
         disp_layout = QVBoxLayout()
         
         ## Color mode
-        colorlay = QHBoxLayout()
-        color_label = QLabel()
-        color_label.setText("Color by:")
-        colorlay.addWidget(color_label)
-        self.color_choice = QComboBox()
-        colorlay.addWidget(self.color_choice)
+        colorlay, self.color_choice = wid.list_line( "Color by:", "Choose color to display the events", self.color_events )
         self.color_choice.addItem("None")
         self.color_choice.addItem("score")
-        self.color_choice.addItem("tracking-2->1")
-        self.color_choice.addItem("tracking-1-2-*")
+        self.color_choice.addItem("track-2->1")
+        self.color_choice.addItem("track-1-2-*")
         self.color_choice.addItem("track-length")
+        self.color_choice.addItem("track-gap")
         self.color_choice.addItem("division")
         self.color_choice.addItem("area")
         self.color_choice.addItem("solidity")
         self.color_choice.addItem("intensity")
         self.color_choice.addItem("tubeness")
-        self.color_choice.currentIndexChanged.connect(self.color_events)
         disp_layout.addLayout(colorlay)
-        
-        sizelay, self.event_size = wid.slider_line( "Point size:", minval=0, maxval=50, step=1, value=10, slidefunc=self.display_event_size, descr="Choose the current point size display" ) 
+
+        esize = int(self.epicure.reference_size/75+10)
+        msize = 100
+        if esize > 70:
+            msize = 200
+        esize = min( esize, 100 )
+        sizelay, self.event_size = wid.slider_line( "Point size:", minval=0, maxval=msize, step=1, value=esize, slidefunc=self.display_event_size, descr="Choose the current point size display" ) 
         disp_layout.addLayout(sizelay)
 
         ### Interface to select a event and zoom on it
@@ -301,6 +322,8 @@ class Inspecting(QWidget):
         features = list( self.event_types.keys() )
         if "division" in features:
             features.remove( "division" )
+        if "extrusion" in features:
+            features.remove( "extrusion" )
         return features
 
     def show_subset_event( self, feature, show=True ):
@@ -367,8 +390,8 @@ class Inspecting(QWidget):
         disp["Outliers ON"] = self.outlier_vis.isChecked()
         disp["Track ON"] = self.track_vis.isChecked()
         disp["EventDisp ON"] = self.event_disp.isChecked()
-        disp["Show divisions"] = self.show_divisions.isChecked()
-        disp["Show suspects"] = self.show_suspects.isChecked()
+        for i, eclass in enumerate(self.event_class):
+            disp["Show "+eclass] = self.show_class[i].isChecked()
         disp["Ignore border"] = self.ignore_borders.isChecked()
         disp["Flag length"] = self.check_length.isChecked()
         disp["length"] = self.min_length.text()
@@ -376,7 +399,9 @@ class Inspecting(QWidget):
         disp["Check shape"] = self.check_shape.isChecked()
         disp["Get apparitions"] = self.get_apparition.isChecked()
         disp["Get disparitions"] = self.get_disparition.isChecked()
+        disp["Get gaps"] = self.get_gaps.isChecked()
         disp["threshold disparition"] = self.threshold_disparition.text()
+        disp["Min gap"] = self.min_gaps.text()
         disp["Min area"] = self.min_area.text()
         disp["Max area"] = self.max_area.text()
         disp["Current frame"] = self.feat_onframe.isChecked()
@@ -394,12 +419,10 @@ class Inspecting(QWidget):
             if setting == "Point size":
                 self.event_size.setValue( int(val) )
                 #self.display_event_size()
-            if setting == "Show divisions":
-                self.show_divisions.setChecked( val )
-                self.show_hide_divisions()
-            if setting == "Show suspects":
-                self.show_suspects.setChecked( val )        
-                self.show_hide_suspects()
+            for i, eclass in enumerate(self.event_class):
+                if setting == "Show "+eclass:
+                    self.show_class[i].setChecked( val )
+            self.show_hide_events()
             if setting == "Ignore border":
                 self.ignore_borders.setChecked( val )
             if setting == "Flag length":
@@ -414,8 +437,12 @@ class Inspecting(QWidget):
                 self.get_apparition.setChecked( val )
             if setting == "Get disparitions":
                 self.get_disparition.setChecked( val )
+            if setting == "Get gaps":
+                self.get_gaps.setChecked( val )    
             if setting == "Threshold disparition":
                 self.threshold_disparition.setText( val )
+            if setting == "Min gap":
+                self.min_gaps.setText( val )
             if setting == "Min area":
                 self.min_area.setText( val )
             if setting == "Max area":
@@ -457,7 +484,7 @@ class Inspecting(QWidget):
         features["label"] = np.array([label], dtype=self.epicure.dtype)
         features["score"] = np.array([0], dtype="uint8")
         pts = [pos]
-        self.events = self.viewer.add_points( np.array(pts), properties=features, face_color="score", size = 10, symbol="x", name="Events", )
+        self.events = self.viewer.add_points( np.array(pts), properties=features, face_color="score", size = int( self.event_size.value() ), symbol="x", name="Events", )
         self.add_event_type(0, sid, featurename)
         self.events.refresh()
         self.update_nevents_display()
@@ -825,9 +852,16 @@ class Inspecting(QWidget):
         track_layout.addWidget(self.get_apparition)
        
         ## Look for sudden disappearance of tracks
-        disp_line, self.get_disparition, self.threshold_disparition = wid.check_value( check="Flag track disparition", checkfunc=None, checked=True, value="50", descr="Add a suspect if a track disappears (not last frame, not border) and the cell area is above threshold", label="if cell area above" )
+        disp_line, self.get_disparition, self.threshold_disparition = wid.check_value( check="Flag track disparition", checkfunc=None, checked=True, value="200", descr="Add a suspect if a track disappears (not last frame, not border) and the cell area is above threshold", label="if cell area above" )
         track_layout.addLayout( disp_line )
 
+        self.get_extrusions = wid.add_check( "Get extrusions", True, None, "Add extrusions events when a track is disappearing normally (below cell area threshold)" )
+        track_layout.addWidget( self.get_extrusions )
+
+        ## Look for temporal gaps in tracks
+        gap_line, self.get_gaps, self.min_gaps = wid.check_value( check="Flag track gaps", checkfunc=None, checked=True, value="1", descr="Add a suspect if a track has gaps longer than threshold (in nb of frames)", label="if gap above" )
+        track_layout.addLayout( gap_line )
+
         ## track length event_types
         ilengthlay, self.check_length, self.min_length = wid.check_value( check="Flag tracks smaller than", checkfunc=None, checked=True, value="1", descr="Add a suspect event for each track smaller than chosen value (in number of frames)" )
         track_layout.addLayout(ilengthlay)
@@ -848,13 +882,14 @@ class Inspecting(QWidget):
 
     def reset_tracking_event(self):
         """ Remove events from tracking """
-        self.reset_event_type("tracking-1-2-*", None)
-        self.reset_event_type("tracking-2->1", None)
+        self.reset_event_type("track-1-2-*", None)
+        self.reset_event_type("track-2->1", None)
         self.reset_event_type("track-length", None)
         self.reset_event_type("track-size", None)
         self.reset_event_type("track-shape", None)
         self.reset_event_type("track-apparition", None)
         self.reset_event_type("track-disparition", None)
+        self.reset_event_type("track-gap", None)
         self.reset_event_range()
 
     def track_length(self):
@@ -871,7 +906,7 @@ class Inspecting(QWidget):
         """ Look for suspicious tracks """
         self.viewer.window._status_bar._toggle_activity_dock(True)
         ut.set_visibility( self.viewer, "Events", True )
-        progress_bar = progress( total=8 )
+        progress_bar = progress( total=9 )
         progress_bar.update(0)
         self.reset_tracking_event()
         progress_bar.update(1)
@@ -899,6 +934,10 @@ class Inspecting(QWidget):
             progress_bar.set_description("Check track disparition")
             self.track_disparition( tracks, progress_bar )
         progress_bar.update(7)
+        if self.get_gaps.isChecked():
+            progress_bar.set_description("Check temporal gaps in tracks")
+            self.track_gaps( tracks, progress_bar )
+        progress_bar.update(8)
         progress_bar.close()
         self.viewer.window._status_bar._toggle_activity_dock(False)
         ut.set_active_layer( self.viewer, "Segmentation" )
@@ -924,7 +963,7 @@ class Inspecting(QWidget):
                 pos = [ fframe, posxy[0], posxy[1] ]
                 if self.epicure.verbose > 2:
                     print("Appearing track: "+str(track_id)+" at frame "+str(fframe) )
-                self.add_event(pos, track_id, "tracking-apparition", refresh=False)
+                self.add_event(pos, track_id, "track-apparition", refresh=False)
         self.refresh_events()
         if self.epicure.verbose > 1:
             ut.show_duration( start_time, "Tracks apparition took " )
@@ -947,10 +986,19 @@ class Inspecting(QWidget):
             ## Not on border, check if potential division
             if self.epicure.tracking.is_parent( track_id ):
                 continue
+       
             ## check if the cell area is below the threshold, then considered as ok (likely extrusion)
             if (threshold_area > 0):
                 cell_area = self.epicure.cell_area( track_id, lframe )
                 if cell_area < threshold_area:
+                    if self.get_extrusions.isChecked():
+                        ## event extrusion
+                        posxy = self.epicure.tracking.get_position( track_id, lframe )
+                        if posxy is not None:
+                            pos = [ lframe, posxy[0], posxy[1] ]
+                        if self.epicure.verbose > 2:
+                            print("Add extrusion: "+str(track_id)+" at frame "+str(lframe) )
+                        self.add_event( pos, track_id, "extrusion", symb="diamond", color="red", refresh=False )
                     continue
             ## event disparition
             posxy = self.epicure.tracking.get_position( track_id, lframe )
@@ -958,12 +1006,42 @@ class Inspecting(QWidget):
                 pos = [ lframe, posxy[0], posxy[1] ]
                 if self.epicure.verbose > 2:
                     print("Disappearing track: "+str(track_id)+" at frame "+str(lframe) )
-                self.add_event(pos, track_id, "tracking-disparition", refresh=False)
+                self.add_event(pos, track_id, "track-disparition", refresh=False)
         sub_bar.close()
         self.refresh_events()
         if self.epicure.verbose > 1:
             ut.show_duration( start_time, "Tracks disparition took " )
 
+
+    def track_gaps( self, tracks, progress_bar ):
+        """ Check if some track have temporal gaps above a given threshold of frames """
+        start_time = time.time()
+        ## Track disappears in the movie, not last frame
+        ctracks = tracks
+        min_gaps = int(self.min_gaps.text())
+        sub_bar = progress( total = len( ctracks ), desc="Check gaps in tracks", nest_under = progress_bar )
+        gaped = self.epicure.tracking.check_gap( ctracks, verbose=0 )
+        if len( gaped ) > 0:
+            for i, track_id in enumerate( gaped ):
+                sub_bar.update( i )
+                gap_frames = self.epicure.tracking.gap_frames( track_id )
+                if len( gap_frames ) > 0:
+                    gaps = ut.get_consecutives( gap_frames )
+                    if self.epicure.verbose > 1:
+                        print("Found gaps in track "+str(track_id)+" : "+str(gaps) )
+                    for gapy in gaps:
+                        if (gapy[1]-gapy[0]+1) >= min_gaps:
+                            ## flag gap as it's long enough
+                            poszxy = self.epicure.tracking.get_middle_position( track_id, gapy[0]-1, gapy[1]+1 )
+                            if poszxy is not None:
+                                if self.epicure.verbose > 2:
+                                    print("Gap in track: "+str(track_id)+" at frame "+str(poszxy[0]) )
+                                self.add_event(poszxy, track_id, "track-gap", refresh=False)
+        sub_bar.close()
+        self.refresh_events()
+        if self.epicure.verbose > 1:
+            ut.show_duration( start_time, "Tracks gaps took " )
+
     def track_21(self):
         """ Look for event track: 2->1 """
         if self.epicure.tracking.tracklayer is None:
@@ -974,7 +1052,7 @@ class Inspecting(QWidget):
         if graph is not None:
             for child, parent in graph.items():
                 ## 2->1, merge, event
-                if len(parent) == 2:
+                if isinstance(parent, list) and len(parent) == 2:
                     onetwoone = False
                     ## was it only one before ?
                     if (parent[0] in graph.keys()) and (parent[1] in graph.keys()):
@@ -983,7 +1061,7 @@ class Inspecting(QWidget):
                             if pos is not None:
                                 if self.epicure.verbose > 1:
                                     print("event 1->2->1 track: "+str(graph[parent[0]][0])+"-"+str(parent)+"-"+str(child)+" frame "+str(pos[0]) )
-                                self.add_event(pos, parent[0], "tracking-1-2-*")
+                                self.add_event(pos, parent[0], "track-1-2-*")
                                 onetwoone = True
                 
                     if not onetwoone:
@@ -991,7 +1069,7 @@ class Inspecting(QWidget):
                         if pos is not None:
                             if self.epicure.verbose > 2:
                                 print("event 2->1 track: "+str(parent)+"-"+str(child)+" frame "+str(int(pos[0])) )
-                            self.add_event(pos, parent[0], "tracking-2->1", refresh=False)
+                            self.add_event(pos, parent[0], "track-2->1", refresh=False)
                         else:
                             if self.epicure.verbose > 1:
                                 print("Something weird, "+str(child)+" mean position")
@@ -1053,13 +1131,18 @@ class Inspecting(QWidget):
         self.show_hide_divisions()
         self.epicure.finish_update()
 
+    def show_hide_events( self ):
+        """ Update which type of events to show or hide """
+        for i, eclass in enumerate( self.event_class ):
+            self.show_subset_event( eclass, self.show_class[i].isChecked() )
+
     def show_hide_divisions( self ):
         """ Show or hide division events """
-        self.show_subset_event( "division", self.show_divisions.isChecked() )
+        self.show_subset_event( "division", self.show_class[0].isChecked() )
 
     def show_hide_suspects( self ):
         """ Show or hide suspect events """
-        self.show_subset_event( "suspect", self.show_suspects.isChecked() )
+        self.show_subset_event( "suspect", self.show_class[2].isChecked() )
 
     def add_division( self, labela, labelb, parent, frame ):
         """ Add a division event given the two daughter labels, the parent one and frame of division """
@@ -1067,7 +1150,7 @@ class Inspecting(QWidget):
         indexes = indexes.flatten()
         pos = self.epicure.tracking.mean_position( indexes )
         self.events.selected_data = {}
-        if self.show_divisions.isChecked():
+        if self.show_class[0].isChecked():
             self.events.current_size = int(self.event_size.value())
         else:
             self.events.current_size = 0.1
@@ -1097,14 +1180,30 @@ class Inspecting(QWidget):
                             print( "Removed suspect event of daughter cell "+str(child)+" cleared by the division flag" )
             self.update_nevents_display()
 
+    def get_event_class( self, ind ):
+        """ Return the class of event of index ind """
+        if self.is_division( ind ):
+            return 0
+        if self.is_extrusion( ind ):
+            return 1
+        return 2
+
+    def is_extrusion( self, ind ):
+        """ Return if the event of current index is a division """
+        return ("extrusion" in self.event_types) and (self.id_from_index(ind) in self.event_types["extrusion"])
+    
 
     def is_division( self, ind ):
         """ Return if the event of current index is a division """
         return ("division" in self.event_types) and (self.id_from_index(ind) in self.event_types["division"])
+    
+    def is_suspect( self, ind ):
+        """ Return if the event of current index is a suspect event """
+        return not self.is_division( ind )
 
     def is_begin_event( self, sid ):
         """ Return True if the event has a type corresponding to begin of a track (too small or appearing) """
-        beg_events = ["tracking-apparition", "track-length"]
+        beg_events = ["track-apparition", "track-length"]
         for event in beg_events:
             if event in self.event_types:
                 if sid in self.event_types[event]:
@@ -1113,7 +1212,7 @@ class Inspecting(QWidget):
 
     def is_end_event( self, sid ):
         """ Return True if the event has a type corresponding to end of a track (too small or disappearing) """
-        end_events = ["tracking-disparition", "track-length"]
+        end_events = ["track-disparition", "track-length"]
         for event in end_events:
             if event in self.event_types:
                 if sid in self.event_types[event]:
diff --git a/src/epicure/laptrack_centroids.py b/src/epicure/laptrack_centroids.py
index dcd0e0817ff7b1cb06977fd10c576967bf17069b..83bc8af2b25f7d4df26e9c3dc69ec0ab6fa9bd2d 100644
--- a/src/epicure/laptrack_centroids.py
+++ b/src/epicure/laptrack_centroids.py
@@ -7,17 +7,12 @@ Inspired from example https://github.com/yfukai/laptrack/blob/main/docs/examples
 import napari
 import numpy as np
 import pandas as pd
-import time
 
 from laptrack import LapTrack
 from laptrack import datasets
 import epicure.Utils as ut
-from napari.utils import progress
-
-from napari.utils.notifications import show_info
-#from multiprocessing.pool import ThreadPool as Pool
-#from functools import partial
     
+
 def squared_difference(a1, a2):
     """ Squared difference, normalized """
     return ((a1-a2)**2)/(max(a1, a2)**2)
@@ -60,25 +55,18 @@ class LaptrackCentroids():
         
         track_ids = [None]*len(labels)
         ## look for track id associated with label
-        frame_df = track_df[track_df["frame"]==1]
-        for i, row in frame_df.iterrows():
-            tid = int(row["track_id"])
-            curlabel = int(row["label"])
-            ind = labels.index(curlabel)
-            track_ids[ind] = tid
+        frame_df = track_df[track_df["frame"] == 1]
+        label_to_tid = {int(row["label"]): int(row["track_id"]) for i, row in frame_df.iterrows()}
+        for i, curlabel in enumerate(labels):
+            track_ids[i] = label_to_tid.get(curlabel, None)
 
         tracklabels = [None]*len(labels)
         ## look for cell label associated with track_id in the first frame, if any
         frame_df = track_df[track_df["frame"]==0]
-        frame_df = frame_df[np.isin(frame_df["track_id"].astype(int).values, track_ids)]
-        for i, row in frame_df.iterrows():
-            tid = int(row["track_id"])
-            if (tid in track_ids):
-                #xc = int(row["centroid-0"])
-                #yc = int(row["centroid-1"])
-                ind = track_ids.index(tid)
-                label = int(row["label"])
-                tracklabels[ind] = label #img[0, xc, yc]
+        frame_df = frame_df[frame_df["track_id"].isin(track_ids)]
+        label_to_tid = {int(row["track_id"]): int(row["label"]) for i, row in frame_df.iterrows()}
+        for i, tid in enumerate(track_ids):
+            tracklabels[i] = label_to_tid.get(tid, None)
         #show_info("Finished in "+"{:.3f}".format((time.time()-start_time)/60)+" min")
         return tracklabels
 
diff --git a/src/epicure/outputing.py b/src/epicure/outputing.py
index 39c8bd1ae095d4c8b18f2b9381d67b3f1817aad2..3c5cbf6d894f150df9ccc370b038f42ce20370ec 100644
--- a/src/epicure/outputing.py
+++ b/src/epicure/outputing.py
@@ -1,11 +1,10 @@
-from qtpy.QtWidgets import QHBoxLayout, QVBoxLayout, QWidget, QGroupBox, QComboBox, QLabel, QCheckBox, QTableWidget, QTableWidgetItem, QGridLayout, QListWidget
+from qtpy.QtWidgets import QHBoxLayout, QVBoxLayout, QWidget, QTableWidget, QTableWidgetItem, QGridLayout, QListWidget
 from qtpy.QtWidgets import QAbstractItemView as aiv
 from qtpy.QtCore import Qt
 import pandas as pand
 import numpy as np
 import roifile
 from skimage.morphology import binary_erosion, binary_dilation, disk
-from skimage.segmentation import expand_labels
 import os, time
 import napari
 from napari.utils import progress
@@ -16,10 +15,6 @@ from matplotlib.backends.backend_qt5agg import FigureCanvas
 from matplotlib.figure import Figure
 import matplotlib.pyplot as plt
 
-try:
-    from skimage.graph import RAG
-except:
-    from skimage.future.graph import RAG  ## older version of scikit-image
     
 
 class Outputing(QWidget):
@@ -33,7 +28,7 @@ class Outputing(QWidget):
         self.seglayer = self.viewer.layers["Segmentation"]
         self.movlayer = self.viewer.layers["Movie"]
         self.selection_choices = ["All cells", "Only selected cell"]
-        self.output_options = ["", "Export to extern plugins", "Export segmentations", "Measure cell features", "Measure track features", "Export events"]
+        self.output_options = ["", "Export to extern plugins", "Export segmentations", "Measure cell features", "Measure track features", "Export/Measure events"]
         self.tplots = None
         
         chanlist = ["Movie"]
@@ -41,11 +36,10 @@ class Outputing(QWidget):
             for chan in self.epicure.others_chanlist:
                 chanlist.append( "MovieChannel_"+str(chan) )
         self.cell_features = CellFeatures( chanlist )
-        self.event_types = EventTypes() 
+        self.event_classes = EventClass( self.epicure ) 
         
         all_layout = QVBoxLayout()
-        
-        self.choose_output = QComboBox()
+        self.choose_output = wid.listbox() 
         all_layout.addWidget(self.choose_output)
         for option in self.output_options:
             self.choose_output.addItem(option)
@@ -59,8 +53,7 @@ class Outputing(QWidget):
         all_layout.addLayout(selection_layout)
        
         ## Choice of interface
-        self.export_group = QGroupBox("Export to extern plugins")
-        export_layout = QVBoxLayout()
+        self.export_group, export_layout = wid.group_layout( "Export to extern plugins" )
         griot_btn = wid.add_button( "Current frame to Griottes", self.to_griot, "Launch(in new window) Griottes plugin on current frame" )
         export_layout.addWidget(griot_btn)
         ncp_btn = wid.add_button( "Current frame to Cluster-Plotter", self.to_ncp, "Launch (in new window) cluster-plotter plugin on current frame" )
@@ -69,8 +62,7 @@ class Outputing(QWidget):
         all_layout.addWidget(self.export_group)
         
         ## Option to export segmentation results
-        self.export_seg_group = QGroupBox(self.output_options[2])
-        layout = QVBoxLayout()
+        self.export_seg_group, layout = wid.group_layout(self.output_options[2])
         save_line, self.save_choice = wid.button_list( "Save segmentation as", self.save_segmentation, "Save the current segmentation either as ROI, label image or skeleton" ) 
         self.save_choice.addItem( "labels" )
         self.save_choice.addItem( "ROI" )
@@ -81,8 +73,7 @@ class Outputing(QWidget):
         all_layout.addWidget(self.export_seg_group)
         
         #### Features group
-        self.feature_group = QGroupBox(self.output_options[3])
-        featlayout = QVBoxLayout()
+        self.feature_group, featlayout = wid.group_layout(self.output_options[3])
         
         self.choose_features_btn = wid.add_button( "Choose features", self.choose_features, "Open a window to select the features to measure" )
         featlayout.addWidget(self.choose_features_btn)
@@ -105,41 +96,46 @@ class Outputing(QWidget):
 
         self.save_table = wid.add_button( "Save features table", self.save_measure_features, "Save the current table in a .csv file" )
         featlayout.addWidget(self.save_table)
+
+        ## skrub table
+        self.stat_table = wid.add_button( "Open statistiques table", self.skrub_features, "Open interactive table with the features statistiques (skrub library)" )
+        featlayout.addWidget(self.stat_table)
         
         self.feature_group.setLayout(featlayout)
         self.feature_group.hide()
         all_layout.addWidget(self.feature_group)
 
         ## Track features
-        self.trackfeat_group = QGroupBox(self.output_options[4])
-        trackfeatlayout = QVBoxLayout()
+        self.trackfeat_group, trackfeatlayout = wid.group_layout(self.output_options[4])
         self.trackfeat_table = wid.add_button( "Track features table", self.show_trackfeature_table, "Measure track-related feature and show a table by track" )
         trackfeatlayout.addWidget(self.trackfeat_table)
         self.trackTable = FeaturesTable(self.viewer, self.epicure)
         trackfeatlayout.addWidget(self.trackTable)
+        self.save_table_track = wid.add_button( "Save track table", self.save_table_tracks, "Save the current table in a .csv file" )
+        trackfeatlayout.addWidget(self.save_table_track)
         
         self.trackfeat_group.setLayout(trackfeatlayout)
         self.trackfeat_group.hide()
         all_layout.addWidget(self.trackfeat_group)
 
-        ## Option to export events (Fiji ROI or table), + graphs ?
-        self.export_event_group = QGroupBox(self.output_options[5])
-        elayout = QVBoxLayout()
+        ## Option to export/measure events (Fiji ROI or table), + graphs ?
+        self.handle_event_group, elayout = wid.group_layout(self.output_options[5])
         self.choose_events_btn = wid.add_button( "Choose events", self.choose_events, "Open a window to select the events to export/measure" )
         elayout.addWidget( self.choose_events_btn )
         save_evt_line, self.save_evt_choice = wid.button_list( "Export events as", self.export_events, "Save the checked events as Fiji ROIs or .csv table" ) 
         self.save_evt_choice.addItem( "Fiji ROI" )
+        self.save_evt_choice.addItem( "CSV File" )
         elayout.addLayout( save_evt_line )
+        count_evt_btn = wid.add_button( "Count events", self.temporal_graphs_events, descr="Create temporal plot of number of events" )
+        elayout.addWidget( count_evt_btn )
 
-        self.export_event_group.setLayout( elayout )
-        self.export_event_group.hide()
-        all_layout.addWidget( self.export_event_group )
-        
+        self.handle_event_group.setLayout( elayout )
+        self.handle_event_group.hide()
+        all_layout.addWidget( self.handle_event_group )
         
         ## Finished
         self.setLayout(all_layout)
         self.show_output_option()
-        #self.setStyleSheet('QGroupBox {color: grey; background-color: rgb(35,45,50)} ')
 
     def get_current_settings( self ):
         """ Returns current settings of the widget """
@@ -147,7 +143,7 @@ class Outputing(QWidget):
         disp["Apply on"] = self.output_mode.currentText() 
         disp["Current option"] = self.choose_output.currentText()
         disp = self.cell_features.get_current_settings( disp )
-        disp = self.event_types.get_current_settings( disp )
+        disp = self.event_classes.get_current_settings( disp )
         return disp
 
     def apply_settings( self, settings ):
@@ -159,12 +155,12 @@ class Outputing(QWidget):
                 self.choose_output.setCurrentText( val )
             
         self.cell_features.apply_settings( settings )
-        self.event_types.apply_settings( settings )
+        self.event_classes.apply_settings( settings )
 
     def events_select( self, event, check ):
         """ Check/Uncheck the event in event types list """
-        if event in self.event_types.types:
-            self.event_types.types[ event ][0].setChecked( check )
+        if event in self.event_classes.evt_classes:
+            self.event_classes.evt_classes[ event ][0].setChecked( check )
         else:
             print(event+" not found in possible event types to export")
 
@@ -175,7 +171,7 @@ class Outputing(QWidget):
         self.export_seg_group.setVisible( cur_option == "Export segmentations" )
         self.feature_group.setVisible( cur_option == "Measure cell features" )
         self.trackfeat_group.setVisible( cur_option == "Measure track features" )
-        self.export_event_group.setVisible( cur_option == "Export events" )
+        self.handle_event_group.setVisible( cur_option == "Export/Measure events" )
 
     def get_current_labels( self ):
         """ Get the cell labels to process according to current selection of apply on"""
@@ -200,15 +196,40 @@ class Outputing(QWidget):
             return ""
         return "_"+self.output_mode.currentText()
 
+    def skrub_features( self ):
+        """ Open html table interactive and stats with skrub module """
+        try:
+            from skrub import TableReport
+        except:
+            ut.show_error( "Needs skrub library for this option. Install it (`pip install skrub`) before" )
+            return
+        if self.table is None:
+            ut.show_warning( "Create/update the table before" )
+            return
+        report = TableReport( self.table )
+        report.open()
+        
+
     def save_measure_features(self):
         """ Save measures table to file whether it was created or not """
         if self.table is None or self.table_selection is None or self.selection_changed() :
             ut.show_warning("Create/update the table before")
             return
         outfile = self.epicure.outname()+"_features"+self.get_selection_name()+".xlsx"
-        self.table.to_excel(outfile, sheet_name='EpiCureMeasures')
+        self.table.to_excel( outfile, sheet_name='EpiCureMeasures' )
         if self.epicure.verbose > 0:
             ut.show_info("Measures saved in "+outfile)
+    
+    def save_table_tracks(self):
+        """ Save tracks table to file whether it was created or not """
+        if self.table is None or self.table_selection is None or self.selection_changed() :
+            ut.show_warning("Create/update the table before")
+            return
+        outfile = self.epicure.outname()+"_trackfeatures"+self.get_selection_name()+".xlsx"
+        self.table.to_excel( outfile, sheet_name='EpiCureTrackMeasures' )
+        if self.epicure.verbose > 0:
+            ut.show_info("Track measures saved in "+outfile)
+
 
     def save_one_roi(self, lab):
         """ Save the Rois of cell with label lab """
@@ -288,9 +309,9 @@ class Outputing(QWidget):
         outname = os.path.join( self.epicure.outdir, self.epicure.imgname+endname )
         if self.save_choice.currentText() == "skeleton":
             tosave = ut.get_skeleton( tosave, verbose=self.epicure.verbose )
-            ut.writeTif( tosave, outname, self.epicure.scale_xy, 'uint8', what="Skeleton" )
+            ut.writeTif( tosave, outname, self.epicure.epi_metadata["ScaleXY"], 'uint8', what="Skeleton" )
         else:
-            ut.writeTif(tosave, outname, self.epicure.scale_xy, 'float32', what="Segmentation")
+            ut.writeTif(tosave, outname, self.epicure.epi_metadata["ScaleXY"], 'float32', what="Segmentation")
                 
     def save_all_rois( self ):
         """ Save all cells to ROI format """
@@ -348,14 +369,12 @@ class Outputing(QWidget):
             for extra in int_extrafeat:
                 if extra == "intensity_junction_cytoplasm":
                     extra_properties = extra_properties + [intensity_junction_cytoplasm]
-        self.table = None
-        for iframe, frame in progress(enumerate(meas)):
-            frame_table = self.measure_one_frame( frame, properties, extra_properties, other_features, do_channels, int_feat, extra_prop, iframe )
-            if self.table is None:
-                self.table = pand.DataFrame(frame_table)
-            else:
-                self.table = pand.concat([self.table, pand.DataFrame(frame_table)])
-
+        self.table = {}
+        labgroups = self.epicure.group_of_labels()
+        for iframe, frame in progress(enumerate(meas), total=self.epicure.nframes):
+            self.measure_one_frame( frame, properties, extra_properties, other_features, do_channels, int_feat, extra_prop, iframe, labgroups)
+        
+        self.table = pand.DataFrame.from_dict( self.table )[self.table.keys()]
         if "intensity_junction_cytoplasm" in self.table.keys():
             self.table = self.table.rename(columns={"intensity_junction_cytoplasm-0": "intensity_cytoplasm", "intensity_junction_cytoplasm-1":"intensity_junction"})
         self.table_selection = self.selection_choices.index(self.output_mode.currentText())
@@ -363,21 +382,35 @@ class Outputing(QWidget):
         if self.epicure.verbose > 0:
             ut.show_info("Features measured in "+"{:.3f}".format((time.time()-start_time)/60)+" min")
         
-    def measure_one_frame(self, img, properties, extra_properties, other_features, channels, int_feat, int_extrafeat, frame):
+    def measure_one_frame(self, img, properties, extra_properties, other_features, channels, int_feat, int_extrafeat, frame, labgroups ):
         """ Measure on one frame """
         if frame is not None:
             intimg = self.movlayer.data[frame]
         else:
             intimg = self.movlayer.data
+        first = "label" not in self.table.keys()
+        nrows = len(self.table["label"]) if "label" in self.table.keys() else 0
+        
+        ## add the basic label measures
         frame_table = ut.labels_table( img, intensity_image=intimg, properties=properties, extra_properties=extra_properties )
         ndata = len(frame_table["label"])
+        for key, value in frame_table.items():
+            if first:
+                self.table[key] = []
+            self.table[key].extend(list(value))
+
+        ## add the frame column
         if frame is not None:
-            frame_table["frame"] = np.repeat(frame, ndata)
+            if first:
+                self.table["frame"] = []
+            self.table["frame"].extend([frame]*ndata)
 
         ## add info of the cell group
         if "group" in other_features:
-            frame_group = self.epicure.get_groups(list(frame_table["label"]), numeric=False)
-            frame_table["group"] = frame_group
+            frame_group = [ labgroups[label] if label in labgroups.keys() else "Ungrouped" for label in frame_table["label"] ]
+            if first:
+                self.table["group"] = []
+            self.table["group"].extend( frame_group )
 
         ### Measure intensity features in other chanels if option is on
         if (channels is not None):
@@ -386,45 +419,51 @@ class Outputing(QWidget):
                 if chan == "Movie":
                     continue
                 ## otherwise, do a new measure on the selected channels
-                oimg = self.viewer.layers[chan].data
                 if frame is not None:
-                    intimg = oimg[frame]
+                    intimg = self.viewer.layers[chan].data[frame]
                 else:
-                    intimg = oimg
+                    intimg = self.viewer.layers[chan].data
                 frame_tab = ut.labels_table( img, intensity_image=intimg, properties=int_feat, extra_properties=int_extrafeat )
                 for add_prop in int_feat:
-                    frame_table[add_prop+"_"+str(chan)] = frame_tab[add_prop]
+                    if first:
+                        self.table[add_prop+"_"+chan] = []
+                    self.table[add_prop+"_"+str(chan)].extend( list(frame_tab[add_prop]) )
                 if "intensity_junction_cytoplasm-0" in frame_tab.keys():
-                    frame_table["intensity_cytoplasm_"+chan] = frame_tab["intensity_junction_cytoplasm-0"]
-                    frame_table["intensity_junction_"+str(chan)] = frame_tab["intensity_junction_cytoplasm-1"]
-
-        
+                    if first:
+                        self.table["intensity_cytoplasm_"+chan] = []
+                        self.table["intensity_junction_"+str(chan)] = []
+                    self.table["intensity_cytoplasm_"+chan].extend( list(frame_tab["intensity_junction_cytoplasm-0"]) )
+                    self.table["intensity_junction_"+str(chan)].extend( list(frame_tab["intensity_junction_cytoplasm-1"]) )
+                
+            
         ## add features of neighbors relationship with graph
         do_neighbor = "NbNeighbors" in other_features
         do_border = "Border" in other_features
         if do_neighbor or do_border:
-            touchlab = expand_labels(img, distance=3)  ## be sure that labels touch
-            graph = RAG( touchlab, connectivity=2)
-            adj_bg = []
-            if 0 in graph.nodes:
-                adj_bg = list(graph.adj[0])
-                graph.remove_node(0)
+            graph = ut.connectivity_graph( img, distance=3 )  ## be sure that labels touch and get graph
+            adj_bg = list(graph.adj[0]) if 0 in graph.nodes else []
+            graph.remove_node(0) if 0 in graph.nodes else None
             
             if do_neighbor:
-                frame_table["NbNeighbors"] = np.repeat(-1, ndata)
+                if first:
+                    self.table["NbNeighbors"] = []
+                self.table["NbNeighbors"].extend( [-1]*ndata )
             if do_border:
-                frame_table["Border"] = np.repeat(-1, ndata)
-            nodes = list(graph.nodes)
-            for label in nodes:
-                nneighbor = len(graph.adj[label])
-                outer = int( label in adj_bg )
-                rlabel = (frame_table["label"] == label)
+                if first:  
+                    self.table["Border"] = []
+                self.table["Border"].extend( [-1]*ndata )
+
+            for label in graph.nodes:
+                rlabel = np.where( (frame_table["label"] == label) )[0]
                 if do_neighbor:
-                    frame_table["NbNeighbors"][rlabel] = nneighbor
+                    nneighbor = len(graph.adj[label])
+                    for ind in rlabel:
+                        self.table["NbNeighbors"][ind+nrows] = nneighbor
                 if do_border:
-                    frame_table["Border"][rlabel] = outer
-
-        return frame_table
+                    outer = int(label in adj_bg)
+                    for ind in rlabel:
+                        self.table["Border"][ind+nrows] = outer
+        
 
     def selection_changed(self):
         if self.table_selection is None:
@@ -570,7 +609,6 @@ class Outputing(QWidget):
         from skimage.segmentation import find_boundaries
         from skimage.morphology import skeletonize
         from skimage.morphology import binary_closing, binary_opening
-        from skimage.segmentation import expand_labels
         if self.epicure.verbose > 0:
             print("********** Generate touching labels image ***********")
 
@@ -579,7 +617,7 @@ class Outputing(QWidget):
         ext = np.zeros(labs.shape, dtype="uint8")
         ext[labs==0] = 1
         ext = binary_opening(ext, footprint=np.ones((2,2)))
-        newimg = expand_labels(labs, distance=4)
+        newimg = ut.touching_labels(labs, expand=4)
         newimg[ext>0] = 0
         return newimg
     
@@ -604,10 +642,20 @@ class Outputing(QWidget):
         except:
             ut.show_error("Plugin napari-clusters-plotter is not installed")
             return
-        gview.window.add_dock_widget(ncp.ClusteringWidget(gview))
-        gview.window.add_dock_widget(ncp.PlotterWidget(gview))
+        gview.window.add_dock_widget( ncp.ClusteringWidget(gview) )
+        gview.window.add_dock_widget( ncp.PlotterWidget(gview) )
 
     ################### Temporal graphs
+    def temporal_graphs_events( self ):
+        """ New window with temporal graph of event counts """
+        if self.tplots is not None:
+            self.tplots.close()
+        self.tplots = TemporalPlots( self.viewer, self.epicure )
+        evt_table = self.count_events()
+        self.tplots.setTable( evt_table )
+        self.tplots.show()
+        self.viewer.dims.events.current_step.connect(self.position_verticalline)
+
 
     def temporal_graphs(self):
         """ New window with temporal graph of the current table selection """
@@ -687,35 +735,74 @@ class Outputing(QWidget):
 
     def choose_events( self ):
         """ Pop-up widget to choose the event types to measure/export """
-        self.event_types.choose()
+        self.event_classes.choose()
+
+    def count_events( self ):
+        """ Count events of selected types """
+        evt_types = self.event_classes.get_evt_classes()
+        if self.epicure.verbose > 2:
+            print("Counting events of type "+str(evt_types)+" " )
+        
+        ## keep only events related to selected cells
+        labels = self.get_current_labels()
+        ## count each type of event
+        table = np.zeros(  (self.epicure.nframes,len(evt_types)), dtype="uint8" )        
+        for itype, evt_type in enumerate( evt_types ):
+            evts = self.epicure.inspecting.get_events_from_type( evt_type )
+            if len( evts ) > 0:
+                for evt_sid in evts:
+                        pos, label = self.epicure.inspecting.get_event_infos( evt_sid )
+                        if label in labels:
+                            table[ pos[0], itype ] += 1
+        df = pand.DataFrame( data=table, columns=evt_types )
+        df["frame"] = range(len(df))
+        df["label"] = [0]*len(df)
+        return df          
 
     def export_events( self ):
         """ Export events of selected types """
-        evt_types = self.event_types.get_types()
+        evt_types = self.event_classes.get_evt_classes()
         export_type = self.save_evt_choice.currentText()
-        if export_type == "Fiji ROI":
-            ## keep only events related to selected cells
-            labels = self.get_current_labels()
-            if self.epicure.verbose > 2:
-                print("Exporting events of type "+str(evt_types)+" to Fiji ROIs" )
-            ## export each type of event in separate files
-            for itype, evt_type in enumerate( evt_types ):
-                evts = self.epicure.inspecting.get_events_from_type( evt_type )
-                if len( evts ) > 0:
-                    rois = []
-                    for evt_sid in evts:
-                        pos, label = self.epicure.inspecting.get_event_infos( evt_sid )
-                        if label in labels:
+        if self.epicure.verbose > 2:
+            print("Exporting events of type "+str(evt_types)+" to "+export_type )
+        
+        ## keep only events related to selected cells
+        labels = self.get_current_labels()
+        groups = self.epicure.get_groups( labels )
+        if export_type == "CSV File":
+            res = pand.DataFrame( columns=["label", "frame", "posY", "posX", "EventClass", "Group"] )  
+        ## export each type of event in separate files
+        for itype, evt_type in enumerate( evt_types ):
+            evts = self.epicure.inspecting.get_events_from_type( evt_type )
+            if len( evts ) > 0:
+                rois = [] 
+                for evt_sid in evts:
+                    pos, label = self.epicure.inspecting.get_event_infos( evt_sid )
+                    ind_lab = np.where( labels==label )
+                    if len( ind_lab[0] ) > 0:
+                        grp = groups[ int(ind_lab[0][0]) ]
+                        if export_type == "Fiji ROI":
                             roi = self.create_point_roi( pos, itype )
                             rois.append( roi )
+                        if export_type == "CSV File":
+                            new_event = pand.DataFrame( [[label, pos[0], pos[1], pos[2], evt_type, grp ]], columns=res.columns )
+                            res = pand.concat( [res, new_event], ignore_index=True )
+                if export_type == "Fiji ROI":            
                     outfile = self.epicure.outname()+"_rois_"+evt_type +""+self.get_selection_name()+".zip" 
                     roifile.roiwrite(outfile, rois, mode='w')
                     if self.epicure.verbose > 0:
                         print( "Events "+str( evt_type )+" saved in ROI file: "+outfile )
-                else:
-                    ## dont save anything if empty, just print info to user
-                    if self.epicure.verbose > 0:
-                        print( "No events of type "+str(evt_type)+"" )
+            ## dont save anything if empty, just print info to user
+            else:
+                if self.epicure.verbose > 0:
+                    print( "No events of type "+str(evt_type)+"" )
+        
+        if export_type == "CSV File":            
+            outfile = self.epicure.outname()+"_events"+self.get_selection_name()+".csv" 
+            res.to_csv( outfile,  sep='\t', header=True, index=False )
+            if self.epicure.verbose > 0:
+                print( "Events data "+" saved in CSV file: "+outfile )
+
 
     def create_point_roi( self, pos, cat=0 ):
         """ Create a point Fiji ROI """
@@ -760,8 +847,7 @@ class CellFeatures(QWidget):
         feat_layout = self.add_feature_group( shape_list, "prop" )
         layout.addLayout( feat_layout )
 
-        int_lab = QLabel()
-        int_lab.setText("Intensity features:")
+        int_lab = wid.label_line( "Intensity features:")
         layout.addWidget( int_lab )
         intensity_list = ["intensity_mean", "intensity_min", "intensity_max"]
         extra_list = ["intensity_junction_cytoplasm"]
@@ -770,8 +856,7 @@ class CellFeatures(QWidget):
         feat_layout = self.add_feature_group( extra_list, "intensity_extra" )
         layout.addLayout( feat_layout )
         if len(chanlist) > 1:
-            chan_lab = QLabel()
-            chan_lab.setText("Measure intensity in channels:")
+            chan_lab = wid.label_line( "Measure intensity in channels:" )
             layout.addWidget( chan_lab )
             self.chan_list = QListWidget()
             self.chan_list.addItems( chanlist )
@@ -790,10 +875,9 @@ class CellFeatures(QWidget):
         for i, feat in enumerate(feat_list):
             if i%ncols == 0:
                 line = QHBoxLayout()
-            feature_check = QCheckBox(text=""+feat)
+            feature_check = wid.add_check( ""+feat, True, None, descr="" )
             line.addWidget(feature_check)
             self.features[ feat ] = [feature_check, feat_type]
-            feature_check.setChecked( True )
             if i%ncols == (ncols-1):
                 layout.addLayout( line )
                 line = None
@@ -856,15 +940,15 @@ class CellFeatures(QWidget):
             return channels
         return None
 
-class EventTypes(QWidget):
+class EventClass( QWidget ):
     """ Choice of event types to export/measure """
-    def __init__(self):
+    def __init__( self, epicure ):
         super().__init__()
         layout = QVBoxLayout()
         
-        self.types = {}
-        possible_types = [ "division", "suspect" ] 
-        event_layout = self.add_events( possible_types )
+        self.evt_classes = {}
+        possible_classes = epicure.event_class
+        event_layout = self.add_events( possible_classes )
         layout.addLayout( event_layout )
 
         bye = wid.add_button( "Ok", self.close, "Close the window" )
@@ -878,10 +962,8 @@ class EventTypes(QWidget):
         for i, event in enumerate( event_list ):
             if i%ncols == 0:
                 line = QHBoxLayout()
-            event_check = QCheckBox(text=""+event)
-            line.addWidget( event_check )
-            self.types[ event ] = [ event_check ]
-            event_check.setChecked( True )
+            event_check = wid.add_check_tolayout( line, ""+event, checked=True, descr="")
+            self.evt_classes[ event ] = [ event_check ]
             if i%ncols == (ncols-1):
                 layout.addLayout( line )
                 line = None
@@ -900,20 +982,20 @@ class EventTypes(QWidget):
 
     def get_current_settings( self, setting ):
         """ Get current settings of check or not of features """
-        for event, event_cbox in self.types.items():
+        for event, event_cbox in self.evt_classes.items():
             setting[event] = event_cbox[0].isChecked()
         return setting
 
     def apply_settings( self, settings ):
         """ Set the checkboxes from preferenced settings """
         for evt, checked in settings.items():
-            if evt in self.types.keys():
-                self.types[evt][0].setChecked( checked )
+            if evt in self.evt_classes.keys():
+                self.evt_classes[evt][0].setChecked( checked )
         
-    def get_types( self ):
+    def get_evt_classes( self ):
         """ Returns the list of events to measure """
         events = []
-        for evt, evt_cbox in self.types.items():
+        for evt, evt_cbox in self.evt_classes.items():
             if evt_cbox[0].isChecked():
                 events.append( evt )
         return events
@@ -1000,8 +1082,8 @@ class TemporalPlots(QWidget):
         feat_choice, self.feature_choice = wid.list_line( label="Plot feature", descr="Choose the feature to plot", func=self.plot_feature )
         layout.addLayout(feat_choice)
         ## option to average by group
-        self.avg_group = wid.add_check( "Average by groups", False, self.plot_feature, descr="Show a line by cell or a line by group" )
-        layout.addWidget(self.avg_group)
+        ck_line, self.avg_group, self.smooth = wid.double_check( "Average by groups", False, self.plot_feature, "Show a line by cell or a line by group", "Smooth lines", False, self.plot_feature, "Smooth temporally (moving average) the plotted lines" )
+        layout.addLayout(ck_line)
         ## show the plot
         self.plot_wid = self.create_plotwidget()
         layout.addWidget(self.plot_wid)
@@ -1041,6 +1123,7 @@ class TemporalPlots(QWidget):
         if feat == "":
             return
         self.ax.cla()
+
         if "group" in self.table:
             tab = list(zip(self.table["frame"], self.table[feat], self.table["label"], self.table["group"]))
             self.df = pand.DataFrame( tab, columns=["frame", feat, "label", "group"] )
@@ -1048,6 +1131,17 @@ class TemporalPlots(QWidget):
             tab = list(zip(self.table["frame"], self.table[feat], self.table["label"]))
             self.df = pand.DataFrame( tab, columns=["frame", feat, "label"] )
         self.df.set_index('frame', inplace=True)
+        if self.smooth.isChecked():
+            rollsize = 20
+            ## average on a smaller scale if only few frames
+            if np.max( self.table["frame"] ) <= 20:
+                rollsize = 5    
+            if feat+"_smooth" in self.df.columns:
+                feat = feat+"_smooth"
+            else:
+                self.df[feat+"_smooth"] = self.df[feat].rolling(rollsize).mean()
+                feat = feat+"_smooth"
+
         if "group" in self.table and self.avg_group.isChecked():
             self.dfmean = self.df.groupby(['group', 'frame'])[feat].mean().reset_index()
             self.dfmean.set_index('frame', inplace=True)
diff --git a/src/epicure/start_epicuring.py b/src/epicure/start_epicuring.py
index d7164bab6b884ee6eff0f0b244adada36b82a459..0bd728386e871a9d3560c596bb5d3fc627406983 100644
--- a/src/epicure/start_epicuring.py
+++ b/src/epicure/start_epicuring.py
@@ -27,6 +27,7 @@ def start_epicure():
         get_files.nbparallel_threads.visible = get_files.advanced_parameters.value
         get_files.junction_half_thickness.visible = get_files.advanced_parameters.value
         get_files.verbose_level.visible = get_files.advanced_parameters.value
+        get_files.allow_gaps.visible = get_files.advanced_parameters.value
 
     def load_movie():
         start_time = ut.start_time()
@@ -48,6 +49,8 @@ def start_epicure():
         get_files.timeframe.value = Epic.epi_metadata["ScaleT"]
         get_files.unit_xy.value = Epic.epi_metadata["UnitXY"]
         get_files.unit_t.value = Epic.epi_metadata["UnitT"]
+        get_files.allow_gaps.value = bool(Epic.epi_metadata["Allow gaps"])
+        get_files.verbose_level.value = int(Epic.epi_metadata["Verbose"])
         ut.show_duration(start_time, header="Movie loaded in ")
 
     def show_others():
@@ -82,6 +85,7 @@ def start_epicure():
                    unit_t = "min",
                    advanced_parameters = False,
                    show_other_chanels = True,
+                   allow_gaps = True,
                    process_frames_parallel = False,
                    nbparallel_threads = ncpus,
                    junction_half_thickness = 1,
@@ -94,11 +98,12 @@ def start_epicure():
         update_save_history(imdir)
         ut.remove_widget(viewer, "Start EpiCure (epicure)")
         Epic.process_parallel = process_frames_parallel
-        Epic.verbose = verbose_level
+        Epic.set_verbose( verbose_level )
         Epic.nparallel = nbparallel_threads
         #Epic.load_segmentation(segmentation_file)
         Epic.set_thickness(junction_half_thickness)
         Epic.set_scales(scale_xy, timeframe, unit_xy, unit_t)
+        Epic.set_gaps_option( allow_gaps )
         Epic.go_epicure(outdir, segmentation_file)
 
     set_visibility()
diff --git a/src/epicure/tracking.py b/src/epicure/tracking.py
index 5777462ea405efc6fef2b5a372cb3ec4656596d6..360a14aebc9ecfda19ecfa1f8e440040bf5abdb2 100644
--- a/src/epicure/tracking.py
+++ b/src/epicure/tracking.py
@@ -1,12 +1,13 @@
-from qtpy.QtWidgets import QVBoxLayout, QWidget, QGroupBox 
+from qtpy.QtWidgets import QVBoxLayout, QWidget
 from epicure.laptrack_centroids import LaptrackCentroids
 from epicure.laptrack_overlaps import LaptrackOverlaps
 from laptrack.data_conversion import convert_split_merge_df_to_napari_graph
 from napari.utils import progress
+from skimage.transform import warp
+from skimage.registration import optical_flow_ilk
 import time
 import pandas as pd
 import numpy as np
-from skimage.measure import regionprops, regionprops_table
 from multiprocessing.pool import ThreadPool as Pool
 import epicure.Utils as ut
 import epicure.epiwidgets as wid
@@ -44,6 +45,9 @@ class Tracking(QWidget):
         self.track_choice.addItem("Laptrack-Overlaps")
         self.create_laptrack_overlap()
         layout.addWidget(self.gLapOverlap)
+
+        drift_layout, self.drift_correction, self.drift_radius = wid.check_value( check="With drift correction", checked=False, value=str(50), descr="Taking into account local drift in tracking calculations") 
+        layout.addLayout( drift_layout )
         
         self.track_go = wid.add_button( "Track", self.do_tracking, "Launch the tracking with the current parameter. Can take time" )
         layout.addWidget(self.track_go)
@@ -160,7 +164,11 @@ class Tracking(QWidget):
 
     def replace_tracks(self, track_df):
         """ Replace all tracks based on the dataframe """
-        track_table, track_prop = self.build_tracks(track_df)
+        if not self.undrifted and self.drift_correction.isChecked():
+            ## recalculate the label centroids as it was corrected for drift
+            track_table, track_prop = self.create_tracks()
+        else:
+            track_table, track_prop = self.build_tracks( track_df )
         self.tracklayer.data = track_table
         self.track_data = self.tracklayer.data
         self.tracklayer.properties = track_prop
@@ -186,6 +194,8 @@ class Tracking(QWidget):
         """ Measure features (length, total displacement...) of given track """
         features = {}
         track = self.get_track_data( track_id )
+        if track.shape[0] == 0:
+            return features
         start = int(np.min(track[:,1]))
         end = int(np.max(track[:,1]))
         features["Label"] = track_id
@@ -213,7 +223,7 @@ class Tracking(QWidget):
         for feat in features:
             res[feat] = []
         for frame in mask:
-            props = regionprops( frame )
+            props = ut.labels_properties( frame )
             if len(props) > 0:
                 if "Area" in features:
                     res["Area"].append( props[0].area )
@@ -371,6 +381,12 @@ class Tracking(QWidget):
         """ Get ind-th data of track track_id """
         track = self.get_track_data( track_id )
         return track[ind]
+    
+    def get_middle_position( self, track_id, framea, frameb ):
+        """ Get track position in middle of frame a and frame b """
+        inda = self.get_index( track_id, framea ) 
+        indb = self.get_index( track_id, frameb )
+        return self.mean_position( np.ravel( np.vstack((inda, indb)) ), only_first=False )
 
     def get_position( self, track_id, frame ):
         """ Get position of the track at given frame """
@@ -437,20 +453,19 @@ class Tracking(QWidget):
 
     def update_track_on_frame(self, track_ids, frame):
         """ Update (add or modify) tracks at given frame """
-        frame_table = regionprops_table( np.where(np.isin(self.epicure.seg[frame], track_ids), self.epicure.seg[frame], 0), properties=self.properties )
+        frame_table = ut.labels_table( labimg = np.where(np.isin(self.epicure.seg[frame], track_ids), self.epicure.seg[frame], 0), properties=self.properties )
         for x, y, tid in zip(frame_table["centroid-0"], frame_table["centroid-1"], frame_table["label"]):
             index = self.get_index(tid, frame)
             if len(index) > 0:
                 self.update_centroid( tid, frame, index, int(x), int(y) )
             else:
-                cur_cell = np.array( [tid, frame, int(x), int(y)] )
-                cur_cell = np.expand_dims(cur_cell, axis=0)
+                cur_cell = np.array( [[tid, frame, int(x), int(y)]] )
                 self.track_data = np.append(self.track_data, cur_cell, axis=0)
     
     def add_one_frame(self, track_ids, frame, refresh=True):
         """ Add one frame from track """
         for tid in track_ids:
-            frame_table = regionprops_table(np.uint8(self.epicure.seg[frame]==tid), properties=self.properties)
+            frame_table = ut.labels_table( np.uint8(self.epicure.seg[frame]==tid), properties=self.properties ) 
             cur_cell = np.array( [tid, frame, int(frame_table["centroid-0"]), int(frame_table["centroid-1"])], dtype=np.uint32 )
             cur_cell = np.expand_dims(cur_cell, axis=0)
             self.track_data = np.append(self.track_data, cur_cell, axis=0)
@@ -480,9 +495,8 @@ class Tracking(QWidget):
                 self.epicure.fix_gaps( gaped )
         
     def get_current_value(self, track_id, frame):
-        ind = self.get_index( track_id, frame )
-        centx = int(self.track_data[ind, 2])
-        centy = int(self.track_data[ind, 3])
+        ind = self.get_index(track_id, frame)
+        centx, centy = self.track_data[ind, 2:4].astype(int).flatten()
         return self.epicure.seg[frame, centx, centy]
 
     def update_graph(self, track_id, frame):
@@ -491,17 +505,14 @@ class Tracking(QWidget):
         if self.graph is not None:
             ## handles current node is last of his branch
             parents = self.last_in_graph( track_id, frame )
-            if len(parents) > 0:
-                current_label = self.get_current_value( track_id, frame )
+            current_label = self.get_current_value( track_id, frame )
+            for parent in parents:
                 if current_label == 0:
-                    for parent in parents:
-                        del self.graph[parent]
+                    del self.graph[parent]
                 else:
-                    for parent in parents:
-                        self.update_child( parent, track_id, current_label )
+                    self.update_child( parent, track_id, current_label )
             ## handles when current track is first frame of a division
             if self.first_in_graph( track_id, frame ):
-                current_label = self.get_current_value( track_id, frame )
                 if current_label == 0:
                     del self.graph[track_id]
                 else:
@@ -511,13 +522,7 @@ class Tracking(QWidget):
 
     def update_child(self, parent, prev_key, new_key):
         """ Change the value of a key in the graph """
-        vals = self.graph[parent]
-        self.graph[parent] = []
-        for val in vals:
-            if val == prev_key:
-                self.graph[parent].append(new_key)
-            else:
-                self.graph[parent].append(val)
+        self.graph[parent] = [new_key if val == prev_key else val for val in self.graph[parent]]
 
     def update_key(self, prev_key, new_key):
         """ Change the value of a key in the graph """
@@ -533,41 +538,19 @@ class Tracking(QWidget):
         """ Add info of a division to the graph of divisions/merges """
         if self.graph is None:
             self.graph = {}
-        self.graph[childa] = parent
-        self.graph[childb] = parent
+        self.graph.update({childa: parent, childb: parent})
 
     def remove_division( self, parent ):
         """ Remove a division event from the graph """
-        keys = list(self.graph.keys())
-        for key in keys:
-            vals = self.graph[key]
-            if isinstance(vals, list):
-                if vals[0] == parent:
-                    del self.graph[key]
-            else:
-                if vals == parent:
-                    del self.graph[key]
+        self.graph = {key: vals for key, vals in self.graph.items() if not (isinstance(vals, list) and vals[0] == parent) and vals != parent}
 
     def last_in_graph(self, track_id, frame):
         """ Check if given label and frame is the last of a branch, in the graph """
-        parents = []
-        for key, vals in self.graph.items():
-            if not isinstance(vals, list):
-                vals = [vals]
-            for val in vals:
-                if val == track_id:
-                    last_frame = self.get_last_frame( val )
-                    if last_frame == frame:
-                        parents.append(key)
-        return parents
+        return [key for key, vals in self.graph.items() if track_id in (vals if isinstance(vals, list) else [vals]) and self.get_last_frame(track_id) == frame]
 
     def first_in_graph(self, track_id, frame):
         """ Check if the given label and frame is the first in the branch so the node in the graph """
-        if track_id in self.graph.keys():
-            first_frame = self.get_first_frame(track_id)
-            if first_frame == frame:
-                return True
-        return False
+        return track_id in self.graph and self.get_first_frame(track_id) == frame
 
     def remove_tracks(self, track_ids):
         """ Remove track with given id """
@@ -605,13 +588,9 @@ class Tracking(QWidget):
 
     def create_tracks(self):
         """ Create tracks from labels (without tracking) """
-        properties = ['label', 'centroid']
-        track_table = np.empty((0,4), int)
-        if self.epicure.nframes >= 0:
-            for iframe, frame in progress(enumerate(self.epicure.seg)):
-                frame_track, frame_prop = self.get_one_frame( frame, iframe )
-                track_table = np.vstack((track_table, frame_track))
-        
+        track_table = np.empty( (0,4), int )   
+        for iframe, frame in progress(enumerate(self.epicure.seg), total=self.epicure.nframes):
+            track_table = self.get_one_frame(frame, iframe, track_table)
         return track_table, None # track_prop
 
     def add_track_features(self, labels):
@@ -624,21 +603,14 @@ class Tracking(QWidget):
             nframes[ list(cur_track) ] = len(cur_track)
         return nframes
     
-    def get_one_frame(self, seg, frame):
+    def get_one_frame(self, seg, frame, track_table):
         """ Get the regionprops results into the dataframe """
-        frame_table = regionprops_table(seg, properties=self.properties)
-        ndata = len(frame_table["label"])
-        if frame is not None:
-            frame_table["frame"] = np.repeat(frame, ndata)
-        frame_table["track_id"] = frame_table["label"]
-        #frame_table["tree_id"] = [0]*ndata    ##### @Tochange to read div/merge infos ?
-        #frame_table["group"] = [0]*ndata    ##### @Tochange to read div/merge infos ?
-        #frame_table["nframes"] = [0]*ndata
-        frame_table = pd.DataFrame(frame_table)
-        frame_track = frame_table[["track_id", "frame", "centroid-0", "centroid-1"]]
-        #frame_prop = frame_table[["tree_id", "label", "nframes", "group"]]
-        return np.array(frame_track, int), None #dict(frame_prop)
-
+        frame_table = ut.labels_table(seg, properties=self.properties)
+        labels = frame_table["label"]
+        frame_data = np.column_stack((labels.astype(int), np.full(len(labels), frame), frame_table["centroid-0"].astype(int), frame_table["centroid-1"].astype(int)))
+        track_table = np.vstack( (track_table, frame_data) )
+        #return frame_data.astype(int)
+        return track_table
 
     ##########################################
     #### Tracking functions
@@ -736,79 +708,134 @@ class Tracking(QWidget):
 
     def relabel_trackids(self, track_df, splitdf, mergedf):
         """ Change the trackids to take the first label of each track """
-        replace_map = dict()
-        new_trackids = track_df['track_id'].copy() 
+        start_time = time.time()
+        new_trackids = track_df['track_id'].copy()
         new_splitdf = splitdf.copy()
         new_mergedf = mergedf.copy()
-        for tid in np.unique(track_df['track_id']):
-            ctrack_df = track_df[track_df['track_id']==tid]
-            newval = int( ctrack_df.loc[ ctrack_df["frame"]==np.min(ctrack_df["frame"]), "label" ].iloc[0] )
-            ## have to replace if different
+        
+        unique_track_ids = np.unique(track_df['track_id'])
+        first_labels = track_df.groupby('track_id').apply(lambda x: x.loc[x['frame'].idxmin(), 'label']).to_dict()
+        
+        for tid in unique_track_ids:
+            newval = first_labels[tid]
             if tid != newval:
-                new_trackids.loc[ track_df['track_id']==tid ] = newval
+                new_trackids[track_df['track_id'] == tid] = newval
                 if not new_splitdf.empty:
-                    new_splitdf.loc[ splitdf["parent_track_id"]==tid, "parent_track_id" ] = newval
-                    new_splitdf.loc[ splitdf["child_track_id"]==tid, "child_track_id" ] = newval
+                    new_splitdf.loc[splitdf["parent_track_id"] == tid, "parent_track_id"] = newval
+                    new_splitdf.loc[splitdf["child_track_id"] == tid, "child_track_id"] = newval
                 if not new_mergedf.empty:
-                    new_mergedf.loc[ mergedf["parent_track_id"]==tid, "parent_track_id" ] = newval
-                    new_mergedf.loc[ mergedf["child_track_id"]==tid, "child_track_id" ] = newval
+                    new_mergedf.loc[mergedf["parent_track_id"] == tid, "parent_track_id"] = newval
+                    new_mergedf.loc[mergedf["child_track_id"] == tid, "child_track_id"] = newval
+        if self.epicure.verbose > 1:
+            ut.show_duration( start_time, header="Relabeling done in " )            
         return new_trackids, new_splitdf, new_mergedf
 
     def change_labels(self, track_df):
         """ Change the labels at each frame according to tracks """
-        frames = track_df["frame"]
-        ## change the other ones
-        for frame in np.unique(frames):
-            self.change_frame_labels(frame, track_df)
+        for frame, frame_df in track_df.groupby("frame"):
+            self.change_frame_labels(frame, frame_df)
 
-    def change_frame_labels(self, frame, track_df):
+    def change_frame_labels(self, frame, frame_df):
         """ Change the labels at given frame according to tracks """
-        frame_df = track_df[track_df["frame"]==frame]
-        #coordinates = frame_df[['centroid-0', 'centroid-1']].astype(int).values
         track_ids = frame_df['track_id'].astype(int).values
         old_labels = frame_df["label"].astype(int).values
         seglayer = np.copy(self.epicure.seglayer.data[frame])
-        for i, lab in enumerate(old_labels):
-            mask = (seglayer==lab)
-            self.epicure.seglayer.data[frame][mask] = track_ids[i]
+        for old_lab, new_lab in zip(old_labels, track_ids):
+            mask = (seglayer==old_lab)
+            self.epicure.seglayer.data[frame][mask] = new_lab
 
     def label_to_dataframe( self, labimg, frame ):
         """ from label, get dataframe of centroids with properties """
-        df = pd.DataFrame(regionprops_table(labimg, properties=self.region_properties))
+        df = pd.DataFrame( ut.labels_table(labimg, properties=self.region_properties))
         df["frame"] = frame
         return df
     
-    def labels_to_centroids(self, start_frame, end_frame, locked=True):
+    def optical_flow( self, img0, img1, radius ):
+        """ Compute the optical flow between two images """
+        v, u = optical_flow_ilk( img0, img1, radius=radius)
+        return v, u
+    
+    def apply_flow( self, flowv, flowu, labimg ):
+        """ Apply the calculated optical flow on a label image """
+        nr, nc = labimg.shape
+        rowc, colc = np.meshgrid( np.arange(nr), np.arange(nc), indexing="ij" )
+        lab_reg = warp( labimg, np.array( [rowc+flowv, colc+flowu] ), order=0, mode="edge" )
+        return lab_reg
+    
+    def labels_to_centroids( self, start_frame, end_frame ):
+        """ Get centroids of each cell in dataframe """
+        regionprops = [
+            self.label_to_dataframe(self.epicure.seg[frame], frame)
+            for frame in range(start_frame, end_frame + 1)
+        ]
+        return pd.concat(regionprops)
+    
+    def labels_to_centroids_flow(self, start_frame, end_frame):
         """ Get centroids of each cell in dataframe """
-        regionprops = []
-        #if self.epicure.process_parallel:
-        #    with Pool(self.epicure.nparallel) as pool:
-        #        regionprops = pool.map( self.label_frame_todf, range(start_frame, end_frame+1) )
-        #    regionprops_df = pd.concat(regionprops)
-        #else:
+        regionprops = []    
+        radius = float( self.drift_radius.text() )
+        if self.epicure.verbose > 1:
+            if self.drift_correction.isChecked():
+                print( "Apply drift correction to tracking with optical flow of radius "+str(radius) )
+        prev_movie = None
+        flow_v = None
         for frame in range(start_frame, end_frame+1):
-            df = self.label_frame_todf(frame)
+            if self.drift_correction.isChecked():
+                cur_movie = self.epicure.img[frame]
+                if frame > start_frame:
+                    v, u = self.optical_flow( prev_movie, cur_movie, radius )
+                    if flow_v is None:
+                        flow_v = v
+                        flow_u = u
+                    else:
+                        flow_v = flow_v + v
+                        flow_u = flow_u + u
+                prev_movie = cur_movie
+            clabel = self.epicure.seg[frame]  
+            df = self.label_to_dataframe( clabel, frame )
+            if flow_v is not None:
+                c0 = np.array( np.floor( df["centroid-0"] ), dtype="uint8" )
+                c1 = np.array( np.floor( df["centroid-1"] ), dtype="uint8" )
+                df["centroid-0"] = df["centroid-0"] - flow_v[c0,c1]
+                df["centroid-1"] = df["centroid-1"] - flow_u[c0,c1]
             regionprops.append(df)
         regionprops_df = pd.concat(regionprops)
         return regionprops_df
     
+    def labels_flow(self, start_frame, end_frame ):
+        """ Get registered label image corrected for optical flow """
+        radius = float( self.drift_radius.text() )
+        flow_v = None
+        prev_movie = None
+        res_labels = []
+        for frame in range(start_frame, end_frame+1):
+            cur_movie = self.epicure.img[frame]
+            if prev_movie is not None:
+                v, u = self.optical_flow( prev_movie, cur_movie, radius )
+                if flow_v is None:
+                    flow_v = v
+                    flow_u = u
+                else:
+                    flow_v = flow_v + v
+                    flow_u = flow_u + u
+            prev_movie = cur_movie
+            clabel = np.copy( self.epicure.seg[frame] ) 
+            if flow_v is not None:         
+                clabel = self.apply_flow( flow_v, flow_u, clabel )
+            res_labels.append( clabel )
+        res_labels = np.array(res_labels)
+        return res_labels
+
     def labels_ready(self, start_frame, end_frame, locked=True):
         """ Get labels of unlocked cells to track """
-        res_labels = []
-        if self.epicure.process_parallel:
-            with Pool(self.epicure.nparallel) as pool:
-                labels = pool.map( self.current_label_frame, range(start_frame, end_frame+1) )
-            res_labels = np.array(labels)
-        else:
-            for frame in range(start_frame, end_frame+1):
-                labels = self.current_label_frame(frame)
-                res_labels.append(labels)
-            res_labels = np.array(res_labels)
+        if self.drift_correction.isChecked():
+            return self.labels_flow( start_frame, end_frame )
+        res_labels = self.epicure.seg[start_frame:end_frame+1] 
         return res_labels
     
     def label_frame_todf( self, frame ):
         """ For current frame, get label frame image then dataframe of centroids """
-        clabel = self.current_label_frame(frame)
+        clabel = self.epicure.seg[frame] #self.current_label_frame(frame)
         return self.label_to_dataframe( clabel, frame )
     
     def current_label_frame( self, frame ):
@@ -820,7 +847,7 @@ class Tracking(QWidget):
             clabel = self.epicure.seg[frame]
         return clabel
 
-    def after_tracking(self, track_df, split_df, merge_df, progress_bar, indprogress):
+    def after_tracking( self, track_df, split_df, merge_df, progress_bar, indprogress ):
         """ Steps after tracking: get/show the graph from the track_df """
         graph = None
         progress_bar.set_description( "Update labels and tracks" )
@@ -836,9 +863,9 @@ class Tracking(QWidget):
         ## relabel if some track have the same label
         self.relabel_nonunique_labels(track_df)
         ## relabel track ids so that they are equal to the first label of the track
-        newtids, split_df, merge_df = self.relabel_trackids(track_df, split_df, merge_df)
+        newtids, split_df, merge_df = self.relabel_trackids( track_df, split_df, merge_df )
         track_df["track_id"] = newtids
-        self.change_labels(track_df)
+        self.change_labels( track_df )
 
         # create graph of division/merging
         self.graph = convert_split_merge_df_to_napari_graph(split_df, merge_df)
@@ -846,7 +873,7 @@ class Tracking(QWidget):
         progress_bar.update(indprogress+1)
         
         ## update display if active
-        self.replace_tracks(track_df)
+        self.replace_tracks( track_df )
 
         progress_bar.update(indprogress+2)
         ## update the list of events, or others 
@@ -860,8 +887,7 @@ class Tracking(QWidget):
     
     def create_laptrack_centroids(self):
         """ GUI of the laptrack option """
-        self.gLapCentroids = QGroupBox("Laptrack-Centroids")
-        glap_layout = QVBoxLayout()
+        self.gLapCentroids, glap_layout = wid.group_layout( "Laptrack-Centroids" )
         mdist, self.max_dist = wid.value_line( "Max distance", "15.0", "Maximal distance between two labels in consecutive frames to link them (in pixels)" )
         glap_layout.addLayout(mdist)
         ## splitting ~ cell division
@@ -934,7 +960,11 @@ class Tracking(QWidget):
         progress_bar.set_description( "Prepare tracking" )
         if self.epicure.verbose > 1:
             print("Convert labels to centroids: use track info ?")
-        df = self.labels_to_centroids(start, end, locked=True)
+        self.undrifted = False
+        if self.drift_correction.isChecked():
+            df = self.labels_to_centroids_flow( start, end )
+        else:
+            df = self.labels_to_centroids( start, end )
         progress_bar.update(1)
         if self.epicure.verbose > 1:
             print("GO tracking")
@@ -951,8 +981,7 @@ class Tracking(QWidget):
 
     def create_laptrack_overlap(self):
         """ GUI of the laptrack overlap option """
-        self.gLapOverlap = QGroupBox("Laptrack-Overlaps")
-        glap_layout = QVBoxLayout()
+        self.gLapOverlap, glap_layout = wid.group_layout( "Laptrack-Overlaps" )
         miou, self.min_iou = wid.value_line( "Min IOU", "0.1", "Minimum Intersection Over Union score to link to labels together" )
         glap_layout.addLayout(miou)
         
@@ -978,28 +1007,28 @@ class Tracking(QWidget):
 
         progress_bar = progress(total=6)
         progress_bar.set_description( "Prepare tracking" )
-        labels = self.labels_ready(start, end, locked=True)
+        labels = self.labels_ready( start, end )
+        self.undrifted = False
         progress_bar.update(1)
         progress_bar.set_description( "Do tracking with LapTrack Overlaps" )
-        track_df, split_df, merge_df = laptrack.track_overlaps(labels)
+        track_df, split_df, merge_df = laptrack.track_overlaps( labels )
         progress_bar.update(2)
         
         ## get dataframe of coordinates to create the graph 
-        df = self.labels_to_centroids(start, end, locked=True)
+        df = self.labels_to_centroids( start, end )
+        self.undrifted = True
         progress_bar.update(3)
         coordinate_df = df.set_index(["frame", "label"])
         tdf = track_df.set_index(["frame", "label"])
         track_df2 = pd.merge( tdf, coordinate_df, right_index=True, left_index=True).reset_index()
-        self.after_tracking(track_df2, split_df, merge_df, progress_bar, 3)
+        self.after_tracking( track_df2, split_df, merge_df, progress_bar, 3 )
         progress_bar.update(6)
         progress_bar.close()
     
     def laptrack_overlaps_twoframes(self, labels, twoframes):
         """ Perform tracking of two frames with current parameters """
         laptrack = LaptrackOverlaps(self, self.epicure)
-        miniou = float(self.min_iou.text())
-        if miniou >= 1.0:
-            miniou = 1.0
+        miniou = max( float(self.min_iou.text()), 1.0 )
         laptrack.cost_cutoff = 1.0 - miniou
         self.region_properties = ["label", "centroid"]
 
diff --git a/src/epicure/tracking_editing.py b/src/epicure/tracking_editing.py
index b2f7d405c84d9c5525e57d6396761cc30eb34cc7..6956bd9e3f4dd1ba727cee41b4fa718593c06f42 100644
--- a/src/epicure/tracking_editing.py
+++ b/src/epicure/tracking_editing.py
@@ -1,7 +1,5 @@
 from qtpy.QtWidgets import QPushButton, QVBoxLayout, QWidget
-from napari import Viewer
 import numpy as np
-from skimage.measure import regionprops, regionprops_table
 import epicure.Utils as ut
 
 class trackEditingWidget(QWidget):
@@ -28,7 +26,7 @@ class trackEditingWidget(QWidget):
     def inspect_oneframe(self):
         """ Find suspicious cell that exists in only one frame """
         seg = self.viewer.layers["Segmentation"]
-        props = regionprops(seg.data)
+        props = ut.labels_properties( seg.data )
         imshape = seg.data.shape
         self.events.data = np.zeros(imshape, dtype="uint8")
         for prop in props:
@@ -50,9 +48,7 @@ class trackEditingWidget(QWidget):
     def show_names(self, lablayer, name):
         ut.remove_layer(self.viewer, name)
         # create the properties dictionary
-        properties = regionprops_table(
-            lablayer.data, properties=('label', 'bbox')
-        )
+        properties = ut.labels_bbox( lablayer.data )
 
         # create the bounding box rectangles
         bbox_rects = self.make_label_bbox([properties[f'bbox-{i}'] for i in range(6)], dim=3)
@@ -117,24 +113,5 @@ class trackEditingWidget(QWidget):
         bbox_rect = np.moveaxis(bbox_rect, 2, 0)
         return bbox_rect
         
-    def inspect_labels(self):
-        rprops = regionprops(self.viewer.layers["Segmentation"].data, intensity_image=self.viewer.layers["Movie"].data)
-
-        supported = []
-        unsupported = []
-
-        for prop in rprops[0]:
-            try:
-                rprops[0][prop]
-                supported.append(prop)
-            except NotImplementedError:
-                unsupported.append(prop)
-
-        print("Supported properties:")
-        print("  " + "\n  ".join(supported))
-        print()
-        print("Unsupported properties:")
-        print("  " + "\n  ".join(unsupported))
-
 
 
diff --git a/src/tests/test_outputs.py b/src/tests/test_outputs.py
index 8fcb03157caf591abdc5193a30a69d7d5531a36a..749bcbf7df3254d0c27b132f5197d6b5595b9c16 100644
--- a/src/tests/test_outputs.py
+++ b/src/tests/test_outputs.py
@@ -20,10 +20,13 @@ def test_output_selected():
     output = epic.outputing
     assert output is not None
     sel = output.get_selection_name()
-    assert sel == "_cell_1"
+    assert sel == ""
     output.output_mode.setCurrentText("All cells")
     sel = output.get_selection_name()
     assert sel == ""
+    output.output_mode.setCurrentText("Only selected cell")
+    sel = output.get_selection_name()
+    assert sel == "_cell_1"
     roi_file = os.path.join(".", "data_test", "test_epics", "area3_t100-101_rois_cell_1.zip")
     if os.path.exists(roi_file):
         os.remove(roi_file)
diff --git a/src/tests/test_suspects.py b/src/tests/test_suspects.py
index a17fa80eb4c42698255d931fe4be4ab080e3dc36..267cacfa40eec31807742ea208dbf73337451f7b 100644
--- a/src/tests/test_suspects.py
+++ b/src/tests/test_suspects.py
@@ -52,21 +52,28 @@ def test_suspect_track():
     susp.add_event( (5,50,50), 10, "test" )
     assert susp.nb_events() == (nev+1)
     ## test default parameter inspection
+    susp.check_size.setChecked( False )
+    susp.check_length.setChecked( False )
     susp.inspect_tracks()
     assert susp.nb_events() > 50
     assert susp.nb_events() < 100
     ## test minimum track length inspection
-    susp.check_size.setChecked( False )
+    susp.check_length.setChecked( True )
     susp.min_length.setText("5")
+    nmin_prev =  susp.nb_events()
     susp.inspect_tracks()
     nmin =  susp.nb_events()
-    assert nmin > 50
+    assert nmin > nmin_prev
+    assert nmin > 100 
     ## test reset all
     susp.reset_all_events()
     assert susp.nb_events() == 0
+    ## Test reloading the divisions from the track graph
+    susp.get_divisions()
+    assert susp.nb_events() == nev
     ## Track feature change test
-    ## A CHECKER
-    #susp.check_size.setChecked( True )
-    #susp.inspect_tracks()
-    #assert susp.nb_events() > nmin
+    susp.check_size.setChecked( True )
+    susp.inspect_tracks()
+    assert susp.nb_events() > nmin
 
+#test_suspect_track()