From 8ec72e5c6b4891a4680fdbd266624e0a177408b2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Lo=C3=AFc=20Lehnhoff?= <loic.lehnhoff@gmail.com>
Date: Thu, 16 Nov 2023 13:47:09 +0100
Subject: [PATCH] Optimization of display

+ visibility depends on visible frame

~ changed canvas.draw() to canvas.draw_idle()

+ huge re-organisation packed in lite_line_clicker.py
---
 .gitignore                                    |   6 +-
 functions.py                                  |  14 +-
 interface.py                                  | 146 ++--
 .../__pycache__/__init__.cpython-39.pyc       | Bin 164 -> 0 bytes
 .../__pycache__/line_clicker.cpython-39.pyc   | Bin 19273 -> 0 bytes
 line_clicker/line_clicker.py                  | 344 ++++----
 line_clicker/lite_line_clicker.py             | 787 ++++++++++++++++++
 7 files changed, 1076 insertions(+), 221 deletions(-)
 delete mode 100644 line_clicker/__pycache__/__init__.cpython-39.pyc
 delete mode 100644 line_clicker/__pycache__/line_clicker.cpython-39.pyc
 create mode 100644 line_clicker/lite_line_clicker.py

diff --git a/.gitignore b/.gitignore
index ba372b7..accacbb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
-
+audio_examples/SCW1807_20200711_114100.wav
 audio_examples/SCW1807_20200713_064545.wav
+outputs/SCW1807_20200711_114100-contours.json
+
 line_clicker/__pycache__
-__pycache__
+__pycache__
\ No newline at end of file
diff --git a/functions.py b/functions.py
index 80d4566..202bbce 100644
--- a/functions.py
+++ b/functions.py
@@ -7,7 +7,7 @@ from librosa import load, amplitude_to_db, stft, pcen
 from scipy.signal import resample
 
 ##### FUNCTIONS #####
-def save_dict(dictionary, folder, name):
+def save_dict(dictionary, folder, name, contours=True):
     """
     A function that saves a dictionary to a given path.
 
@@ -28,6 +28,18 @@ def save_dict(dictionary, folder, name):
     None : save dict to json file.
     """
     if len(dictionary) > 0:
+
+        if contours:
+            # delete labels with empty labels
+            for key in dictionary.keys():
+                if len(dictionary[key])<=1:
+                    del dictionary[key]
+            
+            # sort contouts by starting time.
+            dictionary = dict(
+                sorted(dictionary.items(), 
+                key=lambda item: (np.min(np.array(item[1])[:,0]))))
+
         with open(os.path.join(folder, name), "w") as f:
             json.dump(dictionary, f, indent=4) 
 
diff --git a/interface.py b/interface.py
index 9ebcfa6..1921c60 100644
--- a/interface.py
+++ b/interface.py
@@ -1,19 +1,21 @@
 ##### IMPORTATIONS #####
-import os 
 import json
-import numpy as np
+import os
 from tkinter import *
-from tkinter import simpledialog as sd
 from tkinter import filedialog as fd
+from tkinter import simpledialog as sd
 from tkinter import ttk
-from matplotlib.backends.backend_tkagg import (NavigationToolbar2Tk, 
-    FigureCanvasTkAgg)
-from matplotlib.figure import Figure
+
 import matplotlib.colors as mc
-from line_clicker.line_clicker import clicker
+import numpy as np
+
+from functions import load_waveform, save_dict, wave_to_spectrogram
+from line_clicker.lite_line_clicker import clicker
 
-# Import external functions
-from functions import load_waveform, wave_to_spectrogram, save_dict
+from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
+                                               NavigationToolbar2Tk)
+from matplotlib.figure import Figure
+from matplotlib.patches import Rectangle
 
 
 ##### CLASSES #####
@@ -250,9 +252,10 @@ class App(object):
             Saves contours and closes the app.
     """
 
-    from parameters import (_default_width, _default_height, _default_hop_length, 
-        _default_nfft, _default_clipping, _default_cmap, _default_bounds,
-        _default_left_panel_width)
+    from parameters import (_default_bounds, _default_clipping, _default_cmap,
+                            _default_height, _default_hop_length,
+                            _default_left_panel_width, _default_nfft,
+                            _default_width)
 
     def __init__(
         self, 
@@ -298,7 +301,8 @@ class App(object):
         self.root.style.theme_use('clam')
         self.create_canvas()
 
-        # addon
+        # addons
+        self.figure_bboxes = []
         self.klicker = clicker(
             axis=self.axis,
             names=["Line" + str(i+1) for i in range(self.NAME0, self.NAME1)],
@@ -313,10 +317,11 @@ class App(object):
         # To avoid problems, disconnect matplotlib keypress 
         self.figure.canvas.mpl_disconnect(self.klicker.key_press)
         self.root.bind('<Key>', self.get_key_pressed)
+        self.figure.canvas.mpl_connect('button_press_event', self._draw_bbox)
 
         # just to be sure
         self.root.protocol("WM_DELETE_WINDOW", self.on_close)
-
+        self.root.resizable(True, True)
         self.root.mainloop()
 
     def on_close(self):
@@ -341,11 +346,10 @@ class App(object):
         (It uses the "wait" parameter to force straigth lines).
         """
         if self.CHECK_bspline.get():
-            self.klicker.wait = 2
+            self.klicker.wait_before_interpolation = 2
         else:
-            self.klicker.wait = np.inf
+            self.klicker.wait_before_interpolation = np.inf
         self.klicker.update_lines()
-        self.klicker.figure.canvas.draw()
 
     def create_canvas(self):
         """
@@ -389,9 +393,10 @@ class App(object):
         """
         self.FFT_IN = IntVar(value=self._default_nfft)
         self.HOP_IN = IntVar(value=self._default_hop_length)
-        self.CHECK_bspline = IntVar(value=1)
+        self.CHECK_bspline = BooleanVar(value=True)
+        self.CHECK_bbox = BooleanVar(value=False)
         self.CLIP_IN = DoubleVar(value=self._default_clipping)
