diff --git a/global_ipi.py b/global_ipi.py
index adad1badcae57432b62080a3e31dfda4be4c3e20..f4c52415443fd69d71f6e4d946b53c86327d07e4 100644
--- a/global_ipi.py
+++ b/global_ipi.py
@@ -2,7 +2,7 @@ import argparse
 import numpy as np
 import matplotlib.pyplot as plt
 import scipy.signal as sg
-from scipy. stats import gaussian_kde
+from scipy.stats import gaussian_kde
 from fractions import Fraction
 import os
 import sys
@@ -18,6 +18,7 @@ def norm(x, axis=None, eps=1e-18):
 def norm_abs(x, axis=None, eps=1e-18):
     return (x-x.mean(axis, keepdims=axis is not None))/(np.abs(x).max(axis, keepdims=axis is not None) + eps)
 
+
 def read(file_path, always_2d=True):
     try:
         return sf.read(file_path, always_2d=always_2d)
diff --git a/ipi_extract.py b/ipi_extract.py
index e5f30b2f533e741880b22d54850d57a8809bd626..04ab46f7129510774fe342a9a937041d99dbeb1c 100644
--- a/ipi_extract.py
+++ b/ipi_extract.py
@@ -483,10 +483,11 @@ class Callback(object):
 
     def _update_spectrogram(self, ax_group):
         click = ax_group.signal.im.get_ydata()
-        spec = np.flipud(20*np.log10(plt.mlab.specgram(click, Fs=self.sr, NFFT=self.nfft, noverlap=self.nfft-1,
-                                                       pad_to=2*self.nfft)[0]))
+        spec, _, t = plt.mlab.specgram(click, Fs=self.sr, NFFT=self.nfft, noverlap=self.nfft-1, pad_to=2*self.nfft)
+        spec = np.flipud(20*np.log10(spec))
         ax_group.spectrogram.im.set_data(spec)
         ax_group.spectrogram.im.set_clim(spec.max() - SPSC, spec.max())
+        ax_group.spectrogram.im.set_extent([t[0] - 1/self.sr/2, t[-1] + 1/self.sr/2, 0., self.sr/2])
 
     def increase_res(self, event):
         if self.nfft > int(10e-3*self.sr):
