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()))