-        self.OPTIONS = Variable(value=self.klicker.legend_labels)
+        self.OPTIONS = Variable(value=list(self.klicker.coords.keys()))
 
     def get_key_pressed(self, event):
         """
@@ -424,10 +429,10 @@ class App(object):
             self.klicker.add_category(False)
 
             # if a category is added. Update listbox and canvas.
-            if len(self.klicker.legend_labels) > self.listbox.size():
+            if len(self.klicker.coords) > self.listbox.size():
                 self.listbox.insert(
                     self.listbox.size(), 
-                    self.klicker.legend_labels[-1])
+                    list(self.klicker.coords.keys())[-1])
                 self.listbox.itemconfig(
                     self.listbox.size()-1, 
                     {
@@ -472,7 +477,7 @@ class App(object):
         self.root.geometry(
             f"{str(self._default_width)}x{str(self._default_height)}")
         self.root.rowconfigure(1, weight=1)
-        self.root.rowconfigure(14, weight=1)
+        self.root.rowconfigure(15, weight=1)
         self.root.configure(bg='gainsboro')
 
         # Add Panel for line selection on Left side
@@ -492,12 +497,19 @@ class App(object):
             command=self.bspline_activation)
         self.activate_bspline.grid(row=4, column=0)
 
+        self.show_bbox = Checkbutton(
+            self.root, 
+            text='Show Bounding Boxes',
+            variable=self.CHECK_bbox,
+            command=self._draw_bbox)
+        self.show_bbox.grid(row=5, column=0)
+
         # Add space between panels
         self.empty_frame = Label(
             self.root, 
             width=self._default_left_panel_width,
             height=30)
-        self.empty_frame.grid(row=5, column=0)
+        self.empty_frame.grid(row=6, column=0)
 
         # Add panel for spectrogram personalisation on Left side.
         self.fft_label = Label(
@@ -505,69 +517,69 @@ class App(object):
             width=self._default_left_panel_width,
             text='FFT window size:', 
             font=('calibre',10, 'bold'))
-        self.fft_label.grid(row=6, column=0)
+        self.fft_label.grid(row=7, column=0)
 
         self.fft_entry = Entry(
             self.root, 
             width=self._default_left_panel_width,
             textvariable=self.FFT_IN, 
             font=('calibre',10,'normal'))
-        self.fft_entry.grid(row=7, column=0)
+        self.fft_entry.grid(row=8, column=0)
 
         self.win_label = Label(
             self.root, 
             width=self._default_left_panel_width,
             text='Hop length:', 
             font=('calibre',10,'bold'))
-        self.win_label.grid(row=8, column=0)
+        self.win_label.grid(row=9, column=0)
 
         self.win_entry = Entry(
             self.root, 
             width=self._default_left_panel_width,
             textvariable=self.HOP_IN, 
             font=('calibre',10,'normal'))
-        self.win_entry.grid(row=9, column=0)
+        self.win_entry.grid(row=10, column=0)
 
         self.clip_label = Label(
             self.root, 
             width=self._default_left_panel_width,
             text='Clipping (dB):',
             font=('calibre',10,'bold'))
-        self.clip_label.grid(row=10, column=0)
+        self.clip_label.grid(row=11, column=0)
 
         self.clip_entry = Entry(
             self.root, 
             width=self._default_left_panel_width,
             textvariable=self.CLIP_IN,
             font=('calibre',10,'normal'))
-        self.clip_entry.grid(row=11, column=0)
+        self.clip_entry.grid(row=12, column=0)
 
         self.submit_button = Button(
             self.root, 
             text='Update display',
             width=self._default_left_panel_width,
             command=self.submit)
-        self.submit_button.grid(row=12, column=0)
+        self.submit_button.grid(row=13, column=0)
 
         self.switch_view_button = Button(
             self.root,
             text=self.initial_text_pcen,
             width=self._default_left_panel_width,
             command=self.switch)
-        self.switch_view_button.grid(row=13, column=0)
+        self.switch_view_button.grid(row=14, column=0)
 
         # Add buttons at the bottom of the interface
         self.quit_button = Button(
             self.root, 
             text="Save & Quit", 
             command=self._quit)
-        self.quit_button.grid(row=15, column=0)
+        self.quit_button.grid(row=16, column=0)
 
         self.explore_button = Button(
             self.root,
             text="Open file explorer",
             command=self.select_file)
-        self.explore_button.grid(row=15, column=1)
+        self.explore_button.grid(row=16, column=1)
 
         # Add matplotlib tools at the top of the interface
         self.toolbarFrame = Frame(self.root)
@@ -747,6 +759,37 @@ class App(object):
             vmax=np.nanmax(self.spectrogram))
         self.canvas.draw()
 
+    def _draw_bbox(self, *args):
+        # first, fetch coords
+        self.bboxes = []
+        for line in self.klicker.lines:
+            x, y = line.get_data()
+            if len(x)>0:
+                self.bboxes += [[[min(x), min(y)], [max(x)-min(x), max(y)-min(y)], line.get_color()]]
+    
+        # remove current bbox if there are
+        if len(self.figure_bboxes)>0:
+            for rectangle in self.figure_bboxes:
+                rectangle.remove()
+
+        # add new bbox if needed
+        self.figure_bboxes = []
+        if self.CHECK_bbox.get():
+            for bbox in self.bboxes:
+                self.figure_bboxes += [
+                        Rectangle(
+                            xy=(bbox[0][0], bbox[0][1]), 
+                            width=bbox[1][0], height=bbox[1][1],
+                            edgecolor = bbox[2],
+                            facecolor='none',
+                            fill=False,
+                            lw=2)]
+
+            for rectangle in self.figure_bboxes:
+                self.axis.add_patch(rectangle)
+
+        self.klicker.figure.canvas.draw()
+
     def _rename_label(self, event):
         """
         A function that allows the user to rename a category
@@ -780,13 +823,13 @@ class App(object):
         # get new name from user
         new_name = sd.askstring(
             "Rename window",
-            f"Insert new name for '{self.klicker.legend_labels[index_item]}':"
+            f"Insert new name for '{list(self.klicker.coords.keys())[index_item]}':"
         )
 
         if isinstance(new_name, str):
-            if new_name in self.klicker.legend_labels:
+            if new_name in self.klicker.coords.keys():
                 count=0
-                for label in self.klicker.legend_labels:
+                for label in self.klicker.coords.keys():
                     if (label==new_name) or ("_".join(label.split("_")[:-1])==new_name):
                         count+=1
                 new_name = new_name + f"_{count}"
@@ -797,15 +840,16 @@ class App(object):
             # destroy item
             self.listbox.delete(
                 index_item)
-            old_name = self.klicker.legend_labels.pop(index_item)
+            old_name = list(self.klicker.coords.keys())[index_item]
+
+            # new coordinates
+            self.klicker.coords = {
+                new_name if key==old_name else key:value 
+                for key,value in self.klicker.coords.items()}
 
-            # handle klicker
-            self.klicker.legend_labels.insert(index_item, new_name) 
-            self.klicker.set_legend()
+            self.klicker._set_legend()
             self.klicker.current_line = index_item
-            if old_name in self.klicker.coords.keys():
-                self.klicker.coords[new_name] = self.klicker.coords.pop(old_name)
-                self.klicker.update_lines()
+            self.klicker.update_lines()
 
             # insert item at the same index in listbox, with new name
             self.listbox.insert(
@@ -842,7 +886,7 @@ class App(object):
             width=self._default_left_panel_width,
             selectmode=SINGLE, 
             listvariable=self.OPTIONS)
