From 3ea89a1786e85cc94e1987bb6c2177e1e1972523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Lehnhoff?= <loic.lehnhoff@gmail.com> Date: Thu, 20 Jul 2023 21:02:25 +0200 Subject: [PATCH] Fixed duplication of x-coordinates (time) Added popup save window before closing --- .gitignore | 2 + args.py | 8 +- functions.py | 3 - interface.py | 275 +++++++++++------- .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 164 bytes .../__pycache__/line_clicker.cpython-39.pyc | Bin 0 -> 19117 bytes line_clicker/line_clicker.py | 27 +- outputs/SCW1807_20200713_064554-contours.json | 32 +- post_annotation.py | 19 +- 9 files changed, 221 insertions(+), 145 deletions(-) create mode 100644 line_clicker/__pycache__/__init__.cpython-39.pyc create mode 100644 line_clicker/__pycache__/line_clicker.cpython-39.pyc diff --git a/.gitignore b/.gitignore index 1b60786..ba372b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ audio_examples/SCW1807_20200713_064545.wav +line_clicker/__pycache__ +__pycache__ diff --git a/args.py b/args.py index 32eeed4..36389f1 100644 --- a/args.py +++ b/args.py @@ -1,5 +1,6 @@ ##### IMPORTATIONS ##### import os +import sys import argparse from argparse import RawTextHelpFormatter @@ -36,8 +37,9 @@ def fetch_inputs(): parser = argparse.ArgumentParser( formatter_class=RawTextHelpFormatter, description= - ("This script requires LIBRARIES." - "\nAnd it does things, TO BE DESCRIBED.") + ("This script requires libraries that can be installed with: \n" + "'$ pip install -r requirements.txt'" + "Read README.md to get an overview of this software.") ) parser.add_argument( @@ -122,7 +124,7 @@ def fetch_inputs(): f"\nInputError: Max number of contours cannot exceed 50, got {contour}.") except Exception as e: print(e) - exit() + sys.exit(1) return (explore, contour, resampl, outputs, modify_file, from_wav) diff --git a/functions.py b/functions.py index 669efca..80d4566 100644 --- a/functions.py +++ b/functions.py @@ -6,9 +6,6 @@ import numpy as np from librosa import load, amplitude_to_db, stft, pcen from scipy.signal import resample -from line_clicker.line_clicker import to_curve - - ##### FUNCTIONS ##### def save_dict(dictionary, folder, name): """ diff --git a/interface.py b/interface.py index 75e1def..2d982ad 100644 --- a/interface.py +++ b/interface.py @@ -82,116 +82,166 @@ class FileExplorer(object): ('All files', '*.*') )) -class App(object): + +class Popup(object): """ - A Class to construct an contours annotation tool for audio data. - - ... + A Class that opens a popup asking if the user wants to save its work + before leaving. Parameters ---------- - DIR : str - Path to a folder in which the file explorer will be opened. - DIR_OUT : str - Path to a folder where the contours will be saved. - MAX_C : int - Maximum number of contours that can be drawn at once. - NEW_SR : int - Resampling rate of the audio recording. - WAVEFILE : str - Path to the audio recording to be opened. Should be a '.wav' file. - coords_to_modify : dict - Coordinates of points (from a previous annotation) that can be used - as input to add modifications. + options : list + The list of options that will show in popup. + prompt : str + The prompt that will be shown on top of the buttons. + """ - Attributes - ---------- - _default_bounds : list of float - Boundaries for the matplotlib canvas. - (Default value is loaded from 'PARAMETERS.py' file). - _default_clipping : int - Default clipping value for spectrogram, in dB. - (Default value is loaded from 'PARAMETERS.py' file). - _default_cmap : str - Name of a matplotlib.pyplot color map. - (Default value is loaded from 'PARAMETERS.py' file). - _default_height : int - Default height of the window in which the app will run, in pixels. - (Default value is loaded from 'PARAMETERS.py' file). - _default_hop_length : int - Default hop length for spectrogram, in samples. - (Default value is loaded from 'PARAMETERS.py' file). - _default_left_panel_width : int - Default width for the left panel of the window of the app. - (Default value is loaded from 'PARAMETERS.py' file). - _default_nfft : int - Default fft size for spectrogram, in samples. - (Default value is loaded from 'PARAMETERS.py' file). - _default_width: int - (Default) width of the window in which the app will run, in pixels. - (Default value is loaded from 'PARAMETERS.py' file). - - canvas : matplotlib object - Interface to include matplotlib plot in tkinter canvas. - CHECK_bspline : tkinter int variable - User checkbox about plotting curves or not. - CLIP_IN : tkinter float variable (Double) - User input for clipping value. - FFT_IN : tkinter int variable - User input for fft. - figure, axis, data_showed : matplotlib objects - Objects used to show matplotlib.pyplot plot. - HOP_IN : tkinter int variable - User input for hop length. - klicker : mpl_point_clicker instance - Adds widgets to matplotlib plot that allows to draw contours. - NAME0 : int - Number used to name the first contour available for annotation. - NAME1 : int - Number used to name the last contour available for annotation. - OPTIONS : tkinter list variable - User listbox to select category item. - root : tkinter Tk instance - Initialises tkinter interpreter and creates root window. - spectrogram : numpy array - Spectrogram of the waveform. - waveform : numpy array - Waveform of the audio recording. - - Other attributes, buttons and labels have self explenatory names. + def __init__(self, options, prompt): + self.options = options + self.prompt = prompt + self.popup_window() # start function auto - Methods - ------- - bspline_activation(): - Activates/deactivates the visualisation of lines as curves. - create_canvas(): - Creates matplotlib figure to show spectrogram in tkinter canvas. - entry_setup(): - Creates variables that save inputs from the user in the entry fields. - get_key_pressed(event): - Updates plot and tkinter interface - when a key is pressed to add a new category. - layout(): - Lays the main structure of the tkinter window. - link_select(event): - Changes the focus to be on a new category, corresponding to - the selected item in listbox widget. - load_audio(): - Loads audio data. Waveform and spectrogram. - select_file(): - Opens a file explorer window to select a new wavefile. - Saves contours if a new file is selected. - Updates the canvas to show the new spectrogram. - setup(): - Loads default variables to local variables. - submit(): - Loads user inputs to local variables. - switch(self) - Updates spectrogram displayed to PCEN (and conversely). - _frame_listbox_scroll(): - Just a callable part of layout() - _quit(): - Saves contours and closes the app. + def popup_window(self): + """ + Creates the windown and the layout for buttons/text. + """ + self.root = Toplevel() + Label(self.root, text=self.prompt).grid(row=0, columnspan=len(self.options)) + + for i, option in enumerate(self.options): + Button( + self.root, + text=option, + command=lambda x=option: self.button_pressed(event=x) + ).grid(row=1, column=i) + + self.root.mainloop() + + def button_pressed(self, event): + """ + Saves the button that is pressed to self.pressed. + Then shuts down the window. + + Parameters + ---------- + event : str + The text from the selected option. + """ + self.pressed = event + self.root.quit() + self.root.destroy() + + +class App(object): + """ + A Class to construct an contours annotation tool for audio data. + + ... + + Parameters + ---------- + DIR : str + Path to a folder in which the file explorer will be opened. + DIR_OUT : str + Path to a folder where the contours will be saved. + MAX_C : int + Maximum number of contours that can be drawn at once. + NEW_SR : int + Resampling rate of the audio recording. + WAVEFILE : str + Path to the audio recording to be opened. Should be a '.wav' file. + coords_to_modify : dict + Coordinates of points (from a previous annotation) that can be used + as input to add modifications. + + Attributes + ---------- + _default_bounds : list of float + Boundaries for the matplotlib canvas. + (Default value is loaded from 'PARAMETERS.py' file). + _default_clipping : int + Default clipping value for spectrogram, in dB. + (Default value is loaded from 'PARAMETERS.py' file). + _default_cmap : str + Name of a matplotlib.pyplot color map. + (Default value is loaded from 'PARAMETERS.py' file). + _default_height : int + Default height of the window in which the app will run, in pixels. + (Default value is loaded from 'PARAMETERS.py' file). + _default_hop_length : int + Default hop length for spectrogram, in samples. + (Default value is loaded from 'PARAMETERS.py' file). + _default_left_panel_width : int + Default width for the left panel of the window of the app. + (Default value is loaded from 'PARAMETERS.py' file). + _default_nfft : int + Default fft size for spectrogram, in samples. + (Default value is loaded from 'PARAMETERS.py' file). + _default_width: int + (Default) width of the window in which the app will run, in pixels. + (Default value is loaded from 'PARAMETERS.py' file). + + canvas : matplotlib object + Interface to include matplotlib plot in tkinter canvas. + CHECK_bspline : tkinter int variable + User checkbox about plotting curves or not. + CLIP_IN : tkinter float variable (Double) + User input for clipping value. + FFT_IN : tkinter int variable + User input for fft. + figure, axis, data_showed : matplotlib objects + Objects used to show matplotlib.pyplot plot. + HOP_IN : tkinter int variable + User input for hop length. + klicker : mpl_point_clicker instance + Adds widgets to matplotlib plot that allows to draw contours. + NAME0 : int + Number used to name the first contour available for annotation. + NAME1 : int + Number used to name the last contour available for annotation. + OPTIONS : tkinter list variable + User listbox to select category item. + root : tkinter Tk instance + Initialises tkinter interpreter and creates root window. + spectrogram : numpy array + Spectrogram of the waveform. + waveform : numpy array + Waveform of the audio recording. + + Other attributes, buttons and labels have self explenatory names. + + Methods + ------- + bspline_activation(): + Activates/deactivates the visualisation of lines as curves. + create_canvas(): + Creates matplotlib figure to show spectrogram in tkinter canvas. + entry_setup(): + Creates variables that save inputs from the user in the entry fields. + get_key_pressed(event): + Updates plot and tkinter interface + when a key is pressed to add a new category. + layout(): + Lays the main structure of the tkinter window. + link_select(event): + Changes the focus to be on a new category, corresponding to + the selected item in listbox widget. + load_audio(): + Loads audio data. Waveform and spectrogram. + select_file(): + Opens a file explorer window to select a new wavefile. + Saves contours if a new file is selected. + Updates the canvas to show the new spectrogram. + setup(): + Loads default variables to local variables. + submit(): + Loads user inputs to local variables. + switch(self) + Updates spectrogram displayed to PCEN (and conversely). + _frame_listbox_scroll(): + Just a callable part of layout() + _quit(): + Saves contours and closes the app. """ from parameters import (_default_width, _default_height, _default_hop_length, @@ -243,8 +293,21 @@ class App(object): self.figure.canvas.mpl_disconnect(self.klicker.key_press) self.root.bind('<Key>', self.get_key_pressed) + # just to be sure + self.root.protocol("WM_DELETE_WINDOW", self.on_close) + self.root.mainloop() + def on_close(self): + save = Popup(prompt="Save and exit?", options=["Yes", "No", "Cancel"]).pressed + if save == "Yes": + self._quit() + if save == "No": + self.root.quit() + self.root.destroy() + else: + pass + def bspline_activation(self): """ Activates/deactivates the visualisation of lines as curves. @@ -554,7 +617,7 @@ class App(object): os.path.basename(self.WAVEFILE)[:-4]+"-contours.json") self.WAVEFILE = new_wavefile - # display loading scree + # display loading screen self.loading_screen.grid(row=1, column=1, rowspan=14) self.canvas.get_tk_widget().destroy() @@ -578,7 +641,7 @@ class App(object): self.entry_setup() self.layout() self.loading_screen.grid_forget() - + def setup(self): """ A function to create variables based on default values diff --git a/line_clicker/__pycache__/__init__.cpython-39.pyc b/line_clicker/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7f4827996913926ebbfe4cdca728bac46115bac GIT binary patch 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 literal 0 HcmV?d00001 diff --git a/line_clicker/__pycache__/line_clicker.cpython-39.pyc b/line_clicker/__pycache__/line_clicker.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4fdac3d06e156ecb3508baef0ebe5e9db968a698 GIT binary patch literal 19117 zcmYe~<>g{vU|^^y-kGu}n1SIjh=YvT7#J8F7#J9e_b@Opq%cG=q%fv1<uFDuf@!8G zW-!f?%NoVT2$EyYVbA4=;sCRmayWCjqPW3qmK>g3-Y8y1h7^$$wiNaj#wb2_h7^tz z&K8Cg&Q$(p<|u(w!4$4+rlK9G%nO9l8B(|xF{U%5@PJ7Wn|UE)lyE9rmPiV33STc{ z3{#Y7lvt`*mUyag3V(_~FB2m}sz9n(3TujBFH@96DqEIhicpGhFC!yE3R5tHrf8KX zmt#tbLP}~{Vrfo^LP26tVs2_lYEiL5T7Hp2c4}p@LSkNuLT-L(acWX&NlAX5LUF!A zW{E;_VxB@$szP#UaY=q|W>snm*Go_k`DrrV;`Rlpb^@!`WW2?ZnOBlpRA88Li?gsa zF{LQ6Br`dg8KfABLBYw+z`)=P3epG$28J5O5{3ngH4F<GB^hc#Y$g!9mZ5~XhM|VB zhAD-ym${a?gkb?o4Z{M~g$y+e3)mJiq%f7R*Dy3QrZ7t|h%hW<u4SoVu3>3rtYxiX zsbNh6d6%U$or!^gi_=jdtu!yWBr`uxp(I}+IX}0cv?Ns_Q6agssH{>UCo?ZqAt|+_ zJT)~>p`<)tp&&mqucTOyi_^120pgkboWzpUVueJA+Vaej3<X021GqviPCY$6E-uah zcyMxY>cRmRXN7{5LQZCJi9&vnLSAWZL8U@sQBh(g7bm9=STs!`IX}NBB{L6fOMae0 zg+gLQCddVqr0T4M>CDc|OHr^=C@v|=%uDCu<P6D3Me+<NC~{LvGV)VEAyQhLs;A%# ziQ&Y&N(HD)W}bqT!lQ;6pa4oNdekseM}doz^HIZ$ywt>^)Z!Ab2!g9yS^yKNN-fHV zv5S$kKWdl(PZKct<kF<fWDo~pc0o~US!RA|F-QayUSRVpKz8YIadL46rIwTy<rTxj zmy5F^Q^5*HsFq};qK0gm0$8ZDs4P_h6wJD?U?$&;N|+fd8E>(G0{vwQ0|UcLMg|53 zO^#bkc?GxF6N}P|^NUJuv4F@TP*%9bk&~HMT#%TYdW$(XGw&92ZeqnP=A6_#O}1N% z6}K2Gi$Iy=7Gv5iri#p4OqH1{8Hz+17#M!}>SyHVrt0V9XC~{rB$g!VyZHMAczF7` z1-ZKF2b3n|WF{-5<>#cN78UCUR62$^>VrZ*J~<~dIXkrog`-zcd5fbYKOU3`Qu#r7 zkq4APrT7@RnE05K7&(}V#2FYEl0kyV7*tk*OT%Lf3=F9ZQH&`JQA{a}QOqe!Da<J> zDXeptqgc`zQrH$TMzN-{WwEESWpSjkWpSpkgUY%Tt`zP$>``1PJSn^>JSlvjaw|nJ zMJPomlOc^OML0#Ig)xddMKndMg&~S3MLb2Kg&~SJMKVRIg&~SBMLI>Mg&~SRMK(pQ zg&|5HMLtELg&|5XMKMLGg&|5P#UMo`MYV-7N;pL=MZJX~N+d-iMYDw=N;E|)MZ1L| zN-RYuMYn|^N<2j`MZbk1N+N|Pm_gI<7CSVE65k6zN(|?m#NuLwl8nR>g-lSPsGyXX zlA@cRrvxr36Y~^OD>92qGV{_EauZ7ma`H=ZGLsb2GSf?oQej!pF(oAxoWBzDz(P8p zf;2HFC%?QHl<QK863f8_O0hy(QGPC@&{Y7tqgYRYi!%UR48cm?qSV~{veXoX(qfRE zpk!Q>nVwMsQ4cD7Tr$(rQj1daKxK7FYI=TAW@<4^b8%`;YH|sR?)22W)S^U?SWaqs zD!8oR;!LW9m<O^VKMz*k<rgU=RYEN;EznQNFV6!NVEN^s5(-pC7Aqv?6obmm;^NGt zoK%qK5>r66NKvXnaw6O@5T}DnPxa)Iq8#l+b%o-L{GyWN(h^YKMk+)R8C+Lamx~jW zO%<$=LpnbxD>b=<i<8r_BC}W_Gq1QLF)uk)kBc*@xB!&o;YA;)3`xq*&q+<p(^1GT z0968sIeJ{2oStb4ZizX?sX7XY1qBM_nK?NMplE~^>Y#|pPE7%M9Aq5Gryw1W;*_8k zaGnBL;+9_ou4Ph-bredAQx(*~rJXv+QR?uLP@Ri2IX@@As2Ez<qq-AOMdaicDQIM* zRwSmRCTHd*=4gTfI=M1ACsiT8EVW3XJR>y^><4hNgge^>TG?lUl5;A&?uS%JpsELK zH6#hbld>KcCnR0OCne>B>aUX0f}B)Tmw~F3g8br4P?##@L&{c&CIyWOaIvVP04W|d z6^c@G5=%16z-a@Ncp*7OkBc)mu_!yWNWn@WB{R7M)qKa46tLNeImitaPz|1un3tXk zF3WTB^RwZ0XXd4XBMQmKe))N+I-tl*Er!H#QmR66eokhJ0yrH)s(OXQVukXI%#u`v zlzec-pPN_#4n74dP@ReDD&NG4%-qskP?eLES_HB?Cow5C2b2gEAif7hJdzWP4bTL@ z$xe@pGcPeW6%^;iB}JeF0g4b*tNg$UKrJ(f!3q$YK*o8dL92Eh1&A@=1e}x#=4n@h zTn}|&EhMc&Oa~{CL|CS*1Q%_fQVLu&fsM#W1VvH?#2^JFs6Rk1iBJMr3>5(BRPxEp zO9ct$#Y5bHCA9oXbD^PxR1hSVDC8uT<|Su9k}OCw$h<_Tm(Z*+1chHgW^#6X3aGV& z#S)iHNcIQi2WUt^ZL0*A)*#KGA`RTE#b$bWQ6jk10L4aT9$GRa9N`K{scHG3vaTpK z5uEoD74lNc6_WFFN^|qj?K4Fa&@j|dFwisA1Qi1LMJXUBU}gnp<o02jLTX}i2Be?@ z`wz^8`&a`M`8uG+JScI3G8@P|O^~(7#v~S{Dx_8v6r~myr=}=m<|$~DWag&oD5Mpo z7M7;wC0A;KT6>@(ry#Q;HAhDQOlc})rYYp*E2LJGfU27G%(7H;A6D1uDR`zSfTeU4 zGV?M^z|C4nj)!Es#Js%xl0;CsQLM+s#l`7ZQc{$eR9cdXR_-U47J+KCc#w}Ec@dPD z^HNePU|vfG*DjR`U{7m;%36?yoJs{)mB+;iwHQbBk(2~#aDv?e4ogr~qX*HV2h$r5 ztyDm91IkAr%aLk*BNs>k!^O!NoSK}Umy%cn=~Wbiybmt-Gt(5HW+>$5r=)^XJIsNg zs-DxOD6za4<odEyP(e~$lA4$TvZc7BC=p!EgF*mQwn1D54iLhw@lDLkgE&n`Atkf8 zASbahH3ii0&CE?qPX(2FsOE#>7Us;%Vo3D}N;aMd*Jl<hloXZ1iqw+I9I#(tjT2Dc zu_PlE)W1nZvN5q3R<LHLR>p&q9BOTzT9%pzs?0s}((;RPA&~*<tYxGs<RliCfYLHl z8(1a0azZr)-X?&S0njQ5RI9^lGe`*vt!SYk%*DwG6^817W+hMq3KW3g_7TWVQ2he- zGPqv_N{T1}69NvH;zUp!g5wLM7F5N7`o~cBqohKxXF$f_@HV)-g0#p$85AkWB`4;U zC4$lesA!|qXs|QTEPw=|9v3G#wo*Y|Nl+^a<k(_RiUzl9lQUA2vq6a_QK7gXH90da zGa1^7g^nF$7AwH?gSx)?C7_0?e@RAakwPLo%PWAZoZ{4+G*EezlbV-Ul3!E_uE;>8 z1E(*zpNc3MxHuD2QsSX;RH;#%kzcN91!@VVq=35UFbPm)04n^z85R+Z3J~q!ieIBb zrxIB!SP-TUZcb*NLRx-uX|Y0L3Ao7tF4&O!mEaug23C?;tWa8z0&Z59<bwuGz^OR1 z7*ZcVn!=TOT%5@{sfk4hKY$(K3=#u1rE?%fJG5H>@&krj^td=v@=KF)QXwUQ2E+hR z^#dNJNX{>)1SM{;UPu|LP@Gx<5&|boNQVQtECFY8a95`kR2`J$gBm`mMFrrI4_Ltj z8ze#22z4vSLC{(_9-M47z=2t;14$6^6)*-Qz}-_zpsi7;CWWNb@)FPhOd>>s0=!`f zEmJ^lfE4XqoSd+-6VZ@?S_i7-(o;*~!4akbPT1g}2`DWA4RL{D5!|mq4p3-PS8#;( z%ra6z3A_lD4#1;7iOD69S}U_ekBbvzI;a!_n~h|HV{%Dm8Kk0vjx~YWl&N`$oROVc znUtSclmaTviWNZBFEnpJO$7%VBrikE)dBU6%56c#3fL2btpFRL0LgslR)D&X@!&{- zm<~45Hy>13CBkb<NNj@A2~yHQs!b6AUXrf>Zsl-sa>AnuYOtO{C^!!k!~4U@$>8Cw z^c+yyFUo~F1D5-Oz+GEV-UT}kk~qQkLo*DhYy!t9-Yi(0S`rV5OGtKb1eFI60Z?3m zDt1VtM30NJxID8YIRl(|G!l!7pd|=uDuIne>F1?Zz&q8IAh$pbO-cl16;OHtnV^uF zS5OK{A&>$9Y<4lkC9vQ~Ni0bO>jzaNpb@v6oYWLZ8yGwQSPW_xB!fFQDWH-vC>7kJ zPeyMRd88(mW#)jKQe2Q(l$rtwvD|_jNWj8|dlW#WVs1f>E;v|q!6OT)MS4Z4i76!+ zsVVu%#d?|f`dpm181rwjg8M(e7<Dz7Z?Qr8WVe`e(~@s-f(r~#O{U3J#jb3aW^SHj zQpK*EmS&!2kXpsAY?Pd6V3b_NuAE|KWNu_p#jb2=VrHI{QpK)pk!)&al2pa6oN8=t zo@`Xbu56wLf>rFwNy$kmMj#PG^Q6?|v|Eghw-|$NF_jkFVoAv_&(mbT1uo^mF2BVA zOER~(5UKbU4{|kdix0I(y~P1eF!7}Yx40lIkg<Mlw^%@(mX~s%F*?womnKsY2Ll5` zI%I_Ff&EL+@K!Q-Bn%?N0AjN+Fff3|?TU?<K*QA?4Dk##932etj5VAc4Dn1I3|Y(z zSQavLFvPQ#u+=b?us1Um8PqU!FvN4@@pLd`aYAJ|YM4_Pvzdx~YM471;<@tJQb20B zQb0WJ61Eza5}szJqO2O04u*JMuueXxPTmr>8rBm2W~QPAHLM*B@dE7(X^f!Jwib>O z!5Xe+Mi+)=#tw#fp*$O~N{~5wYFMGVg;Ll+dh!%H7_x*xEU<3&6jpTIC2TcpB_hpC zMek}rIz;n0IvBFVIvC<bOW11IK{68<i@0joI~d}{!EzEHRtG~C*968QgARr)juZ|_ zP&i1Ya6))C3?*ze+@KJdz*tmM!=1^H%?x36FvLrhfI>%Vf%HO#W=5D>!EWOMyG^Ep zAxjAEH`zReOpw`9U=`fhR6t#_rh{RD+(HIMh7N`-hAjCGhIsiDo(_h1g${-VicoR+ zETsj?DZCvFSt<)bv7p+)kOdLrgNv!b#rQiIved!qG{AAD36~M*V93$}%V@*J1UneA zbiiV|DGb33nnHd>pd{q>k_j}T3K|=RCN2h$0BEdN7&HJ2N|oshH4L$0wTvK_moU~a zrZ9*zG&71bOk^r#31(Qy2u`jmnQpOydd<bRn2Sq_Rx;gU(lfZlm<bw!2DPKWW5q>c z3=9mnxNJb}AJCMC-FpTGhR+~-t5je`L3})@dlnzBXOokkoS0K=r-x7;#SQK1gZlo- z6u7xapMim)$N)qbg9tMa0UFH)yRgWDfq~&GNXQaIfP&x_1B!K^jK|5qzyJ=S9B^i> zVTetMVX9@UWh!CFVk}`=z}&%*#Ztp0$*_<yg&~EZl_`ZWjY*OLs|*uJCXES{J+oMA z7)#hX7_wNK8O0bR8B&<%fHE`t62?A8NPugy_!WWt4i9#4PvI6Tc)(7R;}%;kxO}|D z1}avJT#7&ry~PTi-nqpFDbztUXo#%{RNfTHFfcHzWG*rRMGkXjO2sW6NNo}iHn<2& z;IK0?Fcgb0Ffg#Ovi)XZlwy?n$HdIUDD;Pgh2;<Kkg|b=lnElF(izejpgw1+VJrcK z8WYs#3}BzLAo-lJ$Qm?>!CGVoiWqwk;RGV!-Y5di;9+wyKgh+Pa+8gT?H>=L(0>+2 zmjB>jyd?x_oIsj6@!&?(EfJ_#QEE<VVsR=|R5&*uG(ZramtT^ZRtXj40{3?y+W0}u zFmMwJBB`l&iv?84-{OXbC}`yJ7AIH-XyoV?uZydjW2jF^Jb2pk77M7Ae2W#r;mRzA z4I<xS0gab{BkC3(ObfJ1WQTT-Z?TjoW|rJy0cEZtP^chOf>TCukr61U3_-EXS_B%c zyu}N36*!)Yqj+E@gYxk$=FGg3TO6=q$0z}qBuwNMCwz!1N)RRq7b*hfnj%M#Wh|gM z+*_Qfd8N7F;r&}Y$c^<|JjlI<TWk=yTRh0)EVsB24XImfkdEdpZcyVQIX^EiHM!&# zKd221ZfZiJF-j1L1Br_$VTce)loqKnFfc^%<mQ*;XXZiF-4cXaodX>gNKLr~ZeoM$ z>>^K)mv}+ef#Mnx?VKPcINCLNip)Ww><uD7`Kl-c!~#V}5h&z~qCwmk5a9zNd_mT; zLkBCt!38NO!Fdi`T5`Yȅ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 D0sxC( literal 0 HcmV?d00001 diff --git a/line_clicker/line_clicker.py b/line_clicker/line_clicker.py index 8b68908..687ed06 100644 --- a/line_clicker/line_clicker.py +++ b/line_clicker/line_clicker.py @@ -364,8 +364,8 @@ class clicker(object): """ if self.legend_labels[self.current_line] in list(self.coords.keys()): # does this point already exists ? - if [x,y] in self.coords[self.legend_labels[self.current_line]]: - warnings.warn("This point already exists!", UserWarning, stacklevel=2) + if x 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) else: # where should it be inserted ? here = np.where(np.array(self.coords[self.legend_labels[self.current_line]])[:,0] > x)[0] @@ -592,15 +592,20 @@ class clicker(object): if (self.currently_pressed and event.xdata != None and event.ydata != None): - # update coords - current_lines = self.figure_lines[self.current_line].get_data() - current_lines[0][self.index] = event.xdata - current_lines[1][self.index] = event.ydata - self.coords[self.legend_labels[self.current_line]][self.index] = [event.xdata, event.ydata] - - # update plot - self.update_lines() - self.figure.canvas.draw() + + # 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) + else: + # update coords + current_lines = self.figure_lines[self.current_line].get_data() + current_lines[0][self.index] = event.xdata + current_lines[1][self.index] = event.ydata + self.coords[self.legend_labels[self.current_line]][self.index] = [event.xdata, event.ydata] + + # update plot + self.update_lines() + self.figure.canvas.draw() def set_legend(self): """ diff --git a/outputs/SCW1807_20200713_064554-contours.json b/outputs/SCW1807_20200713_064554-contours.json index 9ee2ef6..55c8c0c 100644 --- a/outputs/SCW1807_20200713_064554-contours.json +++ b/outputs/SCW1807_20200713_064554-contours.json @@ -101,8 +101,8 @@ 9632.580796877977 ], [ - 1.1769355956020835, - 9171.762083884514 + 1.1794070626717919, + 9479.66604006841 ], [ 1.2234946472488883, @@ -187,28 +187,32 @@ 15162.405352799564 ], [ - 1.446383724281465, - 16496.35425883328 + 1.4413724760470132, + 16287.542250441644 ], [ - 1.44676171875, - 17700.91851851852 + 1.4490057835548567, + 19078.485781877196 ], [ - 1.44676171875, - 19269.57037037037 + 1.4515679818356721, + 19196.707818930037 ], [ - 1.4532515624999998, - 19254.340740740743 + 1.4733184911158894, + 18661.618655692724 ], [ - 1.4662312499999999, - 19117.274074074077 + 1.4937878542675378, + 17396.273242381794 ], [ - 1.4857007812499998, - 18751.762962962963 + 1.507527807781656, + 16956.60405592277 + ], + [ + 1.5294099559708072, + 16899.255901167242 ] ], "Line3": [ diff --git a/post_annotation.py b/post_annotation.py index e0dc130..f3fd29d 100644 --- a/post_annotation.py +++ b/post_annotation.py @@ -126,10 +126,13 @@ class Results(object): for idx, key in enumerate(list(self.coords.keys())): if mode=="curves": - cx, cy = to_curve( - np.array(self.coords[key])[:,0], - np.array(self.coords[key])[:,1], - kind="quadratic") + if len(np.array(self.coords[key])[:,0])>2: + cx, cy = to_curve( + np.array(self.coords[key])[:,0], + np.array(self.coords[key])[:,1], + kind="quadratic") + else: + cx, cy = np.array(self.coords[key])[:,0], np.array(self.coords[key])[:,1] ax.plot(cx, cy, color=self.colors[idx%len(self.colors)]) else: @@ -150,10 +153,10 @@ class Results(object): fig, ax = self.display_image(img) for idx, key in enumerate(list(self.coords.keys())): - min_min = (min(np.array(annot_data.coords[key])[:,0]), - min(np.array(annot_data.coords[key])[:,1])) - max_max = (max(np.array(annot_data.coords[key])[:,0]), - max(np.array(annot_data.coords[key])[:,1])) + min_min = (min(np.array(self.coords[key])[:,0]), + min(np.array(self.coords[key])[:,1])) + max_max = (max(np.array(self.coords[key])[:,0]), + max(np.array(self.coords[key])[:,1])) min_min = (min_min[0]-tol*min_min[0], min_min[1]-tol*min_min[1]) max_max = (max_max[0]+tol*max_max[0], max_max[1]+tol*max_max[1]) -- GitLab