diff --git a/README.md b/README.md
index b03990193f870835612c15be5e1321f9e70dbc61..5b5eb517ab987a0d8f462ba10bf47c94ab82440f 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,7 @@
 [![PyPI](https://img.shields.io/pypi/v/epicure.svg?color=green)](https://pypi.org/project/epicure)
 [![Python Version](https://img.shields.io/pypi/pyversions/epicure.svg?color=green)](https://python.org)
 [![napari hub](https://img.shields.io/endpoint?url=https://api.napari-hub.org/shields/epicure)](https://napari-hub.org/plugins/epicure)
+[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.13952184.svg)](https://doi.org/10.5281/zenodo.13952184)
 
 ![EpiCure logo](https://gitlab.pasteur.fr/gletort/epicure/-/raw/main/imgs/epicure_logo.png?raw=True "EpiCure logo")
 
diff --git a/notebooks/EpiCure_ExportAllDivision_OnFolder.ipynb b/notebooks/EpiCure_ExportAllDivision_OnFolder.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..5956d8428c4dcf59f07ce7512e93aa40f1d47a83
--- /dev/null
+++ b/notebooks/EpiCure_ExportAllDivision_OnFolder.ipynb
@@ -0,0 +1,99 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "9629f8e8-86e7-4a6d-85a6-a6b34d548545",
+   "metadata": {},
+   "source": [
+    "# Export division/other events for all movies in a folder\n",
+    "\n",
+    "Notebook to automatically do an export step of EpiCure:\n",
+    "* **export all division events** (optionnaly, can export other events)\n",
+    "\n",
+    "This step correspond to the selection of `Export events` in the `Output` panel of EpiCure. \n",
+    "It will be done on all the movies in a given folder (in all sub-folders), provided that there is saved EpiCure files in the ouput `epics` folder(s).\n",
+    "All division (for all cells) will be exported as Fiji ROI. \n",
+    "\n",
+    "You can modify this notebook to change it to export only the division corresponding to a EpiCure Group or export other events.\n",
+    "\n",
+    "*This notebook is part of EpiCure release, see https://gitlab.pasteur.fr/gletort/epicure for more informations*"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "47dd0f63-87ef-4dce-8034-413b45e77992",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "#### Parent directory to process. It will go through all the sub-folders\n",
+    "main_path = \"../data/small/\""
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "48bf6960-5798-45e9-8271-de1ece6897a4",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import epicure.epicuring as epicure\n",
+    "import os\n",
+    "\n",
+    "def doOneMovie( imgname ):\n",
+    "    \"\"\" Process one movie: clear (or not) the border cells and performs tracking \"\"\"\n",
+    "    print( \"EpiCuring movie \"+imgname+\".tif\" )\n",
+    "    epic = epicure.EpiCure()\n",
+    "    epic.verbose = 0         ## Choose the level of printing infos during process (0: minimal to 3: debug informations)\n",
+    "    epic.load_movie( imgname+\".tif\" )\n",
+    "    epic.go_epicure()  ## start EpiCure, load the segmentation, prepare everything\n",
+    "    \n",
+    "    ## choose export division parameters\n",
+    "    epic.outputing.output_mode.setCurrentText( \"All cells\" )  ## save division concerning all cells (change it to the group name to save only the one linked to a given group)\n",
+    "    epic.outputing.events_select( \"division\", True )   ## output division\n",
+    "    epic.outputing.events_select( \"suspect\", False )   ## dont output suspects\n",
+    "    \n",
+    "    ## do the export\n",
+    "    epic.outputing.export_events()\n",
+    "    \n",
+    "### Main loop, go through all subdirectories and process movies for which the associated segmentation file correspond to the segmentation_extension name\n",
+    "for root, dirs, files in os.walk( main_path ):\n",
+    "    for file in files:\n",
+    "        if (os.path.basename( root ) == \"epics\") and file.endswith( \"labels.tif\" ):\n",
+    "            imgname = file[:len(file)-len(\"_labels.tif\")]\n",
+    "            cdir = os.path.dirname( root )\n",
+    "            imgname = os.path.join( cdir, imgname )\n",
+    "            doOneMovie( imgname )"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9e374937-76fe-4f14-94dc-92562bedf397",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "epic",
+   "language": "python",
+   "name": "epic"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.12"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/notebooks/EpiCure_Initialize_OnFolder.ipynb b/notebooks/EpiCure_Initialize_OnFolder.ipynb
new file mode 100644
index 0000000000000000000000000000000000000000..cd4a015a15245c1b08a6e2428163a50996710f36
--- /dev/null
+++ b/notebooks/EpiCure_Initialize_OnFolder.ipynb
@@ -0,0 +1,98 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "9629f8e8-86e7-4a6d-85a6-a6b34d548545",
+   "metadata": {},
+   "source": [
+    "# Initialize: remove border cells + tracking all movies in a folder\n",
+    "\n",
+    "Notebook to automatically do initial steps of EpiCure:\n",
+    "* **removing the border cells** (optionnaly, cells that are partly outside of the image)\n",
+    "* **performs the tracking** (with the default parameters)\n",
+    "* **save** the results\n",
+    "\n",
+    "These 3 steps will be done on all the movies in a given folder (in all sub-folders), provided that there is an associated segmentation file with the same name as the input image + given extension string (for example `_imagename_epyseg.tif`).\n",
+    "\n",
+    "*This notebook is part of EpiCure release, see https://gitlab.pasteur.fr/gletort/epicure for more informations*"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "47dd0f63-87ef-4dce-8034-413b45e77992",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "#### Parent directory to process. It will go through all the sub-folders\n",
+    "main_path = \"../data/small/\"\n",
+    "#### Name of the associated segmentation file for each image. It must have the same name as the image, with the additional segmentation_extension\n",
+    "segmentation_extension = \"_epyseg\"\n",
+    "#### Option clear border of EpiCure: ON (True) or OFF (False)\n",
+    "remove_border_cells = True"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "48bf6960-5798-45e9-8271-de1ece6897a4",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import epicure.epicuring as epicure\n",
+    "import os\n",
+    "\n",
+    "def doOneMovie( imgname ):\n",
+    "    \"\"\" Process one movie: clear (or not) the border cells and performs tracking \"\"\"\n",
+    "    print( \"EpiCuring movie \"+imgname+\".tif\" )\n",
+    "    epic = epicure.EpiCure()\n",
+    "    epic.verbose = 0         ## Choose the level of printing infos during process (0: minimal to 3: debug informations)\n",
+    "    epic.load_movie( imgname+\".tif\" )\n",
+    "    epic.go_epicure( \"epics\", imgname+segmentation_extension+\".tif\" )  ## start EpiCure, load the segmentation, prepare everything\n",
+    "    if remove_border_cells:\n",
+    "        epic.editing.border_size.setText(\"1\") ## Choose the border size parameter to remove the cells that are within the given distance of the image border\n",
+    "        epic.editing.remove_border()  ## EpiCure option to remove all cells that are touching the image border\n",
+    "    epic.tracking.do_tracking()       ## Performs tracking with the default parameters. If you have saved preferences, it will use it.\n",
+    "    epic.save_epicures()              ## save the results in the ouput \"epics\" folder(s)\n",
+    "    \n",
+    "    \n",
+    "### Main loop, go through all subdirectories and process movies for which the associated segmentation file correspond to the segmentation_extension name\n",
+    "for root, dirs, files in os.walk( main_path ):\n",
+    "    for file in files:\n",
+    "        if file.endswith( segmentation_extension+\".tif\" ):\n",
+    "            imgname = file[:len(file)-4-len(segmentation_extension)]\n",
+    "            imgname = os.path.join( root, imgname ) \n",
+    "            doOneMovie( imgname )"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "9e374937-76fe-4f14-94dc-92562bedf397",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "epic",
+   "language": "python",
+   "name": "epic"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.12"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/setup.cfg b/setup.cfg
index 8f7736a4ac676cac08d209f743349e005faa2e91..039071f2b3eb0e8b90ee12c81df9d90a8768405a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = epicure
-version = 0.2.0
+version = 0.2.2
 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 c0715e670f39045fd44973b408adabfafd4d3653..f82ae114fbdaed9f77d5266030c3820d7a015eea 100644
--- a/src/epicure/Utils.py
+++ b/src/epicure/Utils.py
@@ -2,7 +2,7 @@ import numpy as np
 import os
 import time
 import math
-from skimage.measure import regionprops
+from skimage.measure import regionprops, find_contours, regionprops_table
 from skimage.segmentation import find_boundaries, expand_labels
 from napari.utils.translations import trans
 from napari.utils.notifications import show_info
@@ -10,7 +10,11 @@ from napari.utils import notifications as nt
 from skimage.morphology import binary_dilation, disk
 import pandas as pd
 from epicure.laptrack_centroids import LaptrackCentroids
-from skimage.measure import regionprops_table
+
+try:
+    from skimage.graph import RAG
+except:
+    from skimage.future.graph import RAG  ## older version of scikit-image
 
 def show_info(message):
     """ Display info in napari """
@@ -32,7 +36,7 @@ def show_debug(message):
 
 def show_documentation():
     import webbrowser
-    webbrowser.open_new_tab("https://gitlab.pasteur.fr/gletort/epicure/-/wikis/EpiCure")
+    webbrowser.open_new_tab("https://gitlab.pasteur.fr/gletort/epicure/-/wikis/Home")
     return
 
 def show_documentation_page(page):
@@ -167,6 +171,8 @@ def opentif(imagepath, verbose=True):
     #print(metadata)
     scale = 1
     scalet = 1
+    unitxy = "um"
+    unitt = "min"
     nchan = -1
     if metadata is not None:
         if verbose:
@@ -183,6 +189,9 @@ def opentif(imagepath, verbose=True):
                 scale = float(metadata['physicalsizex'])
             if metadata['finterval'] is not None:
                 scalet = float(metadata['finterval'])
+            if 'unit' in metadata:
+                if metadata['unit'] is not None:
+                    unitxy = metatdata['unit']
         except:
             metadatas = None
             #print(info)
@@ -193,7 +202,7 @@ def opentif(imagepath, verbose=True):
             nchan = -1
     image = img.asarray()
     img.close()
-    return image, scale, nchan
+    return image, nchan, scale, unitxy, scalet, unitt
 
 def writeTif(img, imgname, scale, imtype, what=""):
     import tifffile
@@ -308,14 +317,14 @@ def convert_coords( coord ):
     int_coord = int_coord[1:3]
     return tframe, int_coord
 
-def outerBBox2D(bbox, imshape):
-    if bbox[0] <= 0:
+def outerBBox2D(bbox, imshape, margin=0):
+    if (bbox[0]-margin) <= 0:
         return True
-    if bbox[2] >= imshape[0]:
+    if (bbox[2]+margin) >= imshape[0]:
         return True
-    if bbox[1] <= 0:
+    if (bbox[1]-margin) <= 0:
         return True
-    if bbox[3] >= imshape[1]:
+    if (bbox[3]+margin) >= imshape[1]:
         return True
     return False
 
@@ -385,6 +394,12 @@ def getBBoxFromPts(pts, extend, imshape, outdim=None, frame=None):
             bbox[(outdim==3)+i+outdim] = min(bbox[(outdim==3)+i+outdim] + extend, imshape[(outdim==3)+i] )
     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
 
 def extendBBox2D( bbox, extend_factor, imshape ):
     """ Extend bounding box with given margin """
@@ -406,7 +421,7 @@ def getBBox2D(img, label):
 def getPropLabel(img, label):
     """ Get the properties of label """
     props = regionprops(np.uint8(img==label))
-    print(props)
+    #print(props)
     return props[0]
 
 def getBBoxLabel(img, label):
@@ -588,6 +603,14 @@ def match_labels( sega, segb ):
     parent_labels = laptrack.twoframes_track(df, labels)
     return parent_labels, labels
 
+def labels_table( labimg, intensity_image=None, properties=None, extra_properties=None ):
+    """ Returns the regionprops_table of the labels """
+    if properties is None:
+        properties = ['label', 'centroid']
+    if intensity_image is not None:
+        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 tuple_int(pos):
     if len(pos) == 3:
         return ( (int(pos[0]), int(pos[1]), int(pos[2])) )
@@ -638,6 +661,20 @@ def mean_nonzero( array ):
         return np.sum(array)/nonzero
     return 0
 
+def get_contours( binimg ):
+    """ Return the contour of a binary shape """
+    return find_contours( binimg )
+
+###### Connectivity labels
+def touching_labels( img, expand=3 ):
+    """ Extends the labels to make them touch """
+    return expand_labels( img, distance=expand )
+
+def connectivity_graph( img, distance ):
+    """ Returns the region adjancy graph of labels """
+    touchlab = touching_labels( img, expand=distance )
+    return RAG( touchlab, connectivity=2 )
+
 ####### Distance measures
 
 def total_distance( pts_pos ):
@@ -651,7 +688,7 @@ def net_distance( pts_pos ):
     """ Net distance travelled by point with coordinates xpos and ypos """
     disp = pts_pos[len(pts_pos)-1] - pts_pos[0]
     return np.sum( np.sqrt( np.square(disp[0]) + np.square(disp[1]) ) )
-    
+
 
 ###### Time measures
 def start_time():
@@ -663,3 +700,35 @@ def show_duration(start_time, header=None):
     #show_info(header+"{:.3f}".format((time.time()-start_time)/60)+" min")
     print(header+"{:.3f}".format((time.time()-start_time)/60)+" min")
 
+###### Preferences/shortcuts 
+
+def shortcut_click_match( shortcut, event ):
+    """ Test if the click event corresponds to the shortcut """
+    button = 1
+    if shortcut["button"] == "Right":
+        button = 2
+    if event.button != button:
+        return False
+    if "modifiers" in shortcut.keys():
+        return set(list(event.modifiers)) == set(shortcut["modifiers"])
+    else:
+        if len(event.modifiers) > 0:
+            return False
+        return True
+        
+def print_shortcuts( shortcut_group ):
+    """ Put to text the subset of shortcuts """
+    text = ""
+    for short_name, vals in shortcut_group.items():
+        if vals["type"] == "key":
+            text += "  <"+vals["key"]+"> "+vals["text"]+"\n"
+        if vals["type"] == "click":
+            modif = ""
+            if "modifiers" in vals.keys():
+                modifiers = vals["modifiers"]
+                for mod in modifiers:
+                    modif += mod+"-"
+            text += "  <"+modif+vals["button"]+"-click> "+vals["text"]+"\n"
+    return text
+
+
diff --git a/src/epicure/concatenate_movie.py b/src/epicure/concatenate_movie.py
index 7b4871690d6d89e15e9b217829df5ab27273da57..044f6b904e93662c66a9ab6d2ba09211305a459d 100644
--- a/src/epicure/concatenate_movie.py
+++ b/src/epicure/concatenate_movie.py
@@ -47,7 +47,7 @@ def merge_epicures( first_movie, first_labels, second_movie, second_labels, full
     ## create the full movie
     full_mov = np.concatenate( (first_epicure.img, second_epicure.img[1:]), axis=0 )
     full_movie_name = os.path.join(os.path.dirname(first_epicure.imgpath), fullname) 
-    ut.writeTif( full_mov, full_movie_name, first_epicure.scale, first_epicure.img.dtype, what="Full movie") 
+    ut.writeTif( full_mov, full_movie_name, first_epicure.epi_metadata["ScaleXY"], first_epicure.img.dtype, what="Full movie") 
     print("Full movie created")
 
     ###### Read and merge the EpiCure data and merge the movie labels
@@ -120,7 +120,7 @@ def merge_epicures( first_movie, first_labels, second_movie, second_labels, full
                 if second_epicure.groups is not None:
                     second = second_epicure.find_group( lab )
                     if second is not None:
-                        epic.cell_ingroup( nextlabel, second )
+                        epic.cells_ingroup( [nextlabel], second )
                 if len(nextlabels) <= 0:
                     ## the list of unused labels has been completly used, regenerates
                     nextlabels = ut.get_free_labels( used_labels, 20 )
@@ -149,7 +149,7 @@ def merge_epicures( first_movie, first_labels, second_movie, second_labels, full
     full_lab = np.concatenate( (first_epicure.seg, second_epicure.seg[1:]), axis=0 )
     epic.seg = full_lab
     epic.save_epicures()
-    print("Movie and EpiCure files merged; Suspects (if any) are not merged, use inspect tracks on merged movie to generate them")
+    print("Movie and EpiCure files merged; Suspects/Events (if any) are not merged, use inspect tracks on merged movie to generate them")
 
 def fullmovie_group( epic, first_label, second_label, second_epicure ):
     """ Check if second_label is in a group, and add it to full movie groups if relevant """
@@ -164,7 +164,7 @@ def fullmovie_group( epic, first_label, second_label, second_epicure ):
             print("Keep only the first movie group: "+first_group)
     else:
         if second is not None:
-            epic.cell_ingroup( first_label, second )
+            epic.cells_ingroup( [first_label], second )
 
 def concatenate_movies():
     hist = get_save_history()
diff --git a/src/epicure/displaying.py b/src/epicure/displaying.py
index d7ad247a173522dbf21cbb47dfcf48152df22aac..76ecc40726a3dc20cfbcd2061d3100c560e253df 100644
--- a/src/epicure/displaying.py
+++ b/src/epicure/displaying.py
@@ -1,10 +1,10 @@
 import numpy as np
-import os
 from math import ceil
-from qtpy.QtWidgets import QPushButton, QHBoxLayout, QVBoxLayout, QWidget, QGroupBox, QCheckBox, QSlider, QLabel, QDoubleSpinBox, QComboBox, QLineEdit
+from qtpy.QtWidgets import QHBoxLayout, QVBoxLayout, QWidget, QComboBox
 from qtpy.QtCore import Qt
 from magicgui.widgets import TextEdit
 import epicure.Utils as ut
+import epicure.epiwidgets as wid
 
 
 class Displaying(QWidget):
@@ -18,71 +18,128 @@ class Displaying(QWidget):
         self.seglayer = self.viewer.layers["Segmentation"]
         self.gmode = 0  ## view only movie mode on/off
         self.dmode = 0  ## view with light segmentation on/off
+        self.grid_color = [0.6, 0.7, 0.7, 0.7]  ## default grid color
 
         layout = QVBoxLayout()
         
         ## Show a text window with some summary of the file
-        show_summary = QPushButton("Show summary", parent=self)
+        show_summary = wid.add_button( "Show summary", self.show_summary_window, "Pops-up a summary of the movie and segmentation informations" )
         layout.addWidget(show_summary)
-        show_summary.clicked.connect(self.show_summary_window)
         
         ## Option show segmentation skeleton
-        show_skeleton_line = QHBoxLayout()
-        self.show_skeleton = QCheckBox(text="Show segmentation skeleton")
-        show_skeleton_line.addWidget(self.show_skeleton)
-        self.show_skeleton.stateChanged.connect(self.show_skeleton_segmentation)
-        layout.addLayout(show_skeleton_line)
-        self.show_skeleton.setChecked(False)
+        self.show_skeleton = wid.add_check( "Show segmentation skeleton", False, self.show_skeleton_segmentation, "Add a layer with the segmentation skeleton (not automatically updated)" )
+        layout.addWidget(self.show_skeleton)
+        
+        ## Option to show the movie and seg side by side
+        show_sides = QHBoxLayout()
+        self.show_side = wid.add_check( "Side by side view", False, self.show_side_side, "View the movie and the other layers side by side" )
+        show_sides.addWidget( self.show_side )
+        self.directions = QComboBox()
+        self.directions.addItem( "Horizontal" )
+        self.directions.addItem( "Vertical" )
+        show_sides.addWidget( self.directions )
+        self.directions.currentIndexChanged.connect( self.show_side_side )
+        layout.addLayout( show_sides )
         
         ## Option show shifted segmentation
-        show_shifted_line = QHBoxLayout()
-        self.show_shifted = QCheckBox(text="Overlay previous segmentation")
-        show_shifted_line.addWidget(self.show_shifted)
-        self.show_shifted.stateChanged.connect(self.show_shifted_segmentation)
-        layout.addLayout(show_shifted_line)
-        self.show_shifted.setChecked(False)
+        self.show_shifted = wid.add_check( "Overlay previous segmentation", False, self.show_shifted_segmentation, "Overlay the (frame-1) segmentation on the current segmentation")
+        layout.addWidget(self.show_shifted)
         
         ## Option show shifted movie (previous or next)
         show_prevmovie_line = QHBoxLayout()
-        self.show_previous_movie = QCheckBox(text="Overlay previous movie")
-        show_prevmovie_line.addWidget(self.show_previous_movie)
-        self.show_previous_movie.stateChanged.connect(self.show_shifted_previous_movie)
-        layout.addLayout(show_prevmovie_line)
-        self.show_previous_movie.setChecked(False)
-        show_nextmovie_line = QHBoxLayout()
-        self.show_next_movie = QCheckBox(text="Overlay next movie")
-        show_nextmovie_line.addWidget(self.show_next_movie)
-        self.show_next_movie.stateChanged.connect(self.show_shifted_next_movie)
-        layout.addLayout(show_nextmovie_line)
-        self.show_next_movie.setChecked(False)
+        self.show_previous_movie = wid.add_check( "Overlay previous movie", False, self.show_shifted_previous_movie, "Overlay the (frame-1) of the movie on the current movie" )
+        layout.addWidget(self.show_previous_movie)
+        self.show_next_movie = wid.add_check( "Overlay next movie", False, self.show_shifted_next_movie, "Overlay (frame+1) of the movie on the current frame" )
+        layout.addWidget(self.show_next_movie)
         
         ## Option create/show grid
-        self.show_grid_options = QCheckBox(text="Grid options")
+        grid_line, self.show_grid_options, self.group_grid = wid.checkgroup_help( "Grid options", True, "Show/hide subpanel to control grid view", "Display#grid-options", self.epicure.display_colors, groupnb="group" )
         self.grid_parameters()
         layout.addWidget(self.show_grid_options)
         layout.addWidget(self.group_grid)
-        self.show_grid_options.setChecked(False)
-        self.show_grid_options.stateChanged.connect(self.grid_group_visibility)
-        self.grid_group_visibility()
+        
+        save_pref = wid.add_button( "Set current settings as default", self.save_current_display, "Save the current settings so that EpiCure will open in the same state next time" )
+        layout.addWidget(save_pref)
         
         self.add_display_overlay_message()
         self.key_bindings()        ## activate shortcuts for display options
         self.setLayout(layout)
         ut.set_active_layer( self.viewer, "Segmentation" )
     
+    ### set current display as defaut
+    def save_current_display( self ):
+        """ Set current display parameters as defaut display """
+        self.epicure.update_settings()
+        self.epicure.pref.save()
+
+    def get_current_settings( self ):
+        """ Returns current display settings """
+        disp = {}
+        disp["Layers"] = {}
+        for layer in self.viewer.layers:
+            disp["Layers"][layer.name] = layer.visible
+        disp["Show Grid"] = self.show_grid_options.isChecked()
+        disp["Grid nrows"] = self.nrows.text()
+        disp["Grid ncols"] = self.ncols.text()
+        disp["Grid width"] = self.gwidth.text()
+        disp["Grid color"] = self.grid_color
+        disp["Show side on"] = self.show_side.isChecked()
+        disp["Side direction"] = self.directions.currentText()
+        if "EpicGrid" in self.viewer.layers:
+            disp["Grid text"] = self.viewer.layers["EpicGrid"].text.visible
+            disp["Grid color"] = self.viewer.layers["EpicGrid"].edge_color[0]
+        return disp
+    
+    def apply_settings( self, settings ):
+        """ Set current display to prefered settings """
+        add_grid = False
+        show_text = False
+        ## read the settings and apply them
+        for setty, val in settings.items():
+            if setty == "Layers":
+                for layname, layvis in val.items():
+                    if layname in self.viewer.layers:
+                        self.viewer.layers[layname].visible = layvis
+                    else:
+                        if layname == "EpicGrid":
+                            add_grid = layvis 
+                continue
+            if setty == "Show Grid":
+                self.show_grid_options.setChecked( val )
+                continue
+            if setty == "Grid nrows":
+                self.nrows.setText( val )
+                continue
+            if setty == "Grid ncols":
+                self.ncols.setText( val )
+                continue
+            if setty == "Grid width":
+                self.gwidth.setText( val )
+                continue
+            if setty == "Grid text":
+                show_text = val
+                continue
+            if setty == "Grid color":
+                self.grid_color = val
+                continue
+            if setty == "Show side on":
+                self.show_side.setChecked( val )
+            if setty == "Side direction":
+                self.directions.setCurrentText( val )
+            
+        ## if grid should be added, do it at the end when all values are updated
+        if add_grid:
+            self.add_grid()
+            self.viewer.layers["EpicGrid"].text.visible = show_text
+
+
 
     ######### overlay message
     def add_display_overlay_message(self):
         """ Shortcut list for display options """
         disptext = "--- Display options --- \n"
-        disptext = disptext + "  <b> show/hide segmentation layer \n"
-        disptext = disptext + "  <v> show/hide movie layer \n"
-        disptext = disptext + "  <x> show/hide suspects layer \n"
-        disptext = disptext + "  <c> show ONLY movie layer \n"
-        disptext = disptext + "  <d> on/off light segmentation view \n"
-        disptext = disptext + "  <Ctrl-c>/<Ctrl-d> increase/decrease label contour \n"
-        disptext = disptext + "  <k> show/update segmentation skeleton \n"
-        disptext = disptext + "  <g> show/hide grid \n"
+        sdisp = self.epicure.shortcuts["Display"]
+        disptext += ut.print_shortcuts( sdisp )
         self.epicure.overtext["Display"] = disptext
 
     def show_summary_window(self):
@@ -95,40 +152,46 @@ class Displaying(QWidget):
 
     ################  Key binging for display options
     def key_bindings(self):
+        sdisp = self.epicure.shortcuts["Display"]
         
-        @self.seglayer.bind_key('b', overwrite=True)
+        @self.seglayer.bind_key( sdisp["vis. segmentation"]["key"], overwrite=True )
         def see_segmentlayer(seglayer):
             seglayer.visible = not seglayer.visible
         
-        @self.seglayer.bind_key('v', overwrite=True)
+        @self.seglayer.bind_key( sdisp["vis. movie"]["key"], overwrite=True )
         def see_movielayer(seglayer):
             ut.inv_visibility(self.viewer, "Movie")
         
-        @self.seglayer.bind_key('x', overwrite=True)
-        def see_suspectslayer(seglayer):
-            suslayer = self.viewer.layers["Suspects"]
-            suslayer.visible = not suslayer.visible
+        @self.seglayer.bind_key( sdisp["vis. event"]["key"], overwrite=True )
+        def see_eventslayer(seglayer):
+            evlayer = self.viewer.layers["Events"]
+            evlayer.visible = not evlayer.visible
         
-        @self.seglayer.bind_key('k', overwrite=True)
+        @self.seglayer.bind_key( sdisp["skeleton"]["key"], overwrite=True )
         def show_skeleton(seglayer):
             """ On/Off show skeleton """
             if self.show_skeleton.isChecked():
                 self.show_skeleton.setChecked(False)
             else:
                 self.show_skeleton.setChecked(True)
+        
+        @self.seglayer.bind_key( sdisp["show side"]["key"], overwrite=True )
+        def show_byside(seglayer):
+            self.show_side.setChecked( not self.show_side.isChecked() )
+            self.show_side_side()
 
-        @self.seglayer.bind_key('Control-c', overwrite=True)
+        @self.seglayer.bind_key( sdisp["increase"]["key"], overwrite=True )
         def contour_increase(seglayer):
             if seglayer is not None:
                 seglayer.contour = seglayer.contour + 1
         
-        @self.seglayer.bind_key('Control-d', overwrite=True)
+        @self.seglayer.bind_key( sdisp["decrease"]["key"], overwrite=True )
         def contour_decrease(seglayer):
             if seglayer is not None:
                 if seglayer.contour > 0:
                     seglayer.contour = seglayer.contour - 1
         
-        @self.seglayer.bind_key('c', overwrite=True)
+        @self.seglayer.bind_key( sdisp["only movie"]["key"], overwrite=True )
         def see_onlymovielayer(seglayer):
             """ if in "g" mode, show only movie, else put back to previous views """
             if self.gmode == 0:
@@ -143,7 +206,7 @@ class Displaying(QWidget):
                     lay.visible = vis
                 self.gmode = 0
 
-        @self.seglayer.bind_key('d', overwrite=True)
+        @self.seglayer.bind_key( sdisp["light view"]["key"], overwrite=True )
         def segmentation_lightmode(seglayer):
             """ if in "d" mode, show only movie and light segmentation, else put back to previous views """
             if self.dmode == 0:
@@ -165,11 +228,12 @@ class Displaying(QWidget):
                 self.seglayer.opacity = self.unlight_opacity
                 self.dmode = 0
         
-        @self.seglayer.bind_key('g', overwrite=True)
+        @self.seglayer.bind_key( sdisp["grid"]["key"], overwrite=True )
         def show_grid(seglayer):
             """ show/hide the grid to have a repere in space """
             self.show_grid()
 
+    ### Display options
     def show_skeleton_segmentation(self):
         """ Show/hide/update skeleton """
         if "Skeleton" in self.viewer.layers:
@@ -178,6 +242,19 @@ class Displaying(QWidget):
             self.epicure.add_skeleton()
             ut.set_active_layer( self.viewer, "Segmentation" )
 
+    def show_side_side( self ):
+        """ Show the layers side by side """
+        layout_grid = self.viewer.grid
+        if self.show_side.isChecked():
+            stride =  len( self.viewer.layers ) - 1
+            layout_grid.stride = stride
+            layout_grid.shape = (2,1)
+            if self.directions.currentText() == "Horizontal":
+                layout_grid.shape = (1,2)
+            layout_grid.enabled = True
+        else:
+            layout_grid.enabled = False
+
 
     def show_shifted_segmentation(self):
         """ Show/Hide temporally shifted segmentation on top of current one """
@@ -234,46 +311,21 @@ class Displaying(QWidget):
         ut.set_active_layer( self.viewer, "Segmentation" )
 
     #### Show/load a grid to have a repere in space
-    def grid_group_visibility(self):
-        """ Show/hide grid parameters """
-        self.group_grid.setVisible(self.show_grid_options.isChecked())
-
     def grid_parameters(self):
         """ Interface to get grid parameters """
-        self.group_grid = QGroupBox("Grid setup")
         grid_layout = QVBoxLayout()
         ## nrows
-        rows_line = QHBoxLayout()
-        rows_lab = QLabel()
-        rows_lab.setText("Nb rows:")
-        rows_line.addWidget(rows_lab)
-        self.nrows = QLineEdit()
-        self.nrows.setText("3")
-        rows_line.addWidget(self.nrows)
+        rows_line, self.nrows = wid.value_line( "Nb rows", "4", "Number of rows of the grid" )
         grid_layout.addLayout(rows_line)
         ## ncols
-        cols_line = QHBoxLayout()
-        cols_lab = QLabel()
-        cols_lab.setText("Nb columns:")
-        cols_line.addWidget(cols_lab)
-        self.ncols = QLineEdit()
-        self.ncols.setText("3")
-        cols_line.addWidget(self.ncols)
+        cols_line, self.ncols = wid.value_line( "Nb columns:", "4", "Number of columns in the grid" )
         grid_layout.addLayout(cols_line)
         ## grid edges width
-        width_line = QHBoxLayout()
-        width_lab = QLabel()
-        width_lab.setText("Grid width:")
-        width_line.addWidget(width_lab)
-        self.gwidth = QLineEdit()
-        self.gwidth.setText("4")
-        width_line.addWidget(self.gwidth)
+        width_line, self.gwidth = wid.value_line( "Grid width:", "3", "Width of the grid displayed lines/columns" )
         grid_layout.addLayout(width_line)
-        #self.gwidth.changed.connect(self.add_grid)
-        ## go for grid
-        btn_add_grid = QPushButton("Add grid", parent=self)
+       ## go for grid
+        btn_add_grid = wid.add_button( "Add grid", self.add_grid, "Add a grid overlay to the main view" )
         grid_layout.addWidget(btn_add_grid)
-        btn_add_grid.clicked.connect(self.add_grid)
         self.group_grid.setLayout(grid_layout)
 
     def add_grid(self):
@@ -295,7 +347,8 @@ class Displaying(QWidget):
                 rect = np.array([[x*wid, y*hei], [(x+1)*wid, (y+1)*hei]])
                 rects.append(rect)
                 rects_names.append(chr(65+x)+"_"+str(y))
-        self.viewer.add_shapes(rects, name="EpicGrid", text=rects_names, face_color=[1,0,0,0], edge_color=[0.7,0.7,0.7,0.7], edge_width=gwidth, opacity=0.8)
+        self.viewer.add_shapes(rects, name="EpicGrid", text=rects_names, face_color=[1,0,0,0], edge_color=self.grid_color, edge_width=gwidth, opacity=0.7)
+        self.viewer.layers["EpicGrid"].text.visible = False
         ut.set_active_layer( self.viewer, "Segmentation" )
 
     def show_grid(self):
@@ -305,3 +358,4 @@ class Displaying(QWidget):
         else:
             gridlay = self.viewer.layers["EpicGrid"]
             gridlay.visible = not gridlay.visible
+            gridlay.edge_color = self.grid_color
diff --git a/src/epicure/editing.py b/src/epicure/editing.py
index 6f48669d26f37cfa50929ea24cec33a67e85ac86..6d40953bd9e8a532e1efaedd1ce8d2425e2e9ef9 100644
--- a/src/epicure/editing.py
+++ b/src/epicure/editing.py
@@ -1,20 +1,20 @@
 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.morphology import binary_closing, binary_dilation, binary_erosion, disk
-from qtpy.QtWidgets import QPushButton, QHBoxLayout, QVBoxLayout, QWidget, QGroupBox, QCheckBox, QSlider, QLabel, QDoubleSpinBox, QComboBox, QLineEdit
-from qtpy.QtCore import Qt
+from skimage.morphology import binary_closing, binary_opening, binary_dilation, binary_erosion, disk
+from qtpy.QtWidgets import QVBoxLayout, QWidget, QGroupBox 
 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 
-from multiprocessing.pool import ThreadPool as Pool
 from napari.layers.labels._labels_utils import sphere_indices
-import epicure.Utils as ut
-import time
 from napari.utils import progress
-import edt
+import epicure.Utils as ut
+import epicure.epiwidgets as wid
+from napari.qt.threading import thread_worker
 
-class Editing(QWidget):
+class Editing( QWidget ):
     """ Handle user interaction to edit the segmentation """
 
     def __init__(self, napari_viewer, epic):
@@ -30,59 +30,31 @@ class Editing(QWidget):
         layout = QVBoxLayout()
         
         ## Option to remove all border cells
-        clean_line = QHBoxLayout()
-        clean_vis = QCheckBox(text="Cleaning options")
-        clean_line.addWidget(clean_vis)
-        clean_helpbtn = QPushButton("Help", parent=self)
-        clean_helpbtn.clicked.connect(self.help_clean)
-        clean_line.addWidget(clean_helpbtn)
+        clean_line, self.clean_vis, self.gCleaned = wid.checkgroup_help( name="Cleaning options", checked=False, descr="Show/hide options to clean the segmentation", help_link="Edit#cleaning-options", display_settings=self.epicure.display_colors, groupnb="group" )
         layout.addLayout(clean_line)
-        clean_vis.stateChanged.connect(self.show_cleaningBlock)
         self.create_cleaningBlock()
         layout.addWidget(self.gCleaned)
-        clean_vis.setChecked(False)
         self.gCleaned.hide()
 
         ## handle grouping cells into categories
-        group_line = QHBoxLayout()
-        group_vis = QCheckBox(text="Group cells options")
-        group_line.addWidget(group_vis)
-        group_helpbtn = QPushButton("Help", parent=self)
-        group_helpbtn.clicked.connect(self.help_group)
-        group_line.addWidget(group_helpbtn)
+        group_line, self.group_vis, self.gGroup = wid.checkgroup_help( name="Cell group options", checked=False, descr="Show/hide options to define cell groups", help_link="Edit#group-options", display_settings=self.epicure.display_colors, groupnb="group2"  )
         layout.addLayout(group_line)
-        group_vis.stateChanged.connect(self.show_groupCellsBlock)
         self.create_groupCellsBlock()
         layout.addWidget(self.gGroup)
-        group_vis.setChecked(False)
         self.gGroup.hide()
         
         ## Selection option: crop, remove cells
-        select_line = QHBoxLayout()
-        self.select_vis = QCheckBox(text="ROI options")
-        select_line.addWidget(self.select_vis)
-        help_selectbtn = QPushButton("Help", parent=self)
-        help_selectbtn.clicked.connect(self.help_selection)
-        select_line.addWidget(help_selectbtn)
+        select_line, self.select_vis, self.gSelect = wid.checkgroup_help( name="ROI options", checked=False, descr="Show/hide options to work on Regions", help_link="Edit#roi-options", display_settings=self.epicure.display_colors, groupnb="group3" )
         layout.addLayout(select_line)
-        self.select_vis.stateChanged.connect(self.show_selectBlock)
         self.create_selectBlock()
         layout.addWidget(self.gSelect)
-        self.select_vis.setChecked(False)
         self.gSelect.hide()
         
         ## Put seeds and do watershed from it
-        seed_line = QHBoxLayout()
-        self.seed_vis = QCheckBox(text="Seeds options")
-        seed_line.addWidget(self.seed_vis)
-        help_seedbtn = QPushButton("Help", parent=self)
-        help_seedbtn.clicked.connect(self.help_seeds)
-        seed_line.addWidget(help_seedbtn)
+        seed_line, self.seed_vis, self.gSeed = wid.checkgroup_help( name="Seeds options", checked=False, descr="Show/hide options to segment from seeds", help_link="Edit#seeds-options", display_settings=self.epicure.display_colors, groupnb="group4" )
         layout.addLayout(seed_line)
-        self.seed_vis.stateChanged.connect(self.show_hide_seedMapBlock)
         self.create_seedsBlock()
         layout.addWidget(self.gSeed)
-        self.seed_vis.setChecked(False)
         self.gSeed.hide()
         
         self.setLayout(layout)
@@ -97,12 +69,47 @@ class Editing(QWidget):
         self.napari_fill = self.epicure.seglayer.fill
         self.epicure.seglayer.fill = self.epicure_fill
         self.napari_paint = self.epicure.seglayer.paint
-        self.epicure.seglayer.paint = self.epicure_paint
+        self.epicure.seglayer.paint = self.lazy #self.epicure_paint
         ### scale and radius for paiting
         self.paint_scale = np.array([self.epicure.seglayer.scale[i+1] for i in range(2)], dtype=float)
         self.epicure.seglayer.events.brush_size.connect( self.paint_radius )
         self.paint_radius()
         self.disk_one = disk(radius=1)
+
+
+    def apply_settings( self, settings ):
+        """ Load the prefered settings for Edit panel """
+        for setting, val in settings.items():
+            if setting == "Show group option":
+                self.group_vis.setChecked( val )
+            if setting == "Show clean option":
+                self.clean_vis.setChecked( val )
+            if setting ==  "Show ROI option":
+                self.select_vis.setChecked( val )
+            if setting == "Show seed option":
+                self.seed_vis.setChecked( val )
+            if setting == "Show groups":
+                self.group_show.setChecked( val )
+            if setting == "Border size":
+                self.border_size.setText( val )
+            if setting == "Seed method":
+                self.seed_method.setCurrentText( val )
+            if setting == "Seed max cell":
+                self.max_distance.setText( val )
+           
+
+    def get_current_settings( self ):
+        """ Returns the current state of the Edit widget """
+        setting = {}
+        setting["Show group option"] = self.group_vis.isChecked()
+        setting["Show clean option"] = self.clean_vis.isChecked()
+        setting["Show ROI option"] = self.select_vis.isChecked()
+        setting["Show seed option"] = self.seed_vis.isChecked()
+        setting["Show groups"] = self.group_show.isChecked()
+        setting["Border size"] = self.border_size.text()
+        setting["Seed method"] = self.seed_method.currentText()
+        setting["Seed max cell"] = self.max_distance.text()
+        return setting
    
     def paint_radius( self ):
         """ Update painitng radius with brush size """
@@ -182,23 +189,42 @@ class Editing(QWidget):
         ## put the active mode of the layer back to the zoom one
         self.epicure.seglayer.mode = "pan_zoom"
         if prev_label != 0: 
-            self.epicure.tracking.remove_one_frame( [prev_label], tframe )
-
-    def epicure_paint(self, coord, new_label, refresh=True):
-        """ Action when trying to edit a label with paint tool """
-        tframe, int_coord = ut.convert_coords( coord )
-        mask_indices = np.array( int_coord ) + self.brush_indices
+            self.epicure.tracking.remove_one_frame( [prev_label], tframe, handle_gaps=self.epicure.forbid_gaps )
+
+    def lazy( self, coord, new_label, refresh=True ):
+        return
+
+    def epicure_paint( self, coords, new_label, tframe, hascell ):
+        """ Edit a label with paint tool, with several pixels at once """
+        mask_indices = None
+        ## convert the coords with brush size, check that is fully inside
+        for coord in coords:
+            int_coord = np.array( np.round(coord).astype(int)[1:3] ) 
+            for brush in self.brush_indices:
+                pt = int_coord + brush
+                if ut.inside_bounds( pt, self.epicure.imgshape2D ):
+                    if mask_indices is None:
+                        mask_indices = pt
+                    else:
+                        mask_indices = np.vstack( ( mask_indices, pt ) )
+        
+        ## crop around part of the image to update
         bbox = ut.getBBoxFromPts( mask_indices, extend=0, imshape=self.epicure.imgshape2D )
-        bbox = ut.extendBBox2D( bbox, extend_factor=4, imshape=self.epicure.imgshape2D )
+        if hascell:
+            ## extend around points a lot if the label is there already to avoid cutting it
+            extend = 4
+        else:
+            extend = 1.5
+        bbox = ut.extendBBox2D( bbox, extend_factor=extend, imshape=self.epicure.imgshape2D )
         cropdata = ut.cropBBox2D( self.epicure.seglayer.data[tframe], bbox )
         crop_indices = ut.positions2DIn2DBBox( mask_indices, bbox )
+        
+        ## get previous data before painting
         prev_labels = np.unique( cropdata[ tuple(np.array(crop_indices).T) ] ).tolist()
         if 0 in prev_labels:
             prev_labels.remove(0)
 
-        if new_label > 0:
-            ## painting a new or extending a cell
-            hascell = self.epicure.has_label( new_label )
+        if new_label > 0:    
             if hascell:
                 ## check that label is in current frame
                 mask_before = cropdata==new_label
@@ -216,21 +242,34 @@ class Editing(QWidget):
                     return
             else:
                 ## drawing new cell, fill it at the end
-                if self.epicure.verbose > 1:
+                if self.epicure.verbose > 2:
                     print("Painting a new cell")
 
         ## Paint and update everything    
         painted = np.copy(cropdata)
         painted[ tuple(np.array(crop_indices).T) ] = new_label
         if new_label > 0:
-            painted = binary_fill_holes( painted==new_label )
-            crop_indices = np.argwhere(painted>0)    
+            if self.epicure.seglayer.preserve_labels:
+                painted = painted*(np.isin( cropdata, [0, new_label] ))
+                painted = binary_fill_holes( (painted==new_label) )
+                ## remove one-pixel thick lines
+                painted = binary_opening( painted )
+                crop_indices = np.argwhere( (painted>0) )
+            else:
+                painted = binary_fill_holes( painted==new_label )
+                crop_indices = np.argwhere(painted>0)    
+        ### if preseve label is on, there can be nothing left to paint
+        if len(crop_indices) <= 0:
+            return
         mask_indices = ut.toFullMoviePos( crop_indices, bbox, tframe )
         new_labels = np.repeat(new_label, len(mask_indices)).tolist()
 
         ## Update label boundaries if necessary
         cind_bound = self.ind_boundaries( painted )
-        ind_bound = [ ind for ind in cind_bound if cropdata[tuple(ind)] in prev_labels ]
+        if self.epicure.seglayer.preserve_labels:
+            ind_bound = [ ind for ind in cind_bound if (cropdata[tuple(ind)] == new_label) ]
+        else:
+            ind_bound = [ ind for ind in cind_bound if cropdata[tuple(ind)] in prev_labels ]
         if (new_label>0) and (len( ind_bound ) > 0):
             bound_ind = ut.toFullMoviePos( ind_bound, bbox, tframe )
             bound_labels = np.repeat(0, len(bound_ind)).tolist()
@@ -239,6 +278,108 @@ class Editing(QWidget):
 
         ## Go, apply the change, and update the tracks
         self.epicure.change_labels( mask_indices, new_labels )
+
+    def create_cell_from_line( self, tframe, positions ):
+        """ Create new cell(s) from drawn line (junction) """
+        bbox = ut.getBBox2DFromPts( positions, extend=0, imshape=self.epicure.imgshape2D )
+        bbox = ut.extendBBox2D( bbox, extend_factor=2, imshape=self.epicure.imgshape2D )
+
+        segt = self.epicure.seglayer.data[tframe]
+        cropt = ut.cropBBox2D( segt, bbox )
+        crop_positions = ut.positionsIn2DBBox( positions, bbox )
+
+        line = np.zeros(cropt.shape, dtype="uint8")
+        ## fill the already filled pixels by other labels
+        line[ cropt > 0 ] = 1
+        ## expand from one pixel to fill the junction
+        line = binary_dilation( line )
+        ## fill the interpolated line
+        for i, pos in enumerate(crop_positions):
+            if cropt[round(pos[0]), round(pos[1])] == 0:
+                line[round(pos[0]), round(pos[1])] = 1
+            if (i > 0):
+                prev = (crop_positions[i-1][0], crop_positions[i-1][1])
+                cur = (pos[0], pos[1])
+                interp_coords = interpolate_coordinates(prev, cur, 1)
+                for ic in interp_coords:
+                    line[tuple(np.round(ic).astype(int))] = 1
+        
+        ## close the junction gaps, and the line eventually
+        line = binary_closing( line )
+        new_cells, nlabels = label( line, background=1, return_num=True, connectivity=1 )
+        ## no new cell to create
+        if nlabels <= 0:
+            return
+        ## get the new labels to relabel and add as new cells
+        labels = list( set( new_cells.flatten() ) )
+        if 0 in labels:
+            labels.remove(0)
+       
+        ## try to get new cell labels from previous and next slices
+        parents = [None]*len(labels)
+        if tframe > 0:
+            twoframes = ut.crop_twoframes( self.epicure.seglayer.data, bbox, tframe )
+            orig = np.copy( twoframes[1] )
+            twoframes[1] = new_cells
+            ut.keep_orphans( twoframes, orig, [] )
+            parents = self.get_parents( twoframes, labels )
+        childs = [None]*len(labels)
+        if tframe < (self.epicure.nframes-1):
+            twoframes = np.copy( ut.cropBBox2D(self.epicure.seglayer.data[tframe+1], bbox) )
+            twoframes = np.stack( (twoframes, np.copy(new_cells)) )
+            orig = ut.cropBBox2D(self.epicure.seglayer.data[tframe], bbox)
+            ut.keep_orphans( twoframes, orig, [] )
+            childs = self.get_parents( twoframes, labels )
+        
+        free_labels = self.epicure.get_free_labels( nlabels )  
+        torelink = []
+        for i in range( len(labels) ):
+            print(parents[i])
+            print(childs[i])
+            if (parents[i] is not None) and (childs[i] is not None):
+                ## the two propagation agrees (gap allowed)
+                #if parents[i] == childs[i]:
+                #    free_labels[i] = parents[i]
+                #    if self.epicure.verbose > 0:
+                #        print("Link new cell with previous/next "+str(free_labels[i]))
+                free_labels[i] = parents[i]
+                if self.epicure.verbose > 0:
+                    print("Link new cell with previous/next "+str(free_labels[i]))
+                if childs[i] != parents[i]:
+                    torelink.append( [free_labels[i], childs[i]] )
+            ## only one link found, take it
+            if (parents[i] is not None) and (childs[i] is None):
+                free_labels[i] = parents[i]
+                if self.epicure.verbose > 0:
+                    print("Link new cell with previous/next "+str(free_labels[i]))
+            if (parents[i] is None) and (childs[i] is not None):
+                free_labels[i] = childs[i]
+                if self.epicure.verbose > 0:
+                    print("Link new cell with previous/next "+str(free_labels[i]))
+
+        print(free_labels)
+
+        ## get the new indices and labels to draw
+        new_labels = []
+        indices = None
+        for i, lab in enumerate( labels ):
+            curindices = np.argwhere( new_cells == lab )
+            if indices is None:
+                indices = curindices
+            else:
+                indices = np.vstack((indices, curindices))
+            new_labels = new_labels + ([free_labels[i]]*curindices.shape[0])    
+        
+        ## add the label boundary
+        indbound = self.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 )
+        self.epicure.change_labels( indices, new_labels )
+
+        ## relink child tracks if necessary
+        for relink in torelink:
+            self.epicure.replace_label( relink[1], relink[0], tframe )
         
     def touching_masks(self, maska, maskb):
         """ Check if the two mask touch """
@@ -253,39 +394,78 @@ class Editing(QWidget):
 
     ## Merging/splitting cells functions
     def modify_cells(self):
+        sl = self.epicure.shortcuts["Labels"]
         self.epicure.overtext["labels"] = "---- Labels editing ---- \n"
-        self.epicure.overtext["labels"] += "  <n> to set the current label to unused value and go to paint mode \n"
-        self.epicure.overtext["labels"] += "  <Shift+n> to set the current label to unused value and go to fill mode \n"
-        self.epicure.overtext["labels"] += "  Right-click, erase the cell \n"
-        self.epicure.overtext["labels"] += "  <Control>+Left click, from one cell to another to merge them \n"
-        self.epicure.overtext["labels"] += "  <Control>+Right click, accross a junction to split in 2 cells \n"
-        self.epicure.overtext["labels"] += "  <Alt>+Right click drag, draw a junction to split in 2 cells \n"
-        self.epicure.overtext["labels"] += "  <Alt>+Left click drag, draw a junction to correct it \n"
-        #self.epicure.overtext["mergesplit"] += "<Alt>+Left click on a suggestion to accept it \n"
-        self.epicure.overtext["labels"] += "  <w> then <Control>+Left click on one cell to another to swap their values \n"
+        self.epicure.overtext["labels"] += ut.print_shortcuts( sl )
         
+        sgroup = self.epicure.shortcuts["Groups"]
         self.epicure.overtext["grouped"] = "---- Group cells ---- \n"
-        self.epicure.overtext["grouped"] += "  Shift+left click to add a cell to the current group \n"
-        self.epicure.overtext["grouped"] = self.epicure.overtext["grouped"] + "  Shift+right click to remove the cell from its group \n"
-        #self.epicure.overtext["checked"] = self.epicure.overtext["checkmap"] + "<c> to show/hide checkmap \n"
+        self.epicure.overtext["grouped"] += ut.print_shortcuts( sgroup )
         
+        sseed = self.epicure.shortcuts["Seeds"]
         self.epicure.overtext["seed"] = "---- Seed options --- \n"
-        self.epicure.overtext["seed"] += "  <e> then left-click to place a seed \n"
-        #self.epicure.overtext["seed"] = self.epicure.overtext["seed"] +  "\n"
+        self.epicure.overtext["seed"] += ut.print_shortcuts( sseed )
         
+
         @self.epicure.seglayer.mouse_drag_callbacks.append
         def set_checked(layer, event):
             if event.type == "mouse_press":
-                if (len(event.modifiers)==1) and ('Shift' in event.modifiers):
-                    if event.button == 1:
-                        if self.epicure.verbose > 0:
-                            print("Mark cell in group "+self.group_group.text())
-                        self.add_cell_to_group(event)
+                if (event.button == 1) and (len(event.modifiers) == 0):
+                    if layer.mode == "paint":
+                        ### Overwrite the painting to check that everything stays within EpiCure constraints
+                        if self.shapelayer_name not in self.viewer.layers:
+                            self.create_shapelayer()
+                        shape_lay = self.viewer.layers[self.shapelayer_name]
+                        shape_lay.mode = "add_path"
+                        shape_lay.visible = True
+                        @thread_worker
+                        def refresh_image():                       
+                            shape_lay.refresh()
+                            return
+                        pos = np.array( [event.position] )
+                        yield
+                        ## record all the successives position of the mouse while clicked
+                        iter = 0
+                        while (event.type == 'mouse_move'): # and (len(pos)<200):
+                            pos = np.vstack( (pos, np.array(event.position)) )
+                            if iter == 5:
+                                shape_lay.data = pos
+                                shape_lay.shape_type = "path"
+                                refresh_image()
+                                #shape_lay.refresh()
+                                iter = 0
+                            iter = iter + 1
+                            yield
+                        pos = np.vstack( (pos, np.array(event.position)) )    
+                        tframe = int( pos[0][0] )
+                        ## painting a new or extending a cell
+                        new_label = layer.selected_label
+                        hascell = None
+                        if new_label > 0:
+                            hascell = self.epicure.has_label( new_label )
+                        ## paint the selected pixels following EpiCure constraints
+                        self.epicure_paint( pos, new_label, tframe, hascell )
+                        shape_lay.data = []
+                        shape_lay.refresh()
+                        shape_lay.visible = False
 
-                    if event.button == 2:
-                        if self.epicure.verbose > 0:
-                            print("Remove cell from its group")
-                        self.remove_cell_group(event)
+        @self.epicure.seglayer.mouse_drag_callbacks.append
+        def set_checked(layer, event):
+            if event.type == "mouse_press":
+                if ut.shortcut_click_match( sgroup["add group"], event ):
+                    if self.group_choice.currentText() == "":
+                        ut.show_warning("Write a group name before")
+                        return
+                    if self.epicure.verbose > 0:
+                        print("Mark cell in group "+self.group_choice.currentText())
+                    self.add_cell_to_group(event)
+                    return
+                
+                if ut.shortcut_click_match( sgroup["remove group"], event ):
+                    if self.epicure.verbose > 0:
+                        print("Remove cell from its group")
+                    self.remove_cell_group(event)
+                    return
 
         @self.epicure.seglayer.bind_key("Control-z", overwrite=False)
         def undo_operations(seglayer):
@@ -295,20 +475,20 @@ class Editing(QWidget):
             self.epicure.seglayer.undo()
             self.epicure.update_changed_labels_img( img_before, self.epicure.seglayer.data )
 
-        @self.epicure.seglayer.bind_key('n', overwrite=True)
+        @self.epicure.seglayer.bind_key( sl["unused paint"]["key"], overwrite=True )
         def set_nextlabel(layer):
             lab = self.epicure.get_free_label()
             ut.show_info( "Unused label "+": "+str(lab) )
             ut.set_label(layer, lab)
         
-        @self.epicure.seglayer.bind_key('Shift-n', overwrite=True)
+        @self.epicure.seglayer.bind_key( sl["unused fill"]["key"], overwrite=True )
         def set_nextlabel_paint(layer):
             lab = self.epicure.get_free_label()
             ut.show_info( "Unused label "+": "+str(lab) )
             ut.set_label(layer, lab)
             layer.mode = "FILL"
-    
-        @self.epicure.seglayer.bind_key('w', overwrite=True)
+        
+        @self.epicure.seglayer.bind_key( sl["swap mode"]["key"], overwrite=True )
         def key_swap(layer):
             """ Active key bindings for label swapping options """
             ut.show_info("Begin swap mode: Control and click to swap two labels")
@@ -342,9 +522,9 @@ class Editing(QWidget):
                 ut.reactive_bindings( self.epicure.seglayer, self.old_mouse_drag, self.old_key_map )
                 ut.show_info("End swap")
 
-        @self.epicure.seglayer.bind_key('e', overwrite=True)
+        @self.epicure.seglayer.bind_key( sseed["new seed"]["key"], overwrite=True )
         def place_seed(layer):
-            """ Add a seed if left click after pressing <e> """
+            """ Add a seed if left click after pressing the shortcut """
             
             ## desactivate other click-binding
             self.old_mouse_drag = self.epicure.seglayer.mouse_drag_callbacks.copy()
@@ -362,22 +542,28 @@ class Editing(QWidget):
                 else:
                     self.end_place_seed()
 
+        @self.epicure.seglayer.bind_key( sl["draw junction mode"]["key"], overwrite=True )
+        def manual_junction(layer):
+            """ Launch the manual drawing junction mode """
+            self.drawing_junction_mode()
 
         @self.epicure.seglayer.mouse_drag_callbacks.append
         def click(layer, event):
             if event.type == "mouse_press":
-                if len(event.modifiers) == 0:
-                    if event.button == 2:
-                        # single right-click: erase the cell
-                        tframe = ut.current_frame(self.viewer)
-
-                        ## erase the cell and get its value
-                        erased = ut.setLabelValue(self.epicure.seglayer, self.epicure.seglayer, event, 0, tframe, tframe)
-                        if erased is not None:
-                            self.epicure.delete_track(erased, tframe)
+                ## erase cell option
+                if ut.shortcut_click_match( sl["erase"], event ):
+                    # single right-click: erase the cell
+                    tframe = ut.current_frame(self.viewer)
+                    erased = ut.setLabelValue(self.epicure.seglayer, self.epicure.seglayer, event, 0, tframe, tframe)
+                    ## delete also in track data
+                    if erased is not None:
+                        self.epicure.delete_track( erased, tframe )
+                    return
                         
-                if (len(event.modifiers)==1) and ('Control' in event.modifiers):
-                    # on move
+                merging = ut.shortcut_click_match( sl["merge"], event )
+                splitting = ut.shortcut_click_match( sl["split accross"], event )
+                if merging or splitting:
+                    # get the start and last labels
                     start_label = self.epicure.seglayer.get_value(position=event.position, view_direction = event.view_direction, dims_displayed=event.dims_displayed, world=True)
                     start_pos = event.position
                     yield
@@ -392,15 +578,16 @@ class Editing(QWidget):
                             print("One position is not a cell, do nothing")
                         return
 
-                    if event.button == 1:
-                        # Control left-click: merge labels at each end of the click
+                    if merging:
+                        ## Merge labels at each end of the click
                         if start_label != end_label:
                             if self.epicure.verbose > 0:
                                 print("Merge cell "+str(start_label)+" with "+str(end_label))
                             self.merge_labels(tframe, start_label, end_label)
+                            return
                     
-                    if event.button == 2:
-                        # Control right-click: split label at each end of the click
+                    if splitting:
+                        ## split label at each end of the click
                         if start_label == end_label:
                             if self.epicure.verbose > 0:
                                 print("Split cell "+str(start_label))
@@ -408,8 +595,11 @@ class Editing(QWidget):
                         else:
                             if self.epicure.verbose > 0:
                                 print("Not the same cell already, do nothing")
-                
-                if (len(event.modifiers)==1) and ('Alt' in event.modifiers):
+                    return
+
+                drawing_split = ut.shortcut_click_match( sl["split draw"], event )
+                redrawing = ut.shortcut_click_match( sl["redraw junction"], event )
+                if drawing_split or redrawing:
                     if self.shapelayer_name not in self.viewer.layers:
                         self.create_shapelayer()
                     shape_lay = self.viewer.layers[self.shapelayer_name]
@@ -430,22 +620,69 @@ class Editing(QWidget):
                     shape_lay.refresh()
                     ut.set_active_layer(self.viewer, "Segmentation")
                     tframe = int(event.position[0])
-                    if event.button == 1:
-                        # ALT leftt-click: modify junction along the drawn line
+                    if redrawing:
+                        ##  modify junction along the drawn line
                         if self.epicure.verbose > 0:
                             print("Correct junction with the drawn line ")
                         self.redraw_along_line(tframe, pos)
                         shape_lay.data = []
                         shape_lay.refresh()
                         shape_lay.visible = False
-                    if event.button == 2:
-                        # ALT right-click: split labels along the drawn line
+                        return
+                    if drawing_split:
+                        ## split labels along the drawn line
                         if self.epicure.verbose > 0:
                             print("Split cell along the drawn line ")
                         self.split_along_line(tframe, pos)
                         shape_lay.data = []
                         shape_lay.refresh()
                         shape_lay.visible = False
+                        return
+        
+    def drawing_junction_mode( self ):
+        """ Active mouse bindings for manually drawing the junction, and try to fill defined area """
+            
+        sl = self.epicure.shortcuts["Labels"]
+        ut.show_info("Begin drawing junction: Control-Left-click to draw the junction and create new cell(s) from it")
+        self.old_mouse_drag, self.old_key_map = ut.clear_bindings( self.epicure.seglayer )
+        
+        @self.epicure.seglayer.bind_key( sl["draw junction mode"]["key"], overwrite=True )
+        def stop_draw_junction_mode( layer ):
+            ut.reactive_bindings( self.epicure.seglayer, self.old_mouse_drag, self.old_key_map )
+            ut.show_info("End drawing mode")
+        
+        @self.epicure.seglayer.mouse_drag_callbacks.append
+        def click(layer, event):
+            if ut.shortcut_click_match( sl["drawing junction"], event ):
+                shape_lay = self.viewer.layers[self.shapelayer_name]
+                shape_lay.mode = "add_path"
+                shape_lay.visible = True
+                pos = [event.position]
+                yield
+                ## record all the successives position of the mouse while clicked
+                i = 0
+                while event.type == 'mouse_move':
+                    pos.append( event.position )
+                    if i%5 == 0:
+                        # refresh display every n steps
+                        shape_lay.data = np.array( pos ) 
+                        shape_lay.shape_type = "path"
+                        shape_lay.refresh()
+                    i = i + 1
+                    yield
+                pos.append(event.position)
+                shape_lay.data = np.array(pos)
+                shape_lay.shape_type = "path"
+                shape_lay.refresh()
+                ut.set_active_layer(self.viewer, "Segmentation")
+                tframe = int(event.position[0])
+                self.create_cell_from_line( tframe, pos )        
+                shape_lay.data = []
+                shape_lay.refresh()
+                shape_lay.visible = False
+                ut.reactive_bindings( self.epicure.seglayer, self.old_mouse_drag, self.old_key_map )
+                ut.show_info("End drawing mode")
+
 
     def split_label(self, tframe, startlab, start_pos, end_pos):
         """ Split the label in two cells based on the two seeds """
@@ -737,13 +974,36 @@ class Editing(QWidget):
         """ Remove all cells that touch the border """
         start_time = time.time()
         self.viewer.window._status_bar._toggle_activity_dock(True)
-        for i in progress(range(0, self.epicure.nframes)):
-            self.remove_pixel_border( np.copy(self.epicure.seglayer.data[i]), i)
+        size = int(self.border_size.text())
+        if size == 0:
+            for i in progress(range(0, self.epicure.nframes)):
+                img = np.copy( self.epicure.seglayer.data[i] )
+                resimg = clear_border( img )
+                self.epicure.seglayer.data[i] = resimg
+                self.epicure.removed_labels( img, resimg, i )
+        else:
+            maxx = self.epicure.imgshape2D[0] - size - 1
+            maxy = self.epicure.imgshape2D[1] - size - 1
+            for i in progress(range(0, self.epicure.nframes)):
+                frame = self.epicure.seglayer.data[i]
+                img = np.copy( frame ) 
+                crop_img = img[ size:maxx, size:maxy ]
+                crop_img = clear_border( crop_img )
+                frame[0:size, :] = 0
+                frame[:, 0:size] = 0
+                frame[maxx:, :] = 0
+                frame[:, maxy:] = 0
+                frame[size:maxx, size:maxy] = crop_img
+                ## update the tracks after the potential disappearance of some cells
+                self.epicure.removed_labels( img, frame, i )
+        
         self.viewer.window._status_bar._toggle_activity_dock(False)
         self.epicure.seglayer.refresh()
         if self.epicure.verbose > 0:
             ut.show_duration( start_time, "Border cells removed in ")
 
+               
+
     def remove_smalls( self ):
         """ Remove all cells smaller than given area (in nb pixels) """
         start_time = time.time()
@@ -799,30 +1059,14 @@ class Editing(QWidget):
                     self.merge_labels( frame, label, nlabel, 1.05 )
                     if self.epicure.verbose > 0:
                         print( "Merged label "+str(label)+" into label "+str(nlabel)+" at frame "+str(frame) )
-               
-            
-
-    def remove_pixel_border(self, img, frame):
-        """ Remove if few pixels wide along border (cellpose) """
-        size = int(self.border_size.text())
-        if size == 0:
-            resimg = clear_border(img)
-        else:
-            crop_img = img[size:(img.shape[0]-size-1), size:(img.shape[1]-size-1)]
-            crop_img = clear_border( crop_img )
-            resimg = np.zeros(img.shape)
-            resimg[size:(resimg.shape[0]-size-1), size:(resimg.shape[1]-size-1)] = crop_img
-        ## update the tracks after the potential disappearance of some cells
-        self.epicure.seglayer.data[frame] = resimg
-        self.epicure.removed_labels( img, resimg, frame )
-
 
     ###############
     ## Shapes functions
-    def create_shapelayer(self):
+    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, blending="additive", opacity=1, edge_width=2 )
+        shap.text.visible = False
         shap.visible = False
 
     ######################################"
@@ -832,48 +1076,31 @@ class Editing(QWidget):
         if not self.gSeed.isVisible():
             ut.remove_layer(self.viewer, "Seeds")
     
-    def show_seedMapBlock(self):
-        """ Show the seeds """
-        self.gSeed.setVisible(True)
-        self.seed_vis.setChecked(True)
-
     def create_seedsBlock(self):
-        self.gSeed = QGroupBox("Seeds")
         seed_layout = QVBoxLayout()
-        seed_createbtn = QPushButton("Create seeds layer", parent=self)
-        seed_createbtn.clicked.connect(self.reset_seeds)
+        reset_color = self.epicure.get_resetbtn_color()
+        seed_createbtn = wid.add_button( btn="Create seeds layer", btn_func=self.reset_seeds, descr="Create/reset the layer to add seeds", color=reset_color )
         seed_layout.addWidget(seed_createbtn)
-        seed_loadbtn = QPushButton("Load seeds from previous time point", parent=self)
-        seed_loadbtn.clicked.connect(self.get_seeds_from_prev)
+        seed_loadbtn = wid.add_button( btn="Load seeds from previous time point", btn_func=self.get_seeds_from_prev, descr="Place seeds in background area where cells are in previous time point" )
         seed_layout.addWidget(seed_loadbtn)
         
         ## choose method and segment from seeds
         gseg = QGroupBox("Seed based segmentation")
         gseg_layout = QVBoxLayout()
-        seed_btn = QPushButton("Segment cells from seeds", parent=self)
-        seed_btn.clicked.connect(self.segment_from_points)
+        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)
-        self.seed_method = QComboBox()
+        method_line, self.seed_method = wid.list_line( label="Method", descr="Seed based segmentation method to segment some cells" )
         self.seed_method.addItem("Intensity-based (watershed)")
         self.seed_method.addItem("Distance-based")
         self.seed_method.addItem("Diffusion-based")
-        gseg_layout.addWidget(self.seed_method)
-        maxdist = QHBoxLayout()
-        maxdist_lab = QLabel()
-        maxdist_lab.setText("Max cell radius")
-        maxdist.addWidget(maxdist_lab)
-        self.max_distance = QLineEdit()
-        self.max_distance.setText("100.0")
-        maxdist.addWidget(self.max_distance)
+        gseg_layout.addLayout( method_line )
+        maxdist, self.max_distance = wid.value_line( label="Max cell radius", default_value="100.0", descr="Max cell radius allowed in new cell creation" )
         gseg_layout.addLayout(maxdist)
         gseg.setLayout(gseg_layout)
         
         seed_layout.addWidget(gseg)
         self.gSeed.setLayout(seed_layout)
 
-    def help_seeds(self):
-        ut.show_documentation_page("Seeds")
-
     def create_seedlayer(self):
         pts = []
         points = self.viewer.add_points( np.array(pts), face_color="blue", size = 7,  edge_width=0, name="Seeds" )
@@ -986,7 +1213,8 @@ class Editing(QWidget):
         splitted = label(splitted)
         new_labels = np.unique(markers)
         i = 0
-        for lab in set(splitted.flatten()):
+        lablist = set( splitted.flatten() )
+        for lab in lablist:
             if lab > 0:
                 splitted[splitted==lab] = new_labels[i]
                 i = i + 1
@@ -1019,49 +1247,30 @@ class Editing(QWidget):
 
     def create_cleaningBlock(self):
         """ GUI for cleaning segmentation """
-        self.gCleaned = QGroupBox("Cleaning")
         clean_layout = QVBoxLayout()
         ## cells on border
-        border_line = QHBoxLayout()
-        border_btn = QPushButton("Remove border cells (width pixels)", parent=self)
-        border_btn.clicked.connect(self.remove_border)
-        border_line.addWidget(border_btn)
-        self.border_size = QLineEdit()
-        self.border_size.setText("1")
-        border_line.addWidget(self.border_size)
+        border_line, self.border_size = wid.button_parameter_line( btn="Remove border cells", btn_func=self.remove_border, value="1", descr_btn="Remove all cell at a distance <= value (in pixels)", descr_value="Distance of the cells to be removed (in pixels)" )
         clean_layout.addLayout(border_line)
         
         ## too small cells
-        small_line = QHBoxLayout()
-        small_btn = QPushButton("Remove mini cells (area pixels)", parent=self)
-        small_btn.clicked.connect(self.remove_smalls)
-        small_line.addWidget(small_btn)
-        self.small_size = QLineEdit()
-        self.small_size.setText("4")
-        small_line.addWidget(self.small_size)
+        small_line, self.small_size = wid.button_parameter_line( btn="Remove mini cells", btn_func=self.remove_smalls, value="4", descr_btn="Remove all cells smaller than given value (in pixels^2)", descr_value="Minimal cell area (in pixels^2)" )
         clean_layout.addLayout(small_line)
 
         ## Cell inside another cell
-        inside_btn = QPushButton("Cell inside another: merge", parent=self)
-        inside_btn.clicked.connect(self.merge_inside_cells)
+        inside_btn = wid.add_button( btn="Cell inside another: merge", btn_func=self.merge_inside_cells, descr="Merge all small cells fully contained inside another cell to this cell" )
         clean_layout.addWidget(inside_btn)
 
         ## sanity check
-        sanity_btn = QPushButton("Sanity check", parent=self)
-        sanity_btn.clicked.connect(self.sanity_check)
+        sanity_btn = wid.add_button( btn="Sanity check", btn_func=self.sanity_check, descr="Check that labels and tracks are consistent with EpiCure restrictions, and try to fix some errors" )
         clean_layout.addWidget(sanity_btn)
 
         ## reset labels
-        reset_btn = QPushButton("Reset all", parent=self)
-        reset_btn.clicked.connect(self.reset_all)
+        reset_color = self.epicure.get_resetbtn_color()
+        reset_btn = wid.add_button( btn="Reset all", btn_func=self.reset_all, descr="Reset all tracks, groups, suspects..", color=reset_color )
         clean_layout.addWidget(reset_btn)
 
         self.gCleaned.setLayout(clean_layout)
 
-    def show_cleaningBlock(self):
-        """ Show/hide cleaning interface """
-        self.gCleaned.setVisible(not self.gCleaned.isVisible())
-
     ####################################
     ## Sanity check/correction options
     def sanity_check(self):
@@ -1082,9 +1291,10 @@ class Editing(QWidget):
         self.check_unique_labels( label_list, progress_bar )
         ## check and update if necessary tracks 
         progress_bar.update(2)
-        progress_bar.set_description("Sanity check: track gaps")
-        ut.show_info("Check if some tracks contain gaps")
-        gaped = self.epicure.handle_gaps( track_list=None )
+        if self.epicure.forbid_gaps:
+            progress_bar.set_description("Sanity check: track gaps")
+            ut.show_info("Check if some tracks contain gaps")
+            gaped = self.epicure.handle_gaps( track_list=None )
         ## check that labels and tracks correspond
         progress_bar.set_description("Sanity check: label-track")
         progress_bar.update(3)
@@ -1171,35 +1381,20 @@ class Editing(QWidget):
 
     ######################################
     ## Selection options
-    def help_selection(self):
-        ut.show_documentation_page("Selection options")
 
     def create_selectBlock(self):
         """ GUI for handling selection with shapes """
-        self.gSelect = QGroupBox("Selection options")
         select_layout = QVBoxLayout()
         ## create/select the ROI
-        draw_btn = QPushButton("Draw/Select ROI", parent=self)
-        draw_btn.clicked.connect(self.draw_shape)
+        draw_btn = wid.add_button( btn="Draw/Select ROI", btn_func=self.draw_shape, descr="Draw or select a ROI to apply region action on" )
         select_layout.addWidget(draw_btn)
-        remove_sel_btn = QPushButton("Remove cells inside ROI", parent=self)
-        remove_sel_btn.clicked.connect(self.remove_cells_inside)
+        remove_sel_btn = wid.add_button( btn="Remove cells inside ROI", btn_func=self.remove_cells_inside, descr="Remove all cells inside the selected/first ROI" )
         select_layout.addWidget(remove_sel_btn)
-        remove_line = QHBoxLayout()
-        removeout_sel_btn = QPushButton("Remove cells outside ROI", parent=self)
-        removeout_sel_btn.clicked.connect(self.remove_cells_outside)
-        remove_line.addWidget(removeout_sel_btn)
-        self.keep_new_cells = QCheckBox(text="Keep new cells")
-        self.keep_new_cells.setChecked(True)
-        remove_line.addWidget(self.keep_new_cells)
+        remove_line, self.keep_new_cells = wid.button_check_line( btn="Remove cells outside ROI", btn_func=self.remove_cells_outside, check="Keep new cells", checked=True, checkfunc=None, descr_btn="Remove all cells outside the current ROI", descr_check="Keep new cells tah appear in the ROI in later frames" )
         select_layout.addLayout(remove_line)
 
         self.gSelect.setLayout(select_layout)
 
-    def show_selectBlock(self):
-        """ Show/hide select options block """
-        self.gSelect.setVisible(not self.gSelect.isVisible())
-
     def draw_shape(self):
         """ Draw/select a shape in the Shapes layer """
         if self.shapelayer_name not in self.viewer.layers:
@@ -1277,15 +1472,17 @@ class Editing(QWidget):
 
     def group_cells_inside(self):
         """ Put all cells inside the selected ROI into current group """
+        if self.group_choice.currentText() == "":
+            ut.show_warning("Write a group name before")
+            return
         tocheck = self.get_labels_inside()
         if tocheck is None:
             if self.epicure.verbose > 0:
                 print("No cell to add to group")
             return
-        for lab in tocheck:
-            self.group_label(lab)
+        self.group_labels( tocheck )
         if self.epicure.verbose > 0:
-            print(str(len(tocheck))+" cells assigend to group "+str(self.group_group.text()))
+            print(str(len(tocheck))+" cells assigend to group "+str(self.group_choice.currentText()))
         lay = self.viewer.layers[self.shapelayer_name]
         lay.remove_selected()
         self.epicure.finish_update()
@@ -1293,49 +1490,22 @@ class Editing(QWidget):
 
     ######################################
     ## Group cells functions
-    def show_groupCellsBlock(self):
-        self.gGroup.setVisible(not self.gGroup.isVisible())
-
     def create_groupCellsBlock(self):
-        self.gGroup = QGroupBox("Group cells")
+        """ Create subpanel of Cell group options """
         group_layout = QVBoxLayout()
-        groupgr = QHBoxLayout()
-        groupgr_lab = QLabel()
-        groupgr_lab.setText("Group name")
-        groupgr.addWidget(groupgr_lab)
-        self.group_group = QLineEdit()
-        self.group_group.setText("Positive")
-        groupgr.addWidget(self.group_group)
+        groupgr, self.group_choice = wid.list_line( label="Group name", descr="Choose/Set the current group name" )
         group_layout.addLayout(groupgr)
+        self.group_choice.setEditable(True)
 
-        self.group_show = QCheckBox(text="Show groups")
-        self.group_show.stateChanged.connect(self.see_groups)
-        self.group_show.setChecked(False)
+        self.group_show = wid.add_check( check="Show groups", checked=False, check_func=self.see_groups, descr="Add a layer with the cells colored by group" )
         group_layout.addWidget(self.group_show)
 
-        #group_loadbtn = QPushButton("Load groups", parent=self)
-        #group_loadbtn.clicked.connect(self.load_groups)
-        #group_layout.addWidget(group_loadbtn)
-        #group_savebtn = QPushButton("Save groups", parent=self)
-        #group_savebtn.clicked.connect(self.save_group)
-        #group_layout.addWidget(group_savebtn)
-        group_resetbtn = QPushButton("Reset groups", parent=self)
-        group_resetbtn.clicked.connect(self.reset_group)
+        group_resetbtn = wid.add_button( btn="Reset groups", btn_func=self.reset_group, descr="Remove all groups and cell assignation to groups" )
         group_layout.addWidget(group_resetbtn)
-        #self.lock_checked = QCheckBox("Lock checked cells")
-        #self.lock_checked.setChecked(True)
-        #check_layout.addWidget(self.lock_checked)
-        group_sel_btn = QPushButton("Cells inside ROI to group", parent=self)
-        group_sel_btn.clicked.connect(self.group_cells_inside)
+        group_sel_btn = wid.add_button( btn="Cells inside ROI to group", btn_func=self.group_cells_inside, descr="Add all cells inside ROI to the current group" )
         group_layout.addWidget(group_sel_btn)
         self.gGroup.setLayout(group_layout)
 
-    def help_group(self):
-        ut.show_documentation_page("Edit#group-options")
-    
-    def help_clean(self):
-        ut.show_documentation_page("Edit#cleaning-options")
-
     def load_checked(self):
         cfile = self.get_filename("_checked.txt")
         with open(cfile) as infile:
@@ -1351,6 +1521,7 @@ class Editing(QWidget):
             grouped.data = np.zeros(grouped.data.shape, np.uint8)
             grouped.refresh()
             ut.set_active_layer(self.viewer, "Segmentation")
+        self.group_choice.clear()
 
     def save_groups(self):
         groupfile = self.get_filename("_groups.txt")
@@ -1358,7 +1529,6 @@ class Editing(QWidget):
             out.write(";".join(group.write_group() for group in self.epicure.groups))
         ut.show_info("Cell groups saved in "+groupfile)
 
-
     def see_groups(self):
         if self.group_show.isChecked():
             ut.remove_layer(self.viewer, self.grouplayer_name)
@@ -1370,21 +1540,30 @@ class Editing(QWidget):
             ut.remove_layer(self.viewer, self.grouplayer_name)
             ut.set_active_layer(self.viewer, "Segmentation")
     
-    def group_label(self, label):
-        """ Add label to group """
-        group = self.group_group.text()
-        self.group_ingroup(label, group)
+    def group_labels( self, labels ):
+        """ Add label(s) to group """
+        if self.group_choice.currentText() == "":
+            ut.show_warning("Write group name before")
+            return
+        group = self.group_choice.currentText()
+        self.group_ingroup( labels, group )
        
     def check_label(self, label):
         """ Mark label as checked """
         group = self.check_group.text()
         self.check_ingroup(label, group)
+
+    def update_group_list( self, group ):
+        """ Check if group has been added in the list choices of group """
+        if self.group_choice.findText( group ) < 0:
+            ## not added yet. If user is typing the name and did not press enter, it can be still in edition mode, so not added
+            self.group_choice.addItem( group )
         
-    def group_ingroup(self, label, group):
+    def group_ingroup(self, labels, group):
         """ Add the given label to chosen group """
-        self.epicure.cell_ingroup( label, group )
+        self.epicure.cells_ingroup( labels, group )
         if self.grouplayer_name in self.viewer.layers:
-            self.redraw_label_group( label, group )
+            self.redraw_label_group( labels, group )
        
     def check_load_label(self, labelstr):
         """ Read the label to check from file """
@@ -1396,23 +1575,23 @@ class Editing(QWidget):
     def add_cell_to_group(self, event):
         """ Add cell under click to the current group """
         label = ut.getCellValue( self.epicure.seglayer, event ) 
-        self.group_label(label)
+        self.group_labels( [label] )
 
     def remove_cell_group(self, event):
         """ Remove the cell from the group it's in if any """
         label = ut.getCellValue( self.epicure.seglayer, event ) 
         self.epicure.cell_removegroup( label )
         if self.grouplayer_name in self.viewer.layers:
-            self.redraw_label_group( label, 0 )
+            self.redraw_label_group( [label], 0 )
 
-    def redraw_label_group(self, label, group):
+    def redraw_label_group(self, labels, group):
         """ Update the Group layer for label """
         lay = self.viewer.layers[self.grouplayer_name]
         if group == 0:
-            lay.data[self.epicure.seg==label] = 0
+            lay.data[ np.isin( self.epicure.seg, labels ) ] = 0
         else:
             igroup = self.epicure.get_group_index(group) + 1
-            lay.data[self.epicure.seg==label] = igroup
+            lay.data[ np.isin( self.epicure.seg, labels)  ] = igroup
         lay.refresh()
 
     ######### overlay message
@@ -1424,27 +1603,74 @@ class Editing(QWidget):
         ut.setOverlayText(self.viewer, text, size=10)
 
     ################## Track editing functions
+    def add_division( self, labela, labelb, frame ):
+        """ Add a division event, given the labels of the two daughter cells """
+        if frame == 0:
+            if self.epicure.verbose > 0:
+                print("Cannot define a division before the first frame")
+            return
 
+        if (frame != self.epicure.tracking.get_first_frame( labela )) or (frame != self.epicure.tracking.get_first_frame(labelb) ):
+            if self.epicure.verbose > 0:
+                print("One daughter track is not starting at current frame, don't add division")
+                return
+
+        ## merge the two labels to find their parent
+        bbox, merge = ut.getBBox2DMerge( self.epicure.seglayer.data[frame], labela, labelb )
+        twoframes = ut.crop_twoframes( self.epicure.seglayer.data, bbox, frame )
+        crop_merge = ut.cropBBox2D( merge, bbox )
+        twoframes[1] = crop_merge # merge of the labels and 0 outside
+            
+        ## keep only parent labels that stop at the previous frame
+        orig_frame = ut.cropBBox2D(self.epicure.seglayer.data[frame], bbox)
+        ut.keep_orphans(twoframes, orig_frame, [])
+        ## do mini-tracking to assign most likely parent
+        parent = self.get_parents( twoframes, [1] )
+        if self.epicure.verbose > 0:
+            print( "Found parent "+str(parent[0])+" to clicked cells "+str(labela)+" and "+str(labelb) )
+        ## add division to graph
+        self.epicure.tracking.add_division( labela, labelb, parent[0] )
+        ## add division to event list (if active)
+        self.epicure.inspecting.add_division( labela, labelb, parent[0], frame )
+            
     def key_tracking_binding(self):
         """ active key bindings for tracking options """
         self.epicure.overtext["trackedit"] = "---- Track editing ---- \n"
-        self.epicure.overtext["trackedit"] += "  <r> to show/hide the tracks \n"
-        self.epicure.overtext["trackedit"] += "  <t> for tracks editing mode \n"
-        self.epicure.overtext["trackedit"] += "  <t>, <t> end tracks editing mode \n"
-        self.epicure.overtext["trackedit"] += "  <t>, (Left-Right) clicks to merge two tracks (temporally or spatially) \n"
-        self.epicure.overtext["trackedit"] += "  <t>, <Control>+Left clicks manually do a new track \n(<Control>+Right click to end it) \n"
-        self.epicure.overtext["trackedit"] += "  <t>, <Shift>+Right clicks split the track temporally \n"
-        self.epicure.overtext["trackedit"] += "  <t>, <Shift>+Left drag-click swap 2 tracks from current frame \n"
-        self.epicure.overtext["trackedit"] += "  <t>, <Alt>+(Left-Right) clicks to interpolate labels temporally \n"
-        self.epicure.overtext["trackedit"] += "  <t>, Double-Right click to delete all the track from current frame \n"
+        strack = self.epicure.shortcuts["Tracks"]
+        self.epicure.overtext["trackedit"] += ut.print_shortcuts( strack )
         
-        @self.epicure.seglayer.bind_key('r', overwrite=True)
+        @self.epicure.seglayer.mouse_drag_callbacks.append
+        def manual_add_division(layer, event):
+            ### add an event of a division, selecting the two daughter cells
+            if ut.shortcut_click_match( strack["add division"], event ):
+                # get the start and last labels
+                labela = self.epicure.seglayer.get_value(position=event.position, view_direction = event.view_direction, dims_displayed=event.dims_displayed, world=True)
+                start_pos = event.position
+                yield
+                while event.type == 'mouse_move':
+                    yield
+                labelb = self.epicure.seglayer.get_value(position=event.position, view_direction = event.view_direction, dims_displayed=event.dims_displayed, world=True)
+                end_pos = event.position
+                tframe = int(event.position[0])
+                    
+                if labela == 0 or labelb == 0:
+                    if self.epicure.verbose > 0:
+                        print("One position is not a cell, do nothing")
+                    return
+                self.add_division( labela, labelb, tframe )
+        
+        @self.epicure.seglayer.bind_key( strack["lineage color"]["key"], overwrite=True )
+        def color_tracks_lineage(seglayer):
+            if self.tracklayer_name in self.viewer.layers:
+                self.epicure.tracking.color_tracks_by_lineage()
+        
+        @self.epicure.seglayer.bind_key( strack["show"]["key"], overwrite=True )
         def see_tracks(seglayer):
             if self.tracklayer_name in self.viewer.layers:
                 tlayer = self.viewer.layers[self.tracklayer_name]
                 tlayer.visible = not tlayer.visible
 
-        @self.epicure.seglayer.bind_key('t', overwrite=True)
+        @self.epicure.seglayer.bind_key( strack["mode"]["key"], overwrite=True)
         def edit_track(layer):
             self.label_tr = None 
             self.start_label = None
@@ -1458,15 +1684,15 @@ class Editing(QWidget):
                 """ Edit tracking """
                 if event.type == "mouse_press":
                   
-                    if len(event.modifiers)== 0 and event.button == 1:
-                        """ Merge two tracks, spatially or temporally: left click, select the first label """
+                    """ Merge two tracks, spatially or temporally: left click, select the first label """
+                    if ut.shortcut_click_match( strack["merge first"], event ):
                         self.start_label = self.epicure.seglayer.get_value(position=event.position, view_direction = event.view_direction, dims_displayed=event.dims_displayed, world=True)
                         self.start_pos = event.position
                         # move one frame after for next cell to link
                         #ut.set_frame( self.epicure.viewer, event.position[0]+1 )
                         return
-                    if len(event.modifiers)== 0 and event.button == 2:
-                        """ Merge two tracks, spatially or temporally: right click, select the second label """
+                    """ Merge two tracks, spatially or temporally: right click, select the second label """
+                    if ut.shortcut_click_match( strack["merge second"], event ):
                         if self.start_label is None:
                             if self.epicure.verbose > 0:
                                 print("No left click done before right click, don't merge anything")
@@ -1485,95 +1711,90 @@ class Editing(QWidget):
                         self.end_track_edit()
                         return
 
-                    if (len(event.modifiers) == 1) and ("Shift" in event.modifiers):
-                        if event.button == 2:
-                            ### Split the track in 2: new label for the next frames 
-                            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.end_track_edit()
-                            return
+                    ### Split the track in 2: new label for the next frames 
+                    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.end_track_edit()
+                        return
                         
-                        if event.button == 1:
-                            ### Swap the two track from the current frame 
-                            start_frame = int(event.position[0])
-                            label = ut.getCellValue(self.epicure.seglayer, event) 
+                    ### Swap the two track from the current frame 
+                    if ut.shortcut_click_match( strack["swap"], event ):
+                        start_frame = int(event.position[0])
+                        label = ut.getCellValue(self.epicure.seglayer, event) 
+                        yield
+                        while event.type == 'mouse_move':
                             yield
-                            while event.type == 'mouse_move':
-                                yield
-                            end_label = self.epicure.seglayer.get_value(position=event.position, view_direction = event.view_direction, dims_displayed=event.dims_displayed, world=True)                           
-                            
-                            if label == 0 or end_label == 0:
-                                if self.epicure.verbose > 0:
-                                    print("One position is not a cell, do nothing")
-                                return
-
-                            self.epicure.swap_tracks( label, end_label, start_frame )
+                        end_label = self.epicure.seglayer.get_value(position=event.position, view_direction = event.view_direction, dims_displayed=event.dims_displayed, world=True)                           
                             
+                        if label == 0 or end_label == 0:
                             if self.epicure.verbose > 0:
-                                ut.show_info("Swapped track "+str(label)+" with track "+str(end_label)+" from frame "+str(start_frame))
-                            self.end_track_edit()
+                                print("One position is not a cell, do nothing")
                             return
 
-                    if (len(event.modifiers) == 1) and ("Control" in event.modifiers):
-                        if event.button == 1:
-                            ### Manual tracking: get a new label and spread it to clicked cells on next frames
-                            zpos = int(event.position[0])
-                            if self.label_tr is None:
-                                ## first click: get the track label
-                                self.label_tr = ut.getCellValue(self.epicure.seglayer, event) 
-                            else:
-                                old_label = ut.setCellValue(self.epicure.seglayer, self.epicure.seglayer, event, self.label_tr, layer_frame=zpos, label_frame=zpos)
-                                self.epicure.tracking.remove_one_frame( old_label, zpos )
-                                self.epicure.add_label( [self.label_tr], zpos )
-                            ## advance to next frame, ready for a click
-                            self.viewer.dims.set_point(0, zpos+1)
-                            ## if reach the end, stops here for this track
-                            if (zpos+1) >= self.epicure.seglayer.data.shape[0]:
-                                self.end_track_edit()
-                            return
-                        if event.button == 2:
+                        self.epicure.swap_tracks( label, end_label, start_frame )
+                            
+                        if self.epicure.verbose > 0:
+                            ut.show_info("Swapped track "+str(label)+" with track "+str(end_label)+" from frame "+str(start_frame))
+                        self.end_track_edit()
+                        return
+
+                    # Manual tracking: get a new label and spread it to clicked cells on next frames
+                    if ut.shortcut_click_match( strack["start manual"], event ):
+                        zpos = int(event.position[0])
+                        if self.label_tr is None:
+                            ## first click: get the track label
+                            self.label_tr = ut.getCellValue(self.epicure.seglayer, event) 
+                        else:
+                            old_label = ut.setCellValue(self.epicure.seglayer, self.epicure.seglayer, event, self.label_tr, layer_frame=zpos, label_frame=zpos)
+                            self.epicure.tracking.remove_one_frame( old_label, zpos, handle_gaps=self.epicure.forbid_gaps )
+                            self.epicure.add_label( [self.label_tr], zpos )
+                        ## advance to next frame, ready for a click
+                        self.viewer.dims.set_point(0, zpos+1)
+                        ## if reach the end, stops here for this track
+                        if (zpos+1) >= self.epicure.seglayer.data.shape[0]:
                             self.end_track_edit()
-                            return
+                        return
                     
-                    if (len(event.modifiers) == 1) and ("Alt" in event.modifiers):
-                        if event.button == 1:
-                            ## left click, first cell
-                            self.interp_labela = ut.getCellValue(self.epicure.seglayer, event) 
-                            self.interp_framea = int(event.position[0])
-                            return
-                        if event.button == 2:
-                            ## right click, second cell
-                            labelb = ut.getCellValue(self.epicure.seglayer, event) 
-                            interp_frameb = int(event.position[0])
-                            if self.interp_labela is not None:
-                                if abs(self.interp_framea - interp_frameb) <= 1:
-                                    print("No frames to interpolate, exit")
-                                    self.end_track_edit()
-                                    return
-                                if self.interp_framea < interp_frameb:
-                                    self.interpolate_labels(self.interp_labela, self.interp_framea, labelb, interp_frameb)
-                                else:
-                                    self.interpolate_labels(labelb, interp_frameb, self.interp_labela, self.interp_framea )
+                    ## Finish manual tracking
+                    if ut.shortcut_click_match( strack["end manual"], event ):
+                        self.end_track_edit()
+                        return
+                   
+                    ## Interpolate between two labels: get first label
+                    if ut.shortcut_click_match( strack["interpolate first"], event ):
+                        ## left click, first cell
+                        self.interp_labela = ut.getCellValue(self.epicure.seglayer, event) 
+                        self.interp_framea = int(event.position[0])
+                        return
+                    
+                    ## Interpolate between two labels: get second label and interpolate
+                    if ut.shortcut_click_match( strack["interpolate second"], event ):
+                        ## right click, second cell
+                        labelb = ut.getCellValue(self.epicure.seglayer, event) 
+                        interp_frameb = int(event.position[0])
+                        if self.interp_labela is not None:
+                            if abs(self.interp_framea - interp_frameb) <= 1:
+                                print("No frames to interpolate, exit")
                                 self.end_track_edit()
                                 return
+                            if self.interp_framea < interp_frameb:
+                                self.interpolate_labels(self.interp_labela, self.interp_framea, labelb, interp_frameb)
                             else:
-                                print("No cell selected with left click before. Exit mode")
-                                self.end_track_edit()
-                                return
-
-                ## A right click or other click stops it
-                self.end_track_edit()
-
-            @self.epicure.seglayer.mouse_double_click_callbacks.append
-            def double_click(layer, event):
-                """ Edit tracking : double click options """
-                if event.type == "mouse_double_click":      
-                    if len(event.modifiers)== 0 and event.button == 2:
-                        """ Double right click: delete all the track from the current frame """
+                                self.interpolate_labels(labelb, interp_frameb, self.interp_labela, self.interp_framea )
+                            self.end_track_edit()
+                            return
+                        else:
+                            print("No cell selected with left click before. Exit mode")
+                            self.end_track_edit()
+                            return
+                        
+                    ## Delete all the labels of the track until its end
+                    if ut.shortcut_click_match( strack["delete"], event ):
                         tframe = int(event.position[0])
                         label = ut.getCellValue(self.epicure.seglayer, event)
                         if label > 0:
@@ -1582,11 +1803,17 @@ class Editing(QWidget):
                                 print("Track "+str(label)+" deleted from frame "+str(tframe))
                         self.end_track_edit()
                         return
-                    
-                ## A double click with nothing else stop the mode
+
+                ## A right click or other click stops it
                 self.end_track_edit()
+
+            #@self.epicure.seglayer.mouse_double_click_callbacks.append
+            #def double_click(layer, event):
+            #    """ Edit tracking : double click options """
+            #    if event.type == "mouse_double_click":      
+                    
         
-            @self.epicure.seglayer.bind_key('t', overwrite=True)
+            @self.epicure.seglayer.bind_key( strack["mode"]["key"], overwrite=True )
             def end_edit_track(layer):
                 self.end_track_edit()
 
@@ -1798,7 +2025,6 @@ class Editing(QWidget):
         ## go, do the update
         self.epicure.change_labels(indmodif, new_labels)
 
-
     ############# Test
     def interpolate_labels( self, labela, framea, labelb, frameb ):
         """ 
diff --git a/src/epicure/epicuring.py b/src/epicure/epicuring.py
index 106788025840bf314f8fb56c3357e45967b7bf1e..8514c669d5f9823ca8791c4d47aa2e816de48935 100644
--- a/src/epicure/epicuring.py
+++ b/src/epicure/epicuring.py
@@ -15,9 +15,10 @@ import pandas as pand
 import epicure.Utils as ut
 from epicure.editing import Editing
 from epicure.tracking import Tracking
-from epicure.suspecting import Suspecting
+from epicure.inspecting import Inspecting
 from epicure.outputing import Outputing
 from epicure.displaying import Displaying
+from epicure.preferences import Preferences
 
 """
     EpiCure main
@@ -33,14 +34,14 @@ class EpiCure():
             self.viewer = napari.Viewer(show=False)
         self.viewer.title = "Napari - EpiCure"
         self.img = None
-        self.suspecting = None
+        self.inspecting = None
         self.others = None
         self.imgshape2D = None    ## width, height of the image
         self.nframes = None       ## Number of time frames
         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.verbose = 1  ## level of printing messages (None/few, normal, debug mode)
+        
         self.overtext = dict()
         self.help_index = 1   ## current display index of help overlay
         self.blabla = None    ## help window
@@ -50,7 +51,37 @@ class EpiCure():
         self.nparallel = 4      ## number of parallel threads
         self.dtype = np.uint32  ## label type, default 32 but if less labels, reduce it
         self.outputing = None   ## non initialized yet
-
+        
+        self.forbid_gaps = False  ## allow gaps in track or not
+
+        self.pref = Preferences()
+        self.shortcuts = self.pref.get_shortcuts() ## user specific shortcuts
+        self.settings = self.pref.get_settings() ## user specific preferences
+        ## display settings
+        self.display_colors = None  ## settings for changing some display colors
+        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 """
+        ## scalings and unit names
+        self.epi_metadata = {}
+        self.epi_metadata["ScaleXY"] = 1
+        self.epi_metadata["UnitXY"] = "um"
+        self.epi_metadata["ScaleT"] = 1
+        self.epi_metadata["UnitT"] = "min"
+        self.epi_metadata["MainChannel"] = 0
+
+    def get_resetbtn_color( self ):
+        """ Returns the color of Reset buttons if defined """
+        if "Display" in self.settings:
+            if "Colors" in self.settings["Display"]:
+                if "Reset button" in self.settings["Display"]["Colors"]:
+                    return self.settings["Display"]["Colors"]["Reset button"]
+        return None
 
     def set_thickness( self, thick ):
         """ Thickness of junctions (half thickness) """
@@ -59,7 +90,7 @@ class EpiCure():
     def load_movie(self, imgpath):
         """ Load the intensity movie, and get metadata """
         self.imgpath = imgpath
-        self.img, self.scale, nchan = ut.opentif( self.imgpath, verbose=self.verbose>1 )
+        self.img, nchan, self.epi_metadata["ScaleXY"], self.epi_metadata["UnitXY"], self.epi_metadata["ScaleT"], self.epi_metadata["UnitT"] = ut.opentif( self.imgpath, verbose=self.verbose>1 )
         ## transform static image to movie (add temporal dimension)
         if len(self.img.shape) == 2:
             self.img = np.expand_dims(self.img, axis=0)
@@ -91,15 +122,19 @@ class EpiCure():
     def quantiles(self):
         return tuple(np.quantile(self.img, [0.01, 0.9999]))
 
-    def set_scales( self, scalexy, scalet ):
+    def set_scales( self, scalexy, scalet, unitxy, unitt ):
         """ Set the scaling units for outputs """
-        self.scale = scalexy
-        self.scale_time = scalet
-        ut.show_info("Movie scales set to "+str(self.scale)+" (in x,y) and "+str(self.scale_time)+" (in time)")
+        self.epi_metadata["ScaleXY"] = scalexy
+        self.epi_metadata["ScaleT"] = scalet
+        self.epi_metadata["UnitXY"] = unitxy
+        self.epi_metadata["UnitT"] = unitt
+        if self.verbose > 0:
+            ut.show_info( "Movie scales set to "+str(self.epi_metadata["ScaleXY"])+" "+self.epi_metadata["UnitXY"]+" and "+str(self.epi_metadata["ScaleT"])+" "+self.epi_metadata["UnitT"] )
 
     def set_chanel( self, chan, chanaxis ):
         """ Update the movie to the correct chanel """
         self.img = np.rollaxis(np.copy(self.mov), chanaxis, 0)[chan]
+        self.main_channel = chan
         if self.viewer is not None:
             mview = self.viewer.layers["Movie"]
             mview.data = self.img
@@ -118,7 +153,7 @@ class EpiCure():
                 if purechan >= chan:
                     purechan = purechan + 1
                 self.others_chanlist.append(purechan)
-                mview = self.viewer.add_image( self.others[ochan], name="MovieOtherChanel_"+str(purechan), blending="additive", colormap="gray" )
+                mview = self.viewer.add_image( self.others[ochan], name="MovieChannel_"+str(purechan), blending="additive", colormap="gray" )
                 mview.contrast_limits=tuple(np.quantile(self.others[ochan],[0.01, 0.9999]))
                 mview.gamma=0.95
                 mview.visible = False
@@ -127,7 +162,7 @@ class EpiCure():
         """ Load the segmentation file """
         start_time = ut.start_time()
         self.segpath = segpath
-        self.seg,_, _ = ut.opentif( self.segpath, verbose=self.verbose>1 )
+        self.seg,_, _,_,_,_ = ut.opentif( self.segpath, verbose=self.verbose>1 )
         self.seg = np.uint32(self.seg)
         ## transform static image to movie (add temporal dimension)
         if len(self.seg.shape) == 2:
@@ -163,8 +198,9 @@ class EpiCure():
         if self.tracked == 0:
             tracked = "untracked"
         else:
-            progress_bar.set_description( "check and fix track gaps" )
-            self.handle_gaps( track_list=None, verbose=1 )
+            if self.forbid_gaps:
+                progress_bar.set_description( "check and fix track gaps" )
+                self.handle_gaps( track_list=None, verbose=1 )
         ut.show_info(""+str(len(self.tracking.get_track_list()))+" "+tracked+" cells loaded")
 
 
@@ -192,10 +228,11 @@ class EpiCure():
         """ Extract default names from imgpath """
         self.imgname, self.imgdir, self.outdir = ut.extract_names( self.imgpath, outdir, mkdir=True )
 
-    def go_epicure(self, outdir, segmentation_file):
+    def go_epicure( self, outdir="epics", segmentation_file=None ):
         """ Initialize everything and start the main widget """
         self.set_names( outdir )
-        
+        if segmentation_file is None:
+            segmentation_file = self.suggest_segfile( outdir )
         self.viewer.window._status_bar._toggle_activity_dock(True)
         progress_bar = progress(total=5)
         progress_bar.set_description( "Reading segmented image" )
@@ -224,54 +261,106 @@ class EpiCure():
         if self.verbose > 0:
             ut.show_duration(start_time, header="Tracks and graph loaded in ")
         progress_bar.update(5)
+        self.apply_settings()
         progress_bar.close()
         self.viewer.window._status_bar._toggle_activity_dock(False)
+
+###### Settings (preferences) save and load
+    def apply_settings( self ):
+        """ Apply all default or prefered settings """
+        for sety, val in self.settings.items():
+            if sety=="Display":
+                self.display.apply_settings( val )
+                if "Show help" in val:
+                    index = int( val["Show help"] )
+                    self.switchOverlayText( index )
+                if "Contour" in val:
+                    contour = int( val["Contour"] )
+                    self.seglayer.contour = contour
+                    self.seglayer.refresh()
+                if "Colors" in val:
+                    color = val["Colors"]["button"]
+                    check_color = val["Colors"]["checkbox"]
+                    line_edit_color = val["Colors"]["line edit"]
+                    group_color = val["Colors"]["group"]
+                    self.main_gui.setStyleSheet( 'QPushButton {background-color: '+color+'} QCheckBox::indicator {background-color: '+check_color+'} QLineEdit {background-color: '+line_edit_color+'} QGroupBox {color: grey; background-color: '+group_color+'} ')
+                    self.display_colors = val["Colors"]
+            if sety == "events":
+                self.inspecting.apply_settings( val )
+            if sety == "Output":
+                self.outputing.apply_settings( val )
+            if sety == "Track":
+                self.tracking.apply_settings( val )
+            if sety == "Edit":
+                self.editing.apply_settings( val )
+            #case _:
+            #       continue
+            ## match is not compatible with python 3.9
+
+    def update_settings( self ):
+        """ Returns all the prefered settings """
+        disp = self.settings
+        ## load display current settings (layers visibility)
+        disp["Display"] = self.display.get_current_settings()
+        disp["Display"]["Show help"] = self.help_index
+        disp["Display"]["Contour"] = self.seglayer.contour
+        ## load suspect current settings
+        disp["events"] = self.inspecting.get_current_settings()
+        ## get outputs current settings
+        disp["Output"] = self.outputing.get_current_settings()
+        disp["Track"] = self.tracking.get_current_settings()
+        disp["Edit"] = self.editing.get_current_settings()
+
+#### Main widget that contains the tabs of the sub widgets
     
     def main_widget(self):
         """ Open the main widget interface """
-        main_widget = QWidget()
+        self.main_gui = QWidget()
         
         layout = QVBoxLayout()
         tabs = QTabWidget()
         tabs.setObjectName("main")
         layout.addWidget(tabs)
-        main_widget.setLayout(layout)
+        self.main_gui.setLayout(layout)
         
         self.editing = Editing(self.viewer, self)
         tabs.addTab( self.editing, "Edit" )
-        self.suspecting = Suspecting(self.viewer, self)
-        tabs.addTab( self.suspecting, "Suspect" )
+        self.inspecting = Inspecting(self.viewer, self)
+        tabs.addTab( self.inspecting, "Inspect" )
         self.tracking = Tracking(self.viewer, self)
         tabs.addTab( self.tracking, "Track" )
         self.outputing = Outputing(self.viewer, self)
         tabs.addTab( self.outputing, "Output" )
         self.display = Displaying(self.viewer, self)
         tabs.addTab( self.display, "Display" )
-        main_widget.setStyleSheet('QPushButton {background-color: rgb(40, 60, 75)} QCheckBox::indicator {background-color: rgb(40,52,65)}')
+        self.main_gui.setStyleSheet('QPushButton {background-color: rgb(40, 60, 75)} QCheckBox::indicator {background-color: rgb(40,52,65)}')
 
-        self.viewer.window.add_dock_widget( main_widget, name="Main" )
+        self.viewer.window.add_dock_widget( self.main_gui, name="Main" )
 
     def key_bindings(self):
-        """ Activate shortcurs """
+        """ Activate shortcuts """
         self.text = "-------------- ShortCuts -------------- \n "
-        self.text = self.text + "If Segmentation layer is active: \n"
-        self.text = self.text + "  <h> show/next/hide this help message \n"
-        self.text = self.text + "  <a> show ALL shortcuts in separate window \n"
-        self.text = self.text + "  <s> save the updated segmentation \n"
-        self.text = self.text + "  <Shift-s> save the movie with current display \n"
+        self.text += "!! Shortcuts work if Segmentation layer is active !! \n"
+        #for sctype, scvals in self.shortcuts.items():
+        self.text += "\n---"+"General"+" options---\n"
+        sg = self.shortcuts["General"]
+        self.text += ut.print_shortcuts( sg )
         self.text = self.text + "\n"
         
         if self.verbose > 0:
             print("Activating key shortcuts on segmentation layer")
-            print("Press several times <h> to show all the shortcuts list or hide it")
+            print("Press <" + str(sg["show help"]["key"]) + "> to show/hide the main shortcuts")
+            print("Press <" + str(sg["show all"]["key"]) + "> to show ALL shortcuts")
         ut.setOverlayText(self.viewer, self.text, size=10)
         
-        @self.seglayer.bind_key('h', overwrite=True)
+        @self.seglayer.bind_key( sg["show help"]["key"], overwrite=True )
         def switch_shortcuts(seglayer):
-            index = (self.help_index+1)%(len(self.overtext.keys())+1)
-            self.switchOverlayText(index)
+            #index = (self.help_index+1)%(len(self.overtext.keys())+1)
+            #self.switchOverlayText(index)
+            index = (self.help_index+1)%2
+            self.switchOverlayText( index )
         
-        @self.seglayer.bind_key('a', overwrite=True)
+        @self.seglayer.bind_key( sg["show all"]["key"], overwrite=True )
         def list_all_shortcuts(seglayer):
             self.switchOverlayText(0)   ## hide display message in main window
             text = "**************** EPICURE *********************** \n"
@@ -284,11 +373,11 @@ class EpiCure():
                 text += val
             self.update_text_window(text) 
         
-        @self.seglayer.bind_key('s', overwrite=True)
+        @self.seglayer.bind_key( sg["save segmentation"]["key"], overwrite=True )
         def save_seglayer(seglayer):
             self.save_epicures()
         
-        @self.viewer.bind_key('Shift-s', overwrite=True)
+        @self.viewer.bind_key( sg["save movie"]["key"], overwrite=True )
         def save_movie(seglayer):
             endname = "_frames.tif"
             outname = os.path.join( self.outdir, self.imgname+endname )
@@ -304,7 +393,8 @@ class EpiCure():
             return
         else:
             ut.showOverlayText(self.viewer, vis=True)
-        self.setCurrentOverlayText()
+        #self.setCurrentOverlayText()
+        self.setGeneralOverlayText()
 
     def init_text_window(self):
         """ Create and display help text window """
@@ -314,10 +404,14 @@ class EpiCure():
 
     def update_text_window(self, message):
         """ Update message in separate window """
-        if self.blabla is None:
-            self.init_text_window()
+        self.init_text_window()
         self.blabla.value = message
 
+    def setGeneralOverlayText(self):
+        """ set overlay help message to general message """
+        text = self.text
+        ut.setOverlayText(self.viewer, text, size=10)
+
     def setCurrentOverlayText(self):
         """ Set overlay help text message to current selected options list """
         text = self.text
@@ -345,7 +439,8 @@ class EpiCure():
         summ += "Nb cells: "+str( nb_labels )+"\n"
         summ += "Average track lengths: "+str(mean_duration)+" frames\n"
         summ += "Average cell area: "+str(mean_area)+" pixels^2\n"
-        summ += "Nb suspect tracks: "+str(self.suspecting.nb_suspects())+"\n"
+        summ += "Nb suspect events: "+str(self.inspecting.nb_events(only_suspect=True))+"\n"
+        summ += "Nb divisions: "+str(self.inspecting.nb_type("division"))+"\n"
         summ += "\n"
         summ += "--- Parameter infos \n"
         summ += "Junction thickness: "+str(self.thickness)+"\n"
@@ -362,10 +457,10 @@ class EpiCure():
             if self.verbose > 0:
                 print("Reput shape layer")
             self.editing.create_shapelayer()
-        if self.suspecting.suspectlayer_name not in self.viewer.layers:
+        if self.inspecting.eventlayer_name not in self.viewer.layers:
             if self.verbose > 0:
-                print("Reput suspect layer")
-            self.suspecting.create_suspectlayer()
+                print("Reput event layer")
+            self.inspecting.create_eventlayer()
         if "Movie" not in self.viewer.layers:
             if self.verbose > 0:
                 print("Reput movie layer")
@@ -390,43 +485,51 @@ class EpiCure():
         for dlay in duplayers:
             if dlay in self.viewer.layers:
                 (self.viewer.layers[dlay]).refresh()
-        
+    
+    def read_epicure_metadata( self ):
+        """ Load saved infos from file """
+        epiname = self.outname() + "_epidata.pkl" 
+        if os.path.exists( epiname ):
+            infile = open(epiname, "rb")
+            try:
+                epidata = pickle.load( infile )
+                if "EpiMetaData" in epidata.keys():
+                    self.epi_metadata = epidata["EpiMetaData"]
+                infile.close()
+            except:
+                ut.show_warning( "Could not read EpiCure data file "+epiname )
+
+
     def save_epicures( self, imtype="float32" ):
         outname = os.path.join( self.outdir, self.imgname+"_labels.tif" )
-        ut.writeTif(self.seg, outname, self.scale, imtype, what="Segmentation")
+        ut.writeTif(self.seg, outname, self.epi_metadata["ScaleXY"], imtype, what="Segmentation")
         epiname = os.path.join( self.outdir, self.imgname+"_epidata.pkl" )
         outfile = open(epiname, "wb")
+        epidata = {}
+        epidata["EpiMetaData"] = self.epi_metadata
         if self.groups is not None:
-            pickle.dump(self.groups, outfile)
-        else:
-            pickle.dump({}, outfile)
+            epidata["Group"] = self.groups
         if self.tracking.graph is not None:
-            pickle.dump(self.tracking.graph, outfile)
-        else:
-            pickle.dump({}, outfile)
-        if self.suspecting is not None and self.suspecting.suspects is not None:
-            if self.suspecting.suspects.data is not None:
-                pickle.dump(self.suspecting.suspects.data, outfile)
-            else:
-                pickle.dump(None, outfile)
-            pickle.dump(self.suspecting.suspects.properties, outfile)
-            pickle.dump(self.suspecting.suspicions, outfile)
-            pickle.dump(self.suspecting.suspects.symbol, outfile)
-            pickle.dump(self.suspecting.suspects.face_color, outfile)
+            epidata["Graph"] = self.tracking.graph
+        if self.inspecting is not None and self.inspecting.events is not None:
+            epidata["Events"] = {}
+            if self.inspecting.events.data is not None:
+                epidata["Events"]["Points"] = self.inspecting.events.data
+                epidata["Events"]["Props"] = self.inspecting.events.properties
+                epidata["Events"]["Types"] = self.inspecting.event_types
+                epidata["Events"]["Symbols"] = self.inspecting.events.symbol
+                epidata["Events"]["Colors"] = self.inspecting.events.face_color
+        pickle.dump( epidata, outfile )
         outfile.close()
     
-    def read_group_data( self, infile ):
+    def read_group_data( self, groups ):
         """ Read the group EpiCure data from opened file """
-        try:
-            groups = pickle.load(infile)
-            if self.verbose > 0:
-                print("Loaded cell groups info: "+str(groups))
-            return groups
-        except:
-            if self.verbose > 1:
-                print("No group infos found")
-            return None
-
+        if self.verbose > 0:
+            print( "Loaded cell groups info: "+str(list(groups.keys())) )
+            if self.verbose > 2:
+                print( "Cell groups: "+str(groups) )
+        return groups
+        
     def read_graph_data( self, infile ):
         """ Read the graph EpiCure data from opened file """
         try:
@@ -439,47 +542,100 @@ class EpiCure():
                 print("No graph infos found")
             return None
 
-    def read_suspicions_data(self, infile):
-        """ Read info of EpiCure suspicions from opened file """
+    def read_events_data(self, infile):
+        """ Read info of EpiCure events (suspects, divisions) from opened file """
         try:
-            suspects_pts = pickle.load(infile)
-            if suspects_pts is not None:
-                suspects_props = pickle.load(infile)
-                suspicions = pickle.load(infile)
+            events_pts = pickle.load(infile)
+            if events_pts is not None:
+                events_props = pickle.load(infile)
+                events_type = pickle.load(infile)
                 try:
                     symbols = pickle.load(infile)
                     colors = pickle.load(infile)
                 except: 
                     if self.verbose > 1:
-                        print("No suspects display info found")
+                        print("No events display info found")
                     symbols = None
                     colors = None
-                return suspects_pts, suspects_props, suspicions, symbols, colors
+                return events_pts, events_props, events_type, symbols, colors
             else:
                 return None, None, None, None, None
         except:
             if self.verbose > 1:
-                print("Suspects info not complete")
+                print("events info not complete")
             return None, None, None, None, None
 
     def load_epicure_data(self, epiname):
         """ Load saved infos from file """
         infile = open(epiname, "rb")
+        try:
+            epidata = pickle.load( infile )
+            if "EpiMetaData" in epidata.keys():
+                # version of epicure file after Epicure 0.2.0
+                self.read_epidata( epidata )
+                infile.close()
+            else:
+                # version anterior of Epicure 0.2.0
+                self.load_epicure_data_old( epidata, infile )
+        except:
+            ut.show_warning( "Could not read EpiCure data file "+epiname )
+
+    def read_epidata( self, epidata ):
+        """ Read the dict of saved state and initialize all instances with it """
+        for key, vals in epidata.items():
+            if key == "EpiMetaData":
+                ## image data is read on the previous step
+                continue
+            if key == "Group":
+                ## Load groups information
+                self.groups = self.read_group_data( vals )
+                for group in self.groups.keys():
+                    self.editing.update_group_list( group )
+                self.outputing.update_selection_list()
+            if key == "Graph":
+                ## Load graph (lineage) informations
+                self.tracking.graph = vals
+                if self.tracking.graph is not None:
+                    self.tracking.tracklayer.refresh()
+            if key == "Events":
+                ## Load events information
+                if "Points" in vals.keys():
+                    pts = vals["Points"]
+                if "Props" in vals.keys():
+                    props = vals["Props"]
+                if "Types" in vals.keys():
+                    event_types = vals["Types"]
+                if "Symbols" in vals.keys():
+                    symbols = vals["Symbols"]
+                if "Colors" in vals.keys():
+                    colors = vals["Colors"] 
+                if pts is not None:
+                    if len(pts) > 0:
+                        self.inspecting.load_events(pts, props, event_types, symbols, colors)
+                    if len(pts) > 0 and self.verbose > 0:
+                        print("events loaded")
+                    ut.show_info("Loaded "+str(len(pts))+" events")     
+               
+
+    def load_epicure_data_old( self, groups, infile ):
+        """ Load saved infos from file """
         ## Load groups information
-        self.groups = self.read_group_data( infile )
+        self.groups = self.read_group_data( groups )
+        for group in self.groups.keys():
+            self.editing.update_group_list( group )
         self.outputing.update_selection_list()
         ## Load graph (lineage) informations
         self.tracking.graph = self.read_graph_data( infile )
         if self.tracking.graph is not None:
             self.tracking.tracklayer.refresh()
-        ## Load suspects information 
-        pts, props, suspicions, symbols, colors = self.read_suspicions_data( infile )
+        ## Load events information 
+        pts, props, event_types, symbols, colors = self.read_events_data( infile )
         if pts is not None:
             if len(pts) > 0:
-                self.suspecting.load_suspects(pts, props, suspicions, symbols, colors)
+                self.inspecting.load_events(pts, props, event_types, symbols, colors)
                 if len(pts) > 0 and self.verbose > 0:
-                    print("Suspects loaded")
-                    ut.show_info("Loaded "+str(len(pts))+" suspects")
+                    print("events loaded")
+                    ut.show_info("Loaded "+str(len(pts))+" events")
         infile.close()
 
     def save_movie(self, outname):
@@ -510,7 +666,7 @@ class EpiCure():
 
     def reset_data( self ):
         """ Reset EpiCure data (group, suspect, graph) """
-        self.suspecting.reset_all_suspects()
+        self.inspecting.reset_all_events()
         self.reset_groups()
         self.outputing.update_selection_list()
         self.tracking.graph = None
@@ -627,6 +783,10 @@ class EpiCure():
     def has_label(self, label):
         """ Check if label is present in the tracks """
         return self.tracking.has_track(label)
+    
+    def has_labels(self, labels):
+        """ Check if labels are present in the tracks """
+        return self.tracking.has_tracks( labels )
 
     def nlabels(self):
         """ Number of unique tracks """
@@ -642,11 +802,11 @@ class EpiCure():
         self.tracking.remove_tracks( tracks )
 
     def delete_track(self, label, frame=None):
-        """ Remove the track """
+        """ Remove (part of) the track """
         if frame is None:
             self.tracking.remove_track(label)
         else:
-            self.tracking.remove_one_frame(label, frame)
+            self.tracking.remove_one_frame(label, frame, handle_gaps=self.forbid_gaps )
 
     def update_centroid(self, label, frame):
         """ Track label has been change at given frame """
@@ -736,7 +896,23 @@ 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 """
+        area = np.sum( self.seg[frame] == label )
+        radius = math.sqrt(area/math.pi)
+        return radius
+    
+    def cell_area( self, label, frame ):
+        """ Approximate the cell radius at given frame """
+        area = np.sum( self.seg[frame] == label )
+        return area 
 
+    def cell_on_border( self, label, frame ):
+        """ Check if a given cell is on border of the image """
+        bbox = ut.getBBox2D( self.seg[frame], label )
+        out = ut.outerBBox2D( bbox, self.imgshape2D, margin=3 )
+        return out
 
     ###### Synchronize tracks whith labels changed
     def add_label( self, labels, frame=None ):
@@ -765,29 +941,31 @@ class EpiCure():
     def update_changed_labels( self, indmodif, new_labels, old_labels ):
         """ Check what had been modified, and update tracks from it """
         ## check all the old_labels if still present or not
-        min_frame = np.min(indmodif[0])
-        max_frame = np.max(indmodif[0])
-        start_time = time.time()
+        if self.verbose > 1:
+            start_time = time.time()
+        frames = np.sort( np.unique( indmodif[0] ) )
         all_deleted = []
-        for frame in range(min_frame, max_frame+1):
-            if self.verbose > 1:
-                print("Updating labels at frame "+str(frame))
-            keep = np.where(indmodif[0] == frame)[0]
-            nlabels = np.unique(new_labels[keep])
-            olabels = np.unique(old_labels[keep])
+        debug_verb = self.verbose > 2
+        if debug_verb:
+            print( "Updating labels in frames "+str(frames) )
+        for frame in frames:
+            keep = indmodif[0] == frame
             ## check old labels if totally removed or not
-            deleted = np.setdiff1d( olabels, self.seg[frame] )
+            deleted = np.setdiff1d( old_labels[keep], self.seg[frame] )
             if deleted.shape[0] > 0:
                 self.tracking.remove_one_frame( deleted, frame, handle_gaps=False, refresh=False )
                 all_deleted = all_deleted + list(set(deleted) - set(all_deleted))
+            ## now check new labels
+            nlabels = np.unique( new_labels[keep] )
             if nlabels.shape[0] > 0:
                 self.tracking.update_track_on_frame( nlabels, frame )
-            if self.verbose > 1:
-                print("Labels deleted "+str(deleted)+" or added "+str(nlabels))
+            if debug_verb:
+                print("Labels deleted at frame "+str(frame)+" "+str(deleted)+" or added "+str(nlabels))
 
         ## Check if some gaps has been created in tracks (remove middle(s) frame(s))
-        if len(all_deleted) > 0:
-            self.handle_gaps( all_deleted, verbose=0 )
+        if self.forbid_gaps:
+            if len(all_deleted) > 0:
+                self.handle_gaps( all_deleted, verbose=0 )
 
         if self.verbose > 1:
             ut.show_duration(start_time, "updated tracks in ")
@@ -854,7 +1032,7 @@ class EpiCure():
         if frame is None:
             self.tracking.remove_tracks( deleted_labels )
         else:
-            self.tracking.remove_one_frame( track_id=deleted_labels.tolist(), frame=frame, handle_gaps=True)
+            self.tracking.remove_one_frame( track_id=deleted_labels.tolist(), frame=frame, handle_gaps=self.forbid_gaps )
 
     def remove_label(self, label, force=False):
         """ Remove a given label if allowed """
@@ -884,9 +1062,13 @@ class EpiCure():
         ut.setNewLabel(self.seglayer, inds, 0)
         self.tracking.remove_tracks(toremove)
 
-    def get_frame_features( self, frame, props ):
+    def get_frame_features( self, frame ):
         """ Measure the label properties of given frame """
-        return regionprops_table( self.seg[frame], properties=props )
+        return regionprops( self.seg[frame] )
+
+    def updates_after_tracking( self ):
+        """ When tracking has been done, update events, others """
+        self.inspecting.get_divisions()
 
     #######################
     ## Classified cells options
@@ -919,19 +1101,19 @@ class EpiCure():
                 groups[ind] = gr
         return groups 
     
-    def cell_ingroup(self, label, group):
+    def cells_ingroup(self, labels, group):
         """ Put the cell "label" in group group, add it if new group """
-        if not self.has_label(label):
-            if self.verbose > 1:
-                print("Cell "+str(label)+" missing")
-            return
+        presents = self.has_labels( labels )
+        labels = np.array(labels)[ presents ]
         if group not in self.groups.keys():
             self.groups[group] = []
             if self.outputing is not None:
                 self.outputing.update_selection_list()
-        if label not in self.groups[group]:
-            self.groups[group].append(label)
-
+            self.editing.update_group_list( group )
+        ## add only non present label(s)
+        grlabels = self.groups[ group ]
+        self.groups[ group ] = list( set( grlabels + labels.tolist()) )
+        
     def find_group(self, label):
         """ Find in which group the label is """
         for gr, labs in self.groups.items():
@@ -939,7 +1121,6 @@ class EpiCure():
                 return gr
         return None
 
-
     def cell_removegroup(self, label):
         """ Detach the cell from its group """
         if not self.has_label(label):
@@ -959,12 +1140,12 @@ class EpiCure():
 
     def draw_groups(self):
         """ Draw all the epicells colored by their group """
-        grouped = np.zeros(self.seg.shape, np.uint8)
+        grouped = np.zeros( self.seg.shape, np.uint8 ) 
         if (self.groups is None) or len(self.groups.keys()) == 0:
             return grouped
         for group, labels in self.groups.items():
             igroup = self.get_group_index(group) + 1
-            np.place(grouped, np.isin(self.seg, labels), igroup)
+            np.place( grouped, np.isin( self.seg, labels ), igroup )
         return grouped
 
     def get_group_index(self, group):
@@ -972,9 +1153,7 @@ class EpiCure():
         igroup = (list(self.groups.keys())).index(group)
         return igroup
     
-
     ######### ROI
-
     def only_current_roi(self, frame):
         """ Put 0 everywhere outside the current ROI """
         roi_labels = self.editing.get_labels_inside()
diff --git a/src/epicure/epiwidgets.py b/src/epicure/epiwidgets.py
new file mode 100644
index 0000000000000000000000000000000000000000..e9cd9c23376024b8792ea78302cd8150adfb5499
--- /dev/null
+++ b/src/epicure/epiwidgets.py
@@ -0,0 +1,266 @@
+import epicure.Utils as ut
+from qtpy.QtWidgets import QPushButton, QCheckBox, QHBoxLayout, QLabel, QLineEdit, QComboBox, QSpinBox, QSlider, QGroupBox
+from qtpy.QtCore import Qt
+
+def help_button( link, description="", display_settings=None ):
+    """ Create a new Help button with given parameter """
+    def show_doc():
+        """ Open documentation page """
+        ut.show_documentation_page( link )
+
+    help_btn = QPushButton( "help" )
+    if description == "":
+        help_btn.setToolTip( "Open EpiCure documentation" )
+        help_btn.setStatusTip( "Open EpiCure documentation" )
+    else:
+        help_btn.setToolTip( description )
+        help_btn.setStatusTip( description )
+    help_btn.clicked.connect( show_doc )
+    if display_settings is not None:
+        if "Help button" in display_settings:
+            color = display_settings["Help button"]
+            help_btn.setStyleSheet( 'QPushButton {background-color: '+color+'}' )
+    return help_btn
+
+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 )
+    chbox = QCheckBox( text=name )
+
+    ## set group and checkbox to the same specific color
+    if (groupnb is not None) and (display_settings is not None):
+        if groupnb in display_settings:
+            color = display_settings[groupnb]
+            group.setStyleSheet( 'QGroupBox {background-color: '+color+'}' )
+            chbox.setStyleSheet( 'QCheckBox::indicator {background-color: '+color+'}' )
+    
+    def show_hide():
+        group.setVisible( chbox.isChecked() )
+
+    line = QHBoxLayout()
+    ## create checkbox
+    chbox.setToolTip( descr )
+    line.addWidget( chbox )
+    chbox.stateChanged.connect( show_hide )
+    chbox.setChecked( checked )
+    ## create button
+    if help_link is not None:
+        help_btn = help_button( help_link, "", display_settings )
+        line.addWidget( help_btn )
+    return line, chbox, group
+
+def checkhelp_line( checkbox_name, checked, checkfunc, check_descr, help_link, display_settings=None, help_descr="" ):
+    """ Create a layout line with a checkbox associated with help button """
+    line = QHBoxLayout()
+    ## create checkbox
+    chbox = QCheckBox( text=checkbox_name )
+    chbox.setToolTip( check_descr )
+    line.addWidget( chbox )
+    if checkfunc is not None:
+        chbox.stateChanged.connect( checkfunc )
+    chbox.setChecked( checked )
+    ## create button
+    help_btn = help_button( help_link, help_descr, display_settings )
+    line.addWidget( help_btn )
+    return line, chbox
+
+def add_check( check, checked, check_func=None, descr="" ):
+    """ Add a checkbox with set parameters """
+    cbox = QCheckBox( text=check )
+    cbox.setToolTip( descr )
+    if check_func is not None:
+        cbox.stateChanged.connect( check_func )
+    cbox.setChecked( checked )
+    return cbox
+
+def double_check( checka, checkeda, funca, descra, checkb, checkedb, funcb, descrb ):
+    """ Line with two customized checkboxes """
+    line = QHBoxLayout()
+    check_a = add_check( checka, checkeda, funca, descra )
+    check_b = add_check( checkb, checkedb, funcb, descrb )
+    line.addWidget( check_a )
+    line.addWidget( check_b )
+    return line, check_a, check_b
+
+def add_button( btn, btn_func, descr="", color=None ):
+    """ Add a button connected to an action when pushed """
+    btn = QPushButton( btn )
+    if btn_func is not None:
+        btn.clicked.connect( btn_func )
+    if descr != "":
+        btn.setToolTip( descr )
+    else:
+        btn.setToolTip( "Click to perform action" )
+    if color is not None:
+        btn.setStyleSheet( 'QPushButton {background-color: '+color+'}' )
+    return btn
+
+def double_button( btna, funca, descra, btnb, funcb, descrb ):
+    """ Line with two customized buttons """
+    line = QHBoxLayout()
+    btn_a = add_button( btna, funca, descra )
+    btn_b = add_button( btnb, funcb, descrb )
+    line.addWidget( btn_a )
+    line.addWidget( btn_b )
+    return line
+
+def button_parameter_line( btn, btn_func, value, descr_btn="", descr_value="" ):
+    """ Create a layout with a button and an editable value associated """
+    line = QHBoxLayout()
+    ## Action button
+    btn = QPushButton( btn )
+    btn.clicked.connect( btn_func )
+    if descr_btn != "":
+        btn.setToolTip( descr_btn )
+    line.addWidget( btn )
+    ## Value editable
+    val = QLineEdit()
+    val.setText( value )
+    line.addWidget( val )
+    if descr_value != "":
+        val.setToolTip( descr_value )
+    return line, val
+
+def min_button_max( btn, btn_func, min_val, max_val, descr="" ):
+    """ Button inside two values (min and max) interfaces """
+    line = QHBoxLayout()
+    ## left value
+    minv = QLineEdit()
+    minv.setText( min_val )
+    line.addWidget( minv )
+    ## button
+    btn = QPushButton( btn )
+    btn.clicked.connect( btn_func )
+    if descr != "":
+        btn.setToolTip( descr )
+    line.addWidget( btn )
+    ## right value
+    maxv = QLineEdit()
+    maxv.setText( max_val )
+    line.addWidget( maxv )
+    return line, minv, maxv
+
+
+def button_check_line( btn, btn_func, check, checked=False, checkfunc=None, descr_btn="", descr_check="", leftbtn=True ):
+    """ Create a layout with a button and an assiociated checkbox """
+    line = QHBoxLayout()
+    ## Action button
+    btn = QPushButton( btn )
+    btn.clicked.connect( btn_func )
+    if descr_btn != "":
+        btn.setToolTip( descr_btn )
+    ## Value editable
+    cbox = QCheckBox( check )
+    if descr_check != "":
+        cbox.setToolTip( descr_check )
+    if checkfunc is not None:
+        cbox.stateChanged.connect( checkfunc )
+    cbox.setChecked( checked )
+    ## button first (left), then checkbox
+    if leftbtn:
+        line.addWidget( btn )
+        line.addWidget( cbox )
+    else:
+        ## or checkbox first (left), then button
+        line.addWidget( cbox )
+        line.addWidget( btn )
+    return line, cbox
+
+def value_line( label, default_value, descr="" ):
+    """ Create a layout line with a value to edit (non editable name + value part ) """
+    line = QHBoxLayout()
+    ## Value name
+    lab = QLabel()
+    lab.setText( label )
+    line.addWidget( lab )
+    if descr != "":
+        lab.setToolTip( descr )
+    ## Value editable part
+    value = QLineEdit()
+    value.setText( default_value )
+    line.addWidget( value )
+    return line, value
+
+def check_value( check, checkfunc=None, checked=False, value="0", descr="", label=None ):
+    """ Line with a checkbox and an associated editable parameter """
+    line = QHBoxLayout()
+    ## add checkbox
+    cbox = add_check( check, checked=checked, check_func=checkfunc, descr=descr )
+    line.addWidget( cbox )
+    ## add eventually a text
+    if label is not None:
+        lab = QLabel()
+        lab.setText( label )
+        line.addWidget( lab )
+    ## add the editable value
+    val = QLineEdit()
+    val.setText( value )
+    line.addWidget( val )
+    return line, cbox, val
+
+def ranged_value_line( label, minval, maxval, step, val, descr="" ):
+    """ Create a line with a label and a ranged value (limited between min and max) """
+    line = QHBoxLayout()
+    ## Add the name of the value
+    lab = QLabel()
+    lab.setText( label )
+    if descr != "":
+        lab.setToolTip( descr )
+    line.addWidget( lab )
+    ## Ranged-value widget
+    ranged_val = QSpinBox()
+    ranged_val.setMinimum( minval )
+    ranged_val.setMaximum( maxval )
+    ranged_val.setSingleStep( step ) 
+    ranged_val.setValue( val )
+    line.addWidget( ranged_val )
+    return line, ranged_val
+
+def button_list( btn, func, descr ):
+    """ Button associated with a list """
+    line = QHBoxLayout()
+    ## Button part
+    button = add_button( btn, func, descr )
+    line.addWidget( button )
+    ## list part
+    li = QComboBox()
+    line.addWidget( li )
+    return line, li
+
+def list_line( label, descr="", func=None ):
+    """ Create a layout line with a choice list to edit (non editable name + list part ) """
+    line = QHBoxLayout()
+    ## Value name
+    lab = QLabel()
+    lab.setText( label )
+    line.addWidget( lab )
+    if descr != "":
+        lab.setToolTip( descr )
+        lab.setStatusTip( descr )
+    ## Value editable part
+    value = QComboBox()
+    line.addWidget( value )
+    if func is not None:
+        value.currentIndexChanged.connect( func )
+    return line, 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
diff --git a/src/epicure/inspecting.py b/src/epicure/inspecting.py
new file mode 100644
index 0000000000000000000000000000000000000000..7696473249ad92bca2c6ff322bd79b046a22b611
--- /dev/null
+++ b/src/epicure/inspecting.py
@@ -0,0 +1,1203 @@
+import numpy as np
+from skimage import filters
+from skimage.measure import regionprops, label
+from skimage.morphology import binary_erosion, binary_dilation, disk
+from qtpy.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget, QLabel, QComboBox
+from napari.utils import progress
+import epicure.Utils as ut
+import epicure.epiwidgets as wid
+import time
+
+"""
+    EpiCure - Inspection interface
+    Handle supects, events detection layer
+"""
+
+class Inspecting(QWidget):
+    
+    def __init__(self, napari_viewer, epic):
+        super().__init__()
+        self.viewer = napari_viewer
+        self.epicure = epic
+        self.seglayer = self.viewer.layers["Segmentation"]
+        self.border_cells = None    ## list of cells that are on the border (touch the background)
+        self.eventlayer_name = "Events"
+        self.events = None
+        self.win_size = 10
+
+        ## Print the current number of events
+        self.nevents_print = QLabel("")
+        self.update_nevents_display()
+        
+        self.create_eventlayer()
+        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" )
+        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)
+        
+        ## 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" )
+        layout.addLayout( outlier_line )
+        self.create_outliersBlock() 
+        layout.addWidget(self.featOutliers)
+        
+        ## Error suggestions based on tracks
+        track_line, self.track_vis, self.eventTrack = wid.checkgroup_help( "Track options", True, "Show/hide track options", "event#track-based-events", self.epicure.display_colors, "group2" )
+        self.create_tracksBlock() 
+        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()
+
+    def key_binding(self):
+        """ active key bindings for events options """
+        sevents = self.epicure.shortcuts["Events"]
+        self.epicure.overtext["events"] = "---- Events editing ---- \n"
+        self.epicure.overtext["events"] += ut.print_shortcuts( sevents )
+   
+        @self.epicure.seglayer.mouse_drag_callbacks.append
+        def handle_event(seglayer, event):
+            if event.type == "mouse_press":
+                ## remove a event
+                if ut.shortcut_click_match( sevents["delete"], event ):
+                    ind = ut.getCellValue( self.events, event ) 
+                    if self.epicure.verbose > 1:
+                        print("Removing clicked event, at index "+str(ind))
+                    if ind is None:
+                        ## click was not on a event
+                        return
+                    sid = self.events.properties["id"][ind]
+                    if sid is not None:
+                        self.exonerate_one(ind, remove_division=True)
+                        self.update_nevents_display()
+                    else:
+                        if self.epicure.verbose > 1:
+                            print("event with id "+str(sid)+" not found")
+                    self.events.refresh()
+                    return
+
+                ## zoom on a event
+                if ut.shortcut_click_match( sevents["zoom"], event ):
+                    ind = ut.getCellValue( self.events, event ) 
+                    if "id" not in self.events.properties.keys():
+                        print("No event under click")
+                        return
+                    sid = self.events.properties["id"][ind]
+                    if self.epicure.verbose > 1:
+                        print("Zoom on event with id "+str(sid)+"")
+                    self.zoom_on_event( event.position, sid )
+                    return
+
+        @self.epicure.seglayer.bind_key( sevents["next"]["key"], overwrite=True )
+        def go_next(seglayer):
+            """ Select next suspect event and zoom on it """
+            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.epicure.verbose > 0:
+                        print("No more suspect event")
+                    return  
+                else:
+                    self.event_num.setValue(0)
+            else:
+                self.event_num.setValue( (num_event+1)%nevents )
+            self.skip_nonsuspect_event( nevents, nevents )
+            self.go_to_event()       
+
+    def skip_nonsuspect_event( self, nevents, left ):
+        """ Skip next event if not a suspect one (eg division) """
+        index = int(self.event_num.value())
+        if self.is_division( index ):
+            index = (index + 1)%nevents
+            self.event_num.setValue( index )
+            self.skip_nonsuspect_event( nevents, left-1 )
+        if left < 0:
+            return
+
+    def create_eventlayer(self):
+        """ Create a point layer that contains the events """
+        features = {}
+        pts = []
+        self.events = self.viewer.add_points( np.array(pts), properties=features, face_color="red", size = 10, symbol='x', name=self.eventlayer_name, )
+        self.event_types = {}
+        self.update_nevents_display()
+        self.epicure.finish_update()
+
+    def load_events(self, pts, features, event_types, symbols=None, colors=None):
+        """ Load events data from file and reinitialize layer with it"""
+        ut.remove_layer(self.viewer, self.eventlayer_name)
+        if symbols is None:
+            symbols = "x"
+        if colors is None:
+            colors = "red"
+        symb = symbols
+        self.events = self.viewer.add_points( np.array(pts), properties=features, face_color=colors, size = 10, symbol=symbols, name=self.eventlayer_name, )
+        self.event_types = event_types
+        self.update_nevents_display()
+        self.epicure.finish_update()
+
+        
+    ############### Display event options
+    def get_event_types( self ):
+        """ Returns the list of possible event types """
+        return list( self.event_types.keys() )
+
+    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" 
+        self.nevents_print.setText( text )
+
+    def nb_events( self, only_suspect=False ):
+        """ Returns current number of events """
+        if self.events is None:
+            return 0
+        if self.events.properties is None:
+            return 0
+        if "score" not in self.events.properties:
+            return 0
+        if not only_suspect:
+            return len(self.events.properties["score"])
+        return ( len(self.events.properties["score"]) - self.nb_type("division") )
+
+    def get_events_from_type( self, feature ):
+        """ Return the list of events of a given type """
+        if feature == "suspect":
+            sub_features = self.suspect_subtypes()
+            evts_id = []
+            for feat in sub_features:
+                evts_id.extend( eid for eid in self.event_types[ feat ] if eid not in evts_id )
+            return list( evts_id )
+        if feature in self.event_types:
+            return self.event_types[ feature ]
+        return []
+
+    def nb_type( self, feature ):
+        """ Return nb of event of given type """
+        if self.events is None:
+            return 0
+        if (self.event_types is None) or (feature not in self.event_types):
+            return 0
+        return len(self.event_types[feature])
+
+    def create_displayeventBlock(self):
+        ''' Block interface of displaying event layer options '''
+        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)
+        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-length")
+        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" ) 
+        disp_layout.addLayout(sizelay)
+
+        ### Interface to select a event and zoom on it
+        chooselay, self.event_num = wid.ranged_value_line( label="event n°", minval=0, maxval=1000000, step=1, val=0, descr="Choose current event to display/remove" )
+        disp_layout.addLayout(chooselay)
+        go_event_btn = wid.add_button( "Go to event", self.go_to_event, "Zoom and display current event" )
+        disp_layout.addWidget(go_event_btn)
+        clear_event_btn = wid.add_button( "Remove current event", self.clear_event, "Delete current event from the list of events" )
+        disp_layout.addWidget(clear_event_btn)
+        
+        ## all features
+        self.displayevent.setLayout(disp_layout)
+        self.displayevent.setVisible( self.event_disp.isChecked() )
+       
+    #####
+    def reset_event_range(self):
+        """ Reset the max num of event """
+        nsus = len(self.events.data)-1
+        if self.event_num.value() > nsus:
+            self.event_num.setValue(0)
+        self.event_num.setMaximum(nsus)
+
+    def go_to_event(self):
+        """ Zoom on the currently selected event """
+        num_event = int(self.event_num.value())
+        ## if reached the end of possible events
+        if num_event >= self.nb_events():
+            num_event = 0
+            self.event_num.setValue(0)
+        if num_event < 0:
+            if self.nb_events() == 0:
+                if self.epicure.verbose > 0:
+                    print("No more event")
+                return  
+            else:
+                self.event_num.setValue(0)
+                num_event = 0      
+        pos = self.events.data[num_event]
+        event_id = self.events.properties["id"][num_event]
+        self.zoom_on_event( pos, event_id )
+
+    def get_event_infos( self, sid ):
+        """ Get the properties of the event of given id """
+        index = self.index_from_id( sid )
+        pos = self.events.data[ index ]
+        label = self.events.properties[ "label" ][index]
+        return pos, label
+
+    def zoom_on_event( self, event_pos, event_id ):
+        """ Zoom on chose event at given position """
+        self.viewer.camera.center = event_pos
+        self.viewer.camera.zoom = 5
+        self.viewer.dims.set_point( 0, int(event_pos[0]) )
+        crimes = self.get_crimes(event_id)
+        if self.epicure.verbose > 0:
+            print("Suspected because of: "+str(crimes))
+
+    def color_events(self):
+        """ Color points by the selected mode """
+        color_mode = self.color_choice.currentText()
+        self.events.refresh_colors()
+        if color_mode == "None":
+            self.events.face_color = "white"
+        elif color_mode == "score":
+            self.set_colors_from_properties("score")
+        else:
+            self.set_colors_from_event_type(color_mode)
+        self.events.refresh_colors()
+
+    def suspect_subtypes( self ):
+        """ Return the list of suspect-related event types """
+        features = list( self.event_types.keys() )
+        if "division" in features:
+            features.remove( "division" )
+        return features
+
+    def show_subset_event( self, feature, show=True ):
+        """ Show/hide a subset (type) of event """
+        tmp_size = int(self.event_size.value())
+        size = 0.1
+        if show:
+            size = tmp_size
+        ## select the events of corresponding type
+        self.events.selected_data = {}
+        if feature == "suspect":
+            ## take all possible features except non-suspect ones (division, extrusion..)
+            features = self.suspect_subtypes()
+        else:
+            features = [feature]
+        for feat in features:
+            self.select_feature_event( feat )
+        self.events.current_size = size
+        ## reset selection and default size
+        self.events.selected_data = {}
+        self.events.current_size = tmp_size
+        self.events.refresh()
+
+    def select_feature_event( self, feature ):
+        """ Add all event of given feature to currently selected data """
+        if feature not in self.event_types:
+            return
+        posid = self.event_types[feature]
+        for sid in posid:
+            ind = self.index_from_id(sid)
+            self.events.selected_data.add(ind)
+
+    def set_colors_from_event_type(self, feature):
+        """ Set colors from given event_type feature (eg area, tracking..) """
+        if self.event_types.get(feature) is None:
+            self.events.face_color="white"
+            return
+        posid = self.event_types[feature]
+        colors = ["white"]*len(self.events.data)
+        ## change the color of all the positive events for the chosen feature
+        for sid in posid:
+            ind = self.index_from_id(sid)
+            if ind is not None:
+                colors[ind] = (0.8,0.1,0.1)
+        self.events.face_color = colors
+
+    def set_colors_from_properties(self, feature):
+        """ Set colors from given propertie (eg score, label) """
+        ncols = (np.max(self.events.properties[feature]))
+        color_cycle = []
+        for i in range(ncols):
+            color_cycle.append( (0.25+float(i/ncols*0.75), float(i/ncols*0.85), float(i/ncols*0.75)) )
+        self.events.face_color_cycle = color_cycle
+        self.events.face_color = feature
+    
+    def update_display(self):
+        self.events.refresh()
+        self.color_events()
+
+    def get_current_settings(self):
+        """ Returns current event widget parameters """
+        disp = {}
+        disp["Point size"] = int(self.event_size.value())
+        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()
+        disp["Ignore border"] = self.ignore_borders.isChecked()
+        disp["Flag length"] = self.check_length.isChecked()
+        disp["length"] = self.min_length.text()
+        disp["Check size"] = self.check_size.isChecked()
+        disp["Check shape"] = self.check_shape.isChecked()
+        disp["Get apparitions"] = self.get_apparition.isChecked()
+        disp["Get disparitions"] = self.get_disparition.isChecked()
+        disp["threshold disparition"] = self.threshold_disparition.text()
+        disp["Min area"] = self.min_area.text()
+        disp["Max area"] = self.max_area.text()
+        disp["Current frame"] = self.feat_onframe.isChecked()
+        return disp
+
+    def apply_settings( self, settings ):
+        """ Set the current state (display, widget) from preferences if any """
+        for setting, val in settings.items():
+            if setting == "Outliers ON":
+                self.outlier_vis.setChecked( val ) 
+            if setting == "Track ON":
+                self.track_vis.setChecked( val ) 
+            if setting =="EventDisp ON":
+                self.event_disp.setChecked( val ) 
+            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()
+            if setting == "Ignore border":
+                self.ignore_borders.setChecked( val )
+            if setting == "Flag length":
+                self.check_length.setChecked( val )
+            if setting == "length":
+                self.min_length.setText( val )
+            if setting == "Check size":
+                self.check_size.setChecked( val )
+            if setting == "Check shape":
+                self.check_shape.setChecked( val )
+            if setting == "Get apparitions":
+                self.get_apparition.setChecked( val )
+            if setting == "Get disparitions":
+                self.get_disparition.setChecked( val )
+            if setting == "Threshold disparition":
+                self.threshold_disparition.setText( val )
+            if setting == "Min area":
+                self.min_area.setText( val )
+            if setting == "Max area":
+                self.max_area.setText( val )
+            if setting == "Current frame":
+                self.feat_onframe.setChecked( val )
+ 
+
+    def display_event_size(self):
+        """ Change the size of the point display """
+        size = int(self.event_size.value())
+        self.events.size = size
+        self.events.refresh()
+        #### Depend on event type, to update
+
+    ############### eventing functions
+    def get_crimes(self, sid):
+        """ For a given event, get its event_type(s) """
+        crimes = []
+        for feat in self.event_types.keys():
+            if sid in self.event_types.get(feat):
+                crimes.append(feat)
+        return crimes
+
+    def add_event_type(self, ind, sid, feature):
+        """ Add 1 to the event_type score for given feature """
+        #print(self.event_types)
+        if self.event_types.get(feature) is None:
+            self.event_types[feature] = []
+        self.event_types[feature].append(sid)
+        self.events.properties["score"][ind] = self.events.properties["score"][ind] + 1
+
+    def first_event(self, pos, label, featurename):
+        """ Addition of the first event (initialize all) """
+        ut.remove_layer(self.viewer, "Events")
+        features = {}
+        sid = self.new_event_id()
+        features["id"] = np.array([sid], dtype="uint16")
+        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.add_event_type(0, sid, featurename)
+        self.events.refresh()
+        self.update_nevents_display()
+
+    def add_event(self, pos, label, reason, symb="x", color="white", force=False, refresh=True):
+        """ Add a event to the list, evented by a feature """
+        if (not force) and (self.ignore_borders.isChecked()) and (self.border_cells is not None):
+            tframe = int(pos[0])
+            if label in self.border_cells[tframe]:
+                return
+
+        ## initialise if necessary
+        if len(self.events.data) <= 0:
+            self.first_event(pos, label, reason)
+            return
+        
+        self.events.selected_data = []
+       
+       ## look if already evented, then add the charge
+        num, sid = self.find_event(pos[0], label)
+        if num is not None:
+            ## event already in the list. For same crime ?
+            if self.event_types.get(reason) is not None:
+                if sid not in self.event_types[reason]:
+                    self.add_event_type(num, sid, reason)
+            else:
+                self.add_event_type(num, sid, reason)
+        else:
+            ## new event, add to the Point layer
+            ind = len(self.events.data)
+            sid = self.new_event_id()
+            self.events.add(pos)
+            self.events.properties["label"][ind] = label
+            self.events.properties["id"][ind] = sid
+            self.events.properties["score"][ind] = 0
+            self.add_event_type(ind, sid, reason)
+
+        self.events.symbol.flags.writeable = True
+        self.events.current_symbol = symb
+        self.events.current_face_color = color
+        if refresh:
+            self.refresh_events()
+
+    def refresh_events( self ):
+        """ Refresh event view and text """
+        self.events.refresh()
+        self.update_nevents_display()
+        self.reset_event_range()
+
+    def new_event_id(self):
+        """ Find the first unused id """
+        sid = 0
+        if self.events.properties.get("id") is None:
+            return 0
+        while sid in self.events.properties["id"]:
+            sid = sid + 1
+        return sid
+    
+    def reset_all_events(self):
+        """ Remove all event_types """
+        features = {}
+        pts = []
+        ut.remove_layer(self.viewer, "Events")
+        self.events = self.viewer.add_points( np.array(pts), properties=features, face_color="red", size = 10, symbol='x', name="Events", )
+        self.event_types = {}
+        self.update_nevents_display()
+        #self.update_nevents_display()
+
+    def reset_event_type(self, feature, frame):
+        """ Remove all event_types of given feature, for current frame or all if frame is None """
+        if self.event_types.get(feature) is None:
+            return
+        idlist = self.event_types[feature].copy()
+        for sid in idlist:
+            ind = self.index_from_id(sid)
+            if ind is not None:
+                if frame is not None:
+                    if int(self.events.data[ind][0]) == frame:
+                        self.event_types[feature].remove(sid)
+                        self.decrease_score(ind)
+                else:
+                    self.event_types[feature].remove(sid)
+                    self.decrease_score(ind)
+        self.events.refresh()
+        self.update_nevents_display()
+
+    def remove_event_types(self, sid):
+        """ Remove all event_types of given event id """
+        for listval in self.event_types.values():
+            if sid in listval:
+                listval.remove(sid)
+
+    def decrease_score(self, ind):
+        """ Decrease by one score of event at index ind. Delete it if reach 0"""
+        self.events.properties["score"][ind] = self.events.properties["score"][ind] - 1
+        if self.events.properties["score"][ind] == 0:
+            self.exonerate_one( ind, remove_division=False )
+            self.update_nevents_display()
+
+    def index_from_id(self, sid):
+        """ From event id, find the corresponding index in the properties array """
+        for ind, cid in enumerate(self.events.properties["id"]):
+            if cid == sid:
+                return ind
+        return None
+
+    def id_from_index( self, ind ):
+        """ From event index, returns it id """
+        return self.events.properties["id"][ind]
+
+    def find_event(self, frame, label):
+        """ Find if there is already a event at given frame and label """
+        events = self.events.data
+        events_lab = self.events.properties["label"]
+        for i, lab in enumerate(events_lab):
+            if lab == label:
+                if events[i][0] == frame:
+                    return i, self.events.properties["id"][i]
+        return None, None
+
+    def init_suggestion(self):
+        """ Initialize the layer that will contains propostion of tracks/segmentations """
+        suggestion = np.zeros(self.seglayer.data.shape, dtype="uint16")
+        self.suggestion = self.viewer.add_labels(suggestion, blending="additive", name="Suggestion")
+        
+        @self.seglayer.mouse_drag_callbacks.append
+        def click(layer, event):
+            if event.type == "mouse_press":
+                if 'Alt' in event.modifiers:
+                    if event.button == 1:
+                        pos = event.position
+                        # alt+left click accept suggestion under the mouse pointer (in all frames)
+                        self.accept_suggestion(pos)
+    
+    def accept_suggestion(self, pos):
+        """ Accept the modifications of the label at position pos (all the label) """
+        seglayer = self.viewer.layers["Segmentation"]
+        label = self.suggestion.data[tuple(map(int, pos))]
+        found = self.suggestion.data==label
+        self.exonerate( found, seglayer ) 
+        indices = np.argwhere( found )
+        ut.setNewLabel( seglayer, indices, label, add_frame=None )
+        self.suggestion.data[self.suggestion.data==label] = 0
+        self.suggestion.refresh()
+        self.update_nevents_display()
+    
+    def exonerate_one(self, ind, remove_division=True):
+        """ Remove one event at index ind """
+        self.events.selected_data = [ind]
+        sid = self.events.properties["id"][ind]
+        if (remove_division) and (ind in self.event_types["division"]):
+            self.epicure.tracking.remove_division( self.events.properties["label"][ind] )
+        self.events.remove_selected()
+        self.remove_event_types(sid)
+        
+    def clear_event(self):
+        """ Remove the current event """
+        num_event = int(self.event_num.value())
+        self.exonerate_one( num_event, remove_division=True )
+        self.update_nevents_display()
+
+    def exonerate_from_event(self, event):
+        """ Remove all events in the corresponding cell of position """
+        label = ut.getCellValue( self.seglayer, event )
+        if len(self.events.data) > 0:
+            for ind, lab in enumerate(self.events.properties["label"]):
+                if lab == label:
+                    if self.events.data[ind][0] == event.position[0]:      
+                        self.exonerate_one(ind, remove_division=True) 
+        self.update_nevents_display()
+
+    def exonerate(self, indices, seglayer):
+        """ Remove events that have been corrected/cleared """
+        seglabels = np.unique(seglayer.data[indices])
+        selected = []
+        if self.events.properties.get("label") is None:
+            return
+        for ind, lab in enumerate(self.events.properties["label"]):
+            if lab in seglabels:
+                ## label to remove from event list
+                selected.append(ind)
+        if len(selected) > 0:
+            self.events.selected_data = selected
+            self.events.remove_selected()
+            self.update_nevents_display()
+                
+
+    #######################################"
+    ## Outliers suggestion functions
+    def show_outlierBlock(self):
+        self.featOutliers.setVisible( self.outlier_vis.isChecked() )
+
+    def create_outliersBlock(self):
+        ''' Block interface of functions for error suggestions based on cell features '''
+        feat_layout = QVBoxLayout()
+        
+        self.feat_onframe = wid.add_check( check="Only current frame", checked=True, check_func=None, descr="Search for outliers only in current frame" )
+        feat_layout.addWidget(self.feat_onframe)
+        
+        ## area widget
+        tarea_layout, self.min_area, self.max_area = wid.min_button_max( btn="< Area (pix^2) <", btn_func=self.event_area_threshold, min_val="0", max_val="2000", descr="Look for cell which size is outside the given area range" )
+        feat_layout.addLayout( tarea_layout )
+        
+        ## solid widget
+        feat_solid_line, self.fsolid_out = wid.button_parameter_line( btn="Solidity outliers", btn_func=self.event_solidity, value="3.0", descr_btn="Search for outliers in solidity value", descr_value="Inter-quartiles range factor to consider outlier" )
+        feat_layout.addLayout( feat_solid_line )
+        
+        ## intensity widget
+        feat_inten_line, self.fintensity_out = wid.button_parameter_line( btn="Intensity cytoplasm/junction", btn_func=self.event_intensity, value="1.0", descr_btn="Search for outliers in intensity ratio", descr_value="Ratio of intensity above which the cell looks suspect" )
+        feat_layout.addLayout( feat_inten_line )
+        
+        ## tubeness widget
+        feat_tub_line, self.ftub_out = wid.button_parameter_line( btn="Tubeness cytoplasm/junction", btn_func=self.event_tubeness, value="1.0", descr_btn="Search for outliers in tubeness ratio", descr_value="Ratio of tubeness above which the cell looks suspect" )
+        feat_layout.addLayout( feat_tub_line )
+        
+        ## all features
+        self.featOutliers.setLayout(feat_layout)
+        self.featOutliers.setVisible( self.outlier_vis.isChecked() )
+    
+    def event_feature(self, featname, funcname ):
+        """ event in one frame or all frames the given feature """
+        onframe = self.feat_onframe.isChecked()
+        if onframe:
+            tframe = ut.current_frame(self.viewer)
+            self.reset_event_type(featname, tframe)
+            funcname(tframe)
+        else:
+            self.reset_event_type(featname, None)
+            for frame in range(self.seglayer.data.shape[0]):
+                funcname(frame)
+        self.update_display()
+        ut.set_active_layer( self.viewer, "Segmentation" )
+    
+    def inspect_outliers(self, tab, props, tuk, frame, feature):
+        q1 = np.quantile(tab, 0.25)
+        q3 = np.quantile(tab, 0.75)
+        qtuk = tuk * (q3-q1)
+        for sign in [1, -1]:
+            #thresh = np.mean(tab) + sign * np.std(tab)*tuk
+            if sign > 0:
+                thresh = q3 + qtuk
+            else:
+                thresh = q1 - qtuk
+            for i in np.where((tab-thresh)*sign>0)[0]:
+                position = ut.prop_to_pos( props[i], frame )
+                self.add_event( position, props[i].label, feature )
+    
+    def event_area_threshold(self):
+        """ Look for cell's area below/above a threshold """
+        self.event_feature( "area", self.event_area_threshold_oneframe )
+
+    def event_area_threshold_oneframe( self, tframe ):
+        """ Check if area is above/below given threshold """
+        minarea = int(self.min_area.text())
+        maxarea = int(self.max_area.text())
+        frame_props = self.epicure.get_frame_features( tframe )
+        for prop in frame_props:
+            if (prop.area < minarea) or (prop.area > maxarea):
+                position = ut.prop_to_pos( prop, tframe )
+                self.add_event( position, prop.label, "area" )
+
+
+    def event_area(self, state):
+        """ Look for outliers in term of cell area """
+        self.event_feature( "area", self.event_area_oneframe )
+    
+    def event_area_oneframe(self, frame):
+        seglayer = self.seglayer.data[frame]
+        props = regionprops(seglayer)
+        ncell = len(props)
+        areas = np.zeros((ncell,1), dtype="float")
+        for i, prop in enumerate(props):
+            if prop.label > 0:
+                areas[i] = prop.area
+        tuk = self.farea_out.value()
+        self.inspect_outliers(areas, props, tuk, frame, "area")
+
+    def event_solidity(self, state):
+        """ Look for outliers in term ofz cell solidity """
+        self.event_feature( "solidity", self.event_solidity_oneframe )
+
+    def event_solidity_oneframe(self, frame):
+        seglayer = self.seglayer.data[frame]
+        props = regionprops(seglayer)
+        ncell = len(props)
+        sols = np.zeros((ncell,1), dtype="float")
+        for i, prop in enumerate(props):
+            if prop.label > 0:
+                sols[i] = prop.solidity
+        tuk = float(self.fsolid_out.text())
+        self.inspect_outliers(sols, props, tuk, frame, "solidity")
+    
+    def event_intensity(self, state):
+        """ Look for abnormal intensity inside/periph ratio """
+        self.event_feature( "intensity", self.event_intensity_oneframe )
+    
+    def event_intensity_oneframe(self, frame):
+        seglayer = self.seglayer.data[frame]
+        intlayer = self.viewer.layers["Movie"].data[frame] 
+        props = regionprops(seglayer)
+        for i, prop in enumerate(props):
+            if prop.label > 0:
+                self.test_intensity( intlayer, prop, frame )
+    
+    def test_intensity(self, inten, prop, frame):
+        """ Test if intensity inside is much smaller than at periphery """
+        bbox = prop.bbox
+        intbb = inten[bbox[0]:bbox[2], bbox[1]:bbox[3]]
+        footprint = disk(radius=self.epicure.thickness)
+        inside = binary_erosion(prop.image, footprint)
+        ininten = np.mean(intbb*inside)
+        dil_img = binary_dilation(prop.image, footprint)
+        periph = dil_img^inside
+        periphint = np.mean(intbb*periph)
+        if (periphint<=0) or (ininten/periphint > float(self.fintensity_out.text())):
+            position = ( frame, int(prop.centroid[0]), int(prop.centroid[1]) )
+            self.add_event( position, prop.label, "intensity" )
+    
+    def event_tubeness(self, state):
+        """ Look for abnormal tubeness inside vs periph """
+        self.event_feature( "tubeness", self.event_tubeness_oneframe )
+    
+    def event_tubeness_oneframe(self, frame):
+        seglayer = self.seglayer.data[frame]
+        mov = self.viewer.layers["Movie"].data[frame]
+        sated = np.copy(mov)
+        sated = filters.sato(sated, black_ridges=False)
+        props = regionprops(seglayer)
+        for i, prop in enumerate(props):
+            if prop.label > 0:
+                self.test_tubeness( sated, prop, frame )
+
+    def test_tubeness(self, sated, prop, frame):
+        """ Test if tubeness inside is much smaller than tubeness on periph """
+        bbox = prop.bbox
+        satbb = sated[bbox[0]:bbox[2], bbox[1]:bbox[3]]
+        footprint = disk(radius=self.epicure.thickness)
+        inside = binary_erosion(prop.image, footprint)
+        intub = np.mean(satbb*inside)
+        periph = prop.image^inside
+        periphtub = np.mean(satbb*periph)
+        if periphtub <= 0:
+            position = ( frame, int(prop.centroid[0]), int(prop.centroid[1]) )
+            self.add_event( position, prop.label, "tubeness" )
+        else:
+            if intub/periphtub > float(self.ftub_out.text()):
+                position = ( frame, int(prop.centroid[0]), int(prop.centroid[1]) )
+                self.add_event( position, prop.label, "tubeness" )
+
+
+############# event based on track
+
+    def show_tracksBlock(self):
+        self.eventTrack.setVisible( self.track_vis.isChecked() )
+
+    def create_tracksBlock(self):
+        ''' Block interface of functions for error suggestions based on tracks '''
+        track_layout = QVBoxLayout()
+        
+        self.ignore_borders = wid.add_check( "Ignore cells on border", False, None, "When adding suspect, don't add it if the cell is touching the border" )
+        track_layout.addWidget(self.ignore_borders)
+        
+        ## Look for sudden appearance of tracks
+        self.get_apparition = wid.add_check( "Flag track apparition", True, None, "Add a suspect if a track appears in the middle of the movie (not on border)" )
+        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" )
+        track_layout.addLayout( disp_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)
+        
+        ## Variability in feature event_type
+        sizevar_line, self.check_size, self.size_variability = wid.check_value( check="Size variation", checkfunc=None, checked=False, value="1", descr="Add a suspect if the size of the cell varies suddenly in the track" )
+        track_layout.addLayout( sizevar_line )
+        shapevar_line, self.check_shape, self.shape_variability = wid.check_value( check="Shape variation", checkfunc=None, checked=False, value="2.0", descr="Add a suspect if the shape of the cell varies suddenly in the track" )
+        track_layout.addLayout( shapevar_line )
+
+        ## merge/split combinaisons 
+        track_btn = wid.add_button( btn="Inspect track", btn_func=self.inspect_tracks, descr="Start track analysis to look for suspects based on selected features" )
+        track_layout.addWidget(track_btn)
+        
+        ## all features
+        self.eventTrack.setLayout(track_layout)
+        self.eventTrack.setVisible( self.track_vis.isChecked() )
+
+    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-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_range()
+
+    def track_length(self):
+        """ Find all cells that are only in one frame """
+        max_len = int(self.min_length.text())
+        labels, lengths, positions = self.epicure.tracking.get_small_tracks( max_len )
+        for label, nframe, pos in zip(labels, lengths, positions):
+            if self.epicure.verbose > 2:
+                print("event track length "+str(nframe)+": "+str(label)+" frame "+str(pos[0]) )
+            self.add_event(pos, label, "track-length", refresh=False)
+        self.refresh_events()
+
+    def inspect_tracks(self):
+        """ 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.update(0)
+        self.reset_tracking_event()
+        progress_bar.update(1)
+        if self.ignore_borders.isChecked():
+            progress_bar.set_description("Identifying border cells")
+            self.get_border_cells()
+        progress_bar.update(2)
+        tracks = self.epicure.tracking.get_track_list()
+        if self.check_length.isChecked():
+            progress_bar.set_description("Identifying too small tracks")
+            self.track_length()
+        progress_bar.update(3)
+        progress_bar.set_description("Inspect tracks 2->1")
+        self.track_21()
+        progress_bar.update(4)
+        if (self.check_size.isChecked()) or self.check_shape.isChecked():
+            progress_bar.set_description("Inspect track features")
+            self.track_features()
+        progress_bar.update(5)
+        if self.get_apparition.isChecked():
+            progress_bar.set_description("Check new track apparition")
+            self.track_apparition( tracks )
+        progress_bar.update(6)
+        if self.get_disparition.isChecked():
+            progress_bar.set_description("Check track disparition")
+            self.track_disparition( tracks, progress_bar )
+        progress_bar.update(7)
+        progress_bar.close()
+        self.viewer.window._status_bar._toggle_activity_dock(False)
+        ut.set_active_layer( self.viewer, "Segmentation" )
+
+    def track_apparition( self, tracks ):
+        """ Check if some track appears suddenly (in the middle of the movie and not by division) """
+        start_time = time.time()
+        ## remove track on first frame
+        ctracks = list( set(tracks) - set( self.epicure.tracking.get_tracks_on_frame( 0 ) ) )
+        graph = self.epicure.tracking.graph
+        for i, track_id in enumerate( ctracks) :
+            fframe = self.epicure.tracking.get_first_frame( track_id )
+            ## If on the border, ignore
+            outside = self.epicure.cell_on_border( track_id, fframe )
+            if outside:
+                continue
+            ## Not on border, check if potential division
+            if (graph is not None) and (track_id in graph.keys()):
+                continue
+            ## event apparition
+            posxy = self.epicure.tracking.get_position( track_id, fframe )
+            if posxy is not None:
+                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.refresh_events()
+        if self.epicure.verbose > 1:
+            ut.show_duration( start_time, "Tracks apparition took " )
+                
+    def track_disparition( self, tracks, progress_bar ):
+        """ Check if some track disappears suddenly (in the middle of the movie and not by division) """
+        start_time = time.time()
+        ## Track disappears in the movie, not last frame
+        ctracks = list( set(tracks) - set( self.epicure.tracking.get_tracks_on_frame( self.epicure.nframes-1 ) ) )
+        graph = self.epicure.tracking.graph
+        threshold_area = float(self.threshold_disparition.text())
+        sub_bar = progress( total = len( ctracks ), desc="Check non last frame tracks", nest_under = progress_bar )
+        for i, track_id in enumerate( ctracks ):
+            sub_bar.update( i )
+            lframe = self.epicure.tracking.get_last_frame( track_id )
+            ## If on the border, ignore
+            outside = self.epicure.cell_on_border( track_id, lframe )
+            if outside:
+                continue
+            ## 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:
+                    continue
+            ## event disparition
+            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("Disappearing track: "+str(track_id)+" at frame "+str(lframe) )
+                self.add_event(pos, track_id, "tracking-disparition", refresh=False)
+        sub_bar.close()
+        self.refresh_events()
+        if self.epicure.verbose > 1:
+            ut.show_duration( start_time, "Tracks disparition took " )
+
+    def track_21(self):
+        """ Look for event track: 2->1 """
+        if self.epicure.tracking.tracklayer is None:
+            ut.show_error("No tracking done yet!")
+            return
+
+        graph = self.epicure.tracking.graph
+        if graph is not None:
+            for child, parent in graph.items():
+                ## 2->1, merge, event
+                if len(parent) == 2:
+                    onetwoone = False
+                    ## was it only one before ?
+                    if (parent[0] in graph.keys()) and (parent[1] in graph.keys()):
+                        if graph[parent[0]][0] == graph[parent[1]][0]:
+                            pos = self.epicure.tracking.get_mean_position([parent[0], parent[1]])
+                            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-*")
+                                onetwoone = True
+                
+                    if not onetwoone:
+                        pos = self.epicure.tracking.get_mean_position(child, only_first=True)     
+                        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)
+                        else:
+                            if self.epicure.verbose > 1:
+                                print("Something weird, "+str(child)+" mean position")
+
+        self.epicure.finish_update()
+        self.refresh_events()
+
+    def get_border_cells(self):
+        """ Return list of cells that are at the border (touching background) """
+        self.border_cells = dict()
+        for tframe in range(self.epicure.nframes):
+            img = self.epicure.seg[tframe]
+            self.border_cells[tframe] = self.get_border_cells_frame(img)      
+        
+    def get_border_cells_frame(self, imframe):
+        """ Return cells on border in current image """ 
+        height = self.epicure.imgshape2D[1]
+        width = self.epicure.imgshape2D[0]
+        labels = list( np.unique( imframe[ :, 0:2 ] ) )   ## top border
+        labels += list( np.unique( imframe[ :, (height-2): ] ) )   ## bottom border
+        labels += list( np.unique( imframe[ 0:2,] ) )   ## left border
+        labels += list( np.unique( imframe[ (width-2):,] ) )   ## right border
+        #graph = ut.connectivity_graph( imframe, distance=3 )
+        #adj_bg = []
+        #if 0 in graph.nodes:
+        #    adj_bg = list( graph.adj[0 ])
+        #return adj_bg
+        return labels
+
+    def get_divisions( self ):
+        """ Get and add divisions from the tracking graph """
+        self.reset_event_type( "division", frame=None )
+        graph = self.epicure.tracking.graph
+        divisions = {}
+        ## Go through the graph and fill all division by parents
+        if graph is not None:
+            for child, parent in graph.items():
+                ## 1 parent, potential division
+                if (isinstance(parent, int)) or (len(parent) == 1):
+                    if isinstance( parent, list ):
+                        par = parent[0]
+                    else:
+                        par = parent
+                    if par not in divisions:
+                        divisions[par] = [child]
+                    else:
+                        divisions[par].append(child)
+
+        ## Add all the divisions in the event list
+        for parent, childs in divisions.items():
+            indexes = self.epicure.tracking.get_track_indexes(childs)
+            if len(indexes) <= 0:
+                ## something wrong in the graph or in the tracks, ignore for now
+                continue
+            ## get the average first position of the childs just after division
+            pos = self.epicure.tracking.mean_position(indexes, only_first=True)     
+            self.add_event(pos, parent, "division", symb="o", color="#0055ffff", force=True)
+        ## Update display to show/hide the divisions
+        self.show_hide_divisions()
+        self.epicure.finish_update()
+
+    def show_hide_divisions( self ):
+        """ Show or hide division events """
+        self.show_subset_event( "division", self.show_divisions.isChecked() )
+
+    def show_hide_suspects( self ):
+        """ Show or hide suspect events """
+        self.show_subset_event( "suspect", self.show_suspects.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 """
+        indexes = self.epicure.tracking.get_index( [labela, labelb], frame )
+        indexes = indexes.flatten()
+        pos = self.epicure.tracking.mean_position( indexes )
+        self.events.selected_data = {}
+        if self.show_divisions.isChecked():
+            self.events.current_size = int(self.event_size.value())
+        else:
+            self.events.current_size = 0.1
+        self.add_event( pos, parent, "division", symb="o", color="#0055ffff", force=True )
+        self.events.selected_data = {}
+        self.events.current_size = int(self.event_size.value())
+        ## check if there are suspect events to remove, cleared by the division
+        if parent is not None:
+            ## check eventual parent event
+            num, sid = self.find_event(  pos[0]-1, parent )
+            if num is not None:
+                if self.is_end_event( sid ):
+                    ## the parent event correspond to a potential end of track, remove it
+                    ind = self.index_from_id( sid )
+                    self.exonerate_one( ind, remove_division=False )
+                    if self.epicure.verbose > 0:
+                        print( "Removed suspect event of parent cell "+str(parent)+" cleared by the division flag" )
+            ## check each child suspect if cleared by the new division 
+            for child in [labela, labelb]:
+                num, sid = self.find_event( pos[0], child )
+                if num is not None:
+                    if self.is_begin_event( sid ):
+                        ## the child event correspond to a potential begin of track, remove it
+                        ind = self.index_from_id( sid )
+                        self.exonerate_one( ind, remove_division=False )
+                        if self.epicure.verbose > 0:
+                            print( "Removed suspect event of daughter cell "+str(child)+" cleared by the division flag" )
+            self.update_nevents_display()
+
+
+    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_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"]
+        for event in beg_events:
+            if event in self.event_types:
+                if sid in self.event_types[event]:
+                    return True
+        return False
+
+    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"]
+        for event in end_events:
+            if event in self.event_types:
+                if sid in self.event_types[event]:
+                    return True
+        return False
+        
+    def track_features(self):
+        """ Look at outliers in track features """
+        track_ids = self.epicure.tracking.get_track_list()
+        features = []
+        featType = {}
+        if self.check_size.isChecked():
+            features = features + ["Area", "Perimeter"]
+            featType["Area"] = "size"
+            featType["Perimeter"] = "size"
+            size_factor = float(self.size_variability.text())
+        if self.check_shape.isChecked():
+            features = features + ["Eccentricity", "Solidity"]
+            featType["Eccentricity"] = "shape"
+            featType["Solidity"] = "shape"
+            shape_factor = float(self.shape_variability.text())
+        for tid in track_ids:
+            track_indexes = self.epicure.tracking.get_track_indexes( tid )
+            ## track should be long enough to make sense to look for outlier
+            if len(track_indexes) > 3:
+                track_feats = self.epicure.tracking.measure_features( tid, features )
+                for feature, values in track_feats.items():
+                    if featType[feature] == "size":
+                        factor = size_factor
+                    if featType[feature] == "shape":
+                        factor = shape_factor
+                    outliers = self.find_jump( values, factor=factor )
+                    for out in outliers:
+                        tdata = self.epicure.tracking.get_frame_data( tid, out )
+                        if self.epicure.verbose > 1:
+                            print("event track "+feature+": "+str(tdata[0])+" "+" frame "+str(tdata[1]) )
+                        self.add_event(tdata[1:4], tid, "track_"+featType[feature])
+
+    def find_jump( self, tab, factor=1 ):
+        """ Detect brutal jump in the values """
+        jumps = []
+        tab = np.array(tab)
+        diff = tab[:(len(tab)-2)] - 2*tab[1:(len(tab)-1)] + tab[2:]
+        diff = [(tab[1]-tab[0])] + diff.tolist() + [tab[len(tab)-1]-tab[len(tab)-2]] 
+        avg = (tab[:(len(tab)-2)] + tab[2:])/2
+        avg = [(tab[1]+tab[0])/2] + avg.tolist() + [(tab[len(tab)-1]+tab[len(tab)-2])/2]
+        eps = 0.000000001
+        diff = np.array(diff, dtype=np.float32)
+        avg = np.array(avg, dtype=np.float32)
+        diff = abs(diff+eps)/(avg+eps)
+        ## keep only local max above threshold
+        for i, diffy in enumerate(diff):
+            if (i>0) and (i<len(diff)-1):
+                if diffy > factor:
+                    if (diffy > diff[i-1]) and (diffy > diff[i+1]):
+                        jumps.append(i)
+            else:
+                if diffy > factor:
+                    jumps.append(i)
+        #jumps = (np.where( diff > factor )[0]).tolist()
+        return jumps
+
+    def find_outliers_tuk( self, tab, factor=3, below=True, above=True ):
+        """ Returns index of outliers from Tukey's like test """
+        q1 = np.quantile(tab, 0.2)
+        q3 = np.quantile(tab, 0.8)
+        qtuk = factor * (q3-q1)
+        outliers = []
+        if below:
+            outliers = outliers + (np.where((tab-q1+qtuk)<0)[0]).tolist()
+        if above:
+            outliers = outliers + (np.where((tab-q3-qtuk)>0)[0]).tolist()
+        return outliers
+
+    def weirdo_area(self):
+        """ look at area trajectory for outliers """
+        track_df = self.epicure.tracking.track_df
+        for tid in np.unique(track_df["track_id"]):
+            rows = track_df[track_df["track_id"]==tid].copy()
+            if len(rows) >= 3:
+                rows["smooth"] = rows.area.rolling(self.win_size, min_periods=1).mean()
+                rows["diff"] = (rows["area"] - rows["smooth"]).abs()
+                rows["diff"] = rows["diff"].div(rows["smooth"])
+                if self.epicure.verbose > 2:
+                    print(rows)
+
+
diff --git a/src/epicure/laptrack_centroids.py b/src/epicure/laptrack_centroids.py
index b019852c8de7acdc6c0791b7486b715c31eb6b04..dcd0e0817ff7b1cb06977fd10c576967bf17069b 100644
--- a/src/epicure/laptrack_centroids.py
+++ b/src/epicure/laptrack_centroids.py
@@ -37,7 +37,7 @@ class LaptrackCentroids():
         self.penal_solidity = 0
         self.track = track
         self.epicure = epic
-        self.suspecting = False
+        self.inspecting = False
         self.suggesting = False
         self.region_properties = ["label", "frame", "centroid-0", "centroid-1", "area", "solidity"]
 
@@ -129,14 +129,14 @@ class LaptrackCentroids():
         ut.napari_info("Starting tracking with LapTrack centroids metrics...")
         return self.perform_track( regionprops_df )
 
-    def suspect_oneframe(self, graph, trackdf):
+    def inspect_oneframe(self, graph, trackdf):
         for track in np.unique(trackdf["track_id"]):
             tr = trackdf[trackdf["track_id"] == track]
             ## track is only on one frame, suspect
             if len(np.unique(tr["frame"])) == 1:
                 # trackid + 1 as trackid starts as 0
                 pos = (tr.iloc[0]["frame"], int(tr.iloc[0]["centroid-0"]), int(tr.iloc[0]["centroid-1"]))
-                self.epicure.suspecting.add_suspect( pos, track+1, "tracking" )
+                self.epicure.inspecting.add_event( pos, track+1, "tracking" )
                 if self.track.suggesting:
                     if track in graph.keys():
                         sisters = []
diff --git a/src/epicure/laptrack_overlaps.py b/src/epicure/laptrack_overlaps.py
index 192ec0d08c2003965e44c7933f771107963409e0..8d372e1dba7e7c89e1ead4a50d07e3d9cb743aa1 100644
--- a/src/epicure/laptrack_overlaps.py
+++ b/src/epicure/laptrack_overlaps.py
@@ -28,7 +28,7 @@ class LaptrackOverlaps():
         self.merging_cost = 1
         self.track = track
         self.epicure = epic
-        self.suspecting = False
+        self.inspecting = False
         self.suggesting = False
         
 
@@ -85,14 +85,14 @@ class LaptrackOverlaps():
         return self.perform_track( labels )
 
 
-    def suspect_oneframe(self, graph, trackdf):
+    def inspect_oneframe(self, graph, trackdf):
         for track in np.unique(trackdf["track_id"]):
             tr = trackdf[trackdf["track_id"] == track]
             ## track is only on one frame, suspect
             if len(np.unique(tr["frame"])) == 1:
                 # trackid + 1 as trackid starts as 0
                 pos = (tr.iloc[0]["frame"], int(tr.iloc[0]["centroid-0"]), int(tr.iloc[0]["centroid-1"]))
-                self.epicure.suspecting.add_suspect( pos, track+1, "tracking" )
+                self.epicure.inspecting.add_event( pos, track+1, "tracking" )
                 if self.track.suggesting:
                     if track in graph.keys():
                         sisters = []
diff --git a/src/epicure/napari.yaml b/src/epicure/napari.yaml
index 5d1cba8c18f0e8062c737bf2a902734ac9234c79..ef1ca2e3754420399a19165f7de2ef371cb3f0ef 100644
--- a/src/epicure/napari.yaml
+++ b/src/epicure/napari.yaml
@@ -12,6 +12,9 @@ contributions:
     - id: epicure.doc
       title: Documentation
       python_name: epicure.Utils:show_documentation
+    - id: epicure.preferences
+      title: Preferences
+      python_name: epicure.preferences:edit_preferences
   widgets:
     - command: epicure.start
       display_name: Start EpiCure
@@ -20,3 +23,5 @@ contributions:
     - command: epicure.doc
       display_name: Open EpiCure documentation
       autogenerate: false
+    - command: epicure.preferences
+      display_name: Edit Preferences
diff --git a/src/epicure/outputing.py b/src/epicure/outputing.py
index 162b2ba713d495dac7f67be8e30ed670552435d8..39c8bd1ae095d4c8b18f2b9381d67b3f1817aad2 100644
--- a/src/epicure/outputing.py
+++ b/src/epicure/outputing.py
@@ -1,19 +1,16 @@
-from qtpy.QtWidgets import QApplication, QPushButton, QHBoxLayout, QVBoxLayout, QWidget, QGroupBox, QLineEdit, QComboBox, QLabel, QSpinBox, QCheckBox, QTableWidget, QTableWidgetItem, QGridLayout
+from qtpy.QtWidgets import QHBoxLayout, QVBoxLayout, QWidget, QGroupBox, QComboBox, QLabel, QCheckBox, QTableWidget, QTableWidgetItem, QGridLayout, QListWidget
+from qtpy.QtWidgets import QAbstractItemView as aiv
 from qtpy.QtCore import Qt
-from napari import Viewer
 import pandas as pand
 import numpy as np
-import epicure.Utils as ut
 import roifile
-from napari.utils.notifications import show_info
-from skimage.measure import find_contours, regionprops_table
 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
-from magicgui import magicgui
-
+import epicure.Utils as ut
+import epicure.epiwidgets as wid
 import matplotlib as mpl
 from matplotlib.backends.backend_qt5agg import FigureCanvas
 from matplotlib.figure import Figure
@@ -23,6 +20,7 @@ try:
     from skimage.graph import RAG
 except:
     from skimage.future.graph import RAG  ## older version of scikit-image
+    
 
 class Outputing(QWidget):
 
@@ -35,9 +33,16 @@ 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"]
+        self.output_options = ["", "Export to extern plugins", "Export segmentations", "Measure cell features", "Measure track features", "Export events"]
         self.tplots = None
         
+        chanlist = ["Movie"]
+        if self.epicure.others is not None:
+            for chan in self.epicure.others_chanlist:
+                chanlist.append( "MovieChannel_"+str(chan) )
+        self.cell_features = CellFeatures( chanlist )
+        self.event_types = EventTypes() 
+        
         all_layout = QVBoxLayout()
         
         self.choose_output = QComboBox()
@@ -48,12 +53,7 @@ class Outputing(QWidget):
         
         ## Choice of active selection
         layout = QVBoxLayout()
-        selection_layout = QHBoxLayout()
-        selection_lab = QLabel()
-        selection_lab.setText("Apply on")
-        selection_layout.addWidget(selection_lab)
-        self.output_mode = QComboBox()
-        selection_layout.addWidget(self.output_mode)
+        selection_layout, self.output_mode = wid.list_line( "Apply on", descr="Choose on which cell(s) to do the action", func=None )
         for sel in self.selection_choices:
             self.output_mode.addItem(sel)
         all_layout.addLayout(selection_layout)
@@ -61,140 +61,135 @@ class Outputing(QWidget):
         ## Choice of interface
         self.export_group = QGroupBox("Export to extern plugins")
         export_layout = QVBoxLayout()
-        griot_btn = QPushButton("Current frame to Griottes", parent=self)
+        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)
-        griot_btn.clicked.connect(self.to_griot)
-        ncp_btn = QPushButton("Current frame to Cluster-Plotter", parent=self)
+        ncp_btn = wid.add_button( "Current frame to Cluster-Plotter", self.to_ncp, "Launch (in new window) cluster-plotter plugin on current frame" )
         export_layout.addWidget(ncp_btn)
-        ncp_btn.clicked.connect(self.to_ncp)
         self.export_group.setLayout(export_layout)
-        self.export_group.setCheckable(True)
-        self.export_group.clicked.connect(self.show_export_group)
         all_layout.addWidget(self.export_group)
         
         ## Option to export segmentation results
-        self.export_seg_group = QGroupBox(self.output_options[1])
-        self.save_rois = QPushButton("Save ROI(s)", parent=self)
-        layout.addWidget(self.save_rois)
-        self.save_rois.clicked.connect(self.roi_out)
-        
-        self.save_seg = QPushButton("Save segmentation(s)", parent=self)
-        layout.addWidget(self.save_seg)
-        self.save_seg.clicked.connect(self.save_segmentation)
-        
-        self.save_skel = QPushButton("Save Skeleton(s)", parent=self)
-        layout.addWidget(self.save_skel)
-        self.save_skel.clicked.connect(self.save_skeleton)
+        self.export_seg_group = QGroupBox(self.output_options[2])
+        layout = QVBoxLayout()
+        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" )
+        self.save_choice.addItem( "skeleton" )
+        layout.addLayout( save_line )
 
-        self.export_seg_group.setCheckable(True)
-        self.export_seg_group.clicked.connect(self.show_export_seg_group)
         self.export_seg_group.setLayout(layout)
-        self.export_seg_group.hide()
         all_layout.addWidget(self.export_seg_group)
         
         #### Features group
-        self.feature_group = QGroupBox(self.output_options[2])
-        self.feature_group.setCheckable(True)
+        self.feature_group = QGroupBox(self.output_options[3])
         featlayout = QVBoxLayout()
-        self.feature_shape_cbox = QCheckBox(text="Shape features")
-        self.feature_intensity_cbox = QCheckBox(text="Intensity features")
-        self.measure_other_chanels_cbox = QCheckBox(text="Intensity in other chanels")
-        self.feature_graph_cbox = QCheckBox(text="Neighboring features")
-        featlayout.addWidget(self.feature_shape_cbox)
-        featlayout.addWidget(self.feature_intensity_cbox)
-        featlayout.addWidget(self.measure_other_chanels_cbox)
-        featlayout.addWidget(self.feature_graph_cbox)
-        self.feature_table = QPushButton("Create features table", parent=self)
+        
+        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)
+
+        self.feature_table = wid.add_button( "Create features table", self.show_table, "Measure the selected features and display it as a clickable table" )
         featlayout.addWidget(self.feature_table)
-        self.feature_table.clicked.connect(self.show_table)
         self.featTable = FeaturesTable(self.viewer, self.epicure)
         featlayout.addWidget(self.featTable)
         
-        self.temp_graph = QPushButton("Table to temporal graphs", parent=self)
+        ######## Temporal option  
+        self.temp_graph = wid.add_button( "Table to temporal graphs", self.temporal_graphs, "Open a plot interface of measured features temporal evolution" )
         featlayout.addWidget(self.temp_graph)
-        self.temp_graph.clicked.connect(self.temporal_graphs)
         self.temp_graph.setEnabled(False)
-        
-        featmap = QHBoxLayout()
-        featmap_lab = QLabel()
-        featmap_lab.setText("Draw feature map:")
-        featmap.addWidget(featmap_lab)
-        self.show_feature_map = QComboBox()
-        featmap.addWidget(self.show_feature_map)
-        self.show_feature_map.currentIndexChanged.connect(self.show_feature)
+       
+        ######## Drawing option
+        featmap, self.show_feature_map = wid.list_line( "Draw feature map:", descr="Add a layer with the cells colored by the selected feature value", func=self.show_feature )
         featlayout.addLayout(featmap)
-        
-        self.save_table = QPushButton("Save features table", parent=self)
+        orienbtn = wid.add_button( "Draw cell orientation", self.draw_orientation, "Add a layer with each cell main axis orientation and length " )
+        featlayout.addWidget( orienbtn )
+
+        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)
-        self.save_table.clicked.connect(self.save_measure_features)
-        if (self.epicure.others is None):
-            self.measure_other_chanels_cbox.hide()
         
         self.feature_group.setLayout(featlayout)
-        self.feature_group.clicked.connect(self.show_feature_group)
         self.feature_group.hide()
         all_layout.addWidget(self.feature_group)
 
         ## Track features
-        self.trackfeat_group = QGroupBox(self.output_options[3])
-        self.trackfeat_group.setCheckable(True)
+        self.trackfeat_group = QGroupBox(self.output_options[4])
         trackfeatlayout = QVBoxLayout()
-        self.trackfeat_table = QPushButton("Track features table", parent=self)
+        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.trackfeat_table.clicked.connect(self.show_trackfeature_table)
         self.trackTable = FeaturesTable(self.viewer, self.epicure)
         trackfeatlayout.addWidget(self.trackTable)
         
         self.trackfeat_group.setLayout(trackfeatlayout)
-        self.trackfeat_group.clicked.connect(self.show_trackfeature_group)
         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()
+        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" )
+        elayout.addLayout( save_evt_line )
+
+        self.export_event_group.setLayout( elayout )
+        self.export_event_group.hide()
+        all_layout.addWidget( self.export_event_group )
+        
         
         ## Finished
         self.setLayout(all_layout)
-        self.setStyleSheet('QGroupBox {color: grey; background-color: rgb(35,45,50)} ')
+        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 """
+        disp = {}
+        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 )
+        return disp
+
+    def apply_settings( self, settings ):
+        """ Set the current state of the widget from preferences if any """
+        for setting, val in settings.items():
+            if setting == "Apply on":
+                self.output_mode.setCurrentText( val )
+            if setting == "Current option":
+                self.choose_output.setCurrentText( val )
+            
+        self.cell_features.apply_settings( settings )
+        self.event_types.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 )
+        else:
+            print(event+" not found in possible event types to export")
 
     def show_output_option(self):
         """ Show selected output panel """
         cur_option = self.choose_output.currentText()
-        if cur_option == "Export to extern plugins":
-            self.export_group.setChecked(True)
-            self.export_group.show()
-        if cur_option == "Export segmentations":
-            self.export_seg_group.setChecked(True)
-            self.export_seg_group.show()
-        if cur_option == "Measure cell features":
-            self.feature_group.setChecked(True)
-            self.feature_group.show()
-        if cur_option == "Measure track features":
-            self.trackfeat_group.setChecked(True)
-            self.trackfeat_group.show()
-    
-    def show_export_group(self):
-        """ Show/Hide export group """
-        if not self.export_group.isChecked():
-            self.export_group.setChecked(True)
-            self.export_group.hide()
-    
-    def show_export_seg_group(self):
-        """ Show/Hide export segmentaion group """
-        if not self.export_seg_group.isChecked():
-            self.export_seg_group.setChecked(True)
-            self.export_seg_group.hide()
-    
-    def show_feature_group(self):
-        """ Show/Hide feature cell group """
-        if not self.feature_group.isChecked():
-            self.feature_group.setChecked(True)
-            self.feature_group.hide()
-    
-    def show_trackfeature_group(self):
-        """ Show/Hide feature cell group """
-        if not self.trackfeat_group.isChecked():
-            self.trackfeat_group.setChecked(True)
-            self.trackfeat_group.hide()
+        self.export_group.setVisible( cur_option == "Export to extern plugins" )
+        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" )
 
+    def get_current_labels( self ):
+        """ Get the cell labels to process according to current selection of apply on"""
+        if self.output_mode.currentText() == "Only selected cell": 
+            lab = self.epicure.seglayer.selected_label
+            return [lab]
+        if self.output_mode.currentText() == "All cells": 
+            return self.epicure.get_labels()
+        else:
+            group = self.output_mode.currentText()
+            label_group = self.epicure.groups[group]
+            return label_group
 
+            
     def get_selection_name(self):
         if self.output_mode.currentText() == "Only selected cell": 
             lab = self.epicure.seglayer.selected_label
@@ -208,34 +203,12 @@ class Outputing(QWidget):
     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() :
-            show_info("Create/update the table before")
+            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')
-        show_info("Measures saved in "+outfile)
-
-    def roi_out(self):
-        """ Save ROI of cell contours in zip file by cell """
-        if self.output_mode.currentText() == "Only selected cell": 
-            lab = self.seglayer.selected_label
-            self.save_one_roi(lab)
-            show_info("Cell "+str(lab)+" saved to Fiji ROI")
-            return
-        else:
-            if self.output_mode.currentText() == "All cells":
-                ncells = 0
-                for lab in np.unique(self.epicure.seglayer.data):
-                    self.save_one_roi(lab)
-                    ncells += 1
-                show_info(str(ncells)+" cells saved to Fiji ROIs")
-            else:
-                ncells = 0
-                group = self.output_mode.currentText()
-                label_group = self.epicure.groups[group]
-                for lab in label_group:
-                    self.save_one_roi(lab)
-                    ncells += 1
-                show_info(str(ncells)+" cells saved to Fiji ROIs")
+        if self.epicure.verbose > 0:
+            ut.show_info("Measures saved in "+outfile)
 
     def save_one_roi(self, lab):
         """ Save the Rois of cell with label lab """
@@ -245,7 +218,7 @@ class Outputing(QWidget):
             ## add 2D case
             for iframe, frame in enumerate(keep):
                 if np.sum(frame) > 0:
-                    contour = find_contours(frame)
+                    contour = ut.get_contours(frame)
                     roi = self.create_roi(contour[0], iframe, lab)
                     rois.append(roi)
 
@@ -272,59 +245,69 @@ class Outputing(QWidget):
         return croi
     
     def save_segmentation( self ):
-        """ Save label movies of current output selection """
+        """ Save current segmentation in selected format """
         if self.output_mode.currentText() == "Only selected cell": 
+            ## output only the selected cell
             lab = self.seglayer.selected_label
-            tosave = np.zeros(self.seglayer.data.shape, dtype=self.epicure.dtype)
-            if np.sum(self.seglayer.data==lab) > 0:
-                tosave[self.seglayer.data==lab] = lab
-            endname = "_cell_"+str(lab)+".tif"
-        else: 
-            endname = "_checked_cells.tif"
-            if self.output_mode.currentText() == "All cells": 
-                tosave = self.seglayer.data
-                endname = "_labels.tif"
+            if self.save_choice.currentText() == "ROI":
+                self.save_one_roi(lab)
+                if self.epicure.verbose > 0:
+                    ut.show_info("Cell "+str(lab)+" saved to Fiji ROI")
+                return
             else:
                 tosave = np.zeros(self.seglayer.data.shape, dtype=self.epicure.dtype)
-                endname = "_"+self.output_mode.currentText()+".tif"
-                ncells = 0
-                group = self.output_mode.currentText()
-                label_group = self.epicure.groups[group]
-                for lab in label_group:
+                if np.sum(self.seglayer.data==lab) > 0:
                     tosave[self.seglayer.data==lab] = lab
-
-        outname = os.path.join( self.epicure.outdir, self.epicure.imgname+endname )
-        ut.writeTif(tosave, outname, self.epicure.scale, 'float32', what="Segmentation")
-
-    def save_skeleton( self ):
-        """ Save skeleton movies of current output selection """
-        if self.output_mode.currentText() == "Only selected cell": 
-            lab = self.seglayer.selected_label
-            tosave = np.zeros(self.seglayer.data.shape, dtype=self.epicure.dtype)
-            if np.sum(self.seglayer.data==lab) > 0:
-                tosave[self.seglayer.data==lab] = lab
-            endname = "_skeleton_cell_"+str(lab)+".tif"
-        else: 
-            endname = "_checked_cells.tif"
-            if self.output_mode.currentText() == "All cells": 
+                endname = "_"+self.save_choice.currentText()+"_"+str(lab)+".tif"
+        else:
+            ## output all cells
+            if self.output_mode.currentText() == "All cells":
+                if self.save_choice.currentText() == "ROI":
+                    self.save_all_rois()
+                    return
                 tosave = self.seglayer.data
-                endname = "_skeleton.tif"
+                endname = "_"+self.save_choice.currentText()+".tif"
             else:
-                tosave = np.zeros(self.seglayer.data.shape, dtype=self.epicure.dtype)
-                endname = "_skeleton_"+self.output_mode.currentText()+".tif"
-                ncells = 0
+                ## or output only selected group
                 group = self.output_mode.currentText()
                 label_group = self.epicure.groups[group]
+                if self.save_choice.currentText() == "ROI":
+                    ncells = 0
+                    for lab in label_group:
+                        self.save_one_roi(lab)
+                        ncells += 1
+                    if self.epicure.verbose > 0:
+                        ut.show_info(str(ncells)+" cells saved to Fiji ROIs")
+                    return
+                tosave = np.zeros(self.seglayer.data.shape, dtype=self.epicure.dtype)
+                endname = "_"+self.save_choice.currentText()+"_"+self.output_mode.currentText()+".tif"
                 for lab in label_group:
                     tosave[self.seglayer.data==lab] = lab
-
-        tosave = ut.get_skeleton( tosave, verbose=self.epicure.verbose )
+        
+        ## save filled image (for label or skeleton) to file
         outname = os.path.join( self.epicure.outdir, self.epicure.imgname+endname )
-        ut.writeTif( tosave, outname, self.epicure.scale, 'uint8', what="Skeleton" )
+        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" )
+        else:
+            ut.writeTif(tosave, outname, self.epicure.scale_xy, 'float32', what="Segmentation")
+                
+    def save_all_rois( self ):
+        """ Save all cells to ROI format """
+        ncells = 0
+        for lab in np.unique(self.epicure.seglayer.data):
+            self.save_one_roi(lab)
+            ncells += 1
+        if self.epicure.verbose > 0:
+            ut.show_info(str(ncells)+" cells saved to Fiji ROIs")
+
+    def choose_features( self ):
+        """ Pop-up widget to choose the features to measure """
+        self.cell_features.choose()
 
     def measure_features(self):
         """ Measure features and put them to table """
-        def intensities_inside_outside(regionmask, intensity):
+        def intensity_junction_cytoplasm(regionmask, intensity):
             """ Measure the intensity only on the contour of regionmask """
             footprint = disk(radius=self.epicure.thickness)
             inside = binary_erosion(regionmask, footprint)
@@ -352,60 +335,74 @@ class Outputing(QWidget):
                 for lab in label_group:
                     meas[self.epicure.seglayer.data==lab] = lab
             
-        properties = ["label", "area", "centroid"]
+        properties, other_features, int_feat, int_extrafeat = self.cell_features.get_features()
+        do_channels = self.cell_features.get_channels()
+        ## prepare intensity extra properties if necessary
+        extra_prop = []
+        if "intensity_junction_cytoplasm" in int_extrafeat:
+            extra_prop = extra_prop + [intensity_junction_cytoplasm]
+
         extra_properties = []
-        if self.feature_shape_cbox.isChecked():
-            properties = properties + ["area_convex", "axis_major_length", "axis_minor_length", "feret_diameter_max", "equivalent_diameter_area", "eccentricity", "orientation", "perimeter", "solidity"]
-        if self.feature_intensity_cbox.isChecked():
-            properties = properties + ["intensity_mean", "intensity_min", "intensity_max"]
-            extra_properties = extra_properties + [intensities_inside_outside]
+        if (do_channels is not None) and ("Movie" in do_channels):
+            properties = properties + int_feat
+            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, iframe )
+            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)])
 
-        if "intensities_inside_outside-0" in self.table.keys():
-            self.table = self.table.rename(columns={"intensities_inside_outside-0": "intensity_cytoplasm", "intensities_inside_outside-1":"intensity_junction"})
+        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())
         self.viewer.window._status_bar._toggle_activity_dock(False)
-        show_info("Features measured in "+"{:.3f}".format((time.time()-start_time)/60)+" min")
+        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, frame):
+    def measure_one_frame(self, img, properties, extra_properties, other_features, channels, int_feat, int_extrafeat, frame):
         """ Measure on one frame """
         if frame is not None:
             intimg = self.movlayer.data[frame]
         else:
             intimg = self.movlayer.data
-        frame_table = regionprops_table(img, intensity_image=intimg, properties=properties, extra_properties=extra_properties)
+        frame_table = ut.labels_table( img, intensity_image=intimg, properties=properties, extra_properties=extra_properties )
         ndata = len(frame_table["label"])
         if frame is not None:
             frame_table["frame"] = np.repeat(frame, ndata)
+
         ## add info of the cell group
-        frame_group = self.epicure.get_groups(list(frame_table["label"]), numeric=False)
-        frame_table["group"] = frame_group
+        if "group" in other_features:
+            frame_group = self.epicure.get_groups(list(frame_table["label"]), numeric=False)
+            frame_table["group"] = frame_group
 
         ### Measure intensity features in other chanels if option is on
-        if self.measure_other_chanels_cbox.isChecked():
-            prop = ["intensity_mean", "intensity_min", "intensity_max"]
-            extra_prop = extra_properties
-            for ochan, oimg in zip(self.epicure.others_chanlist, self.epicure.others):
+        if (channels is not None):
+            for chan in channels:
+                ## if it's movie, already measured in the general measure
+                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]
                 else:
                     intimg = oimg
-                frame_tab = regionprops_table(img, intensity_image=intimg, properties=prop, extra_properties=extra_prop)
-                for add_prop in prop:
-                    frame_table[add_prop+"_Chanel"+str(ochan)] = frame_tab[add_prop]
-                if "intensities_inside_outside-0" in frame_tab.keys():
-                    frame_table["intensity_cytoplasm_Chanel"+str(ochan)] = frame_tab["intensities_inside_outside-0"]
-                    frame_table["intensity_junction_Chanel"+str(ochan)] = frame_tab["intensities_inside_outside-1"]
+                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 "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"]
 
         
         ## add features of neighbors relationship with graph
-        if self.feature_graph_cbox.isChecked():
+        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 = []
@@ -413,15 +410,19 @@ class Outputing(QWidget):
                 adj_bg = list(graph.adj[0])
                 graph.remove_node(0)
             
-            frame_table["NbNeighbors"] = np.repeat(-1, ndata)
-            frame_table["External"] = np.repeat(-1, ndata)
+            if do_neighbor:
+                frame_table["NbNeighbors"] = np.repeat(-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)
-                frame_table["NbNeighbors"][rlabel] = nneighbor
-                frame_table["External"][rlabel] = outer
+                if do_neighbor:
+                    frame_table["NbNeighbors"][rlabel] = nneighbor
+                if do_border:
+                    frame_table["Border"][rlabel] = outer
 
         return frame_table
 
@@ -486,21 +487,70 @@ class Outputing(QWidget):
 
     def draw_map(self, labels, values, frames, featname):
         """ Add image layer of values by label """
+        ## special feature: orientation, draw the axis instead
         self.viewer.window._status_bar._toggle_activity_dock(True)
         mapfeat = np.empty(self.epicure.seg.shape, dtype="float16")
         mapfeat[:] = np.nan
-        for ind, lab in progress(enumerate(labels)):
-            if frames is not None:
-                frame = frames[ind]
+        if frames is not None:
+            for frame, lab, val in progress(zip(frames, labels, values)):
                 cell = self.seglayer.data[frame]==lab
-                (mapfeat[frame])[cell] = values[ind]
-            else:
+                (mapfeat[frame])[cell] = val
+        else:
+            for lab, val in progress(zip(labels, values)):
                 cell = self.seglayer.data==lab
-                mapfeat[cell] = values[ind]
+                mapfeat[cell] = val
         ut.remove_layer(self.viewer, "Map_"+featname)
         self.viewer.add_image(mapfeat, name="Map_"+featname)
         self.viewer.window._status_bar._toggle_activity_dock(False)
 
+    def draw_orientation( self ):
+        """ Display the cells orientation axis in a new layer """
+        ## check that necessary features are measured
+        ut.remove_layer( self.viewer, "CellOrientation" )
+        feats = ["centroid-0", "centroid-1", "orientation"]
+        if self.table is None:
+            print("Features centroid and orientation necessary to draw orientation, but are not measured yet")
+            return
+        for feat in feats:
+            if feat not in self.table.keys():
+                print("Feature "+feat+" necessary to draw orientation, but was not measured")
+                return
+        ## ok, can work now
+        self.viewer.window._status_bar._toggle_activity_dock(True)
+
+        ## get the coordinates of the axis lines by getting the cell centroid, main orientation
+        xs = np.array( self.table["centroid-0"] )
+        ys = np.array( self.table["centroid-1"] )
+        angles = np.array( self.table["orientation"] )
+        lens = np.array( [10]*len(angles) )
+        oriens = np.zeros( (self.epicure.seg.shape), dtype="uint8" )
+
+        ## draw axis length depending on the eccentricity
+        if "eccentricity" in self.table.keys():
+            lens = np.array(self.table["eccentricity"]*16)             
+        
+        if "frame" in self.table:
+            frames = np.array( self.table["frame"] ).astype(int)
+        else:
+            frames = np.array( [0]*len(angles) )
+
+        ## draw the lines in between the two extreme points (using Shape layer is too slow on display for big movies)
+        npts = 30
+        xmax = oriens.shape[1]-1
+        ymax = oriens.shape[2]-1
+        for i in range(npts):
+            xas = np.clip(xs - lens/2 * np.cos( angles ) * i/float(npts), 0, xmax).astype(int)
+            xbs = np.clip(xs + lens/2 * np.cos( angles ) * i/float(npts), 0, xmax).astype(int)
+            yas = np.clip(ys - lens/2 * np.sin( angles ) * i/float(npts), 0, ymax).astype(int)
+            ybs = np.clip(ys + lens/2 * np.sin( angles ) * i/float(npts), 0, ymax).astype(int)
+            oriens[ (frames, xas, yas) ] = 255
+            oriens[ (frames, xbs, ybs) ] = 255
+        
+        self.viewer.add_image( oriens, name="CellOrientation", blending="additive", opacity=1 )
+        self.viewer.window._status_bar._toggle_activity_dock(False)
+
+    ################### Export to other plugins
+
     def to_griot(self):
         """ Export current frame to new viewer and makes it ready for Griotte plugin """
         try:
@@ -557,12 +607,15 @@ class Outputing(QWidget):
         gview.window.add_dock_widget(ncp.ClusteringWidget(gview))
         gview.window.add_dock_widget(ncp.PlotterWidget(gview))
 
+    ################### Temporal graphs
+
     def temporal_graphs(self):
         """ New window with temporal graph of the current table selection """
         #self.temporal_viewer = napari.Viewer()
-        self.tplots = TemporalPlots(self.viewer)
+        self.tplots = TemporalPlots( self.viewer, self.epicure )
         self.tplots.setTable(self.table)
-        self.plot_wid = self.viewer.window.add_dock_widget( self.tplots, name="Plots" )
+        self.tplots.show()
+        #self.plot_wid = self.viewer.window.add_dock_widget( self.tplots, name="Plots" )
         self.viewer.dims.events.current_step.connect(self.position_verticalline)
     
     def on_close_viewer(self):
@@ -575,14 +628,15 @@ class Outputing(QWidget):
 
     def position_verticalline(self):
         """ Place the vertical line in the temporal graph to the current frame """
-        try:
-            wid = self.plot_wid
-        except:
-            self.on_close_viewer()
+        #try:
+        #    wid = self.tplots
+        #except:
+        #    self.on_close_viewer()
         if self.tplots is not None:
             self.tplots.move_framepos(self.viewer.dims.current_step[0])
 
-    ### track features 
+    ############### track features 
+
     def show_trackfeature_table(self):
         """ Show the measurement of tracks table """
         self.measure_track_features()
@@ -621,14 +675,248 @@ class Outputing(QWidget):
 
         self.table_selection = self.selection_choices.index(self.output_mode.currentText())
         self.viewer.window._status_bar._toggle_activity_dock(False)
-        show_info("Features measured in "+"{:.3f}".format((time.time()-start_time)/60)+" min")
+        if self.epicure.verbose > 0:
+            ut.show_info("Features measured in "+"{:.3f}".format((time.time()-start_time)/60)+" min")
 
     def measure_one_track( self, track_id ):
         """ Measure features of one track """
         track_features = self.epicure.tracking.measure_track_features( track_id )
         return track_features
 
+    ############## Events functions
+
+    def choose_events( self ):
+        """ Pop-up widget to choose the event types to measure/export """
+        self.event_types.choose()
+
+    def export_events( self ):
+        """ Export events of selected types """
+        evt_types = self.event_types.get_types()
+        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:
+                            roi = self.create_point_roi( pos, itype )
+                            rois.append( 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)+"" )
+
+    def create_point_roi( self, pos, cat=0 ):
+        """ Create a point Fiji ROI """
+        croi = roifile.ImagejRoi()
+        croi.version = 227
+        croi.roitype = roifile.ROI_TYPE(10)
+        croi.name = str(pos[0]+1).zfill(4)+'-'+str(pos[1]).zfill(4)+"-"+str(pos[2]).zfill(4)
+        croi.n_coordinates = 1
+        croi.left = int(pos[2])
+        croi.top = int(pos[1])
+        croi.z_position = 1
+        croi.t_position = pos[0]+1
+        croi.c_position = 1
+        croi.integer_coordinates = np.array( [[0,0]] )
+        croi.stroke_width=3
+        ncolors = 3
+        if cat%ncolors == 0:  ## color type 0
+            croi.stroke_color = b'\xff\x00\x00\xff'
+        if cat%ncolors == 1:  ## color type 1
+            croi.stroke_color = b'\xff\x00\xff\x00'
+        if cat%ncolors == 2:  ## color type 2
+            croi.stroke_color = b'\xff\xff\x00\x00'
+        return croi
+
+
+class CellFeatures(QWidget):
+    """ Choice of features to measure """
+    def __init__(self, chanlist):
+        super().__init__()
+        layout = QVBoxLayout()
+        
+        self.required = ["label"]
+        self.features = {}
+        self.chan_list = None
+        
+        other_list = ["group", "NbNeighbors", "Border"]
+        feat_layout = self.add_feature_group( other_list, "other" )
+        layout.addLayout( feat_layout )
+
+        ## Add shape features
+        shape_list = ["centroid", "area", "area_convex", "axis_major_length", "axis_minor_length", "feret_diameter_max", "equivalent_diameter_area", "eccentricity", "orientation", "perimeter", "solidity"]
+        feat_layout = self.add_feature_group( shape_list, "prop" )
+        layout.addLayout( feat_layout )
+
+        int_lab = QLabel()
+        int_lab.setText("Intensity features:")
+        layout.addWidget( int_lab )
+        intensity_list = ["intensity_mean", "intensity_min", "intensity_max"]
+        extra_list = ["intensity_junction_cytoplasm"]
+        feat_layout = self.add_feature_group( intensity_list, "intensity_prop" )
+        layout.addLayout( feat_layout )
+        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:")
+            layout.addWidget( chan_lab )
+            self.chan_list = QListWidget()
+            self.chan_list.addItems( chanlist )
+            self.chan_list.setSelectionMode(aiv.MultiSelection)
+            self.chan_list.item(0).setSelected(True)
+            layout.addWidget( self.chan_list )
+
+        bye = wid.add_button( "Ok", self.close, "Close the window" )
+        layout.addWidget( bye )
+        self.setLayout( layout )
+
+    def add_feature_group( self, feat_list, feat_type ):
+        """ Add features to the GUI """
+        layout = QVBoxLayout()
+        ncols = 3
+        for i, feat in enumerate(feat_list):
+            if i%ncols == 0:
+                line = QHBoxLayout()
+            feature_check = QCheckBox(text=""+feat)
+            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
+        if line is not None:
+            layout.addLayout( line )
+        return layout
+
+
+    def close( self ):
+        """ Close the pop-up window """
+        self.hide()
+
+    def choose( self ):
+        """ Show the interface to select the choices """
+        self.show()
+
+    def get_current_settings( self, setting ):
+        """ Get current settings of check or not of features """
+        for feat, feat_cbox in self.features.items():
+            setting[feat] = feat_cbox[0].isChecked()
+        return setting
+
+    def apply_settings( self, settings ):
+        """ Set the checkboxes from preferenced settings """
+        for feat, checked in settings.items():
+            if feat in self.features.keys():
+                self.features[feat][0].setChecked( checked )
+        
+    def get_features( self ):
+        """ Returns the list of features to measure """
+        feats = self.required
+        int_extra_feats = []
+        int_feats = []
+        other_feats = []
+        self.do_intensity = False
+        for feat, feat_cbox in self.features.items():
+            if feat_cbox[0].isChecked():
+                if feat_cbox[1] == "prop":
+                    feats.append( feat )
+                if feat_cbox[1] == "other":
+                    other_feats.append( feat )
+                if feat_cbox[1] == "intensity_prop":
+                    int_feats.append( feat )
+                    self.do_intensity = True
+                if feat_cbox[1] == "intensity_extra":
+                    int_extra_feats.append( feat )
+                    self.do_intensity = True
+        return feats, other_feats, int_feats, int_extra_feats
+
+    def get_channels( self ):
+        """ Returns the list of channels to measure """
+        if self.do_intensity:
+            if self.chan_list is not None:
+                wid_channels = self.chan_list.selectedItems()
+                channels = []
+                for chan in wid_channels:
+                    channels.append( chan.text() )
+            else:
+                channels = ["Movie"]
+            return channels
+        return None
+
+class EventTypes(QWidget):
+    """ Choice of event types to export/measure """
+    def __init__(self):
+        super().__init__()
+        layout = QVBoxLayout()
+        
+        self.types = {}
+        possible_types = [ "division", "suspect" ] 
+        event_layout = self.add_events( possible_types )
+        layout.addLayout( event_layout )
+
+        bye = wid.add_button( "Ok", self.close, "Close the window" )
+        layout.addWidget( bye )
+        self.setLayout( layout )
+
+    def add_events( self, event_list ):
+        """ Add events to the GUI """
+        layout = QVBoxLayout()
+        ncols = 3
+        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 )
+            if i%ncols == (ncols-1):
+                layout.addLayout( line )
+                line = None
+        if line is not None:
+            layout.addLayout( line )
+        return layout
+
+
+    def close( self ):
+        """ Close the pop-up window """
+        self.hide()
+
+    def choose( self ):
+        """ Show the interface to select the choices """
+        self.show()
+
+    def get_current_settings( self, setting ):
+        """ Get current settings of check or not of features """
+        for event, event_cbox in self.types.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 )
         
+    def get_types( self ):
+        """ Returns the list of events to measure """
+        events = []
+        for evt, evt_cbox in self.types.items():
+            if evt_cbox[0].isChecked():
+                events.append( evt )
+        return events
 
 class FeaturesTable(QWidget):
     """ Widget to visualize and interact with the measurement table """
@@ -693,12 +981,14 @@ class FeaturesTable(QWidget):
 class TemporalPlots(QWidget):
     """ Widget to visualize and interact with temporal plots """
 
-    def __init__(self, napari_viewer):
+    def __init__(self, napari_viewer, epicure):
         super().__init__()
         self.viewer = napari_viewer
+        self.epicure = epicure
         self.features_list = ["frame"]
         self.parameter_gui()
         self.vline = None
+        self.ymin = None
         #self.viewer.window.add_dock_widget( self.plot_wid, name="Temporal plot" )
    
     def parameter_gui(self):
@@ -706,23 +996,19 @@ class TemporalPlots(QWidget):
         
         layout = QVBoxLayout()
 
-        feat_choice = QHBoxLayout()
-        feat_choice_lab = QLabel()
-        feat_choice_lab.setText("Plot feature")
-        feat_choice.addWidget(feat_choice_lab)
-        self.feature_choice = QComboBox()
-        feat_choice.addWidget(self.feature_choice)
+        ## choice of feature to plot
+        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)
-
-        self.avg_group = QCheckBox(text="Average by groups")
+        ## 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)
-        self.avg_group.setChecked(False)
-
+        ## show the plot
         self.plot_wid = self.create_plotwidget()
         layout.addWidget(self.plot_wid)
+        ## save plot or save data of the plot
+        line = wid.double_button( "Save plot image", self.save_plot_image, "Save the grapic in a PNG file", "Save plot data", self.save_plot_data, "Save the value used for the plot in .csv file" )
+        layout.addLayout( line )
         self.setLayout(layout)
-        self.feature_choice.currentIndexChanged.connect(self.plot_feature)
-        self.avg_group.stateChanged.connect(self.plot_feature)
 
     def setTable(self, table):
         """ Data table to plot """
@@ -755,27 +1041,51 @@ class TemporalPlots(QWidget):
         if feat == "":
             return
         self.ax.cla()
-        tab = list(zip(self.table["frame"], self.table[feat], self.table["label"], self.table["group"]))
-        df = pand.DataFrame( tab, columns=["frame", feat, "label", "group"] )
-        #df["group"] = df["group"].replace("None", "Ungrouped")
-        df.set_index('frame', inplace=True)
-        #self.ax.plot(self.table["frame"], self.table[feat])
-        if self.avg_group.isChecked():
-            dfmean = df.groupby(['group', 'frame'])[feat].mean().reset_index()
-            dfmean.set_index('frame', inplace=True)
-            df.columns.name = 'group'
-            dfmean.groupby('group')[feat].plot(legend=False, ax=self.ax)
-            self.ax.legend(np.unique(dfmean['group']))
+        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"] )
         else:
-            df.groupby('label')[feat].plot(legend=False, ax=self.ax)
+            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 "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)
+            self.df.columns.name = 'group'
+            self.dfmean.groupby('group')[feat].plot(legend=False, ax=self.ax)
+            self.ax.legend(np.unique(self.dfmean['group']))
+        else:
+            self.df.groupby('label')[feat].plot(legend=False, ax=self.ax)
         self.ax.set_ylabel(''+feat)
         self.ax.set_xlabel('Time (frame)')
         self.fig.canvas.draw_idle()
         self.ymin, self.ymax = self.ax.get_ylim()
 
+    def save_plot_image( self ):
+        """ Save current plot graphic to PNG image """
+        feat = self.feature_choice.currentText()
+        outfile = self.epicure.outname()+"_plot_"+feat+".png"
+        if self.fig is not None:
+            self.fig.savefig( outfile )
+        if self.epicure.verbose > 0:
+            ut.show_info("Measures saved in "+outfile)
+
+    def save_plot_data( self ):
+        """ Save the raw data to redraw the current plot to csv file """
+        feat = self.feature_choice.currentText()
+        outfile = self.epicure.outname()+"_time_"+feat+".csv"
+        if self.avg_group.isChecked():
+            data = self.dfmean.reset_index()[["frame", "group", feat]]
+            data[["frame", "group", feat]].to_csv( outfile,  sep='\t', header=True, index=False )
+        else:
+            data = self.df.reset_index()[["frame", "label", feat]]
+            data[["frame", "label", feat]].to_csv( outfile,  sep='\t', header=True, index=False )
+
     def move_framepos(self, frame):
         """ Move the vertical line showing the current frame position in the main window """
         if self.ax is not None:
+            if self.ymin is None:
+                self.ymin, self.ymax = self.ax.get_ylim()
             if self.vline is not None:
                 self.vline.remove()
             ymin = float(self.ymin*1.01)
diff --git a/src/epicure/preferences.py b/src/epicure/preferences.py
new file mode 100644
index 0000000000000000000000000000000000000000..f5751bca21d71d5484843199840a352a26beb7c5
--- /dev/null
+++ b/src/epicure/preferences.py
@@ -0,0 +1,392 @@
+from qtpy.QtWidgets import QPushButton, QVBoxLayout, QTabWidget, QWidget, QComboBox, QLabel, QLineEdit, QGroupBox, QHBoxLayout, QColorDialog
+#from qtpy.QtGui import QColor
+import napari
+import epicure.Utils as ut
+from pathlib import Path
+import os, pickle
+
+def edit_preferences():
+    """ Launch preferences edition interface"""
+    viewer = napari.current_viewer()
+    prefgui = PreferencesGUI( viewer )
+    return prefgui
+
+class PreferencesGUI( QWidget ):
+    """ Handles user preferences for shortcuts, default widget state """
+
+    def __init__(self, napari_viewer):
+        """ Initialize the tab with the different widgets """
+        super().__init__()
+        
+        ## preferences (shortcuts, plugin state) object
+        self.pref = Preferences()
+        
+        layout = QVBoxLayout()
+        tabs = QTabWidget()
+        tabs.setObjectName("Preferences")
+        
+        ## shortcut and plugin state preferences tabs
+        self.shortcuts = ShortCut( napari_viewer, self.pref )
+        tabs.addTab( self.shortcuts, "Shortcuts Config." )
+        self.displays = DisplaySettings( napari_viewer, self.pref )
+        tabs.addTab( self.displays, "Display Config." )
+        layout.addWidget(tabs)
+
+        ## save option
+        self.save_pref = QPushButton("Save preferences", parent=self)
+        layout.addWidget( self.save_pref )
+        self.save_pref.clicked.connect( self.save )
+
+        ## add to main interface
+        self.setLayout( layout )
+        #napari_viewer.window.add_dock_widget( main_widget, name="Preferences" )
+
+    def save( self ):
+        """ Save current preferences: update them and save to default file """
+        self.shortcuts.update_pref()
+        self.pref.save()
+        
+
+class Preferences():
+    """ Handles user-specific preferences (shortcuts, widgets states) """
+    
+    def __init__( self ):
+        """ Initialise file path, load current preferences"""
+        self.build_preferences_path()
+
+        self.load_default_shortcuts()
+        self.load_default_settings()
+        if os.path.exists( self.preference_path ):
+            self.load()
+
+    def build_preferences_path( self ):
+        """ Build (create directories if necessary) preference path """
+        home_dir = Path.home()
+        self.preference_path = os.path.join( home_dir, ".napari" )
+        if not os.path.exists( self.preference_path ):
+            os.mkdir( self.preference_path )
+        self.preference_path = os.path.join( self.preference_path, "epicure_preferences.pkl" )
+
+    def save( self ):
+        """ Save the current preferences to the preference files in user home """
+        outfile = open( self.preference_path, "wb" )
+        pickle.dump( self.shortcuts, outfile )
+        pickle.dump( self.settings, outfile )
+        outfile.close()
+        print( "Preferences saved in file "+self.preference_path )
+
+    def set_preferences( self, default_prefs, prefs ):
+        """ Merge (recursively) the preferences with the default ones """
+        for key, vals in prefs.items():
+            if key in default_prefs.keys():
+                if isinstance( vals, dict ):
+                    self.set_preferences( default_prefs[key], vals )
+                else:
+                    default_prefs[key] = vals
+            else:
+                default_prefs[key] = vals
+
+    def load( self ):
+       """ Load the current preferences to the preference files in user home """
+       infile = open( self.preference_path, "rb" )
+       shortcuts = pickle.load( infile )
+       self.set_preferences( self.shortcuts, shortcuts )
+       try:
+           settings = pickle.load( infile )
+           #print(settings)
+           self.set_preferences( self.settings, settings )
+           #print(self.settings)
+       except:
+           self.load_default_settings()
+       #print(self.shortcuts)
+       infile.close()
+       #print( "Preferences loaded from file "+self.preference_path )
+
+    def get_settings( self ):
+        """ Return the dict of prefered settings (widget state) """
+        return self.settings
+
+    def get_shortcuts( self ):
+        """ Return the dict of shortcuts """
+        return self.shortcuts
+
+    def add_key_shortcut( self, main_type, shortname, fulltext, key ):
+        """ Add a keyboard shortcut """
+        if main_type not in self.shortcuts.keys():
+            self.shortcuts[ main_type ] = {}
+        ## initialize the new shortcut object
+        self.shortcuts[ main_type ][ shortname ] = {}
+        sc = self.shortcuts[ main_type ][ shortname ]
+        sc["type"] = "key"
+        sc["text"] = fulltext
+        sc["key"] = key
+    
+    def add_click_shortcut( self, main_type, shortname, fulltext, button, modifiers=None ):
+        """ Add a keyboard shortcut """
+        if main_type not in self.shortcuts.keys():
+            self.shortcuts[ main_type ] = {}
+        ## initialize the new shortcut object
+        self.shortcuts[ main_type ][ shortname ] = {}
+        sc = self.shortcuts[ main_type ][ shortname ]
+        sc["type"] = "click"
+        sc["text"] = fulltext
+        sc["button"] = button
+        if modifiers is not None:
+            sc["modifiers"] = modifiers
+
+    def load_default_shortcuts( self ):
+        """ Load all default shortcuts """
+
+        self.shortcuts = {}
+
+        ## General shortcuts
+        self.add_key_shortcut( "General", shortname="show help", fulltext="show/hide overlay help message", key="h" )
+        self.add_key_shortcut( "General", shortname="show all", fulltext="show all shortcuts in a separate window", key="a" )
+        self.add_key_shortcut( "General", shortname="save segmentation", fulltext="save the segmentation and epicure files", key="s" )
+        self.add_key_shortcut( "General", shortname="save movie", fulltext="save the movie with current display", key="Shift-s" )
+
+        ## Labels edition (static) shortcuts
+        self.add_key_shortcut( "Labels", shortname="unused paint", fulltext="set the current label to unused value and go to paint mode", key="n" )
+        self.add_key_shortcut( "Labels", shortname="unused fill", fulltext="set the current label to unused value and go to fill mode", key="Shift-n" )
+        self.add_key_shortcut( "Labels", shortname="swap mode", fulltext="<key shortcut> then <Control>+Left click on one cell to another to swap their values", key="w" )
+
+        self.add_click_shortcut( "Labels", shortname="erase", fulltext="erase the cell under the click", button="Right", modifiers=None )
+        self.add_click_shortcut( "Labels", shortname="merge", fulltext="drag-click from one cell to another to merge them", button="Left", modifiers=["Control"] )
+        self.add_click_shortcut( "Labels", shortname="split accross", fulltext="drag-click in the cell to split into 2 cells ", button="Right", modifiers=["Control"] )
+        self.add_click_shortcut( "Labels", shortname="split draw", fulltext="drag-click draw a junction to split in 2 cells", button="Right", modifiers=["Alt"] )
+        self.add_click_shortcut( "Labels", shortname="redraw junction", fulltext="drag-click draw a junction to correct it", button="Left", modifiers=["Alt"] )
+        self.add_key_shortcut( "Labels", shortname="draw junction mode", fulltext="<key shortcut> then Right click drawing connected junction(s) to create new cell", key="j" )
+        self.add_click_shortcut( "Labels", shortname="drawing junction", fulltext="Draw junction mode ON. Drag-click draw a junction to create new cell(s)", button="Left", modifiers=["Control"] )
+        
+        ## Seeds (manual segmentation) shortcuts
+        self.add_key_shortcut( "Seeds", shortname="new seed", fulltext="<key shortcut> then left-click to place a seed", key="e" )
+        
+        ## Groups shortcuts
+        self.add_click_shortcut( "Groups", shortname="add group", fulltext="add the clicked cell to the current group", button="Left", modifiers=["Shift"] )
+        self.add_click_shortcut( "Groups", shortname="remove group", fulltext="remove the clicked cell from the group", button="Right", modifiers=["Shift"] )
+        
+        ## events edition shortcuts
+        self.add_key_shortcut( "Events", shortname="next", fulltext="zoom on next event", key="Space" )
+        self.add_click_shortcut( "Events", shortname="zoom", fulltext="Zoom on the clicked event", button="Left", modifiers=["Control", "Alt"] )
+        self.add_click_shortcut( "Events", shortname="delete", fulltext="Remove the clicked event", button="Right", modifiers=["Control", "Alt"] )
+
+        ## Tracks edition shortcuts
+        self.add_key_shortcut( "Tracks", shortname="show", fulltext="show/hide the tracks", key="r" )
+        self.add_key_shortcut( "Tracks", shortname="lineage color", fulltext="color the tracks by lineage", key="l" )
+        self.add_click_shortcut( "Tracks", shortname="add division", fulltext="add a division: drag-click from first to second daugther", button="Left", modifiers=["Control", "Shift"] )
+        self.add_key_shortcut( "Tracks", shortname="mode", fulltext="on/off track editing mode", key="t" )
+        self.add_click_shortcut( "Tracks", shortname="merge first", fulltext="+track mode ON. Merge tracks: select the first", button="Left" )
+        self.add_click_shortcut( "Tracks", shortname="merge second", fulltext="+trackmode ON. Merge tracks: selec the second", button="Right" )
+        self.add_click_shortcut( "Tracks", shortname="split track", fulltext="+trackmode ON. Split the track temporally in 2", button="Right", modifiers=["Shift"] )
+        self.add_click_shortcut( "Tracks", shortname="start manual", fulltext="+trackmode ON. Start manual tracking, clicking on cells", button="Left", modifiers=["Control"] )
+        self.add_click_shortcut( "Tracks", shortname="end manual", fulltext="+trackmode ON. Finish manual tracking", button="Right", modifiers=["Control"] )
+        self.add_click_shortcut( "Tracks", shortname="interpolate first", fulltext="+trackmode ON. Interpolate temporally labels: select first", button="Left", modifiers=["Alt"] )
+        self.add_click_shortcut( "Tracks", shortname="interpolate second", fulltext="+trackmode ON. Interpolate temporally labels: select second", button="Right", modifiers=["Alt"] )
+        self.add_click_shortcut( "Tracks", shortname="swap", fulltext="+trackmode ON. Drag click to swap 2 tracks from current frame", button="Left", modifiers=["Shift"] )
+        self.add_click_shortcut( "Tracks", shortname="delete", fulltext="+trackmode ON. Delete all the track from current frame", button="Right", modifiers=["Control", "Alt"]  )
+
+        ## Visualisation option shortcuts
+        self.add_key_shortcut( "Display", shortname="vis. segmentation", fulltext="show/hide segmentation layer", key="b" )
+        self.add_key_shortcut( "Display", shortname="vis. movie", fulltext="show/hide movie layer", key="v" )
+        self.add_key_shortcut( "Display", shortname="vis. event", fulltext="show.hide events layer", key="x" )
+        self.add_key_shortcut( "Display", shortname="only movie", fulltext="show ONLY movie layer on/off", key="c" )
+        self.add_key_shortcut( "Display", shortname="light view", fulltext="on/off light segmentation view", key="d" )
+        self.add_key_shortcut( "Display", shortname="skeleton", fulltext="show/hide/update segmentation skeleton", key="k" )
+        self.add_key_shortcut( "Display", shortname="show side", fulltext="view layers side by side on/off", key="z" )
+        self.add_key_shortcut( "Display", shortname="grid", fulltext="show/hide grid", key="g" )
+        self.add_key_shortcut( "Display", shortname="increase", fulltext="increase label contour size", key="Control-c" )
+        self.add_key_shortcut( "Display", shortname="decrease", fulltext="decrease label contour size", key="Control-d" )
+    
+    def load_default_settings( self ):
+        """ Load all default widget settings """
+        self.settings = {}
+
+        ## Default visualisation set-up
+        self.settings["Display"] = {}
+        self.settings["Display"]["Layers"] = { 'Tracks': True, 'events': True, 'ROIs': False, 'Segmentation': True, 'Movie': True, 'EpicGrid': False, 'Groups': False }
+
+        ## widgets colors
+        self.load_default_colors()
+
+        ## default visualisation of events widget
+        self.settings["events"] = {}
+
+    def load_default_colors( self ):
+        """ Load the defualt GUI colors """
+        self.settings["Display"]["Colors"] = {}
+        col_set = self.settings["Display"]["Colors"]
+        col_set["button"] = "rgb(40, 60, 75)"
+        col_set["Help button"] = "rgb(62, 60, 75)"
+        col_set["Reset button"] = "rgb(70, 68, 85)"
+        col_set["checkbox"] = "rgb(40, 52, 65)"
+        col_set["line edit"] = "rgb(30, 30, 40)"
+        col_set["group"] = "rgb(33,42,55)"
+        col_set["group4"] = "rgb(37,37,57)"
+        col_set["group3"] = "rgb(30,35,40)"
+        col_set["group2"] = "rgb(30,40,50)"
+        
+
+class ShortCut( QWidget ):
+    """ Class to handle edit EpiCure shortcuts """
+
+    def __init__( self, napari_viewer, pref ):
+        super().__init__()
+        
+        layout = QVBoxLayout()
+
+        self.sc = pref.get_shortcuts()
+        ## choice list to choose which shortcuts to edit
+        self.shortcut_types = self.sc.keys()
+        self.sc_types = QComboBox()
+        self.sc_groups = {}
+        self.sc_guis = {}
+        layout.addWidget( self.sc_types )
+        for sc_type in self.shortcut_types:
+            self.sc_types.addItem( sc_type )
+            self.sc_guis[sc_type] = {}
+            self.sc_groups[sc_type] = self.create_sc_type( sc_type )
+            layout.addWidget( self.sc_groups[sc_type] )
+        self.show_sc_type()
+
+        self.setLayout(layout)
+        self.sc_types.currentIndexChanged.connect( self.show_sc_type )
+
+    def show_sc_type( self ):
+        """ Show only selected shortcut subset """
+        for sc_type in self.shortcut_types:
+            self.sc_groups[ sc_type ].setVisible( self.sc_types.currentText() == sc_type )
+
+    def create_sc_type( self, sc_type ):
+        """ Interface to edit shortcut subset of a given type """
+        sc_curgroup = QGroupBox( "" )
+        sc_layout = QVBoxLayout()
+        
+        ## add each shortcut from the current selected group
+        cur_shortcuts = self.sc[ sc_type ]
+        for shortname, val in cur_shortcuts.items():
+            new_line = QHBoxLayout()
+            ## current keyboard shortcut
+            if val["type"] == "click":
+                ## shortcut is a mouse shortcut
+                if "modifiers" in val.keys():
+                    ind = 0
+                    for modif in val["modifiers"]:
+                        cur_modif = QComboBox()
+                        cur_modif.addItem("")
+                        cur_modif.addItem("Control")
+                        cur_modif.addItem("Shift")
+                        cur_modif.addItem("Alt")
+                        new_line.addWidget( cur_modif )
+                        cur_modif.setCurrentText( modif )
+                        self.sc_guis[sc_type][ shortname+"modifiers"+str(ind) ] = cur_modif
+                        ind = ind + 1
+                cur_click = QComboBox()
+                cur_click.addItem("Left-click")
+                cur_click.addItem("Right-click")
+                new_line.addWidget( cur_click )
+                if val["button"] == "Right":
+                    cur_click.setCurrentText( "Right-click" )
+                self.sc_guis[sc_type][ shortname ] = cur_click
+            if val["type"] == "key":
+                new_line_val = QLineEdit()
+                new_line_val.setText( val["key"] )
+                self.sc_guis[sc_type][ shortname ] = new_line_val
+                new_line.addWidget( new_line_val )
+            ## full description of the shortcut
+            long_description = QLabel()
+            long_description.setText( val["text"] )
+            new_line.addWidget( long_description )
+            sc_layout.addLayout( new_line )
+            #empty = QLabel()
+            #sc_layout.addWidget( empty )
+        
+        sc_curgroup.setLayout( sc_layout )
+        return sc_curgroup
+
+    def update_pref( self ):
+        """ Update the shortcuts in the Preference based on current values """
+        for sc_type in self.shortcut_types:
+            gui = self.sc_guis[ sc_type ]
+            sc_group = self.sc[ sc_type ]
+            for shortname, vals in sc_group.items():
+                if vals["type"] == "click":
+                    ## update the modifiers if there are some
+                    ind = 0
+                    if "modifiers" in vals.keys():
+                        del vals["modifiers"]
+                    while shortname+"modifiers"+str(ind) in gui.keys():
+                        modif = gui[ shortname+"modifiers"+str(ind) ].currentText()
+                        if "modifiers" not in vals.keys():
+                            vals["modifiers"] = []
+                        if modif != "":
+                            vals["modifiers"].append(modif)
+                        ind = ind + 1
+                        if len( vals["modifiers"] ) == 0:
+                            del vals["modifiers"]
+                    ## update the button information
+                    click = gui[shortname].currentText()
+                    if click == "Left-click":
+                        vals["button"] = "Left"
+                    else:
+                        vals["button"] = "Right"
+                if vals["type"] == "key":
+                    vals["key"] = gui[shortname].text()
+
+
+class DisplaySettings( QWidget ):
+    """ Class to handle edit EpiCure display button colors...)"""
+    
+    def __init__( self, napari_viewer, pref ):
+        super().__init__()
+        self.settings = pref.get_settings()
+        if "Colors" not in self.settings["Display"]:
+            pref.load_default_colors()
+        colors = self.settings["Display"]["Colors"]
+
+        ## interface of display choices
+        layout = QVBoxLayout()
+        self.grid_color = QPushButton("EpicGrid color", self)
+        self.grid_color.clicked.connect( self.get_grid_color )
+        layout.addWidget( self.grid_color )
+
+        self.add_color( layout, "Buttons color", "button", "Choose default color of buttons" )
+        self.add_color( layout, "Help buttons color", "Help button", "Choose color of buttons for Help actions" )
+        self.add_color( layout, "Reset buttons color", "Reset button", "Choose color of buttons for Reset actions" )
+        self.add_color( layout, "CheckBox color", "checkbox", "Choose color of checkboxes" )
+        self.add_color( layout, "Input color", "line edit", "Choose color of editable parameters boxes" )
+        self.add_color( layout, "Subpanels color", "group", "Choose color of option subpanels that appears when clicked/selected" )
+        self.add_color( layout, "Subpanels color 2", "group2", "Choose second color of option subpanels that appears when clicked/selected" )
+        self.add_color( layout, "Subpanels color 3", "group3", "Choose third color of option subpanels that appears when clicked/selected" )
+        self.add_color( layout, "Subpanels color 4", "group4", "Choose fourth color of option subpanels that appears when clicked/selected" )
+        
+        self.setLayout(layout)
+
+    def add_color( self, layout, label, setname, descr="" ):
+        """ Add a choice of color (push button that opens a color dialog) """
+        btn = QPushButton( label )
+        if descr != "":
+            btn.setToolTip( descr )
+        def get_color():
+            """ opens color dialog and set button color to it """
+            color = QColorDialog.getColor()
+            if color.isValid():
+                self.settings["Display"]["Colors"][setname] = color.name()
+                btn.setStyleSheet( 'QPushButton {background-color: '+color.name()+'}' )
+        btn.clicked.connect( get_color )
+        if setname in self.settings["Display"]["Colors"]:
+            color = self.settings["Display"]["Colors"][setname]
+            btn.setStyleSheet( 'QPushButton {background-color: '+color+'}' )
+        layout.addWidget( btn )
+
+    def get_grid_color( self ):
+        """ Get the EpiCGrid color """
+        color = QColorDialog.getColor()
+        if color.isValid():
+            if "Display" not in self.settings:
+                self.settings["Display"] = {}
+            self.settings["Display"]["Grid color"] = color.name()
+            self.grid_color.setStyleSheet( 'QPushButton {background-color: '+color.name()+'}' )
+        
+            
+
+
diff --git a/src/epicure/start_epicuring.py b/src/epicure/start_epicuring.py
index 3ffc3c9bbae28ded377ec7f24f9c818563eb2da5..d7164bab6b884ee6eff0f0b244adada36b82a459 100644
--- a/src/epicure/start_epicuring.py
+++ b/src/epicure/start_epicuring.py
@@ -33,46 +33,53 @@ def start_epicure():
         nonlocal caxis, cval
         image_file = get_files.image_file.value
         caxis, cval = Epic.load_movie(image_file)
-        if caxis is not None:
-            get_files.junction_chanel.max = cval-1
-            get_files.junction_chanel.visible = True
-            set_chanel()
-        #get_files.scale_xy.value = Epic.scale
         imgdir = ut.get_directory(image_file)
         get_files.segmentation_file.value = pathlib.Path(imgdir)
         labname = Epic.suggest_segfile( get_files.output_dirname.value )
+        Epic.set_names( get_files.output_dirname.value )
         if labname is not None:
             get_files.segmentation_file.value = pathlib.Path(labname)
+            Epic.read_epicure_metadata()    
+        if caxis is not None:
+            get_files.junction_chanel.max = cval-1
+            get_files.junction_chanel.visible = True
+            set_chanel()
+        get_files.scale_xy.value = Epic.epi_metadata["ScaleXY"]
+        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"]
         ut.show_duration(start_time, header="Movie loaded in ")
 
     def show_others():
         """ Display other chanels from the initial movie """
         for ochan in range(cval):
-            ut.remove_layer(viewer, "MovieOtherChanel_"+str(ochan))
+            ut.remove_layer(viewer, "MovieChannel_"+str(ochan))
         if get_files.show_other_chanels.value == True:
             Epic.add_other_chanels(int(get_files.junction_chanel.value), caxis)
 
     def set_chanel():
         """ Set the correct chanel that contains the junction signal """
         start_time = ut.start_time()
-        Epic.set_chanel(int(get_files.junction_chanel.value), caxis)
+        Epic.set_chanel( int(get_files.junction_chanel.value), caxis )
         show_others()
         ut.show_duration(start_time, header="Movie chanel loaded in ")
 
 
     @magicgui(call_button="Start cure",
             junction_chanel={"widget_type": "Slider", "min":0, "max": 0},
-            #scale_xy = {"widget_type": "LiteralEvalLineEdit"},
-            #scale_t = {"widget_type": "LiteralEvalLineEdit"},
+            scale_xy = {"widget_type": "LiteralEvalLineEdit"},
+            timeframe = {"widget_type": "LiteralEvalLineEdit"},
             junction_half_thickness={"widget_type": "LiteralEvalLineEdit"},
             nbparallel_threads = {"widget_type": "LiteralEvalLineEdit"},
-            verbose_level={"widget_type": "Slider", "min":0, "max": 2},
+            verbose_level={"widget_type": "Slider", "min":0, "max": 3},
             )
     def get_files( image_file = pathlib.Path(cdir),
                    junction_chanel = 0,
                    segmentation_file = pathlib.Path(cdir),
-                   #scale_xy = 1,
-                   #scale_t = 1,
+                   scale_xy = 1,
+                   unit_xy = "um",
+                   timeframe = 1,
+                   unit_t = "min",
                    advanced_parameters = False,
                    show_other_chanels = True,
                    process_frames_parallel = False,
@@ -91,7 +98,7 @@ def start_epicure():
         Epic.nparallel = nbparallel_threads
         #Epic.load_segmentation(segmentation_file)
         Epic.set_thickness(junction_half_thickness)
-        #Epic.set_scales(scale_xy, scale_t)
+        Epic.set_scales(scale_xy, timeframe, unit_xy, unit_t)
         Epic.go_epicure(outdir, segmentation_file)
 
     set_visibility()
diff --git a/src/epicure/suspecting.py b/src/epicure/suspecting.py
deleted file mode 100644
index 6ec750c1e732173e52ed293188223be73d1f6924..0000000000000000000000000000000000000000
--- a/src/epicure/suspecting.py
+++ /dev/null
@@ -1,960 +0,0 @@
-import numpy as np
-import os
-import napari
-from skimage.measure import regionprops, label
-from skimage import filters
-from skimage.morphology import binary_erosion, binary_dilation, disk
-from qtpy.QtWidgets import QPushButton, QVBoxLayout, QHBoxLayout, QGroupBox, QWidget, QCheckBox, QSlider, QLabel, QDoubleSpinBox, QComboBox, QSpinBox, QLineEdit
-from qtpy.QtCore import Qt
-import epicure.Utils as ut
-from skimage.segmentation import expand_labels
-from napari.utils import progress
-try:
-    from skimage.graph import RAG
-except:
-    from skimage.future.graph import RAG  ## older version of scikit-image
-
-"""
-    EpiCure - Suspects interface
-    Handle suspects and suggestion layer
-"""
-
-class Suspecting(QWidget):
-    
-    def __init__(self, napari_viewer, epic):
-        super().__init__()
-        self.viewer = napari_viewer
-        self.epicure = epic
-        self.seglayer = self.viewer.layers["Segmentation"]
-        self.border_cells = None    ## list of cells that are on the border (touch the background)
-        self.suspectlayer_name = "Suspects"
-        self.suspects = None
-        self.win_size = 10
-
-        ## Print the current number of suspects
-        self.nsuspect_print = QLabel("")
-        self.update_nsuspects_display()
-        
-        self.create_suspectlayer()
-        layout = QVBoxLayout()
-        layout.addWidget( self.nsuspect_print )
-        
-        ### Reset: delete all suspects
-        reset_suspect_btn = QPushButton("Reset suspects", parent=self)
-        layout.addWidget(reset_suspect_btn)
-        reset_suspect_btn.clicked.connect(self.reset_all_suspects)
-        
-        ## Error suggestions based on cell features
-        outlier_vis = QCheckBox(text="Outliers options")
-        outlier_vis.setChecked(False)
-        layout.addWidget(outlier_vis)
-        self.create_outliersBlock() 
-        outlier_vis.stateChanged.connect(self.show_outlierBlock)
-        layout.addWidget(self.featOutliers)
-        
-        ## Error suggestions based on tracks
-        track_vis = QCheckBox(text="Track options")
-        track_vis.setChecked(True)
-        layout.addWidget(track_vis)
-        self.create_tracksBlock() 
-        track_vis.stateChanged.connect(self.show_tracksBlock)
-        layout.addWidget(self.suspectTrack)
-        
-        ## Visualisation options
-        suspect_disp = QCheckBox(text="Display options")
-        suspect_disp.setChecked(True)
-        layout.addWidget(suspect_disp)
-        self.create_displaySuspectBlock() 
-        suspect_disp.stateChanged.connect(self.show_displaySuspectBlock)
-        layout.addWidget(self.displaySuspect)
-        self.displaySuspect.setVisible(True)
-        
-        self.setLayout(layout)
-        self.key_binding()
-
-    def key_binding(self):
-        """ active key bindings for suspects options """
-        self.epicure.overtext["suspects"] = "---- Suspects editing ---- \n"
-        self.epicure.overtext["suspects"] += "<Ctrl>+<Alt>+Left click to zoom on a suspect \n"
-        self.epicure.overtext["suspects"] += "<Ctrl>+<Alt>+Right click to remove a suspect \n"
-        self.epicure.overtext["suspects"] += "<Space bar> zoom on next suspect \n"
-   
-        @self.epicure.seglayer.mouse_drag_callbacks.append
-        def handle_suspect(seglayer, event):
-            if event.type == "mouse_press":
-                if len(event.modifiers)==2:
-                    if ("Control" in event.modifiers) and ('Alt' in event.modifiers):
-                        if event.button == 2:
-                            ind = ut.getCellValue( self.suspects, event ) 
-                            if self.epicure.verbose > 1:
-                                print("Removing clicked suspect, at index "+str(ind))
-                            if ind is None:
-                                ## click was not on a suspect
-                                return
-                            sid = self.suspects.properties["id"][ind]
-                            if sid is not None:
-                                self.exonerate_one(ind)
-                            else:
-                                if self.epicure.verbose > 1:
-                                    print("Suspect with id "+str(sid)+" not found")
-                            self.remove_suspicions( sid )
-                            self.suspects.refresh()
-                        if event.button == 1:
-                            ind = ut.getCellValue( self.suspects, event ) 
-                            sid = self.suspects.properties["id"][ind]
-                            if self.epicure.verbose > 1:
-                                print("Zoom on suspect with id "+str(sid)+"")
-                            self.zoom_on_suspect( event.position, sid )
-
-        @self.epicure.seglayer.bind_key('Space', overwrite=True)
-        def go_next(seglayer):
-            """ Select next suspect and zoom on it """
-            num_suspect = int(self.suspect_num.value())
-            if num_suspect < 0:
-                if self.nb_suspects() == "_":
-                    if self.epicure.verbose > 0:
-                        print("No more suspect")
-                    return  
-                else:
-                    self.suspect_num.setValue(0)
-            else:
-                self.suspect_num.setValue( (num_suspect+1)%(self.nb_suspects()) )
-            self.go_to_suspect()       
-
-    def create_suspectlayer(self):
-        """ Create a point layer that contains the suspects """
-        features = {}
-        pts = []
-        self.suspects = self.viewer.add_points( np.array(pts), properties=features, face_color="red", size = 10, symbol='x', name=self.suspectlayer_name, )
-        self.suspicions = {}
-        self.update_nsuspects_display()
-        self.epicure.finish_update()
-
-    def load_suspects(self, pts, features, suspicions, symbols=None, colors=None):
-        """ Load suspects data from file and reinitialize layer with it"""
-        ut.remove_layer(self.viewer, self.suspectlayer_name)
-        if symbols is None:
-            symbols = "x"
-        if colors is None:
-            colors = "red"
-        symb = symbols
-        self.suspects = self.viewer.add_points( np.array(pts), properties=features, face_color=colors, size = 10, symbol=symbols, name=self.suspectlayer_name, )
-        self.suspicions = suspicions
-        self.update_nsuspects_display()
-        self.epicure.finish_update()
-
-        
-    ############### Display suspect options
-
-    def update_nsuspects_display( self ):
-        """ Update the display of number of suspect"""
-        self.nsuspect_print.setText( str(self.nb_suspects())+" suspects" )
-
-    def nb_suspects(self):
-        """ Returns current number of suspects """
-        if self.suspects is None:
-            return "_"
-        if self.suspects.properties is None:
-            return "_"
-        if "score" not in self.suspects.properties:
-            return "_"
-        return len(self.suspects.properties["score"])
-
-    def show_displaySuspectBlock(self):
-        self.displaySuspect.setVisible(not self.displaySuspect.isVisible())
-
-    def create_displaySuspectBlock(self):
-        ''' Block interface of displaying suspect layer options '''
-        self.displaySuspect = QGroupBox("Display options")
-        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)
-        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-length")
-        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_suspects)
-        disp_layout.addLayout(colorlay)
-        
-        sizelay = QHBoxLayout()
-        size_label = QLabel()
-        size_label.setText("Point size:")
-        sizelay.addWidget(size_label)
-        self.suspect_size = QSlider(Qt.Horizontal)
-        self.suspect_size.setMinimum(0)
-        self.suspect_size.setMaximum(50)
-        self.suspect_size.setSingleStep(1)
-        self.suspect_size.setValue(10)
-        self.suspect_size.valueChanged.connect(self.display_suspect_size)
-        sizelay.addWidget(self.suspect_size)
-        disp_layout.addLayout(sizelay)
-
-        ### Interface to select a suspect and zoom on it
-        chooselay = QHBoxLayout()
-        choose_lab = QLabel()
-        choose_lab.setText("Suspect n°")
-        chooselay.addWidget(choose_lab)
-        self.suspect_num = QSpinBox()
-        self.suspect_num.setMinimum(0)
-        self.suspect_num.setMaximum(len(self.suspects.data)-1)
-        self.suspect_num.setSingleStep(1)
-        self.suspect_num.setValue(0)
-        chooselay.addWidget(self.suspect_num)
-        disp_layout.addLayout(chooselay)
-        go_suspect_btn = QPushButton("Go to suspect", parent=self)
-        disp_layout.addWidget(go_suspect_btn)
-        go_suspect_btn.clicked.connect(self.go_to_suspect)
-        clear_suspect_btn = QPushButton("Exonerate current suspect", parent=self)
-        disp_layout.addWidget(clear_suspect_btn)
-        clear_suspect_btn.clicked.connect(self.clear_suspect)
-        
-        ## all features
-        self.displaySuspect.setLayout(disp_layout)
-       
-    #####
-    def reset_suspect_range(self):
-        """ Reset the max num of suspect """
-        nsus = len(self.suspects.data)-1
-        if self.suspect_num.value() > nsus:
-            self.suspect_num.setValue(0)
-        self.suspect_num.setMaximum(nsus)
-
-    def go_to_suspect(self):
-        """ Zoom on the currently selected suspect """
-        num_suspect = int(self.suspect_num.value())
-        if num_suspect < 0:
-            if self.nb_suspects() == "_":
-                if self.epicure.verbose > 0:
-                    print("No more suspect")
-                return  
-            else:
-                self.suspect_num.setValue(0)
-                num_suspect = 0      
-        pos = self.suspects.data[num_suspect]
-        suspect_id = self.suspects.properties["id"][num_suspect]
-        self.zoom_on_suspect( pos, suspect_id )
-
-    def zoom_on_suspect( self, suspect_pos, suspect_id ):
-        """ Zoom on chose suspect at given position """
-        self.viewer.camera.center = suspect_pos
-        self.viewer.camera.zoom = 5
-        self.viewer.dims.set_point( 0, int(suspect_pos[0]) )
-        crimes = self.get_crimes(suspect_id)
-        if self.epicure.verbose > 0:
-            print("Suspected because of: "+str(crimes))
-
-    def color_suspects(self):
-        """ Color points by the selected mode """
-        color_mode = self.color_choice.currentText()
-        self.suspects.refresh_colors()
-        if color_mode == "None":
-            self.suspects.face_color = "white"
-        elif color_mode == "score":
-            self.set_colors_from_properties("score")
-        else:
-            self.set_colors_from_suspicion(color_mode)
-        self.suspects.refresh_colors()
-
-    def set_colors_from_suspicion(self, feature):
-        """ Set colors from given suspicion feature (eg area, tracking..) """
-        if self.suspicions.get(feature) is None:
-            self.suspects.face_color="white"
-            return
-        posid = self.suspicions[feature]
-        colors = ["white"]*len(self.suspects.data)
-        ## change the color of all the positive suspects for the chosen feature
-        for sid in posid:
-            ind = self.index_from_id(sid)
-            if ind is not None:
-                colors[ind] = (0.8,0.1,0.1)
-        self.suspects.face_color = colors
-
-    def set_colors_from_properties(self, feature):
-        """ Set colors from given propertie (eg score, label) """
-        ncols = (np.max(self.suspects.properties[feature]))
-        color_cycle = []
-        for i in range(ncols):
-            color_cycle.append( (0.25+float(i/ncols*0.75), float(i/ncols*0.85), float(i/ncols*0.75)) )
-        self.suspects.face_color_cycle = color_cycle
-        self.suspects.face_color = feature
-    
-    def update_display(self):
-        self.suspects.refresh()
-        self.color_suspects()
-
-    def display_suspect_size(self):
-        """ Change the size of the point display """
-        size = int(self.suspect_size.value())
-        self.suspects.size = size
-        self.suspects.refresh()
-
-    ############### Suspecting functions
-    def get_crimes(self, sid):
-        """ For a given suspect, get its suspicion(s) """
-        crimes = []
-        for feat in self.suspicions.keys():
-            if sid in self.suspicions.get(feat):
-                crimes.append(feat)
-        return crimes
-
-    def add_suspicion(self, ind, sid, feature):
-        """ Add 1 to the suspicion score for given feature """
-        #print(self.suspicions)
-        if self.suspicions.get(feature) is None:
-            self.suspicions[feature] = []
-        self.suspicions[feature].append(sid)
-        self.suspects.properties["score"][ind] = self.suspects.properties["score"][ind] + 1
-
-    def first_suspect(self, pos, label, featurename):
-        """ Addition of the first suspect (initialize all) """
-        ut.remove_layer(self.viewer, "Suspects")
-        features = {}
-        sid = self.new_suspect_id()
-        features["id"] = np.array([sid], dtype="uint16")
-        features["label"] = np.array([label], dtype=self.epicure.dtype)
-        features["score"] = np.array([0], dtype="uint8")
-        pts = [pos]
-        self.suspects = self.viewer.add_points( np.array(pts), properties=features, face_color="score", size = 10, symbol="x", name="Suspects", )
-        self.add_suspicion(0, sid, featurename)
-        self.suspects.refresh()
-        self.update_nsuspects_display()
-
-    def add_suspect(self, pos, label, reason, symb="x", color="white"):
-        """ Add a suspect to the list, suspected by a feature """
-        if (self.ignore_borders.isChecked()) and (self.border_cells is not None):
-            tframe = int(pos[0])
-            if label in self.border_cells[tframe]:
-                return
-
-        ## initialise if necessary
-        if len(self.suspects.data) <= 0:
-            self.first_suspect(pos, label, reason)
-            return
-        
-        self.suspects.selected_data = []
-       
-       ## look if already suspected, then add the charge
-        num, sid = self.find_suspect(pos[0], label)
-        if num is not None:
-            ## suspect already in the list. For same crime ?
-            if self.suspicions.get(reason) is not None:
-                if sid not in self.suspicions[reason]:
-                    self.add_suspicion(num, sid, reason)
-            else:
-                self.add_suspicion(num, sid, reason)
-        else:
-            ## new suspect, add to the Point layer
-            ind = len(self.suspects.data)
-            sid = self.new_suspect_id()
-            self.suspects.add(pos)
-            self.suspects.properties["label"][ind] = label
-            self.suspects.properties["id"][ind] = sid
-            self.suspects.properties["score"][ind] = 0
-            self.add_suspicion(ind, sid, reason)
-
-        self.suspects.symbol.flags.writeable = True
-        self.suspects.current_symbol = symb
-        self.suspects.current_face_color = color
-        self.suspects.refresh()
-        self.reset_suspect_range()
-        self.update_nsuspects_display()
-
-    def new_suspect_id(self):
-        """ Find the first unused id """
-        sid = 0
-        if self.suspects.properties.get("id") is None:
-            return 0
-        while sid in self.suspects.properties["id"]:
-            sid = sid + 1
-        return sid
-    
-    def reset_all_suspects(self):
-        """ Remove all suspicions """
-        features = {}
-        pts = []
-        ut.remove_layer(self.viewer, "Suspects")
-        self.suspects = self.viewer.add_points( np.array(pts), properties=features, face_color="red", size = 10, symbol='x', name="Suspects", )
-        self.suspicions = {}
-        self.update_nsuspects_display()
-        self.update_nsuspects_display()
-
-    def reset_suspicion(self, feature, frame):
-        """ Remove all suspicions of given feature, for current frame or all if frame is None """
-        if self.suspicions.get(feature) is None:
-            return
-        idlist = self.suspicions[feature].copy()
-        for sid in idlist:
-            ind = self.index_from_id(sid)
-            if ind is not None:
-                if frame is not None:
-                    if int(self.suspects.data[ind][0]) == frame:
-                        self.suspicions[feature].remove(sid)
-                        self.decrease_score(ind)
-                else:
-                    self.suspicions[feature].remove(sid)
-                    self.decrease_score(ind)
-        self.suspects.refresh()
-        self.update_nsuspects_display()
-
-    def remove_suspicions(self, sid):
-        """ Remove all suspicions of given suspect id """
-        for listval in self.suspicions.values():
-            if sid in listval:
-                listval.remove(sid)
-
-    def decrease_score(self, ind):
-        """ Decrease by one score of suspect at index ind. Delete it if reach 0"""
-        self.suspects.properties["score"][ind] = self.suspects.properties["score"][ind] - 1
-        if self.suspects.properties["score"][ind] == 0:
-            self.exonerate_one(ind)
-
-    def index_from_id(self, sid):
-        """ From suspect id, find the corresponding index in the properties array """
-        for ind, cid in enumerate(self.suspects.properties["id"]):
-            if cid == sid:
-                return ind
-        return None
-
-    def find_suspect(self, frame, label):
-        """ Find if there is already a suspect at given frame and label """
-        suspects = self.suspects.data
-        suspects_lab = self.suspects.properties["label"]
-        for i, lab in enumerate(suspects_lab):
-            if lab == label:
-                if suspects[i][0] == frame:
-                    return i, self.suspects.properties["id"][i]
-        return None, None
-
-    def init_suggestion(self):
-        """ Initialize the layer that will contains propostion of tracks/segmentations """
-        suggestion = np.zeros(self.seglayer.data.shape, dtype="uint16")
-        self.suggestion = self.viewer.add_labels(suggestion, blending="additive", name="Suggestion")
-        
-        @self.seglayer.mouse_drag_callbacks.append
-        def click(layer, event):
-            if event.type == "mouse_press":
-                if 'Alt' in event.modifiers:
-                    if event.button == 1:
-                        pos = event.position
-                        # alt+left click accept suggestion under the mouse pointer (in all frames)
-                        self.accept_suggestion(pos)
-    
-    def accept_suggestion(self, pos):
-        """ Accept the modifications of the label at position pos (all the label) """
-        seglayer = self.viewer.layers["Segmentation"]
-        label = self.suggestion.data[tuple(map(int, pos))]
-        found = self.suggestion.data==label
-        self.exonerate( found, seglayer ) 
-        indices = np.argwhere( found )
-        ut.setNewLabel( seglayer, indices, label, add_frame=None )
-        self.suggestion.data[self.suggestion.data==label] = 0
-        self.suggestion.refresh()
-        self.update_nsuspects_display()
-    
-    def exonerate_one(self, ind):
-        """ Remove one suspect at index ind """
-        self.suspects.selected_data = [ind]
-        self.suspects.remove_selected()
-        self.update_nsuspects_display()
-
-    def clear_suspect(self):
-        """ Remove the current suspect """
-        num_suspect = int(self.suspect_num.value())
-        self.exonerate_one( num_suspect )
-
-    def exonerate_from_event(self, event):
-        """ Remove all suspects in the corresponding cell of position """
-        label = ut.getCellValue( self.seglayer, event )
-        if len(self.suspects.data) > 0:
-            for ind, lab in enumerate(self.suspects.properties["label"]):
-                if lab == label:
-                    if self.suspects.data[ind][0] == event.position[0]:
-                        sid = self.suspects.properties["id"][ind]
-                        self.exonerate_one(ind)
-                        self.remove_suspicions(sid)
-
-    def exonerate(self, indices, seglayer):
-        """ Remove suspects that have been corrected/cleared """
-        seglabels = np.unique(seglayer.data[indices])
-        selected = []
-        if self.suspects.properties.get("label") is None:
-            return
-        for ind, lab in enumerate(self.suspects.properties["label"]):
-            if lab in seglabels:
-                ## label to remove from suspect list
-                selected.append(ind)
-        if len(selected) > 0:
-            self.suspects.selected_data = selected
-            self.suspects.remove_selected()
-            self.update_nsuspects_display()
-                
-
-    #######################################"
-    ## Outliers suggestion functions
-    def show_outlierBlock(self):
-        self.featOutliers.setVisible(not self.featOutliers.isVisible())
-
-    def create_outliersBlock(self):
-        ''' Block interface of functions for error suggestions based on cell features '''
-        self.featOutliers = QGroupBox("Outliers highlight")
-        feat_layout = QVBoxLayout()
-        # option to avoid checked cell
-        #self.feat_checked = QCheckBox(text="Ignore checked cells")
-        #self.feat_checked.setChecked(True)
-        #feat_layout.addWidget(self.feat_checked)
-        
-        self.feat_onframe = QCheckBox(text="Only current frame")
-        self.feat_onframe.setChecked(True)
-        feat_layout.addWidget(self.feat_onframe)
-        
-        ## area widget
-        feat_area_btn = QPushButton("Area outliers", parent=self)
-        feat_area_btn.clicked.connect(self.suspect_area)
-        farea_layout = QHBoxLayout()
-        farea_layout.addWidget(feat_area_btn)
-        self.farea_out = QDoubleSpinBox()
-        self.farea_out.setRange(0,20)
-        self.farea_out.decimals = 2
-        self.farea_out.setSingleStep(0.25)
-        self.farea_out.setValue(3)
-        farea_layout.addWidget(self.farea_out)
-        feat_layout.addLayout(farea_layout)
-        #self.feat_area.stateChanged.connect(self.show_areaOutliers)
-        
-        ## solid widget
-        feat_solid_btn = QPushButton(text="Solidity outliers", parent=self)
-        feat_solid_btn.clicked.connect(self.suspect_solidity)
-        fsolid_layout = QHBoxLayout()
-        fsolid_layout.addWidget(feat_solid_btn)
-        self.fsolid_out = QDoubleSpinBox()
-        self.fsolid_out.setRange(0,20)
-        self.fsolid_out.decimals = 2
-        self.fsolid_out.setSingleStep(0.25)
-        self.fsolid_out.setValue(3)
-        fsolid_layout.addWidget(self.fsolid_out)
-        feat_layout.addLayout(fsolid_layout)
-        
-        ## intensity widget
-        feat_intensity_btn = QPushButton(text="Intensity (inside/periphery)")
-        feat_intensity_btn.clicked.connect(self.suspect_intensity)
-        fintensity_layout = QHBoxLayout()
-        fintensity_layout.addWidget(feat_intensity_btn)
-        self.fintensity_out = QDoubleSpinBox()
-        self.fintensity_out.setRange(0,10)
-        self.fintensity_out.decimals = 2
-        self.fintensity_out.setSingleStep(0.05)
-        self.fintensity_out.setValue(1.0)
-        fintensity_layout.addWidget(self.fintensity_out)
-        feat_layout.addLayout(fintensity_layout)
-        
-        ## tubeness widget
-        feat_tub_btn = QPushButton(text="Tubeness (inside/periph)", parent=self)
-        feat_tub_btn.clicked.connect(self.suspect_tubeness)
-        ftub_layout = QHBoxLayout()
-        ftub_layout.addWidget(feat_tub_btn)
-        self.ftub_out = QDoubleSpinBox()
-        self.ftub_out.setRange(0,10)
-        self.ftub_out.decimals = 2
-        self.ftub_out.setSingleStep(0.05)
-        self.ftub_out.setValue(1)
-        ftub_layout.addWidget(self.ftub_out)
-        feat_layout.addLayout(ftub_layout)
-        
-        ## all features
-        self.featOutliers.setLayout(feat_layout)
-        self.featOutliers.setVisible(False)
-    
-    def suspect_feature(self, featname, funcname ):
-        """ Suspect in one frame or all frames the given feature """
-        onframe = self.feat_onframe.isChecked()
-        if onframe:
-            tframe = ut.current_frame(self.viewer)
-            self.reset_suspicion(featname, tframe)
-            funcname(tframe)
-        else:
-            self.reset_suspicion(featname, None)
-            for frame in range(self.seglayer.data.shape[0]):
-                funcname(frame)
-        self.update_display()
-        ut.set_active_layer( self.viewer, "Segmentation" )
-    
-    def inspect_outliers(self, tab, props, tuk, frame, feature):
-        q1 = np.quantile(tab, 0.25)
-        q3 = np.quantile(tab, 0.75)
-        qtuk = tuk * (q3-q1)
-        for sign in [1, -1]:
-            #thresh = np.mean(tab) + sign * np.std(tab)*tuk
-            if sign > 0:
-                thresh = q3 + qtuk
-            else:
-                thresh = q1 - qtuk
-            for i in np.where((tab-thresh)*sign>0)[0]:
-                position = ut.prop_to_pos( props[i], frame )
-                self.add_suspect( position, props[i].label, feature )
-    
-    def suspect_area(self, state):
-        """ Look for outliers in term of cell area """
-        self.suspect_feature( "area", self.suspect_area_oneframe )
-    
-    def suspect_area_oneframe(self, frame):
-        seglayer = self.seglayer.data[frame]
-        props = regionprops(seglayer)
-        ncell = len(props)
-        areas = np.zeros((ncell,1), dtype="float")
-        for i, prop in enumerate(props):
-            if prop.label > 0:
-                areas[i] = prop.area
-        tuk = self.farea_out.value()
-        self.inspect_outliers(areas, props, tuk, frame, "area")
-
-    def suspect_solidity(self, state):
-        """ Look for outliers in term ofz cell solidity """
-        self.suspect_feature( "solidity", self.suspect_solidity_oneframe )
-
-    def suspect_solidity_oneframe(self, frame):
-        seglayer = self.seglayer.data[frame]
-        props = regionprops(seglayer)
-        ncell = len(props)
-        sols = np.zeros((ncell,1), dtype="float")
-        for i, prop in enumerate(props):
-            if prop.label > 0:
-                sols[i] = prop.solidity
-        tuk = self.fsolid_out.value()
-        self.inspect_outliers(sols, props, tuk, frame, "solidity")
-    
-    def suspect_intensity(self, state):
-        """ Look for abnormal intensity inside/periph ratio """
-        self.suspect_feature( "intensity", self.suspect_intensity_oneframe )
-    
-    def suspect_intensity_oneframe(self, frame):
-        seglayer = self.seglayer.data[frame]
-        intlayer = self.viewer.layers["Movie"].data[frame] 
-        props = regionprops(seglayer)
-        for i, prop in enumerate(props):
-            if prop.label > 0:
-                self.test_intensity( intlayer, prop, frame )
-    
-    def test_intensity(self, inten, prop, frame):
-        """ Test if intensity inside is much smaller than at periphery """
-        bbox = prop.bbox
-        intbb = inten[bbox[0]:bbox[2], bbox[1]:bbox[3]]
-        footprint = disk(radius=self.epicure.thickness)
-        inside = binary_erosion(prop.image, footprint)
-        ininten = np.mean(intbb*inside)
-        dil_img = binary_dilation(prop.image, footprint)
-        periph = dil_img^inside
-        periphint = np.mean(intbb*periph)
-        if (periphint<=0) or (ininten/periphint > self.fintensity_out.value()):
-            position = ( frame, int(prop.centroid[0]), int(prop.centroid[1]) )
-            self.add_suspect( position, prop.label, "intensity" )
-    
-    def suspect_tubeness(self, state):
-        """ Look for abnormal tubeness inside vs periph """
-        self.suspect_feature( "tubeness", self.suspect_tubeness_oneframe )
-    
-    def suspect_tubeness_oneframe(self, frame):
-        seglayer = self.seglayer.data[frame]
-        mov = self.viewer.layers["Movie"].data[frame]
-        sated = np.copy(mov)
-        sated = filters.sato(sated, black_ridges=False)
-        props = regionprops(seglayer)
-        for i, prop in enumerate(props):
-            if prop.label > 0:
-                self.test_tubeness( sated, prop, frame )
-
-    def test_tubeness(self, sated, prop, frame):
-        """ Test if tubeness inside is much smaller than tubeness on periph """
-        bbox = prop.bbox
-        satbb = sated[bbox[0]:bbox[2], bbox[1]:bbox[3]]
-        footprint = disk(radius=self.epicure.thickness)
-        inside = binary_erosion(prop.image, footprint)
-        intub = np.mean(satbb*inside)
-        periph = prop.image^inside
-        periphtub = np.mean(satbb*periph)
-        if periphtub <= 0:
-            position = ( frame, int(prop.centroid[0]), int(prop.centroid[1]) )
-            self.add_suspect( position, prop.label, "tubeness" )
-        else:
-            if intub/periphtub > self.ftub_out.value():
-                position = ( frame, int(prop.centroid[0]), int(prop.centroid[1]) )
-                self.add_suspect( position, prop.label, "tubeness" )
-
-
-############# Suspect based on track
-
-    def show_tracksBlock(self):
-        self.suspectTrack.setVisible(not self.suspectTrack.isVisible())
-
-    def create_tracksBlock(self):
-        ''' Block interface of functions for error suggestions based on tracks '''
-        self.suspectTrack = QGroupBox("Tracks")
-        track_layout = QVBoxLayout()
-        
-        self.get_div = QCheckBox(text="Get potential divisions")
-        self.get_div.setChecked(True)
-        track_layout.addWidget(self.get_div)
-        
-        self.ignore_borders = QCheckBox(text="Ignore cells on border")
-        self.ignore_borders.setChecked(False)
-        track_layout.addWidget(self.ignore_borders)
-
-        ## track length suspicions
-        ilengthlay = QHBoxLayout()
-        self.check_length = QCheckBox(text="Flag tracks smaller than")
-        self.check_length.setChecked(True)
-        ilengthlay.addWidget(self.check_length)
-        self.min_length = QLineEdit()
-        self.min_length.setText("1")
-        ilengthlay.addWidget(self.min_length)
-        track_layout.addLayout(ilengthlay)
-        
-        ## Variability in feature suspicion
-        self.check_size, self.size_variability = self.add_feature_gui( track_layout, "Size variation" )
-        self.check_shape, self.shape_variability = self.add_feature_gui( track_layout, "Shape variation" )
-        self.shape_variability.setText("2.0")
-
-        ## merge/split combinaisons 
-        track_btn = QPushButton("Inspect track", parent=self)
-        track_btn.clicked.connect(self.inspect_tracks)
-        track_layout.addWidget(track_btn)
-        
-        ## tmp test
-        test_btn = QPushButton("Test", parent=self)
-        test_btn.clicked.connect(self.track_features)
-        track_layout.addWidget(test_btn)
-        
-        ## all features
-        self.suspectTrack.setLayout(track_layout)
-
-    def add_feature_gui( self, layout, feature_name ):
-        """ Interface for a track feature check option """
-        featlay = QHBoxLayout()
-        check_item = QCheckBox(text=feature_name)
-        check_item.setChecked(False)
-        featlay.addWidget( check_item )
-        var_item = QLineEdit()
-        var_item.setText("1")
-        featlay.addWidget( var_item )
-        layout.addLayout( featlay )
-        return check_item, var_item
-        
-
-    def reset_tracking_suspect(self):
-        """ Remove suspects from tracking """
-        self.reset_suspicion("tracking-1-2-*", None)
-        self.reset_suspicion("tracking-2->1", None)
-        self.reset_suspicion("division", None)
-        self.reset_suspicion("track_length", None)
-        self.reset_suspicion("track_size", None)
-        self.reset_suspicion("track_shape", None)
-        self.reset_suspect_range()
-
-    def track_length(self):
-        """ Find all cells that are only in one frame """
-        max_len = int(self.min_length.text())
-        labels, lengths, positions = self.epicure.tracking.get_small_tracks( max_len )
-        for label, nframe, pos in zip(labels, lengths, positions):
-            if self.epicure.verbose > 1:
-                print("Suspect track length "+str(nframe)+": "+str(label)+" frame "+str(pos[0]) )
-            self.add_suspect(pos, label, "track-length")
-
-    def inspect_tracks(self):
-        """ Look for suspicious tracks """
-        self.viewer.window._status_bar._toggle_activity_dock(True)
-        progress_bar = progress(total=6)
-        progress_bar.update(0)
-        self.reset_tracking_suspect()
-        progress_bar.update(1)
-        if self.ignore_borders.isChecked():
-            progress_bar.set_description("Identifying border cells")
-            self.get_border_cells()
-        progress_bar.update(2)
-        if self.check_length.isChecked():
-            progress_bar.set_description("Identifying too small tracks")
-            self.track_length()
-        progress_bar.update(3)
-        progress_bar.set_description("Inspect tracks 2->1")
-        self.track_21()
-        progress_bar.update(4)
-        if (self.check_size.isChecked()) or self.check_shape.isChecked():
-            progress_bar.set_description("Inspect track features")
-            self.track_features()
-        progress_bar.update(5)
-        progress_bar.close()
-        self.viewer.window._status_bar._toggle_activity_dock(False)
-
-    def track_21(self):
-        """ Look for suspect track: 2->1 """
-        if self.epicure.tracking.tracklayer is None:
-            ut.show_error("No tracking done yet!")
-            return
-
-        graph = self.epicure.tracking.graph
-        divisions = dict()
-        undiv = []
-        if graph is not None:
-            for child, parent in graph.items():
-                ## 2->1, merge, suspect
-                if len(parent) == 2:
-                    onetwoone = False
-                    ## was it only one before ?
-                    if (parent[0] in graph.keys()) and (parent[1] in graph.keys()):
-                        if graph[parent[0]][0] == graph[parent[1]][0]:
-                            pos = self.epicure.tracking.get_mean_position([parent[0], parent[1]])
-                            if pos is not None:
-                                if self.epicure.verbose > 1:
-                                    print("Suspect 1->2->1 track: "+str(graph[parent[0]][0])+"-"+str(parent)+"-"+str(child)+" frame "+str(pos[0]) )
-                                self.add_suspect(pos, parent[0], "tracking-1-2-*")
-                                undiv.append(graph[parent[0]][0])
-                                onetwoone = True
-                
-                    if not onetwoone:
-                        pos = self.epicure.tracking.get_mean_position(child, only_first=True)     
-                        if pos is not None:
-                            if self.epicure.verbose > 1:
-                                print("Suspect 2->1 track: "+str(parent)+"-"+str(child)+" frame "+str(int(pos[0])) )
-                            self.add_suspect(pos, parent[0], "tracking-2->1")
-                        else:
-                            if self.epicure.verbose > 1:
-                                print("Something weird, "+str(child)+" mean position")
-                ## 1->2, potential division
-                else:
-                    if self.get_div.isChecked():
-                        if parent[0] not in divisions:
-                            divisions[parent[0]] = [child]
-                        else:
-                            divisions[parent[0]].append(child)
-
-        if self.get_div.isChecked():
-            self.potential_divisions(divisions, undiv)
-        self.epicure.finish_update()
-
-    def get_border_cells(self):
-        """ Return list of cells that are at the border (touching background) """
-        self.border_cells = dict()
-        for tframe in range(self.epicure.nframes):
-            img = self.epicure.seg[tframe]
-            self.border_cells[tframe] = self.get_border_cells_frame(img)        
-        
-    def get_border_cells_frame(self, imframe):
-        """ Return cells on border in current image """ 
-        touchlab = expand_labels(imframe, 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])
-        return adj_bg
-            
-    def potential_divisions(self, divisions, undivisions):
-        """ Look at splitting events """
-        for parent, childs in divisions.items():
-            if parent not in undivisions:
-                indexes = self.epicure.tracking.get_track_indexes(childs)
-                if len(indexes) <= 0:
-                    ## something wrong in the graph or in the tracks, ignore for now
-                    continue
-                ## One of the child exist only in one frame, suspicious
-                if len(indexes) <= 3:
-                    pos = self.epicure.tracking.mean_position(indexes, only_first=True)     
-                    if self.epicure.verbose > 1:
-                        print("Suspect 1-2-0 track: "+str(parent)+"-"+str(childs)+" frame "+str(pos[0]) )
-                    self.add_suspect(pos, parent, "tracking-1-2-*")
-                else:
-                    ## get the average first position of the childs just after division
-                    pos = self.epicure.tracking.mean_position(indexes, only_first=True)     
-                    self.add_suspect(pos, parent, "division", symb="o", color="#0055ffff")
-
-    def track_features(self):
-        """ Look at outliers in track features """
-        track_ids = self.epicure.tracking.get_track_list()
-        features = []
-        featType = {}
-        if self.check_size.isChecked():
-            features = features + ["Area", "Perimeter"]
-            featType["Area"] = "size"
-            featType["Perimeter"] = "size"
-            size_factor = float(self.size_variability.text())
-        if self.check_shape.isChecked():
-            features = features + ["Eccentricity", "Solidity"]
-            featType["Eccentricity"] = "shape"
-            featType["Solidity"] = "shape"
-            shape_factor = float(self.shape_variability.text())
-        for tid in track_ids:
-            track_indexes = self.epicure.tracking.get_track_indexes( tid )
-            ## track should be long enough to make sense to look for outlier
-            if len(track_indexes) > 3:
-                track_feats = self.epicure.tracking.measure_features( tid, features )
-                for feature, values in track_feats.items():
-                    if featType[feature] == "size":
-                        factor = size_factor
-                    if featType[feature] == "shape":
-                        factor = shape_factor
-                    outliers = self.find_jump( values, factor=factor )
-                    for out in outliers:
-                        tdata = self.epicure.tracking.get_frame_data( tid, out )
-                        if self.epicure.verbose > 1:
-                            print("Suspect track "+feature+": "+str(tdata[0])+" "+" frame "+str(tdata[1]) )
-                        self.add_suspect(tdata[1:4], tid, "track_"+featType[feature])
-
-        
-    def find_jump( self, tab, factor=1 ):
-        """ Detect brutal jump in the values """
-        jumps = []
-        tab = np.array(tab)
-        diff = tab[:(len(tab)-2)] - 2*tab[1:(len(tab)-1)] + tab[2:]
-        diff = [(tab[1]-tab[0])] + diff.tolist() + [tab[len(tab)-1]-tab[len(tab)-2]] 
-        avg = (tab[:(len(tab)-2)] + tab[2:])/2
-        avg = [(tab[1]+tab[0])/2] + avg.tolist() + [(tab[len(tab)-1]+tab[len(tab)-2])/2]
-        eps = 0.000000001
-        diff = np.array(diff, dtype=np.float32)
-        avg = np.array(avg, dtype=np.float32)
-        diff = abs(diff+eps)/(avg+eps)
-        ## keep only local max above threshold
-        for i, diffy in enumerate(diff):
-            if (i>0) and (i<len(diff)-1):
-                if diffy > factor:
-                    if (diffy > diff[i-1]) and (diffy > diff[i+1]):
-                        jumps.append(i)
-            else:
-                if diffy > factor:
-                    jumps.append(i)
-        #jumps = (np.where( diff > factor )[0]).tolist()
-        return jumps
-
-    def find_outliers_tuk( self, tab, factor=3, below=True, above=True ):
-        """ Returns index of outliers from Tukey's like test """
-        q1 = np.quantile(tab, 0.2)
-        q3 = np.quantile(tab, 0.8)
-        qtuk = factor * (q3-q1)
-        outliers = []
-        if below:
-            outliers = outliers + (np.where((tab-q1+qtuk)<0)[0]).tolist()
-        if above:
-            outliers = outliers + (np.where((tab-q3-qtuk)>0)[0]).tolist()
-        return outliers
-
-    def weirdo_area(self):
-        """ look at area trajectory for outliers """
-        track_df = self.epicure.tracking.track_df
-        for tid in np.unique(track_df["track_id"]):
-            rows = track_df[track_df["track_id"]==tid].copy()
-            if len(rows) >= 3:
-                rows["smooth"] = rows.area.rolling(self.win_size, min_periods=1).mean()
-                rows["diff"] = (rows["area"] - rows["smooth"]).abs()
-                rows["diff"] = rows["diff"].div(rows["smooth"])
-                if self.epicure.verbose > 2:
-                    print(rows)
-
-
diff --git a/src/epicure/track_optical.py b/src/epicure/track_optical.py
index 45d9b29786bab2122ab845005eb410fc336b03e8..ad4e06ea21eff233a28ee565ce8ab74c53a74be8 100644
--- a/src/epicure/track_optical.py
+++ b/src/epicure/track_optical.py
@@ -134,16 +134,16 @@ class trackOptical():
 
     def suspect_oneframe(self):
         """ Inspect the list of possible divisions if something suspicious """
-        for (parent, frame), suspects in self.divisions.items():
-            haslab = np.sum( self.seglayer.data==suspects[0], axis=(1,2) )
-            # label suspects[0] is present in only one frame
+        for (parent, frame), events in self.divisions.items():
+            haslab = np.sum( self.seglayer.data==events[0], axis=(1,2) )
+            # label events[0] is present in only one frame
             if self.track.suggesting and np.sum(haslab>0) <= 1:
-                self.suggest_merge(frame, suspects[0], suspects[1], parent)
+                self.suggest_merge(frame, events[0], events[1], parent)
             else:
-                # label suspects[1] is present in only one frame
-                haslab = np.sum( self.seglayer.data==suspects[1], axis=(1,2) )
+                # label events[1] is present in only one frame
+                haslab = np.sum( self.seglayer.data==events[1], axis=(1,2) )
                 if self.track.suggesting and np.sum(haslab>0) <= 1:
-                    self.suggest_merge(frame, suspects[1], suspects[0], parent)
+                    self.suggest_merge(frame, events[1], events[0], parent)
 
     def suggest_merge(self, frame, suspect, sister, parent):
         """ Suggest a merge of labels suspect and sister to label parent """
diff --git a/src/epicure/tracking.py b/src/epicure/tracking.py
index a53608ef5323ccd9d8c0ec9c9eb2179c1c7852d1..5777462ea405efc6fef2b5a372cb3ec4656596d6 100644
--- a/src/epicure/tracking.py
+++ b/src/epicure/tracking.py
@@ -1,21 +1,15 @@
-from qtpy.QtWidgets import QPushButton, QHBoxLayout, QVBoxLayout, QWidget, QGroupBox, QLineEdit, QComboBox, QLabel, QSpinBox, QCheckBox
-from napari import Viewer
-import time
-from napari.utils.notifications import show_info
+from qtpy.QtWidgets import QVBoxLayout, QWidget, QGroupBox 
 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 epicure.track_optical import trackOptical
-import epicure.Utils as ut
 from napari.utils import progress
-
-import vispy.color
+import time
 import pandas as pd
 import numpy as np
 from skimage.measure import regionprops, regionprops_table
 from multiprocessing.pool import ThreadPool as Pool
-from functools import partial
-import networkx as nx
+import epicure.Utils as ut
+import epicure.epiwidgets as wid
 
 class Tracking(QWidget):
     """
@@ -34,17 +28,13 @@ class Tracking(QWidget):
 
         layout = QVBoxLayout()
         
-        self.track_update = QPushButton("Update tracks", parent=self)
+        ## Add update track button 
+        self.track_update = wid.add_button( "Update tracks display", self.update_track_layer, "Update the Track layer with the changements made since the last update" )
         layout.addWidget(self.track_update)
-        self.track_update.clicked.connect(self.update_track_layer)
         
-        ## Track only in one ROI
-        #self.track_only_in_roi = QCheckBox(text="Only in ROI")
-        #layout.addWidget(self.track_only_in_roi)
-        #self.track_only_in_roi.setChecked(False)
 
         ## Method specific
-        self.track_choice = QComboBox()
+        track_method, self.track_choice = wid.list_line( "Tracking method", "Choose the tracking method to use and display its parameter", func=None )
         layout.addWidget(self.track_choice)
         
         self.track_choice.addItem("Laptrack-Centroids")
@@ -55,39 +45,18 @@ class Tracking(QWidget):
         self.create_laptrack_overlap()
         layout.addWidget(self.gLapOverlap)
         
-        self.track_go = QPushButton("Track", parent=self)
+        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)
         self.setLayout(layout)
-        self.track_go.clicked.connect(self.do_tracking)
 
         ## General tracking options
-        self.frame_range = QCheckBox( text="Track only some frames" )
-        self.frame_range.setChecked( False )
+        frame_line, self.frame_range, self.range_group = wid.checkgroup_help( "Track only some frames", False, "Option to track only a given range of frames", None ) 
         self.frame_range.clicked.connect( self.show_frame_range )
-        self.range_group = QGroupBox( "Frame range" )
         range_layout = QVBoxLayout()
-        ntrack = QHBoxLayout()
-        ntrack_lab = QLabel()
-        ntrack_lab.setText("Track from frame:")
-        ntrack.addWidget(ntrack_lab)
-        self.start_frame = QSpinBox()
-        self.start_frame.setMinimum(0)
-        self.start_frame.setMaximum(self.nframes-1)
-        self.start_frame.setSingleStep(1)
-        self.start_frame.setValue(0)
-        ntrack.addWidget(self.start_frame)
+        ntrack, self.start_frame = wid.ranged_value_line( "Track from frame:", 0, self.nframes-1, 1, 0, "Set first frame to begin tracking" )
         range_layout.addLayout(ntrack)
         
-        entrack = QHBoxLayout()
-        entrack_lab = QLabel()
-        entrack_lab.setText("Until frame:")
-        entrack.addWidget(entrack_lab)
-        self.end_frame = QSpinBox()
-        self.end_frame.setMinimum(1)
-        self.end_frame.setMaximum(self.nframes-1)
-        self.end_frame.setSingleStep(1)
-        self.end_frame.setValue(self.nframes-1)
-        entrack.addWidget(self.end_frame)
+        entrack, self.end_frame = wid.ranged_value_line( "Until frame:", 1, self.nframes-1, 1, self.nframes-1, "Set the last frame unitl which to track" )
         range_layout.addLayout(entrack)
         self.start_frame.valueChanged.connect( self.changed_start )
         self.end_frame.valueChanged.connect( self.changed_end )
@@ -99,11 +68,47 @@ class Tracking(QWidget):
         self.show_frame_range()
         self.show_trackoptions()
         self.track_choice.currentIndexChanged.connect(self.show_trackoptions)
+        
 
     def show_frame_range( self ):
         """ Show/Hide frame range options """
         self.range_group.setVisible( self.frame_range.isChecked() )
         
+    #### settings
+
+    def get_current_settings( self ):
+        """ Get current settings to save as preferences """
+        settings = {}
+        settings["Track method"] = self.track_choice.currentText() 
+        settings["Add feat"] = self.check_penalties.isChecked()
+        settings["Max distance"] = self.max_dist.text()
+        settings["Splitting cost"] = self.splitting_cost.text()
+        settings["Merging cutoff"] = self.merging_cost.text()
+        settings["Min IOU"] = self.min_iou.text()
+        settings["Over split"] = self.split_cost.text()
+        settings["Over merge"] = self.merg_cost.text()
+        return settings
+
+    def apply_settings( self, settings ):
+        """ Set the parameters/current display from the prefered settings """
+        for setty, val in settings.items():
+            if setty == "Track method":
+                self.track_choice.setCurrentText( val )
+            if setty == "Add feat":
+                self.check_penalties.setChecked( val )
+            if setty == "Max distance":
+                self.max_dist.setText( val )
+            if setty == "Splitting cost":
+                self.splitting_cost.setText( val )
+            if setty == "Merging cutoff":
+                self.merging_cost.setText( val )
+            if setty == "Min IOU":
+                self.min_iou.setText( val )
+            if setty == "Over split":
+                self.split_cost.setText( val )
+            if setty == "Over merge":
+                self.merg_cost.setText( val )
+            
     ##########################################
     #### Tracks layer and function
 
@@ -140,6 +145,18 @@ class Tracking(QWidget):
             cols[i] = (self.epicure.seglayer.get_color(tr))
         self.tracklayer._track_colors = cols
         self.tracklayer.events.color_by()
+    
+    def color_tracks_by_lineage(self):
+        """ Color the tracks by their lineage (daughters same colors as parents) """
+        ## must color it manually by getting the Label layer colors for each track_id
+        cols = np.zeros((len(self.tracklayer.data[:,0]),4))
+        for i, tr in enumerate(self.tracklayer.data[:,0]):
+            ## find the parent cell,n going up the tree until no more parent
+            while tr in self.graph.keys():
+                tr = self.graph[tr][0]
+            cols[i] = (self.epicure.seglayer.get_color(tr))
+        self.tracklayer._track_colors = cols
+        self.tracklayer.events.color_by()
 
     def replace_tracks(self, track_df):
         """ Replace all tracks based on the dataframe """
@@ -164,27 +181,6 @@ class Tracking(QWidget):
         progress_bar.close()
         self.color_tracks_as_labels()
         self.viewer.window._status_bar._toggle_activity_dock(False)
-    
-
-    def update_tracks(self, labels, refresh=True):
-        """ Update the track infos of a few labels """
-        print("DEPRECATED")
-        if self.track_df is not None:
-            ## remove them
-            self.track_df = self.track_df.drop( self.track_df[self.track_df['label'].isin(labels)].index ) 
-            ## and remeasure them
-            seglabels = self.epicure.seg*np.isin(self.epicure.seg, labels)
-            dflabels = self.measure_labels( seglabels )
-            self.track_df = pd.concat( [self.track_df, dflabels] )
-            ## update tracks
-            if refresh:
-                #self.graph = {}
-                print("Graph of division/merges not updated, removed")
-                if "Tracks" not in self.viewer.layers:
-                    self.init_tracks()
-                else:
-                    #self.show_tracks()
-                    self.viewer.layers["Tracks"].refresh()
 
     def measure_track_features(self, track_id):
         """ Measure features (length, total displacement...) of given track """
@@ -262,13 +258,20 @@ class Tracking(QWidget):
             
     def get_track_list(self):
         """ Get list of unique track_ids """
-        #return self.track._manager.unique_track_ids()
-        return np.unique(self.track_data[:,0])
+        return np.unique( self.track_data[:,0] )
+    
+    def get_tracks_on_frame( self, tframe ):
+        """ Return list of tracks present on given frame """
+        return np.unique( self.track_data[ self.track_data[:,1]==tframe, 0] ) 
 
     def has_track(self, label):
         """ Test if track label is present """
         return label in self.track_data[:,0]
     
+    def has_tracks(self, labels):
+        """ Test if track labels are present """
+        return np.isin( labels, self.track_data[:,0] )
+    
     def nb_points(self):
         """ Number of points in the tracks """
         return self.track_data.shape[0]
@@ -288,13 +291,14 @@ class Tracking(QWidget):
 
     def gap_frames(self, track_id):
         """ Returns the frame(s) at which the gap(s) are """
-        min_frame, max_frame = self.get_extreme_frames( track_id )
-        frame = min_frame
-        indexes = self.get_track_indexes(track_id)
-        track_frames = self.track_data[indexes,1]
-        gaps = list(set(range(min_frame, max_frame+1, 1)) - set(track_frames))
-        if len(gaps) > 0:
-            gaps.sort()
+        track_frames = self.get_track_column( track_id, "frame" )
+        gaps = []
+        if len( track_frames ) > 0:
+            min_frame = int( np.min(track_frames) )
+            max_frame = int( np.max(track_frames) )
+            gaps = np.setdiff1d( np.arange(min_frame+1, max_frame), track_frames ).tolist()
+            if len(gaps) > 0:
+                gaps.sort()
         return gaps
             
     def check_gap(self, tracks=None, verbose=None):
@@ -314,9 +318,9 @@ class Tracking(QWidget):
 
     def get_track_indexes(self, track_id):
         """ Get indexes of track_id tracks position in the arrays """
-        if type(track_id) == int:
-            return (np.argwhere( self.track_data[:,0] == track_id )).flatten()
-        return (np.argwhere( np.isin( self.track_data[:,0], track_id ) )).flatten()
+        if isinstance( track_id,  int ):
+            return (np.flatnonzero( self.track_data[:,0] == track_id ) )
+        return (np.flatnonzero( np.isin( self.track_data[:,0], track_id ) ) )
 
     def get_track_indexes_from_frame(self, track_id, frame):
         """ Get indexes of track_id tracks position in the arrays from the given frame """
@@ -350,12 +354,29 @@ class Tracking(QWidget):
         indexes = self.get_track_indexes( track_id )
         track = self.track_data[indexes,]
         return track
+    
+    def get_track_column( self, track_id, column ):
+        """ Return the chosen column (frame, x, y, label) of track track_id """
+        indexes = self.get_track_indexes( track_id )
+        if column == "frame":
+            i = 1
+        if column == "label":
+            i = 0
+        if column == "pos":
+            i = [2,3]
+        track = self.track_data[indexes, i]
+        return track
 
     def get_frame_data( self, track_id, ind ):
         """ Get ind-th data of track track_id """
         track = self.get_track_data( track_id )
         return track[ind]
 
+    def get_position( self, track_id, frame ):
+        """ Get position of the track at given frame """
+        ind = self.get_index( track_id, frame )
+        return [int(self.track_data[ind, 2]), int(self.track_data[ind,3])]
+
     def mean_position(self, indexes, only_first=False):
         """ Mean positions of tracks at indexes """
         if len(indexes) <= 0:
@@ -372,7 +393,8 @@ class Tracking(QWidget):
         track = self.get_track_data( track_id )
         if len(track) <= 0:
             return None
-        return int(np.min(track[:,1]))
+        return int( np.min(track[:,1]) )
+
     
     def get_last_frame(self, track_id):
         """ Returns last frame where track_id is present """
@@ -394,11 +416,7 @@ class Tracking(QWidget):
     def update_centroid(self, track_id, frame, ind=None, cx=None, cy=None):
         """ Update track at given frame """
         if ind is None:
-            ind = self.get_track_indexes( track_id )
-        if len(ind) > 1:
-            if self.epicure.verbose > 1:
-                print(ind)
-                print("Weird")
+            ind = self.get_index( track_id, frame )
         if cx is None:
             prop = ut.getPropLabel( self.epicure.seg[frame], track_id )
             self.track_data[ind, 2:4] = prop.centroid[1]
@@ -417,16 +435,15 @@ class Tracking(QWidget):
                 self.update_graph( track_index, frame )
         self.track_data[[ind, oind],0] = [otid, tid]
 
-
     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)
-        for i, tid in enumerate(frame_table["label"]):
+        frame_table = regionprops_table( 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(frame_table["centroid-0"][i]), int(frame_table["centroid-1"][i]) )
+                self.update_centroid( tid, frame, index, int(x), int(y) )
             else:
-                cur_cell = np.array( [tid, frame, int(frame_table["centroid-0"][i]), int(frame_table["centroid-1"][i])] )
+                cur_cell = np.array( [tid, frame, int(x), int(y)] )
                 cur_cell = np.expand_dims(cur_cell, axis=0)
                 self.track_data = np.append(self.track_data, cur_cell, axis=0)
     
@@ -438,7 +455,7 @@ class Tracking(QWidget):
             cur_cell = np.expand_dims(cur_cell, axis=0)
             self.track_data = np.append(self.track_data, cur_cell, axis=0)
 
-    def remove_one_frame(self, track_id, frame, handle_gaps=True, refresh=True):
+    def remove_one_frame( self, track_id, frame, handle_gaps=False, refresh=True ):
         """ 
         Remove one frame from track(s) 
         """
@@ -455,12 +472,12 @@ class Tracking(QWidget):
                 self.update_graph( tid, frame )
             else:
                 check_for_gaps = True
-        self.track_data = np.delete(self.track_data, inds, axis=0)
+        self.track_data = np.delete( self.track_data, inds, axis=0 )
         ## gaps might have been created in the tracks, for now doesn't allow it so split the tracks
         if handle_gaps and check_for_gaps:
             gaped = self.check_gap( track_id, verbose=0 )
             if len(gaped) > 0:
-                self.epicure.fix_gaps(gaped)
+                self.epicure.fix_gaps( gaped )
         
     def get_current_value(self, track_id, frame):
         ind = self.get_index( track_id, frame )
@@ -506,10 +523,37 @@ class Tracking(QWidget):
         """ Change the value of a key in the graph """
         self.graph[new_key] = self.graph.pop(prev_key)
 
+    def is_parent( self, cur_id ):
+        """ Return if the current id is in the graph (as a parent, so in values) """
+        if self.graph is None:
+            return False
+        return any( cur_id in vals for vals in self.graph.values() )
+
+    def add_division( self, childa, childb, parent ):
+        """ 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
+
+    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]
+
     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 )
@@ -529,6 +573,29 @@ class Tracking(QWidget):
         """ Remove track with given id """
         inds = self.get_track_indexes(track_ids)
         self.track_data = np.delete(self.track_data, inds, axis=0)
+        self.remove_ids_from_graph( track_ids )
+    
+    def remove_ids_from_graph( self, track_ids ):
+        """ Remove all ids from the graph """
+        track_ids_set = set( track_ids )
+        self.graph = {
+            key: vals for key, vals in self.graph.items()
+            if (key not in track_ids_set) and ( not any( val in track_ids_set for val in (vals if isinstance(vals, list) else [vals])) )
+        }
+        """
+        for tid in track_ids:
+            self.graph.pop( tid, None )
+        
+        keys = self.graph.keys()
+        for key in keys:
+            kgr = self.graph[ key ]
+            if not isinstance( kgr, list ):
+                kgr = [kgr]
+            for val in kgr:
+                if val in track_ids:
+                    del self.graph[key]
+                    continue
+                """
        
     def build_tracks(self, track_df):
         """ Create tracks from dataframe (after tracking) """
@@ -550,7 +617,7 @@ class Tracking(QWidget):
     def add_track_features(self, labels):
         """ Add features specific to tracks (eg nframes) """
         nframes = np.zeros(len(labels), int)
-        if self.epicure.verbose > 1:
+        if self.epicure.verbose > 2:
             print("REPLACE BY COUNT METHOD")
         for track_id in np.unique(labels):
             cur_track = np.argwhere(labels == track_id)
@@ -594,10 +661,6 @@ class Tracking(QWidget):
         if self.track_choice.currentText() == "Laptrack-Overlaps":
             return self.laptrack_overlaps_twoframes(labels, twoframes)
         
-        if self.track_choice.currentText() == "Optictrack":
-            if self.epicure.verbose > 1:
-                print("Merge propagation with Optitrack not implemented yet")
-            return [None]*len(labels)
 
     def do_tracking(self):
         """ Start the tracking with the selected options """
@@ -609,7 +672,7 @@ class Tracking(QWidget):
             end = self.nframes-1
         start_time = time.time()
         self.viewer.window._status_bar._toggle_activity_dock(True)
-        self.epicure.suspecting.reset_all_suspects()
+        self.epicure.inspecting.reset_all_events()
         
         if self.track_choice.currentText() == "Laptrack-Centroids":
             if self.epicure.verbose > 1:
@@ -621,9 +684,6 @@ class Tracking(QWidget):
                 print("Starting track with Laptrack-Centroids")
             self.laptrack_overlaps( start, end )
             self.epicure.tracked = 1
-        if self.track_choice.currentText() == "Optictrack":
-            self.optic_track(start, end )
-            self.epicure.tracked = 2
         
         self.epicure.finish_update(contour=2)
         #self.epicure.reset_free_label()
@@ -634,7 +694,6 @@ class Tracking(QWidget):
     def show_trackoptions(self):
         self.gLapCentroids.setVisible(self.track_choice.currentText() == "Laptrack-Centroids")
         self.gLapOverlap.setVisible(self.track_choice.currentText() == "Laptrack-Overlaps")
-        #self.gOptic.setVisible(self.track_choice.currentText() == "Optictrack")
 
     def relabel_nonunique_labels(self, track_df):
         """ After tracking, some track can be splitted and get same label, fix that """
@@ -683,18 +742,16 @@ class Tracking(QWidget):
         new_mergedf = mergedf.copy()
         for tid in np.unique(track_df['track_id']):
             ctrack_df = track_df[track_df['track_id']==tid]
-            newval = ctrack_df["label"][ctrack_df["frame"]==np.min(ctrack_df["frame"])]
+            newval = int( ctrack_df.loc[ ctrack_df["frame"]==np.min(ctrack_df["frame"]), "label" ].iloc[0] )
             ## have to replace if different
-            if tid != int(newval):
-                newval = int(newval)
-                toreplace = track_df['track_id']==tid
-                new_trackids[toreplace] = newval
+            if tid != newval:
+                new_trackids.loc[ track_df['track_id']==tid ] = newval
                 if not new_splitdf.empty:
-                    new_splitdf["parent_track_id"][splitdf["parent_track_id"]==tid] = newval
-                    new_splitdf["child_track_id"][splitdf["child_track_id"]==tid] = 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["parent_track_id"][ mergedf["parent_track_id"]==tid ] = newval
-                    new_mergedf["child_track_id"][ mergedf["child_track_id"]==tid ] = 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
         return new_trackids, new_splitdf, new_mergedf
 
     def change_labels(self, track_df):
@@ -792,8 +849,9 @@ class Tracking(QWidget):
         self.replace_tracks(track_df)
 
         progress_bar.update(indprogress+2)
-        ## update the list of suspects ?
-        #self.epicure.update_epicells()
+        ## update the list of events, or others 
+        self.epicure.updates_after_tracking()
+        progress_bar.update(indprogress+3)
         return graph
 
 
@@ -804,62 +862,29 @@ class Tracking(QWidget):
         """ GUI of the laptrack option """
         self.gLapCentroids = QGroupBox("Laptrack-Centroids")
         glap_layout = QVBoxLayout()
-        mdist = QHBoxLayout()
-        mdist_lab = QLabel()
-        mdist_lab.setText("Max distance")
-        mdist.addWidget(mdist_lab)
-        self.max_dist = QLineEdit()
-        self.max_dist.setText("15.0")
-        mdist.addWidget(self.max_dist)
+        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
-        scost = QHBoxLayout()
-        scost_lab = QLabel()
-        scost_lab.setText("Splitting cutoff")
-        scost.addWidget(scost_lab)
-        self.splitting_cost = QLineEdit()
-        self.splitting_cost.setText("1")
-        scost.addWidget(self.splitting_cost)
+        scost, self.splitting_cost = wid.value_line( "Splitting cutoff", "1", "Weight to split a track in two (increasing it favors division)" )
         glap_layout.addLayout(scost)
         ## merging ~ error ?
-        mcost = QHBoxLayout()
-        mcost_lab = QLabel()
-        mcost_lab.setText("Merging cutoff")
-        mcost.addWidget(mcost_lab)
-        self.merging_cost = QLineEdit()
-        self.merging_cost.setText("1")
-        mcost.addWidget(self.merging_cost)
+        mcost, self.merging_cost = wid.value_line( "Merging cutoff", "1", "Weight to merge to labels together" )
         glap_layout.addLayout(mcost)
 
-        self.check_penalties = QCheckBox(text="Add features cost")
-        glap_layout.addWidget(self.check_penalties)
+        add_feat, self.check_penalties, self.bpenalties = wid.checkgroup_help( "Add features cost", True, "Add cell features in the tracking calculation", None )
         self.create_penalties()
+        glap_layout.addWidget(self.check_penalties)
         glap_layout.addWidget(self.bpenalties)
-        self.check_penalties.setChecked(True)
-        self.check_penalties.stateChanged.connect(self.show_penalties)
         self.gLapCentroids.setLayout(glap_layout)
 
     def show_penalties(self):
         self.bpenalties.setVisible(not self.bpenalties.isVisible())
 
     def create_penalties(self):
-        self.bpenalties = QGroupBox("Features cost")
         pen_layout = QVBoxLayout()
-        areaCost = QHBoxLayout()
-        penarea_lab = QLabel()
-        penarea_lab.setText("Area difference:")
-        self.area_cost = QLineEdit()
-        self.area_cost.setText("2")
-        areaCost.addWidget(penarea_lab)
-        areaCost.addWidget(self.area_cost)
+        areaCost, self.area_cost = wid.value_line( "Area difference", "2", "Weight of the difference of area between two labels to link them (0 to ignore)" )
         pen_layout.addLayout(areaCost)
-        solidCost = QHBoxLayout()
-        pensol_lab = QLabel()
-        pensol_lab.setText("Solidity difference:")
-        self.solidity_cost = QLineEdit()
-        self.solidity_cost.setText("0")
-        solidCost.addWidget(pensol_lab)
-        solidCost.addWidget(self.solidity_cost)
+        solidCost, self.solidity_cost = wid.value_line( "Solidity difference", "0", "Weight of the difference of solidity between two labels to link them (0 to ignore)" )
         pen_layout.addLayout(solidCost)
         self.bpenalties.setLayout(pen_layout)
 
@@ -905,7 +930,7 @@ class Tracking(QWidget):
             laptrack.penal_solidity = float(self.solidity_cost.text())
         laptrack.set_region_properties(with_extra=self.check_penalties.isChecked())
 
-        progress_bar = progress(total=5)
+        progress_bar = progress(total=7)
         progress_bar.set_description( "Prepare tracking" )
         if self.epicure.verbose > 1:
             print("Convert labels to centroids: use track info ?")
@@ -919,7 +944,7 @@ class Tracking(QWidget):
         if self.epicure.verbose > 1:
             print("After tracking, update everything")
         self.after_tracking(track_df, split_df, merge_df, progress_bar, 2)
-        progress_bar.update(5)
+        progress_bar.update(6)
         progress_bar.close()
     
 ############ Laptrack overlap option
@@ -928,31 +953,13 @@ class Tracking(QWidget):
         """ GUI of the laptrack overlap option """
         self.gLapOverlap = QGroupBox("Laptrack-Overlaps")
         glap_layout = QVBoxLayout()
-        miou = QHBoxLayout()
-        miou_lab = QLabel()
-        miou_lab.setText("Min IOU")
-        miou.addWidget(miou_lab)
-        self.min_iou = QLineEdit()
-        self.min_iou.setText("0.1")
-        miou.addWidget(self.min_iou)
+        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)
         
-        scost = QHBoxLayout()
-        scost_lab = QLabel()
-        scost_lab.setText("Splitting cost")
-        scost.addWidget(scost_lab)
-        self.split_cost = QLineEdit()
-        self.split_cost.setText("0.2")
-        scost.addWidget(self.split_cost)
+        scost, self.split_cost = wid.value_line( "Splitting cost", "0.2", "Weight of linking a parent label with two labels (increasing it for more divisions)" )
         glap_layout.addLayout(scost)
         
-        mcost = QHBoxLayout()
-        mcost_lab = QLabel()
-        mcost_lab.setText("Merging cost")
-        mcost.addWidget(mcost_lab)
-        self.merg_cost = QLineEdit()
-        self.merg_cost.setText("0.2")
-        mcost.addWidget(self.merg_cost)
+        mcost, self.merg_cost = wid.value_line( "Merging cost", "0.2", "Weight of merging two parent labels into one" )
         glap_layout.addLayout(mcost)
 
         self.gLapOverlap.setLayout(glap_layout)
@@ -1001,37 +1008,4 @@ class Tracking(QWidget):
         parent_labels = laptrack.twoframes_track(twoframes, labels)
         return parent_labels
 
-    def create_optictrack(self):
-        """ GUI of the Optical track option """
-        self.gOptic = QGroupBox("Optictrack")
-        gOptic_layout = QVBoxLayout()
-        miou = QHBoxLayout()
-        miou_lab = QLabel()
-        miou_lab.setText("Min IOU")
-        miou.addWidget(miou_lab)
-        self.min_iou = QLineEdit()
-        self.min_iou.setText("0.25")
-        miou.addWidget(self.min_iou)
-        gOptic_layout.addLayout(miou)
-        rad = QHBoxLayout()
-        rad_lab = QLabel()
-        rad_lab.setText("Flow radius")
-        rad.addWidget(rad_lab)
-        self.rad = QLineEdit()
-        self.rad.setText("10")
-        rad.addWidget(self.rad)
-        gOptic_layout.addLayout(rad)
-        self.show_opticed = QCheckBox("Show flowed segmentation")
-        self.show_opticed.setChecked(False)
-        gOptic_layout.addWidget(self.show_opticed)
-        self.gOptic.setLayout(gOptic_layout)
-
-    def optic_track(self, start, end):
-        """ Perform track with optical flow registration + best match """
-        optic = trackOptical(self, self.epicure)
-        miniou = float(self.min_iou.text())
-        radius = float(self.rad.text())
-        opticed = self.show_opticed.isChecked()
-        optic.set_parameters(miniou, radius, opticed)
-        optic.track_by_optical_flow( self.viewer, start, end )
 
diff --git a/src/epicure/tracking_editing.py b/src/epicure/tracking_editing.py
index 04dfbb64dad148830e1e5da5b18452bd0cfe22af..b2f7d405c84d9c5525e57d6396761cc30eb34cc7 100644
--- a/src/epicure/tracking_editing.py
+++ b/src/epicure/tracking_editing.py
@@ -10,7 +10,7 @@ class trackEditingWidget(QWidget):
         self.viewer = napari_viewer
         self.epicure = epic
         self.suggestion = self.epicure.suggestion
-        self.suspects = self.epicure.suspects
+        self.events = self.epicure.events
         
         layout = QVBoxLayout()
         self.btn = QPushButton("Nothing yet", parent=self)
@@ -18,33 +18,32 @@ class trackEditingWidget(QWidget):
         layout.addWidget(self.btn)
         self.setLayout(layout)
 
-        self.btn.clicked.connect(self.suspect_oneframe)
+        self.btn.clicked.connect(self.inspect_oneframe)
 
-    
-    def add_suspect(self, pos, label):
+    def add_inspect(self, pos, label):
         """ Add a suspicious point (position and label) """
-        self.suspects = self.epicure.suspects
-        self.epicure.add_suspect(pos, label, "tracking")
+        self.events = self.epicure.events
+        self.epicure.add_inspect(pos, label, "tracking")
 
-    def suspect_oneframe(self):
+    def inspect_oneframe(self):
         """ Find suspicious cell that exists in only one frame """
         seg = self.viewer.layers["Segmentation"]
         props = regionprops(seg.data)
         imshape = seg.data.shape
-        self.suspects.data = np.zeros(imshape, dtype="uint8")
+        self.events.data = np.zeros(imshape, dtype="uint8")
         for prop in props:
             if (prop.bbox[3]-prop.bbox[0]) == 1:
-                ## label present only on one frame, suspect
+                ## label present only on one frame, inspect
                 if (prop.bbox[3] != (imshape[0]-1)) and (prop.bbox[0]!=0):
                     ## not first or last frame
                     if (prop.bbox[1]>0) and (prop.bbox[4]<(imshape[1]-1)):
                         if (prop.bbox[2]>0) and (prop.bbox[5]<(imshape[2]-1)):
                             ## not touching border
-                            self.suspect[ seg.data==prop.label ] = 1
-        self.show_suspects()
+                            self.inspect[ seg.data==prop.label ] = 1
+        self.show_events()
 
-    def show_suspects(self):
-        self.suspects.refresh()
+    def show_events(self):
+        self.events.refresh()
         self.show_names( self.suggestion, "SuggestedId" )
         self.epicure.finish_update()
 
@@ -118,7 +117,7 @@ class trackEditingWidget(QWidget):
         bbox_rect = np.moveaxis(bbox_rect, 2, 0)
         return bbox_rect
         
-    def suspect_labels(self):
+    def inspect_labels(self):
         rprops = regionprops(self.viewer.layers["Segmentation"].data, intensity_image=self.viewer.layers["Movie"].data)
 
         supported = []
diff --git a/src/tests/test_concatenate.py b/src/tests/test_concatenate.py
index fe811323b1e29a3d68ca8242fb76436d31005bc0..c9a2935b3fb78abeef863d0fcd7cbf3fb8706837 100644
--- a/src/tests/test_concatenate.py
+++ b/src/tests/test_concatenate.py
@@ -29,18 +29,18 @@ def test_process_first_movie():
 
     print("Assign some cells to groups")
     epic.reset_groups()
-    epic.cell_ingroup(17, "Bing")
-    epic.cell_ingroup(42, "Bing")
-    epic.cell_ingroup(142, "Bang")
-    epic.cell_ingroup(143, "Bang")
+    epic.cells_ingroup(17, "Bing")
+    epic.cells_ingroup(42, "Bing")
+    epic.cells_ingroup(142, "Bang")
+    epic.cells_ingroup(143, "Bang")
 
     print("Do tracking with default parameters")
     epic.tracking.do_tracking()
     assert epic.nlabels() == 73
 
     print("Generate suspect list")
-    epic.suspecting.inspect_tracks()
-    assert len(epic.suspecting.suspects.data) == 6
+    epic.inspecting.inspect_tracks()
+    assert len(epic.inspecting.events.data) == 4
 
     epic.save_epicures()
     assert os.path.exists(os.path.join( main_dir, "epics", "013-t1-t4_labels.tif") )
@@ -64,8 +64,8 @@ def test_process_second_movie():
     
     epic.tracking.do_tracking()
     epic.reset_groups()
-    epic.cell_ingroup(21, "Bing")
-    epic.cell_ingroup(22, "BAM")
+    epic.cells_ingroup(21, "Bing")
+    epic.cells_ingroup(22, "BAM")
     epic.save_epicures()
     assert os.path.exists(os.path.join( main_dir, "epics", "013-t4-t6_labels.tif") )
     assert os.path.exists(os.path.join( main_dir, "epics", "013-t4-t6_epidata.pkl") )
diff --git a/src/tests/test_editings.py b/src/tests/test_editings.py
index 05daae111acf68e76c1b42a0d49e9f05f7c06160..3e99e18b780bb3f85fc34c8ac632d9b098491c57 100644
--- a/src/tests/test_editings.py
+++ b/src/tests/test_editings.py
@@ -65,7 +65,7 @@ def test_group():
     assert epic.viewer is not None
     layer = epic.viewer.layers["Segmentation"]
     
-    epic.cell_ingroup( 111, "Test" )
+    epic.cells_ingroup( 111, "Test" )
     assert "Test" in epic.groups
 
     segedit = epic.editing
@@ -74,7 +74,7 @@ def test_group():
     event.view_direction = None
     event.dims_displayed = [0,1, 1] 
     assert "GroupTest" not in epic.groups
-    segedit.group_group.setText("GroupTest")
+    segedit.group_choice.setCurrentText("GroupTest")
     segedit.add_cell_to_group(event)
     assert "GroupTest" in epic.groups
     
diff --git a/src/tests/test_outputs.py b/src/tests/test_outputs.py
index ff0c7f24c4e5e6af82d752bdad7359cbed93066e..8fcb03157caf591abdc5193a30a69d7d5531a36a 100644
--- a/src/tests/test_outputs.py
+++ b/src/tests/test_outputs.py
@@ -20,12 +20,15 @@ def test_output_selected():
     output = epic.outputing
     assert output is not None
     sel = output.get_selection_name()
-    assert sel == ""
-    output.output_mode.setCurrentText("Only selected cell")
-    sel = output.get_selection_name()
     assert sel == "_cell_1"
+    output.output_mode.setCurrentText("All cells")
+    sel = output.get_selection_name()
+    assert sel == ""
     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)
-    output.roi_out()
-    assert os.path.exists(roi_file)
+    ## TO UPDATE WITH NEW VERSION
+    #output.roi_out()
+    #assert os.path.exists(roi_file)
+
+#test_output_selected()
diff --git a/src/tests/test_suspects.py b/src/tests/test_suspects.py
index a044091e77f27092380e7b91027a252df25a965c..a17fa80eb4c42698255d931fe4be4ab080e3dc36 100644
--- a/src/tests/test_suspects.py
+++ b/src/tests/test_suspects.py
@@ -17,17 +17,18 @@ def test_suspect_frame():
     assert epic.viewer is not None
     epic.go_epicure("test_epics", test_seg)
     
-    segedit = epic.suspecting
+    segedit = epic.inspecting
     assert segedit is not None
-    segedit.suspect_area(True)
-    assert "Suspects" in epic.viewer.layers
-    outlier = epic.viewer.layers["Suspects"]
-    assert len(outlier.data)>=5
+    segedit.min_area.setText("50")
+    segedit.event_area_threshold()
+    assert "Events" in epic.viewer.layers
+    outlier = epic.viewer.layers["Events"]
+    assert len(outlier.data)>=10
     assert outlier.data[1][0] == 0
     
     nsus = len(outlier.data)
-    segedit.fintensity_out.setValue(0.5)
-    segedit.suspect_intensity(True)
+    segedit.fintensity_out.setText("0.5")
+    segedit.event_intensity(True)
     assert len(outlier.data) > (nsus+5)
 
 def test_suspect_track():
@@ -42,27 +43,30 @@ def test_suspect_track():
     track = epic.tracking
     
     # default tracking
+    susp = epic.inspecting
+    assert susp.nb_events() == 0
     track.do_tracking()
-    susp = epic.suspecting
     ## test basics
-    assert susp.nb_suspects() == "_"
-    susp.add_suspect( (5,50,50), 10, "test" )
-    assert susp.nb_suspects() == 1
+    assert susp.nb_events() == susp.nb_type("division")
+    nev = susp.nb_events()
+    susp.add_event( (5,50,50), 10, "test" )
+    assert susp.nb_events() == (nev+1)
     ## test default parameter inspection
     susp.inspect_tracks()
-    assert susp.nb_suspects() > 50
-    assert susp.nb_suspects() < 100
+    assert susp.nb_events() > 50
+    assert susp.nb_events() < 100
     ## test minimum track length inspection
+    susp.check_size.setChecked( False )
     susp.min_length.setText("5")
     susp.inspect_tracks()
-    nmin =  susp.nb_suspects()
-    assert nmin > 100
+    nmin =  susp.nb_events()
+    assert nmin > 50
     ## test reset all
-    susp.reset_all_suspects()
-    assert susp.nb_suspects() == "_"
+    susp.reset_all_events()
+    assert susp.nb_events() == 0
     ## Track feature change test
-    susp.check_size.setChecked( True )
-    susp.inspect_tracks()
-    assert susp.nb_suspects() > nmin
-
+    ## A CHECKER
+    #susp.check_size.setChecked( True )
+    #susp.inspect_tracks()
+    #assert susp.nb_events() > nmin
 
diff --git a/src/tests/test_tracking.py b/src/tests/test_tracking.py
index 62a748d0ed7070d183acb9380f206ac5fd9751b1..e4804d42313644fd6b16aca92eae12f846a7a74a 100644
--- a/src/tests/test_tracking.py
+++ b/src/tests/test_tracking.py
@@ -52,8 +52,10 @@ def test_track_methods():
     midle = track.get_first_frame(track_id) + 2
     track.remove_one_frame( track_id, midle )
     gaped = track.check_gap()
-    assert len(gaped) <= 0
+    ## gaps are allowed now
+    assert len(gaped) > 0
     epic.handle_gaps( None )
     assert track.nb_tracks() == 310
 
 #track_methods()
+#test_track_methods()
\ No newline at end of file