-        for idx in range(len(self.klicker.legend_labels)):
+        for idx in range(len(self.klicker.coords)):
             self.listbox.itemconfig(idx, 
                 {
                 'bg': self.klicker.colors[idx%len(self.klicker.colors)],
@@ -875,19 +919,21 @@ class App(object):
         save_dict(
             self.klicker.coords, 
             self.DIR_OUT,
-            os.path.basename(self.WAVEFILE)[:-4]+"-contours.json")
+            os.path.basename(self.WAVEFILE)[:-4]+"-contours.json",
+            contours=True)
         
         # save parameters
         save_dict(
             {
             "PCEN": (self.switch_view_button['text'] == "Switch to PCEN"),
-            "SR": self.NEW_SR,
-            "NFFT":self.NFFT,
-            "HOP_LENGTH":self.HOP_LENGTH,
-            "CLIPPING":self.CLIPPING
+            "SR": int(self.NEW_SR),
+            "NFFT": int(self.NFFT),
+            "HOP_LENGTH": int(self.HOP_LENGTH),
+            "CLIPPING": int(self.CLIPPING)
             }, 
             os.path.join(self.DIR_OUT, ".."), 
-            "last-parameters-used.json"
+            "last-parameters-used.json",
+            contours=False
         )
 
         # quit window
diff --git a/line_clicker/__pycache__/__init__.cpython-39.pyc b/line_clicker/__pycache__/__init__.cpython-39.pyc
deleted file mode 100644
index d7f4827996913926ebbfe4cdca728bac46115bac..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 164
zcmYe~<>g{vU|{$lx;z;~KL!!Vn2~{j!GVE+p_qk%fgyz<m_d`#ZzV$!NEku<^3>1B
z&rQ|O$<IvIcS$Ts)OYds3Gndra|?2H)ek65%E?StNXySjNi8bY52$nubJWkt%u9_=
m&dE&9PA$@pkI&4@EQycTE2zB1VUwGmQks)$2eRTb$SD8<?I$Jx

diff --git a/line_clicker/__pycache__/line_clicker.cpython-39.pyc b/line_clicker/__pycache__/line_clicker.cpython-39.pyc
deleted file mode 100644
index f63f3cb63553a38118e9935f9ef90867132fe0e9..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 19273
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

diff --git a/line_clicker/line_clicker.py b/line_clicker/line_clicker.py
index 687ed06..a5d5543 100644
--- a/line_clicker/line_clicker.py
+++ b/line_clicker/line_clicker.py
@@ -12,30 +12,31 @@ import matplotlib.lines as mlines
 from scipy.interpolate import interp1d
 
 ##### FUNCTIONS #####
-def to_curve(x, y, kind="quadratic"):
+def to_curve(x, y, kind="quadratic", precision=10):
 	"""
-	A function to compute a curvy line between two points.
-	It interpolates a line with 100 points.
-
-	...
-
-	Parameters
-	----------
-	x : list or numpy array
-		List of coordinates on x axis.
-	y : list or numpy array
-		List of coordinates on y axis.
-	kind : string
-		The interpolation method to use. Can be any method in : ‘linear’, 
-		‘nearest’, ‘nearest-up’, ‘zero’, ‘slinear’, ‘quadratic’, ‘cubic’,
-		‘previous’, or ‘next’.
-	
-	Returns
-	-------
-	xi : numpy array
-		List of the coordinates of the curve on x-axis.
-	yi : numpy array
-		List of the coordinates of the curve on y-axis.
+		A function to compute a curvy line between two points.
+		It interpolates a line with 100 points.
+
+		...
+
+		Parameters
+		----------
+		x : list or numpy array
+			List of coordinates on x axis.
+		y : list or numpy array
+			List of coordinates on y axis.
+		kind : string
+			The interpolation method to use. Can be any method in : ‘linear’, 
+			‘nearest’, ‘nearest-up’, ‘zero’, ‘slinear’, ‘quadratic’, ‘cubic’,
+			‘previous’, or ‘next’.
+		precision : number of segments between each point.
+		
+		Returns
+		-------
+		xi : numpy array
+			List of the coordinates of the curve on x-axis.
+		yi : numpy array
+			List of the coordinates of the curve on y-axis.
 	"""
 
 	y = y[np.argsort(x)]
@@ -43,7 +44,7 @@ def to_curve(x, y, kind="quadratic"):
 
 	f = interp1d(x,y, kind=kind)
 
-	xi = np.linspace(x.min(), x.max(), 100 * (len(x)-1))
+	xi = np.linspace(x.min(), x.max(), precision * (len(x)-1))
 	yi = f(xi)
 
 	return xi, yi
@@ -51,124 +52,124 @@ def to_curve(x, y, kind="quadratic"):
 ##### CLASSES #####
 class clicker(object):
 	"""
-	A Class that is an "add-on" for an existing matplotlib figure axis.
-	Added to an axis, it allows to draw lines from mouse clicks. 
-	Points can be removed using the right click.
-
-	Different categories can be selected using the generated legend : 
-	by clicking on a line or by using up/down arrows.
-	It is also possible to add more categories using with 'ctrl+a' shortcut
-	
-	...
-
-	Params
-	----------
-	axis : matplotlib object
-		Axis instance.
-	bspline : string or boolean, optional.
-		If False, app will plot points linked by lines.
-		If string, app will plot points linked by curve.
-		For curves, use 'cubic' or 'quadratic'
-	colors : list, optional.
-		List of color (hexadecimal) to cycle over when plotting lines.
-		Default is the default list of colors from matplotlib.
-	legend_bbox : tuple, optional.
-		The position of the legend (x-axis, y-axis) relative to the figure.
-	marker : dict, optional.
-		Additional parameters to change the look of the lines.
-		Default is None, lines will be solid with points as white dots.
-	maxlines : int, optional.
-		Maximum number of labels in legend.
-		Default is 30.
-		Default is False.
-	names : str or list, optional.
-		Names for legend labels.
-		If string, names will be name+{line number}
-		If list, when a category is added, it will have the name "DefaultNameX".
-		Default is "Line".
-	n_names : int, optional.
-		Number of categories at launch when name is a string.
-		Default is 10.
-	pick_dist : int, optional.
-		Distance to legend category for picking mouse.
-		Default is 10.
-	wraplines : int, optional.
-		Maximum number of labels in legend before creating a new column.
-		Default is 15.
-		Default is (1, 0.5)
-	coords : dict, optional.
-		Coordinates of each point for each category (line, key of the dict).
-		Coordinates are expressed in (time, frequency) 
-		or (pixel, pixel) if no extent given.
-		Default is {}. If given, initates figure with annotations.
-
-
-	Attributes
-	----------
-	current_line : int
-		Index of the ctegory (line) currently selected
-	figure : matplotlib object
-		Abbreviation for axis.figure.
-	figure_bsplines : list of matplotlib 2D lines
-		Secondary lines, in use if bspline mode is selected. 
-		Draws curves instead of straight lines.
-	figure_lines : list of matplotlib 2D lines
-		Main lines, displayed on image as straight lines if bspline is False.
-		If bspline is true, linestyle is removed and they are displayed as dots.
-	key_press : matplotlib event
-		Information on the last key_press event.
-	legend : matplotlib object
-		legend of the plot
-	legend_labels : list of str
-		labels of the categories in legend.
-	linestyle : str
-		If bspline is True, saves linestyle.
-	mouse_press : matplotlib event
-		Information on the last mouse click in matplotlib canvas.
-	pick_press : matplotlib event
-		Information on the last mouse click event in matplotlib legend.
-	pressed : bool
-		Is used to check if a specific mouse button is pressed or not.
-
-	Other attributes have self explenatory names.
-
-	Methods
-	-------
-	add_category(show):
-		Adds a category to the list in legend. 
-	add_point(x,y):
-		Adds a point to the category in focus at the given coordinates. 
-		Focus is updated to be on this new category.
-	clear_category():
-		Clears all points from the category in focus.
-	double_lines():
-		Creates a copy of the current set of lines from figure_lines.
-		It is used to interpolate curves between figure_lines points.
-	distance_mouse(points, mouse_x, mouse_y):
-		Gets the distance bewteen a mouse position and the currently
-		selected category (points).
-	get_focus(event):
-		Puts focus on a category in legend. Allows the user to interact with it.
-	get_key_event(event):
-		Activates functions when specific keyboard keys are pressed.
-	get_mouse_press(event, show=True):
-		Activates functions when specific mouse buttons are pressed.
-	move_point(event):
-		Moves a selected point of the currently selected category to a new
-		position (event). Updates figure accordingly.
-	rm_point(x,y):
-		Removes a point from the category in focus at the given coordinates. 
-	set_legend():
-		Adds legend to axis given.
-	switch_line(arrow):
-		Puts focus on previous/next category in legend based on arrow input.
-	update_lines():
-		Updates data in lines. Called when coords is changed.
+		A Class that is an "add-on" for an existing matplotlib figure axis.
+		Added to an axis, it allows to draw lines from mouse clicks. 
+		Points can be removed using the right click.
+
+		Different categories can be selected using the generated legend : 
+		by clicking on a line or by using up/down arrows.
+		It is also possible to add more categories using with 'ctrl+a' shortcut
+		
+		...
 
-	References
-	----------
-	Heavily inspired from mpl_point_clicker :
-		mpl-point-clicker.readthedocs.io/
+		Params
+		----------
+		axis : matplotlib object
+			Axis instance.
+		bspline : string or boolean, optional.
+			If False, app will plot points linked by lines.
+			If string, app will plot points linked by curve.
+			For curves, use 'cubic' or 'quadratic'
+		colors : list, optional.
+			List of color (hexadecimal) to cycle over when plotting lines.
+			Default is the default list of colors from matplotlib.
+		legend_bbox : tuple, optional.
+			The position of the legend (x-axis, y-axis) relative to the figure.
+		marker : dict, optional.
+			Additional parameters to change the look of the lines.
+			Default is None, lines will be solid with points as white dots.
+		maxlines : int, optional.
+			Maximum number of labels in legend.
+			Default is 30.
+			Default is False.
+		names : str or list, optional.
+			Names for legend labels.
+			If string, names will be name+{line number}
+			If list, when a category is added, it will have the name "DefaultNameX".
+			Default is "Line".
+		n_names : int, optional.
+			Number of categories at launch when name is a string.
+			Default is 10.
+		pick_dist : int, optional.
+			Distance to legend category for picking mouse.
+			Default is 10.
+		wraplines : int, optional.
+			Maximum number of labels in legend before creating a new column.
+			Default is 15.
+			Default is (1, 0.5)
+		coords : dict, optional.
+			Coordinates of each point for each category (line, key of the dict).
+			Coordinates are expressed in (time, frequency) 
+			or (pixel, pixel) if no extent given.
+			Default is {}. If given, initates figure with annotations.
+
+
+		Attributes
+		----------
+		current_line : int
+			Index of the ctegory (line) currently selected
+		figure : matplotlib object
+			Abbreviation for axis.figure.
+		figure_bsplines : list of matplotlib 2D lines
+			Secondary lines, in use if bspline mode is selected. 
+			Draws curves instead of straight lines.
+		figure_lines : list of matplotlib 2D lines
+			Main lines, displayed on image as straight lines if bspline is False.
+			If bspline is true, linestyle is removed and they are displayed as dots.
+		key_press : matplotlib event
+			Information on the last key_press event.
+		legend : matplotlib object
+			legend of the plot
+		legend_labels : list of str
+			labels of the categories in legend.
+		linestyle : str
+			If bspline is True, saves linestyle.
+		mouse_press : matplotlib event
+			Information on the last mouse click in matplotlib canvas.
+		pick_press : matplotlib event
+			Information on the last mouse click event in matplotlib legend.
+		pressed : bool
+			Is used to check if a specific mouse button is pressed or not.
+
+		Other attributes have self explenatory names.
+
+		Methods
+		-------
+		add_category(show):
+			Adds a category to the list in legend. 
+		add_point(x,y):
+			Adds a point to the category in focus at the given coordinates. 
+			Focus is updated to be on this new category.
+		clear_category():
+			Clears all points from the category in focus.
+		double_lines():
+			Creates a copy of the current set of lines from figure_lines.
+			It is used to interpolate curves between figure_lines points.
+		distance_mouse(points, mouse_x, mouse_y):
+			Gets the distance bewteen a mouse position and the currently
+			selected category (points).
+		get_focus(event):
+			Puts focus on a category in legend. Allows the user to interact with it.
+		get_key_event(event):
+			Activates functions when specific keyboard keys are pressed.
+		get_mouse_press(event, show=True):
+			Activates functions when specific mouse buttons are pressed.
+		move_point(event):
+			Moves a selected point of the currently selected category to a new
+			position (event). Updates figure accordingly.
+		rm_point(x,y):
+			Removes a point from the category in focus at the given coordinates. 
+		set_legend():
+			Adds legend to axis given.
+		switch_line(arrow):
+			Puts focus on previous/next category in legend based on arrow input.
+		update_lines():
+			Updates data in lines. Called when coords is changed.
+
+		References
+		----------
+		Heavily inspired from mpl_point_clicker :
+			mpl-point-clicker.readthedocs.io/
 	"""
 
 	DEFAULT_marker = {		# Markerstyle for categories.
@@ -300,8 +301,6 @@ class clicker(object):
 			'key_press_event', 
 			self.get_key_event)
 
-
-
 	def add_category(self, show):
 		"""
 		A method to add a line (therefore a new category) to the plot.
@@ -401,14 +400,23 @@ class clicker(object):
 		else:
 			self.figure_bsplines = []
 			for idx, legend_label in enumerate(self.legend_labels):
-				curves = to_curve(
-					np.array(self.coords[legend_label])[:,0], 
-					np.array(self.coords[legend_label])[:,1],
-					kind=self.bspline)
-				self.figure_bsplines += [mlines.Line2D(curves[0], curves[1],
-					label=legend_label, 
-					color=self.colors[idx%len(self.colors)], 
-					**{'linestyle':self.linestyle})]
+				if np.array(self.coords[legend_label]).shape[0] > self.wait:
+					curves = to_curve(
+						np.array(self.coords[legend_label])[:,0], 
+						np.array(self.coords[legend_label])[:,1],
+						kind=self.bspline)
+
+					self.figure_bsplines += [mlines.Line2D(curves[0], curves[1],
+						label=legend_label, 
+						color=self.colors[idx%len(self.colors)], 
+						**{'linestyle':self.linestyle})]
+				else:
+					self.figure_bsplines += [mlines.Line2D(
+						np.array(self.coords[legend_label])[:,0], 
+						np.array(self.coords[legend_label])[:,1],
+						label=legend_label, 
+						color=self.colors[idx%len(self.colors)], 
+						**{'linestyle':self.linestyle})]
 
 	def clear_category(self):
 		"""
@@ -504,7 +512,7 @@ class clicker(object):
 		
 		Returns
 		-------
-		None : is used to trigger methods add_points and rm_points().
+		None : is used to trigger methods add_points and rm_points.
 		"""
 		if self.figure.canvas.widgetlock.available(self):
 			pressed, x, y = event.button, event.xdata, event.ydata
@@ -528,7 +536,7 @@ class clicker(object):
 					np.array(self.coords[self.legend_labels[self.current_line]]),
 					event.xdata, event.ydata)
 				self.index, dist = np.argmin(distances), np.min(distances)
-				if dist < 0.1:
+				if dist < 0.25:
 					self.currently_pressed = True
 
 			elif ((pressed is self.param["move_point"]) and
@@ -537,7 +545,7 @@ class clicker(object):
 				event.name == 'button_release_event'):
 				self.currently_pressed = False
 				self.index = False
-
+			
 	def get_key_event(self, event, show=True):
 		"""
 		A method that retrieve key interactions with matplotlib plot.
@@ -574,25 +582,25 @@ class clicker(object):
 
 	def move_point(self, event):
 		"""
-		Moves a selected point of the currently selected category to a new
-		position (event). Updates figure accordingly.
+			Moves a selected point of the currently selected category to a new
+			position (event). Updates figure accordingly.
 
-		...
+			...
 
-		Parameters
-		----------
-		event : matplotlib object
-			Matplotlib event containig information on positions.
+			Parameters
+			----------
+			event : matplotlib object
+				Matplotlib event containig information on positions.
 
-		Returns
-		-------
-		None : it updates the coordinates of a point in a category 
-		and re-draws the figure.
+			Returns
+			-------
+			None : it updates the coordinates of a point in a category 
+			and re-draws the figure.
 		"""
 		if (self.currently_pressed and 
 			event.xdata != None and 
 			event.ydata != None):
-			
+
 			# does this point already exists ?
 			if event.xdata in np.array(self.coords[self.legend_labels[self.current_line]])[:,0]:
 				warnings.warn("Cannot place two points at the same timestamp!", UserWarning, stacklevel=2)
@@ -744,9 +752,9 @@ class clicker(object):
 ##### MAIN #####
 if __name__ == '__main__':
 	# dummy example
-	img = np.array([[0,1,0],[1,0,1],[0,1,0]])
+	img = np.tile([[0, 0],[0, 0]], (25,25))
 
 	fig, ax = plt.subplots(figsize=(16, 9))
-	ax.imshow(img, cmap="viridis")
+	ax.imshow(img, cmap="gray")
 	base = clicker(axis=ax, bspline="quadratic")
 	plt.show(block=True)
\ No newline at end of file
diff --git a/line_clicker/lite_line_clicker.py b/line_clicker/lite_line_clicker.py
new file mode 100644
index 0000000..ca0271b
--- /dev/null
+++ b/line_clicker/lite_line_clicker.py
@@ -0,0 +1,787 @@
+"""
+Add default parameters for keys and mousebutton so it can be customized
+"""
+
+##### IMPORTATIONS ######
+import warnings
+import numpy as np
+import matplotlib.pyplot as plt
+from matplotlib.backend_bases import MouseButton
+import matplotlib.lines as mlines
+
+from scipy.interpolate import interp1d
+
+##### FUNCTIONS #####
+def to_curve(x, y, kind="quadratic", precision=10):
+	"""
+		A function to compute a curvy line between two points.
+		It interpolates a line with 100 points.
+
+		...
+
+		Parameters
+		----------
+		x : list or numpy array
+			List of coordinates on x axis.
+		y : list or numpy array
+			List of coordinates on y axis.
+		kind : string
+			The interpolation method to use. Can be any method in : ‘linear’, 
+			‘nearest’, ‘nearest-up’, ‘zero’, ‘slinear’, ‘quadratic’, ‘cubic’,
+			‘previous’, or ‘next’.
+		precision : number of segments between each point.
+		
+		Returns
+		-------
+		xi : numpy array
+			List of the coordinates of the curve on x-axis.
+		yi : numpy array
+			List of the coordinates of the curve on y-axis.
+	"""
+
+	y = y[np.argsort(x)]
+	x = np.sort(x)
+
+	f = interp1d(x,y, kind=kind)
+
+	xi = np.linspace(x.min(), x.max(), precision * (len(x)-1))
+	yi = f(xi)
+
+	return xi, yi
+
+##### CLASSES #####
+class clicker(object):
+	"""
+		A Class that is an "add-on" for an existing matplotlib figure axis.
+		Added to an axis, it allows to draw lines from mouse clicks. 
+		Points can be removed using the right click.
+
+		Different categories can be selected using the generated legend : 
+		by clicking on a line or by using up/down arrows.
+		It is also possible to add more categories using with 'ctrl+a' shortcut
+		
+		...
+
+		Params
+		----------
+		axis : matplotlib object
+			Axis instance.
+		bspline : string or boolean, optional.
+			If False, app will plot points linked by lines.
+			If string, app will plot points linked by curve.
+			For curves, use 'cubic' or 'quadratic'
+		colors : list, optional.
+			List of color (hexadecimal) to cycle over when plotting lines.
+			Default is the default list of colors from matplotlib.
+		legend_bbox : tuple, optional.
+			The position of the legend (x-axis, y-axis) relative to the figure.
+		marker : dict, optional.
+			Additional parameters to change the look of the lines.
+			Default is None, lines will be solid with points as white dots.
+		maxlines : int, optional.
+			Maximum number of labels in legend.
+			Default is 30.
+			Default is False.
+		names : str or list, optional.
+			Names for legend labels.
+			If string, names will be name+{line number}
+			If list, when a category is added, it will have the name "DefaultNameX".
+			Default is "Line".
+		n_names : int, optional.
+			Number of categories at launch when name is a string.
+			Default is 10.
+		pick_dist : int, optional.
+			Distance to legend category for picking mouse.
+			Default is 10.
+		wraplines : int, optional.
+			Maximum number of labels in legend before creating a new column.
+			Default is 15.
+			Default is (1, 0.5)
+		coords : dict, optional.
+			Coordinates of each point for each category (line, key of the dict).
+			Coordinates are expressed in (time, frequency) 
+			or (pixel, pixel) if no extent given.
+			Default is {}. If given, initates figure with annotations.
+
+
+		Attributes
+		----------
+		current_line : int
+			Index of the ctegory (line) currently selected
+		figure : matplotlib object
+			Abbreviation for axis.figure.
+		figure_bsplines : list of matplotlib 2D lines
+			Secondary lines, in use if bspline mode is selected. 
+			Draws curves instead of straight lines.
+		figure_lines : list of matplotlib 2D lines
+			Main lines, displayed on image as straight lines if bspline is False.
+			If bspline is true, linestyle is removed and they are displayed as dots.
+		key_press : matplotlib event
+			Information on the last key_press event.
+		legend : matplotlib object
+			legend of the plot
+		legend_labels : list of str
+			labels of the categories in legend.
+		linestyle : str
+			If bspline is True, saves linestyle.
+		mouse_press : matplotlib event
+			Information on the last mouse click in matplotlib canvas.
+		pick_press : matplotlib event
+			Information on the last mouse click event in matplotlib legend.
+		pressed : bool
+			Is used to check if a specific mouse button is pressed or not.
+
+		Other attributes have self explenatory names.
+
+		Methods
+		-------
+		add_category(show):
+			Adds a category to the list in legend. 
+		add_point(x,y):
+			Adds a point to the category in focus at the given coordinates. 
+			Focus is updated to be on this new category.
+		clear_category():
+			Clears all points from the category in focus.
+		double_lines():
+			Creates a copy of the current set of lines from figure_lines.
+			It is used to interpolate curves between figure_lines points.
+		distance_mouse(points, mouse_x, mouse_y):
+			Gets the distance bewteen a mouse position and the currently
+			selected category (points).
+		get_focus(event):
+			Puts focus on a category in legend. Allows the user to interact with it.
+		get_key_event(event):
+			Activates functions when specific keyboard keys are pressed.
+		get_mouse_press(event, show=True):
+			Activates functions when specific mouse buttons are pressed.
+		move_point(event):
+			Moves a selected point of the currently selected category to a new
+			position (event). Updates figure accordingly.
+		rm_point(x,y):
+			Removes a point from the category in focus at the given coordinates. 
+		set_legend():
+			Adds legend to axis given.
+		switch_line(arrow):
+			Puts focus on previous/next category in legend based on arrow input.
+		update_lines():
+			Updates data in lines. Called when coords is changed.
+
+		References
+		----------
+		Heavily inspired from mpl_point_clicker :
+			mpl-point-clicker.readthedocs.io/
+	"""
+
+	DEFAULT_marker = {		# Markerstyle for categories.
+	"marker":"o",
+	"mfc":"white",
+	"linestyle":"-",
+	}
+
+	DEFAULT_colors = [	# Colors for categories. Will cycle through them.
+	'#1f77b4',			# List can be appened or reduced.
+	'#ff7f0e',
+	'#2ca02c',
+	'#d62728',
+	'#9467bd',
+	'#8c564b',
+	'#e377c2',
+	'#7f7f7f',
+	'#bcbd22',
+	'#17becf'
+	]
+
+	DEFAULT_param = {
+	"add_point" : MouseButton.LEFT,
+	"rm_point" : MouseButton.RIGHT,
+	"move_point" : MouseButton.MIDDLE,
+	"add_category" : "A", # shift+a
+	"clear_category" : "R", # shift+r
+	"focus_up" : "up",
+	"focus_down" : "down",
+	}
+
+
+	def __init__(
+		self, 
+		axis,
+		marker=None,
+		bspline=False,
+		colors=None,
+		names="Line",
+		maxlines=30,
+		wraplines=15,
+		legend_bbox=(1,0.5),
+		pick_dist=10,
+		n_names=10,
+		coords={}):
+
+		# Variable assignement
+		self.axis = axis
+		self.legend_bbox = legend_bbox
+		self.pick_dist = pick_dist
+		self.param = self.DEFAULT_param.copy()
+		self.wait_before_interpolation = 2
+		self.bspline = bspline
+
+		if isinstance(marker, dict):
+			self.marker = marker
+		else:
+			self.marker = self.DEFAULT_marker.copy()
+
+		if isinstance(colors, list):
+			self.colors = colors
+		else:
+			self.colors = self.DEFAULT_colors.copy()
+
+		if isinstance(names, str):
+			self.names = names
+			coord_keys = [self.names + str(i+1) for i in range(n_names)]
+		elif isinstance(names, list):
+			coord_keys = names.copy()
+			self.names = "DefaultName"
+		else:
+			self.names = self.DEFAULT_name
+			coord_keys = [self.names + str(i+1) for i in range(n_names)]
+		
+		if isinstance(coords, dict) and coords != {}:
+			self.coords = coords
+		else:
+			self.coords = {key: [] for key in coord_keys}
+
+		if isinstance(maxlines, int):
+			self.maxlines = maxlines
+		else: 
+			self.maxlines = self.DEFAULT_maxlines
+		
+		if isinstance(wraplines, int):
+			self.wraplines = wraplines
+		else:
+			self.wraplines = self.DEFAULT_wraplines
+
+		# Line creation
+		self._create_lines_and_points()
+
+		# Drawing lines
+		self.figure = self.axis.figure
+		self.current_line = 0
+		self._set_plot()
+
+		# Linking actions in matplotlib canvas
+		self.mouse_press_event = self.figure.canvas.mpl_connect(
+			'button_press_event', 
+			self.get_mouse_press)
+		self.mouse_release_event = self.figure.canvas.mpl_connect(
+			'button_release_event', 
+			self.get_mouse_press)
+		self.activate_move = False
+
+		self.pick_event = self.figure.canvas.mpl_connect(
+			'pick_event', 
+			self.get_focus)
+		self.key_press = self.figure.canvas.mpl_connect(
+			'key_press_event', 
+			self.get_key_event)
+
+		# callbacks
+		self.xlims = self.axis.get_xlim()
+		self.ylims = self.axis.get_ylim()
+		self.axis.callbacks.connect('xlim_changed', self._on_xlims_change)
+		self.axis.callbacks.connect('ylim_changed', self._on_ylims_change)
+
+	def _on_xlims_change(self, event_ax):
+		self.xlims = event_ax.get_xlim()
+		self.update_lines()
+
+	def _on_ylims_change(self, event_ax):
+		self.ylims = event_ax.get_ylim()
+
+
+	def _create_lines_and_points(self):
+		self.lines = []
+		self.points = []
+		for idx, legend_label in enumerate(self.coords.keys()):
+			if len(self.coords[legend_label]) > 0:
+				self.points += [
+					mlines.Line2D(
+						np.array(self.coords[legend_label])[:,0], 
+						np.array(self.coords[legend_label])[:,1], 
+						label=legend_label, 
+						color=self.colors[idx%len(self.colors)],
+						zorder=3,
+						linestyle="",
+						**{x: self.marker[x] for x in self.marker if x not in {"linestyle"}}) 
+				]
+				self.lines += [
+					mlines.Line2D(
+						np.array(self.coords[legend_label])[:,0], 
+						np.array(self.coords[legend_label])[:,1], 
+						label=legend_label, 
+						zorder=2,
+						color=self.colors[idx%len(self.colors)]) 
+				]
+			else:
+				self.points += [
+					mlines.Line2D(
+						[], 
+						[], 
+						label=legend_label, 
+						color=self.colors[idx%len(self.colors)],
+						zorder=3,
+						linestyle="",
+						**{x: self.marker[x] for x in self.marker if x not in {"linestyle"}})  
+				]
+				self.lines += [
+					mlines.Line2D(
+						[], 
+						[], 
+						label=legend_label, 
+						zorder=2,
+						color=self.colors[idx%len(self.colors)])
+				]
+
+	def _set_plot(self):
+		"""
+			A method to create matplotlib.pyplot legend and draw ut.
+			Legend contains empty lines and can be clicked.
+			
+			...
+
+			Returns
+			-------
+			None : updates axis, creates legend.
+			"""
+		# Make some space to include legend
+		scale = (19-((len(self.coords.keys())//self.wraplines)+1))/19
+		self.axis.set_position([0, 0, scale, 1])
+
+		# Add legend to plot
+		self.legend = self.axis.legend(
+			loc="center left", 
+			bbox_to_anchor=self.legend_bbox,
+			ncol=(len(self.coords.keys())//self.wraplines)+1,
+			title="Selection of lines",
+			handles=self.lines)
+
+		# Add lines and points to plot
+		for line, point in zip(self.lines, self.points):
+			self.axis.add_line(point)
+			self.axis.add_line(line)
+
+		for legend_l, line in zip(self.legend.get_lines(), self.lines):
+			legend_l.set_picker(True)
+			legend_l.set_pickradius(self.pick_dist)
+			legend_l.set_alpha(0.2)
+			self.axis.add_line(line)
+
+		# Focus on selected line
+		self.legend.get_lines()[self.current_line].set_alpha(1)
+
+	def get_mouse_press(self, event):
+		"""
+			A Method that retrieves mouse interactions with matplotlib plot.
+
+			...
+
+			Parameters
+			----------
+			event : matplotlib object
+				Contains 3 attributes : button pressed, x and y at that time.
+			
+			Returns
+			-------
+			None : is used to trigger methods add_points and rm_points.
+		"""
+		if self.figure.canvas.widgetlock.available(self):
+			pressed, x, y = event.button, event.xdata, event.ydata
+			if ((pressed is self.param["add_point"]) and
+				(isinstance(x, float)) and
+				(isinstance(y, float)) and
+				event.name == 'button_press_event'):
+				self.add_point(x, y)
+
+			elif ((pressed is self.param["rm_point"]) and
+				(isinstance(x, float)) and
+				(isinstance(y, float)) and
+				event.name == 'button_press_event'):
+				self.rm_point(x, y)
+
+			elif ((pressed is self.param["move_point"]) and
+				(isinstance(x, float)) and
+				(isinstance(y, float)) and
+				event.name == 'button_press_event'):
+				distances = self.distance_mouse(
+					np.array(self.coords[list(self.coords.keys())[self.current_line]]),
+					event.xdata, 
+					event.ydata)
+				self.point_to_move = np.argmin(distances)
+				dist = np.min(distances)
+				if dist < 0.5:
+					self.activate_move = True
+					self.move_point(event)
+					self.motion_event = self.figure.canvas.mpl_connect(
+						'motion_notify_event', 
+						self.move_point)
+
+			elif ((pressed is self.param["move_point"]) and
+				(isinstance(x, float)) and
+				(isinstance(y, float)) and
+				event.name == 'button_release_event'):
+				if self.activate_move:
+					self.figure.canvas.mpl_disconnect(self.motion_event)
+				self.activate_move = False
+				self.point_to_move = False
+				
+	def update_lines(self):
+		"""
+			Updates data in lines. Called when coords is changed.
+
+			...
+
+			Returns
+			-------
+			None : Updates figure lines
+		"""
+		for i_line in range(len(self.coords)):
+			line_coords = np.array(self.coords[list(self.coords.keys())[i_line]])
+
+			if (line_coords.shape[0] > 0):			
+				# [ ] MAIN OPTIMIZATION : check if line is in visible frame
+				in_xframe = (
+					np.any(line_coords[:,0] >= min(self.xlims)) and 
+					np.any(line_coords[:,0] <= max(self.xlims)))
+				in_yframe = (
+					np.any(line_coords[:,1] >= min(self.ylims)) and 
+					np.any(line_coords[:,1] <= max(self.ylims)))
+
+				if in_xframe and in_yframe:
+					self.points[i_line].set_data(
+						np.array(line_coords)[:,0],
+						np.array(line_coords)[:,1])
+
+					if (self.bspline) and (len(self.coords[list(self.coords.keys())[i_line]]) > self.wait_before_interpolation):
+						curvex, curvey = to_curve(
+							line_coords[:,0],
+							line_coords[:,1],
+							kind="quadratic")
+						self.lines[i_line].set_data(curvex, curvey)
+					else:
+						self.lines[i_line].set_data(
+							np.array(line_coords)[:,0],
+							np.array(line_coords)[:,1])
+				else: 
+					self.points[i_line].set_data([], [])	
+					self.lines[i_line].set_data([], [])	
+
+			else:
+				self.points[i_line].set_data([], [])	
+				self.lines[i_line].set_data([], [])	
+
+		self.figure.canvas.draw_idle()
+
+	def distance_mouse(self, points, mouse_x, mouse_y):
+		"""
+		Gets the distance bewteen a mouse position
+		and the currently selected category (points).
+
+		...
+		Parameters
+		----------
+		points : numpy array
+			A list of coordinates with shape (2,n).
+		mouse_x : int or float
+			Coordinate of the mouse on x-axis.
+		mouse_y : int or float
+			Coordinate of the mouse on y-axis.
+		Returns
+		-------
+		distances : numpy array
+			Distances of each point to mous coordinates.
+		"""
+		size_x, size_y = self.figure.get_size_inches()
+		max_x, max_y=self.axis.get_xbound()[1],self.axis.get_ybound()[1]
+
+		# look for closest coordinates of the category currently selected.
+		distances = [np.linalg.norm([(point[0]/max_x)*size_x, 
+			(point[1]/max_y)*size_y] - np.array([(mouse_x/max_x)*size_x, 
+				(mouse_y/max_y)*size_y])) 
+			for point in points]	 
+
+		return distances
+
+	def add_point(self, x, y):
+		"""
+			A method to add a point on plot at given coordinates.
+
+			...
+
+			Parameters
+			----------
+			x : float
+				x-axis coordinate.
+			y : float
+				y-axis coordinate.
+
+			Returns
+			-------
+			None : updates coords, figure_lines, figure.
+		"""
+
+		if len(self.coords[list(self.coords.keys())[self.current_line]])==0:
+			self.coords[list(self.coords.keys())[self.current_line]] += [[x, y]]
+		else:
+			if x in np.array(self.coords[list(self.coords.keys())[self.current_line]])[:,0]:
+				warnings.warn("Cannot place two points at the same timestamp!", UserWarning, stacklevel=2)
+			else:
+				# where should it be inserted ?
+				here = np.where(
+					np.array(
+						self.coords[
+							list(self.coords.keys())[self.current_line]
+							]
+						)[:,0] > x
+					)[0]
+
+				if len(here) != 0:
+					self.coords[list(self.coords.keys())[self.current_line]].insert(here[0] ,[x, y])
+				else:
+					self.coords[list(self.coords.keys())[self.current_line]] += [[x, y]]
+		
+		self.update_lines()
+
+	def rm_point(self, x, y):
+		"""
+		A method to remove closest point in plot to given coordinates.
+
+		...
+
+		Parameters
+		----------
+		x : float
+			x-axis coordinate.
+		y : float
+			y-axis coordinate.
+
+		Returns
+		-------
+		None : updates coords, figure_lines, figure.
+		"""
+		list_coords = np.array(
+				self.coords[
+					list(self.coords.keys())[self.current_line]
+					]
+				)
+		
+		distances = self.distance_mouse(list_coords, x, y)
+
+		if len(distances) > 0:
+			if min(distances) < 1:
+				if len(list_coords) > 1:
+					# remove closest point of selected
+					list_coords = np.delete(list_coords, np.argmin(distances), axis=0)
+					self.coords[list(self.coords.keys())[self.current_line]] = list_coords.tolist()
+				else:
+					self.coords[list(self.coords.keys())[self.current_line]] = []
+
+		self.update_lines()
+
+	def move_point(self, event):
+		"""
+			Moves a selected point of the currently selected category to a new
+			position (event). Updates figure accordingly.
+
+			...
+
+			Parameters
+			----------
+			event : matplotlib object
+				Matplotlib event containig information on positions.
+
+			Returns
+			-------
+			None : it updates the coordinates of a point in a category 
+			and re-draws the figure.
+		"""
+		if (self.activate_move and 
+			event.xdata != None and 
+			event.ydata != None):
+
+			# does this point already exists ?
+			if event.xdata in np.array(self.coords[list(self.coords.keys())[self.current_line]])[:,0]:
+				warnings.warn("Cannot place two points at the same timestamp!", UserWarning, stacklevel=2)
+			else:
+				# update coords
+				self.coords[list(self.coords.keys())[self.current_line]][self.point_to_move] = [event.xdata, event.ydata]
+				self.update_lines()
+
+	def get_key_event(self, event, show=True):
+		"""
+		A method that retrieve key interactions with matplotlib plot.
+
+		...
+
+		Parameters
+		----------
+		event : matplotlib object
+			Contains 3 attributes : button pressed, x and y at that time.
+		show : boolean
+			Parameter passed to add_category()
+		
+		Returns
+		-------
+		None : is used to trigger other functions.
+		"""
+		key = event.key
+		if ((key == self.param["add_category"]) and 
+			(len(self.coords) < self.maxlines)):
+			self.add_category(show)
+
+		elif ((key == self.param["clear_category"]) and
+			(len(self.coords) > 1)):
+			self.clear_category()
+
+		elif ((key == self.param["focus_up"]) and
+			(self.current_line != 0)):
+			self.switch_line(-1)
+
+		elif ((key == self.param["focus_down"]) and
+			(self.current_line != len(self.coords)-1)):
+			self.switch_line(1)
+
+	def add_category(self, show):
+		"""
+		A method to add a line (therefore a new category) to the plot.
+		Also change current focus to be on the newly created category.
+
+		...
+
+		Parameters
+		----------
+		show : boolean
+			Shows change in legend.
+
+		Returns
+		-------
+		None : updates legend_labels, figure_lines, legend, figure 
+		and current_line.
+		"""
+		# make new name
+		self.coords[self.names + str(len(self.coords)+1)] = []
+
+		# add to points and lines
+		self.points += [
+			mlines.Line2D(
+				[], 
+				[], 
+				label=list(self.coords.keys())[-1], 
+				color=self.colors[(len(self.coords)-1)%len(self.colors)],
+				zorder=3,
+				linestyle="",
+				**{x: self.marker[x] for x in self.marker if x not in {"linestyle"}})  
+		]
+		self.lines += [
+			mlines.Line2D(
+				[], 
+				[], 
+				label=list(self.coords.keys())[-1], 
+				zorder=2,
+				color=self.colors[(len(self.coords)-1)%len(self.colors)])
+		]
+
+		self._set_plot()
+		self.update_lines()
+
+		# focus auto on new category
+		self.current_line = len(self.coords)-1
+
+		for legend_line in self.legend.get_lines():
+			legend_line.set_alpha(0.2)
+		self.legend.get_lines()[self.current_line].set_alpha(1)
+		if show:
+			self.figure.canvas.draw_idle()
+
+	def clear_category(self):
+		"""
+		A method to remove a line (therefore a whole category) from the plot.
+		Removes the category that is in focus.
+
+		...
+
+		Returns
+		-------
+		None : updates legend_labels, figure_lines, legend, figure.
+		"""
+
+		# we only need to remove data from coords
+		self.coords[list(self.coords.keys())[self.current_line]] = []
+		self.points[self.current_line].set_data([], [])
+		self.lines[self.current_line].set_data([], [])
+
+		# update plot
+		self.update_lines()
+
+	def switch_line(self, arrow):
+		"""
+		Puts focus on previous/next category in legend based on arrow input.
+
+		...
+
+		Parameters
+		----------
+		arrow : int
+			Can be 1 or -1. Change the index of the current category in focus.
+		
+		Returns
+		-------
+		None : updates currentline, legend and figures attributes.
+		"""
+		# new focus
+		self.current_line += arrow
+
+		# adapt alpha
+		for legend_line in self.legend.get_lines():
+			legend_line.set_alpha(0.2)
+		self.legend.get_lines()[self.current_line].set_alpha(1)
+		self.figure.canvas.draw_idle()	
+
+	def get_focus(self, event):
+		"""
+		A method to highlight a given marker in legend.
+		Points can be added/removed only to the category in focus.
+		
+		...
+
+		Parameters
+		----------
+		event : matplotlib object
+			Is used to acces event.artist attribute.
+		
+		Returns
+		-------
+		None : updates figure, current_line and legend attributes.
+		"""
+		# set all legend lines to alpha = 0.2
+		for legend_line in self.legend.get_lines():
+			legend_line.set_alpha(0.2)
+		# set legend line in focus to alpha = 1
+		selected_legend = event.artist
+		current_alpha = selected_legend._alpha
+		selected_legend.set_alpha(1.0 if (current_alpha==0.2) else 0.2)
+		self.figure.canvas.draw_idle()	
+
+		# new focus
+		self.current_line = int(np.where(
+			np.array(self.legend.get_lines()) == event.artist)[0])
+
+
+##### MAIN #####
+if __name__ == '__main__':
+	# dummy example
+	img = np.tile([[0.45, 0.55],[0.55, 0.45]], (25,25))
+	img[0][0] = 0
+	img[-1][-1] = 1
+
+	fig, ax = plt.subplots(figsize=(16, 9))
+	ax.imshow(img, cmap="gray")
+	base = clicker(axis=ax, bspline="quadratic")
+	plt.show(block=True)
\ No newline at end of file
-- 
GitLab