diff --git a/video_measure.py b/video_measure.py
new file mode 100644
index 0000000000000000000000000000000000000000..6f21698174a72c8f6ea8b4f4b32aa54a5148ceb8
--- /dev/null
+++ b/video_measure.py
@@ -0,0 +1,236 @@
+import argparse
+import numpy as np
+import matplotlib.pyplot as plt
+from matplotlib.colors import rgb_to_hsv, hsv_to_rgb
+import imageio as io
+from skimage.filters import scharr
+from scipy.ndimage import label, binary_opening
+import os
+import sys
+from matplotlib.widgets import Button, Slider
+from matplotlib.backend_bases import MouseButton
+
+
+def wb(channel, perc=0.05):
+    mi, ma = (np.percentile(channel, perc, axis=(0, 1)), np.percentile(channel, 100.0-perc, axis=(0, 1)))
+    channel = np.uint8(np.clip((channel-mi)*255.0/(ma-mi+1e-18), 0, 255))
+    return channel
+
+
+def wb2(channel, per=0.05):
+    hsv = rgb_to_hsv(channel/255)
+    grey = (channel/255).sum(-1)
+    mi, ma = (np.percentile(grey, per), np.percentile(grey, 100-per))
+    if mi > 0.01:
+        hue = (hsv[grey <= mi][..., 0].mean() + hsv[grey >= ma][..., 0].mean())/2
+    else:
+        hue = hsv[grey >= ma][..., 0].mean()
+    hsv[..., 0] += 0.5 - hue
+    hsv[..., 0] %= 1
+    return hsv_to_rgb(hsv)
+
+
+def sat(arr):
+    m = arr.max(-1)
+    return (m - arr.min(-1))/m
+
+
+def fo(m):
+    a = m.any(1)
+    b = m.any(0)
+    return (np.argmax(a), len(a) - np.argmax(a[::-1]), np.argmax(b), len(b) - np.argmax(b[::-1]))
+
+
+def unmapping(x,y,fov_h,fov_v,width,height):
+    x = -(x - width/2) * np.tan(fov_h / 4) / (width / 2)
+    y = (y - height/2) * np.tan(fov_v / 4) / (height / 2)
+    theta = 2*np.arctan(np.sqrt(x**2 + y**2))
+    delta = np.arctan2(y,x)
+    return np.cos(delta)*np.tan(theta), np.sin(delta)*np.tan(theta)
+
+
+def sat_val(arr):
+    m = arr.max(-1)
+    return -3*np.clip((m - arr.min(-1))/m - 0.15, 0, 0.5) + np.clip(m/255-0.8, 0, 0.2)*10
+
+
+def hori(im):
+    sv = sat_val(im) > 0.8
+    sv[:np.median(np.argwhere(sv)[:, 0]).astype(int)] = True
+    lab = label(scharr(binary_opening(sv)) > 0.5)[0]
+
+    v_min = np.inf
+    l_min = 0
+    for l, n in enumerate(np.unique(lab, return_counts=True)[1][1:], start=1):
+        if n < 1024:
+            continue
+        v = np.polyfit(*np.argwhere(lab == l).T[::-1], 2, full=True)[1]
+        if v < v_min:
+            l_min = l
+            v_min = v
+    return np.argwhere(lab == l_min)
+
+
+def unmap_hori(h_pos, im):
+    a = unmapping(*h_pos.T[::-1], 94.4 * np.pi / 180, 55 * np.pi / 180, im.shape[1], im.shape[0])
+    return np.polyfit(*a, 2)
+
+
+class Callback(object):
+    def __init__(self, inp):
+        self.reader = io.get_reader(inp, format='ffmpeg')
+        self._len = self.reader.count_frames()
+        self.fig = plt.figure(inp, figsize=[16, 9], constrained_layout=True)
+        gs = self.fig.add_gridspec(2, 1, height_ratios=[9, 1])
+        controls = gs[1].subgridspec(2, 5, height_ratios=[1, 3])
+        self.screen = self.fig.add_subplot(gs[0])
+        self.screen.set(xticks=[], yticks=[])
+        self.idx = 0
+        self.frame = self.reader.get_data(self.idx)
+        self.img = self.screen.imshow(self.frame)
+        self.bar = Slider(self.fig.add_subplot(controls[0, :], zorder=10),
+                          'frame #', 0, self._len, valinit=0, valstep=1, valfmt='%5d ')
+        self.bar.valtext.set_family('monospace')
+        self.bar.on_changed(self.new_frame)
+        self.resize_b = Button(self.fig.add_subplot(controls[1, 0], zorder=10), 'Resize plot')
+        self.resize_b.on_clicked(self.resize)
+        self.normal_b = Button(self.fig.add_subplot(controls[1, 1], zorder=10), 'Auto\nwhite balance')
+        self.normal_b.on_clicked(self.normalize)
+
+        for v in "fullscreen,home,back,forward,pan,zoom,save,quit,grid,yscale,xscale,all_axes".split(','):
+            plt.rcParams[f'keymap.{v}'] = []
+        self.cid = self.fig.canvas.mpl_connect('key_press_event', self.key_pressed)
+        self.cid3 = self.fig.canvas.mpl_connect('button_release_event', self.mouse_release)
+        self.sperm_pos = np.zeros((2, 2, 2)) #sperm whale, start/stop, x/y
+        self.screen.set_xlim(self.screen.get_xlim())
+        self.screen.set_ylim(self.screen.get_ylim())
+        self.marker = self.screen.scatter(100, 100, marker='+', c=[4*[0]])
+        self.t = np.arange(self.frame.shape[1])
+        h_pos = hori(self.frame)
+        self.line, = self.screen.plot(self.t, np.polyval(np.polyfit(*h_pos.T[::-1], 2), self.t), c='r')
+        self.poly = unmap_hori(h_pos, self.frame)
+        text_ax = self.fig.add_subplot(controls[1, -2:])
+        self.text = text_ax.text(0.5, 0.5, 'Sperm whale 1 : (     ,      ) - (     ,      )   ratio       -      \n'
+                                           'Sperm whale 2 : (     ,      ) - (     ,      )   angle       °      ',
+                            horizontalalignment='center',
+                            verticalalignment='center', transform=text_ax.transAxes, family='monospace')
+        text_ax.axis('off')
+
+        self.closed = False
+
+        plt.draw()
+        plt.pause(0.2)
+        self.fig.set_constrained_layout(False)
+
+    def new_frame(self, val):
+        self.idx = int(val)
+        self.frame = self.reader.get_data(self.idx)
+        self.img.set_data(self.frame)
+        self.marker.set_offsets(np.array([[100, 100]]))
+        self.marker.set_color([4*[0]])
+        self.sperm_pos[:] = 0
+        self.text.set_text('Sperm whale 1 : (     ,      ) - (     ,      )   ratio       -      \n'
+                           'Sperm whale 2 : (     ,      ) - (     ,      )   angle       °      ')
+        plt.draw()
+
+    def resize(self, event):
+        self.fig.set_constrained_layout(True)
+        plt.draw()
+        plt.pause(0.2)
+        self.fig.set_constrained_layout(False)
+
+    def normalize(self, event):
+        self.img.set_data(wb(wb2(self.frame)))
+        plt.draw()
+
+    def key_pressed(self, event):
+        key = event.key
+        move = 0
+        if key == 'j' or key == 'left':
+            move = -1
+        elif key == 'k' or key == 'right':
+            move = 1
+        elif key == 'up':
+            move = -5
+        elif key == 'pageup':
+            move = -10
+        elif key == 'down':
+            move = 5
+        elif key == 'pagedown':
+            move = 10
+        elif key == 'r':
+            self.resize(None)
+        elif key == 'n':
+            self.normalize(None)
+        elif key == 'h':
+            h_pos = hori(self.frame)
+            self.line.set_data(self.t, np.polyval(np.polyfit(*h_pos.T[::-1], 2), self.t))
+            self.poly = unmap_hori(h_pos, self.frame)
+            plt.draw()
+        elif key == 'enter':
+            print('hello')
+        if move:
+            self.bar.set_val(min(max(0, self.idx + move), self._len - 1))
+            plt.draw()
+
+    def mouse_release(self, event):
+        if event.inaxes != self.screen or self.fig.canvas.manager.toolbar.mode:
+            return None
+        if event.button == MouseButton.LEFT:
+                self.sperm_pos[0, self.pos_select[0]] = event.xdata, event.ydata
+
+
+        elif event.button == MouseButton.RIGHT:
+            self.sperm_pos[1, self.pos_select[1]] = event.xdata, event.ydata
+
+        if False:
+            self.text.set_text(f'Sperm whale 1 : ({self.sperm_pos[0,0,0]:5.0f}, {self.sperm_pos[0,0,1]:5.0f}) '
+                               f'- ({self.sperm_pos[0,1,0]:5.0f}, {self.sperm_pos[0,1,1]:5.0f})'
+                               f'   ratio {len1/len2:5.2f} - {len2/len1:5.2f}\n'
+                               f'Sperm whale 2 : ({self.sperm_pos[1,0,0]:5.0f}, {self.sperm_pos[1,0,1]:5.0f}) '
+                               f'- ({self.sperm_pos[1,1,0]:5.0f}, {self.sperm_pos[1,1,1]:5.0f})'
+                               f'   angle {np.arccos(np.abs(np.dot(vec1,vec2))/(len1*len2))*180/np.pi:5.2f} °      ')
+        else:
+            self.text.set_text(f'Sperm whale 1 : ({self.sperm_pos[0,0,0]:5.0f}, {self.sperm_pos[0,0,1]:5.0f}) '
+                               f'- ({self.sperm_pos[0,1,0]:5.0f}, {self.sperm_pos[0,1,1]:5.0f})   ratio       -      \n'
+                               f'Sperm whale 2 : (     ,      ) - (     ,      )   angle       °      ')
+        plt.draw()
+
+    def close(self):
+        self.reader.close()
+        del self.resize_b
+        self.closed = True
+
+    def __del__(self):
+        if not self.closed:
+            self.close()
+
+
+def main(args):
+    if args.out == '':
+        outpath = args.input.rsplit('.', 1)[0] + '.pred.h5'
+    else:
+        outpath = args.out
+    if os.path.isfile(outpath):
+        if not (args.erase or args.resume):
+            print(f'Out file {outpath} already exist and erase or resume option isn\'t set.')
+            return 1
+    elif args.resume:
+        print(f'Out file {outpath} does not already exist and resume option is set.')
+        return 1
+    callback = Callback(args.input)
+    plt.show()
+    callback.close()
+    return 0
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+    parser.add_argument("input", type=str, help="Input file")
+    parser.add_argument("--out", type=str, default='', help="Output file. Default to the input_path'.pred.h5'")
+    group = parser.add_mutually_exclusive_group()
+    group.add_argument("--erase", action='store_true', help="If out file exist and this option is not given,"
+                                                            " the computation will be halted")
+    group.add_argument("--resume", action='store_true', help="If out file exist and this option is given,"
+                                                             " the previous annotation file will be loaded")
+    sys.exit(main(parser.parse_args()))