├── license.txt ├── pase ├── __init__.py ├── __pycache__ │ ├── __init__.cpython-38.pyc │ └── pase.cpython-38.pyc └── pase.py ├── readme.md ├── screenshots ├── m1.JPG ├── m2.JPG ├── m3.JPG ├── m4.JPG ├── m5.JPG ├── m6.JPG ├── minke_video.mp4 ├── minke_video_example.gif ├── pase_icon.ico └── pase_icon.png └── to_compile_yourself ├── compilecommands.txt └── pase_compile.py /license.txt: -------------------------------------------------------------------------------- 1 | CC BY-NC 4.0 2 | 3 | Attribution-NonCommercial 4.0 International (CC BY-NC 4.0) 4 | This is a human-readable summary of (and not a substitute for) the license. Disclaimer. 5 | 6 | You are free to: 7 | Share — copy and redistribute the material in any medium or format 8 | Adapt — remix, transform, and build upon the material 9 | The licensor cannot revoke these freedoms as long as you follow the license terms. 10 | Under the following terms: 11 | Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 12 | 13 | NonCommercial — You may not use the material for commercial purposes. 14 | 15 | No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 16 | Notices: 17 | You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. 18 | No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. 19 | -------------------------------------------------------------------------------- /pase/__init__.py: -------------------------------------------------------------------------------- 1 | from pase import pase 2 | -------------------------------------------------------------------------------- /pase/__pycache__/__init__.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianmenze/Python-Audio-Spectrogram-Explorer/fe322c18cd2f29e709f55b2256ead655bfdee779/pase/__pycache__/__init__.cpython-38.pyc -------------------------------------------------------------------------------- /pase/__pycache__/pase.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianmenze/Python-Audio-Spectrogram-Explorer/fe322c18cd2f29e709f55b2256ead655bfdee779/pase/__pycache__/pase.cpython-38.pyc -------------------------------------------------------------------------------- /pase/pase.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Sep 6 17:28:37 2021 4 | 5 | @author: Sebastian Menze, sebastian.menze@gmail.com 6 | """ 7 | import sys 8 | import matplotlib 9 | # matplotlib.use('Qt5Agg') 10 | 11 | from PyQt5 import QtCore, QtGui, QtWidgets 12 | # import qdarktheme 13 | # from qt_material import apply_stylesheet 14 | 15 | # from PyQt5.QtWidgets import QShortcut 16 | # from PyQt5.QtGui import QKeySequence 17 | 18 | from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar 19 | from matplotlib.figure import Figure 20 | 21 | import scipy.io.wavfile as wav 22 | 23 | import soundfile as sf 24 | 25 | 26 | from scipy import signal 27 | import numpy as np 28 | from matplotlib import pyplot as plt 29 | import pandas as pd 30 | import datetime as dt 31 | import time 32 | import os 33 | 34 | from matplotlib.widgets import RectangleSelector 35 | 36 | 37 | # from pydub import AudioSegment 38 | # from pydub.playback import play 39 | # import threading 40 | 41 | import simpleaudio as sa 42 | 43 | from skimage import data, filters, measure, morphology 44 | from skimage.morphology import (erosion, dilation, opening, closing, # noqa 45 | white_tophat) 46 | from skimage.morphology import disk # noqa 47 | from matplotlib.path import Path 48 | from skimage.transform import rescale, resize, downscale_local_mean 49 | 50 | from scipy.signal import find_peaks 51 | from skimage.feature import match_template 52 | 53 | from moviepy.editor import VideoClip, AudioFileClip 54 | from moviepy.video.io.bindings import mplfig_to_npimage 55 | 56 | #%% 57 | 58 | 59 | class MplCanvas(FigureCanvasQTAgg ): 60 | 61 | # def __init__(self, parent=None, width=5, height=4, dpi=100): 62 | # self.fig = Figure(figsize=(width, height), dpi=dpi) 63 | # self.axes = self.fig.add_subplot(111) 64 | # super(MplCanvas, self).__init__(self.fig) 65 | 66 | def __init__(self, parent=None, dpi=150): 67 | self.fig = Figure(figsize=None, dpi=dpi) 68 | # self.axes = self.fig.add_subplot(111) 69 | # self.axes.set_facecolor('gray') 70 | 71 | super(MplCanvas, self).__init__(self.fig) 72 | class gui(QtWidgets.QMainWindow): 73 | 74 | 75 | def __init__(self, *args, **kwargs): 76 | super(gui, self).__init__(*args, **kwargs) 77 | 78 | self.canvas = MplCanvas(self, dpi=150) 79 | 80 | # self.call_time=pd.Series() 81 | # self.call_frec=pd.Series() 82 | 83 | self.f_min = QtWidgets.QLineEdit(self) 84 | self.f_min.setText('10') 85 | self.f_max = QtWidgets.QLineEdit(self) 86 | self.f_max.setText('16000') 87 | self.t_length = QtWidgets.QLineEdit(self) 88 | self.t_length.setText('120') 89 | self.db_saturation=QtWidgets.QLineEdit(self) 90 | self.db_saturation.setText('155') 91 | self.db_vmin=QtWidgets.QLineEdit(self) 92 | self.db_vmin.setText('30') 93 | self.db_vmax=QtWidgets.QLineEdit(self) 94 | self.db_vmax.setText('') 95 | # self.fft_size = QtWidgets.QLineEdit(self) 96 | # self.fft_size.setText('32768') 97 | self.fft_size = QtWidgets.QComboBox(self) 98 | self.fft_size.addItem('1024') 99 | self.fft_size.addItem('2048') 100 | self.fft_size.addItem('4096') 101 | self.fft_size.addItem('8192') 102 | self.fft_size.addItem('16384') 103 | self.fft_size.addItem('32768') 104 | self.fft_size.addItem('65536') 105 | self.fft_size.addItem('131072') 106 | self.fft_size.setCurrentIndex(4) 107 | 108 | 109 | self.colormap_plot = QtWidgets.QComboBox(self) 110 | self.colormap_plot.addItem('plasma') 111 | self.colormap_plot.addItem('viridis') 112 | self.colormap_plot.addItem('inferno') 113 | self.colormap_plot.addItem('gist_gray') 114 | self.colormap_plot.addItem('gist_yarg') 115 | self.colormap_plot.setCurrentIndex(2) 116 | 117 | self.checkbox_logscale=QtWidgets.QCheckBox('Log. scale') 118 | self.checkbox_logscale.setChecked(True) 119 | self.checkbox_background=QtWidgets.QCheckBox('Remove background') 120 | self.checkbox_background.setChecked(False) 121 | 122 | # self.fft_overlap = QtWidgets.QLineEdit(self) 123 | # self.fft_overlap.setText('0.9') 124 | 125 | self.fft_overlap = QtWidgets.QComboBox(self) 126 | self.fft_overlap.addItem('0.2') 127 | self.fft_overlap.addItem('0.5') 128 | self.fft_overlap.addItem('0.7') 129 | self.fft_overlap.addItem('0.9') 130 | self.fft_overlap.setCurrentIndex(3) 131 | 132 | 133 | 134 | self.filename_timekey = QtWidgets.QLineEdit(self) 135 | # self.filename_timekey.setText('aural_%Y_%m_%d_%H_%M_%S.wav') 136 | 137 | self.playbackspeed = QtWidgets.QComboBox(self) 138 | self.playbackspeed.addItem('0.5') 139 | self.playbackspeed.addItem('1') 140 | self.playbackspeed.addItem('2') 141 | self.playbackspeed.addItem('5') 142 | self.playbackspeed.addItem('10') 143 | self.playbackspeed.setCurrentIndex(1) 144 | 145 | 146 | self.time= dt.datetime(2000,1,1,0,0,0) 147 | self.f=None 148 | self.t=[-1,-1] 149 | self.Sxx=None 150 | self.draw_x=pd.Series(dtype='float') 151 | self.draw_y=pd.Series(dtype='float') 152 | self.cid1=None 153 | self.cid2=None 154 | 155 | self.plotwindow_startsecond= float( self.t_length.text() ) 156 | # self.plotwindow_length=120 157 | self.filecounter=-1 158 | self.filenames=np.array( [] ) 159 | self.current_audiopath=None 160 | 161 | self.detectiondf=pd.DataFrame([]) 162 | 163 | # openfilebutton=QtWidgets.QPushButton('Open files') 164 | button_save=QtWidgets.QPushButton('Save annotation csv') 165 | button_save.clicked.connect(self.func_savecsv) 166 | 167 | button_quit=QtWidgets.QPushButton('Quit') 168 | button_quit.clicked.connect(QtWidgets.QApplication.instance().quit) 169 | 170 | 171 | self.fft_size.currentIndexChanged.connect(self.new_fft_size_selected) 172 | self.colormap_plot.currentIndexChanged.connect( self.plot_spectrogram) 173 | self.checkbox_background.stateChanged.connect(self.plot_spectrogram ) 174 | self.checkbox_logscale.stateChanged.connect(self.plot_spectrogram ) 175 | 176 | 177 | self.checkbox_log=QtWidgets.QCheckBox('Real-time Logging') 178 | self.checkbox_log.toggled.connect(self.func_logging) 179 | 180 | button_plot_all_spectrograms=QtWidgets.QPushButton('Plot all spectrograms') 181 | button_plot_all_spectrograms.clicked.connect(self.plot_all_spectrograms) 182 | 183 | button_draw_shape=QtWidgets.QPushButton('Draw shape') 184 | 185 | button_draw_shape.clicked.connect(self.func_draw_shape) 186 | 187 | ####### play audio 188 | button_play_audio=QtWidgets.QPushButton('Play/Stop [spacebar]') 189 | button_play_audio.clicked.connect(self.func_playaudio) 190 | 191 | button_save_audio=QtWidgets.QPushButton('Export selected audio') 192 | button_save_audio.clicked.connect(self.func_saveaudio) 193 | 194 | button_save_video=QtWidgets.QPushButton('Export video') 195 | 196 | button_save_video.clicked.connect(self.func_save_video) 197 | 198 | ############# menue 199 | menuBar = self.menuBar() 200 | 201 | # Creating menus using a title 202 | openMenu = menuBar.addAction("Open files") 203 | openMenu.triggered.connect(self.openfilefunc) 204 | 205 | 206 | exportMenu = menuBar.addMenu("Export") 207 | e1 =exportMenu.addAction("Spectrogram as .wav file") 208 | e1.triggered.connect(self.func_saveaudio) 209 | e2 =exportMenu.addAction("Spectrogram as animated video") 210 | e2.triggered.connect(self.func_save_video) 211 | e3 =exportMenu.addAction("Spectrogram as .csv table") 212 | e3.triggered.connect(self.export_zoomed_sgram_as_csv) 213 | e4 =exportMenu.addAction("All files as spectrogram images") 214 | e4.triggered.connect(self.plot_all_spectrograms) 215 | e5 =exportMenu.addAction("Annotations as .csv table") 216 | e5.triggered.connect(self.func_savecsv) 217 | e6 =exportMenu.addAction("Automatic detections as .csv table") 218 | e6.triggered.connect(self.export_automatic_detector) 219 | 220 | drawMenu = menuBar.addAction("Draw") 221 | drawMenu.triggered.connect(self.func_draw_shape) 222 | 223 | 224 | autoMenu = menuBar.addMenu("Automatic detection") 225 | a1 =autoMenu.addAction("Shapematching on current file") 226 | a1.triggered.connect(self.automatic_detector_shapematching) 227 | a3 =autoMenu.addAction("Shapematching on all files") 228 | a3.triggered.connect(self.automatic_detector_shapematching_allfiles) 229 | 230 | a2 =autoMenu.addAction("Spectrogram correlation on current file") 231 | a2.triggered.connect(self.automatic_detector_specgram_corr) 232 | 233 | a4 =autoMenu.addAction("Spectrogram correlation on all files") 234 | a4.triggered.connect(self.automatic_detector_specgram_corr_allfiles) 235 | 236 | a5 =autoMenu.addAction("Show regions based on threshold") 237 | a5.triggered.connect(self.plot_spectrogram_threshold) 238 | 239 | 240 | 241 | quitMenu = menuBar.addAction("Quit") 242 | quitMenu.triggered.connect(self.exitfunc) 243 | 244 | ################# 245 | 246 | ######## layout 247 | outer_layout = QtWidgets.QVBoxLayout() 248 | 249 | 250 | top2_layout = QtWidgets.QHBoxLayout() 251 | 252 | top2_layout.addWidget(self.checkbox_log) 253 | top2_layout.addWidget(self.checkbox_logscale) 254 | top2_layout.addWidget(self.checkbox_background) 255 | 256 | top2_layout.addWidget(QtWidgets.QLabel('Timestamp:')) 257 | top2_layout.addWidget(self.filename_timekey) 258 | top2_layout.addWidget(QtWidgets.QLabel('f_min[Hz]:')) 259 | top2_layout.addWidget(self.f_min) 260 | top2_layout.addWidget(QtWidgets.QLabel('f_max[Hz]:')) 261 | top2_layout.addWidget(self.f_max) 262 | top2_layout.addWidget(QtWidgets.QLabel('Spec. length [sec]:')) 263 | top2_layout.addWidget(self.t_length) 264 | 265 | top2_layout.addWidget(QtWidgets.QLabel('Saturation dB:')) 266 | top2_layout.addWidget(self.db_saturation) 267 | 268 | top2_layout.addWidget(QtWidgets.QLabel('dB min:')) 269 | top2_layout.addWidget(self.db_vmin) 270 | top2_layout.addWidget(QtWidgets.QLabel('dB max:')) 271 | top2_layout.addWidget(self.db_vmax) 272 | 273 | 274 | 275 | # annotation label area 276 | top3_layout = QtWidgets.QHBoxLayout() 277 | top3_layout.addWidget(QtWidgets.QLabel('Annotation labels:')) 278 | 279 | 280 | self.checkbox_an_1=QtWidgets.QCheckBox() 281 | top3_layout.addWidget(self.checkbox_an_1) 282 | self.an_1 = QtWidgets.QLineEdit(self) 283 | top3_layout.addWidget(self.an_1) 284 | self.an_1.setText('') 285 | 286 | self.checkbox_an_2=QtWidgets.QCheckBox() 287 | top3_layout.addWidget(self.checkbox_an_2) 288 | self.an_2 = QtWidgets.QLineEdit(self) 289 | top3_layout.addWidget(self.an_2) 290 | self.an_2.setText('') 291 | 292 | self.checkbox_an_3=QtWidgets.QCheckBox() 293 | top3_layout.addWidget(self.checkbox_an_3) 294 | self.an_3 = QtWidgets.QLineEdit(self) 295 | top3_layout.addWidget(self.an_3) 296 | self.an_3.setText('') 297 | 298 | self.checkbox_an_4=QtWidgets.QCheckBox() 299 | top3_layout.addWidget(self.checkbox_an_4) 300 | self.an_4 = QtWidgets.QLineEdit(self) 301 | top3_layout.addWidget(self.an_4) 302 | self.an_4.setText('') 303 | 304 | self.checkbox_an_5=QtWidgets.QCheckBox() 305 | top3_layout.addWidget(self.checkbox_an_5) 306 | self.an_5 = QtWidgets.QLineEdit(self) 307 | top3_layout.addWidget(self.an_5) 308 | self.an_5.setText('') 309 | 310 | self.checkbox_an_6=QtWidgets.QCheckBox() 311 | top3_layout.addWidget(self.checkbox_an_6) 312 | self.an_6 = QtWidgets.QLineEdit(self) 313 | top3_layout.addWidget(self.an_6) 314 | self.an_6.setText('') 315 | 316 | 317 | self.bg = QtWidgets.QButtonGroup() 318 | self.bg.addButton(self.checkbox_an_1,1) 319 | self.bg.addButton(self.checkbox_an_2,2) 320 | self.bg.addButton(self.checkbox_an_3,3) 321 | self.bg.addButton(self.checkbox_an_4,4) 322 | self.bg.addButton(self.checkbox_an_5,5) 323 | self.bg.addButton(self.checkbox_an_6,6) 324 | 325 | # combine layouts together 326 | 327 | plot_layout = QtWidgets.QVBoxLayout() 328 | tnav = NavigationToolbar( self.canvas, self) 329 | 330 | toolbar = QtWidgets.QToolBar() 331 | 332 | button_plot_prevspectro=QtWidgets.QPushButton('<--Previous spectrogram') 333 | button_plot_prevspectro.clicked.connect(self.plot_previous_spectro) 334 | toolbar.addWidget(button_plot_prevspectro) 335 | 336 | ss=' ' 337 | toolbar.addWidget(QtWidgets.QLabel(ss)) 338 | 339 | button_plot_spectro=QtWidgets.QPushButton('Next spectrogram-->') 340 | button_plot_spectro.clicked.connect(self.plot_next_spectro) 341 | toolbar.addWidget(button_plot_spectro) 342 | 343 | toolbar.addWidget(QtWidgets.QLabel(ss)) 344 | 345 | 346 | toolbar.addWidget(button_play_audio) 347 | toolbar.addWidget(QtWidgets.QLabel(ss)) 348 | 349 | toolbar.addWidget(QtWidgets.QLabel('Playback speed:')) 350 | toolbar.addWidget(QtWidgets.QLabel(ss)) 351 | toolbar.addWidget(self.playbackspeed) 352 | toolbar.addWidget(QtWidgets.QLabel(ss)) 353 | 354 | toolbar.addSeparator() 355 | toolbar.addWidget(QtWidgets.QLabel(ss)) 356 | 357 | 358 | toolbar.addWidget(QtWidgets.QLabel('fft_size[bits]:')) 359 | toolbar.addWidget(QtWidgets.QLabel(ss)) 360 | toolbar.addWidget(self.fft_size) 361 | toolbar.addWidget(QtWidgets.QLabel(ss)) 362 | toolbar.addWidget(QtWidgets.QLabel('fft_overlap[0-1]:')) 363 | toolbar.addWidget(QtWidgets.QLabel(ss)) 364 | toolbar.addWidget(self.fft_overlap) 365 | 366 | toolbar.addWidget(QtWidgets.QLabel(ss)) 367 | 368 | 369 | toolbar.addWidget(QtWidgets.QLabel('Colormap:')) 370 | toolbar.addWidget(QtWidgets.QLabel(ss)) 371 | toolbar.addWidget( self.colormap_plot) 372 | toolbar.addWidget(QtWidgets.QLabel(ss)) 373 | 374 | toolbar.addSeparator() 375 | 376 | toolbar.addWidget(tnav) 377 | 378 | 379 | plot_layout.addWidget(toolbar) 380 | plot_layout.addWidget(self.canvas) 381 | 382 | # outer_layout.addLayout(top_layout) 383 | outer_layout.addLayout(top2_layout) 384 | outer_layout.addLayout(top3_layout) 385 | 386 | outer_layout.addLayout(plot_layout) 387 | 388 | # self.setLayout(outer_layout) 389 | 390 | # Create a placeholder widget to hold our toolbar and canvas. 391 | widget = QtWidgets.QWidget() 392 | widget.setLayout(outer_layout) 393 | self.setCentralWidget(widget) 394 | 395 | #### hotkeys 396 | self.msgSc1 = QtWidgets.QShortcut(QtCore.Qt.Key_Right, self) 397 | self.msgSc1.activated.connect(self.plot_next_spectro) 398 | self.msgSc2 = QtWidgets.QShortcut(QtCore.Qt.Key_Left, self) 399 | self.msgSc2.activated.connect(self.plot_previous_spectro) 400 | self.msgSc3 = QtWidgets.QShortcut(QtCore.Qt.Key_Space, self) 401 | self.msgSc3.activated.connect(self.func_playaudio) 402 | 403 | self.show() 404 | 405 | def exitfunc(self): 406 | QtWidgets.QApplication.instance().quit 407 | self.close() 408 | 409 | ######################## 410 | def find_regions(self,db_threshold): 411 | 412 | y1=int(self.f_min.text()) 413 | y2=int(self.f_max.text()) 414 | if y2>(self.fs/2): 415 | y2=(self.fs/2) 416 | t1=self.plotwindow_startsecond 417 | t2=self.plotwindow_startsecond+self.plotwindow_length 418 | 419 | ix_time=np.where( (self.t>=t1) & (self.t=y1) & (self.f db_threshold 449 | mask = morphology.remove_small_objects(mask, 50,connectivity=30) 450 | mask = morphology.remove_small_holes(mask, 50,connectivity=30) 451 | 452 | mask = closing(mask, disk(3) ) 453 | # op_and_clo = opening(closed, disk(1) ) 454 | 455 | labels = measure.label(mask) 456 | 457 | probs=measure.regionprops_table(labels,spectrog,properties=['label','area','mean_intensity','orientation','major_axis_length','minor_axis_length','weighted_centroid','bbox']) 458 | df=pd.DataFrame(probs) 459 | 460 | # get corect f anf t 461 | ff=self.f[ ix_f[0]:ix_f[-1] ] 462 | ix=df['bbox-0']>len(ff)-1 463 | df.loc[ix,'bbox-0']=len(ff)-1 464 | ix=df['bbox-2']>len(ff)-1 465 | df.loc[ix,'bbox-2']=len(ff)-1 466 | 467 | df['f-1']=ff[df['bbox-0']] 468 | df['f-2']=ff[df['bbox-2']] 469 | df['f-width']=df['f-2']-df['f-1'] 470 | 471 | ix=df['bbox-1']>len(t)-1 472 | df.loc[ix,'bbox-1']=len(t)-1 473 | ix=df['bbox-3']>len(t)-1 474 | df.loc[ix,'bbox-3']=len(t)-1 475 | 476 | df['t-1']=t[df['bbox-1']] 477 | df['t-2']=t[df['bbox-3']] 478 | df['duration']=df['t-2']-df['t-1'] 479 | 480 | indices=np.where( (df['area']=spectrog.shape[1]: ix2=spectrog.shape[1]-1 517 | # sgram[ df['id'][ix] ] = spectrog[:,ix1:ix2] 518 | self.detectiondf = df 519 | self.patches = patches 520 | self.p_t_dict = p_t_dict 521 | self.p_f_dict = p_f_dict 522 | 523 | self.region_labels=labels 524 | 525 | # return df, patches,p_t_dict,p_f_dict 526 | 527 | def match_bbox_and_iou(template): 528 | 529 | shape_f=template['Frequency_in_Hz'].values 530 | shape_t=template['Time_in_s'].values 531 | shape_t=shape_t-shape_t.min() 532 | 533 | df=self.detectiondf 534 | patches=self.patches 535 | p_t_dict=self.p_t_dict 536 | p_f_dict=self.p_f_dict 537 | 538 | # f_lim=[ shape_f.min()-10 ,shape_f.max()+10 ] 539 | 540 | # score_smc=[] 541 | score_ioubox=[] 542 | smc_rs=[] 543 | 544 | for ix in df.index: 545 | 546 | # breakpoint() 547 | patch=patches[ix] 548 | pf=p_f_dict[ix] 549 | pt=p_t_dict[ix] 550 | pt=pt-pt[0] 551 | 552 | 553 | if df.loc[ix,'f-1'] < shape_f.min(): 554 | f1= df.loc[ix,'f-1'] 555 | else: 556 | f1= shape_f.min() 557 | if df.loc[ix,'f-2'] > shape_f.max(): 558 | f2= df.loc[ix,'f-2'] 559 | else: 560 | f2= shape_f.max() 561 | 562 | # f_lim=[ f1,f2 ] 563 | 564 | time_step=np.diff(pt)[0] 565 | f_step=np.diff(pf)[0] 566 | k_f=np.arange(f1,f2,f_step ) 567 | 568 | if pt.max()>shape_t.max(): 569 | k_t=pt 570 | 571 | else: 572 | k_t=np.arange(0,shape_t.max(),time_step) 573 | 574 | 575 | ### iou bounding box 576 | 577 | iou_kernel=np.zeros( [ k_f.shape[0] ,k_t.shape[0] ] ) 578 | ixp2=np.where((k_t>=shape_t.min()) & (k_t<=shape_t.max()))[0] 579 | ixp1=np.where((k_f>=shape_f.min()) & (k_f<=shape_f.max()))[0] 580 | iou_kernel[ ixp1[0]:ixp1[-1] , ixp2[0]:ixp2[-1] ]=1 581 | 582 | iou_patch=np.zeros( [ k_f.shape[0] ,k_t.shape[0] ] ) 583 | ixp2=np.where((k_t>=pt[0]) & (k_t<=pt[-1]))[0] 584 | ixp1=np.where((k_f>=pf[0]) & (k_f<=pf[-1]))[0] 585 | iou_patch[ ixp1[0]:ixp1[-1] , ixp2[0]:ixp2[-1] ]=1 586 | 587 | intersection= iou_kernel.astype('bool') & iou_patch.astype('bool') 588 | union= iou_kernel.astype('bool') | iou_patch.astype('bool') 589 | iou_bbox = np.sum( intersection ) / np.sum( union ) 590 | score_ioubox.append(iou_bbox) 591 | 592 | patch_rs = resize(patch, (50,50)) 593 | n_resize=50 594 | k_t=np.linspace(0,shape_t.max(),n_resize ) 595 | k_f=np.linspace(shape_f.min(), shape_f.max(),n_resize ) 596 | kk_t,kk_f=np.meshgrid(k_t,k_f) 597 | # kernel=np.zeros( [ k_f.shape[0] ,k_t.shape[0] ] ) 598 | x, y = kk_t.flatten(), kk_f.flatten() 599 | points = np.vstack((x,y)).T 600 | p = Path(list(zip(shape_t, shape_f))) # make a polygon 601 | grid = p.contains_points(points) 602 | kernel_rs = grid.reshape(kk_t.shape) # now you have a mask with points inside a polygon 603 | smc_rs.append( np.sum( kernel_rs.astype('bool') == patch_rs.astype('bool') ) / len( patch_rs.flatten() ) ) 604 | 605 | smc_rs=np.array(smc_rs) 606 | score_ioubox=np.array(score_ioubox) 607 | 608 | # df['score'] =score_ioubox * (smc_rs-.5)/.5 609 | score = score_ioubox * (smc_rs-.5)/.5 610 | return score 611 | # self.detectiondf = df.copy() 612 | 613 | def automatic_detector_specgram_corr(self): 614 | # open template 615 | self.detectiondf=pd.DataFrame([]) 616 | 617 | templatefiles, ok1 = QtWidgets.QFileDialog.getOpenFileNames(self,"QFileDialog.getOpenFileNames()", r"C:\Users","CSV file (*.csv)") 618 | if ok1: 619 | 620 | templates=[] 621 | for fnam in templatefiles: 622 | template=pd.read_csv(fnam,index_col=0) 623 | templates.append( template ) 624 | 625 | corrscore_threshold, ok = QtWidgets.QInputDialog.getDouble(self, 'Input Dialog', 626 | 'Enter correlation threshold in (0-1):',decimals=2) 627 | if corrscore_threshold>1: 628 | corrscore_threshold=1 629 | if corrscore_threshold<0: 630 | corrscore_threshold=0 631 | 632 | # print(templates) 633 | # print(templates[0]) 634 | 635 | if templates[0].columns[0]=='Time_in_s': 636 | 637 | # print(template) 638 | offset_f=10 639 | offset_t=0.5 640 | 641 | # shape_f=template['Frequency_in_Hz'].values 642 | # shape_t=template['Time_in_s'].values 643 | # shape_t=shape_t-shape_t.min() 644 | shape_f=np.array([]) 645 | shape_t_raw=np.array([]) 646 | for template in templates: 647 | shape_f=np.concatenate( [shape_f, template['Frequency_in_Hz'].values ] ) 648 | shape_t_raw=np.concatenate( [shape_t_raw, template['Time_in_s'].values ]) 649 | shape_t=shape_t_raw-shape_t_raw.min() 650 | 651 | 652 | f_lim=[ shape_f.min() - offset_f , shape_f.max() + offset_f ] 653 | k_length_seconds=shape_t.max()+offset_t*2 654 | 655 | # generate kernel 656 | time_step=np.diff(self.t)[0] 657 | 658 | k_t=np.linspace(0,k_length_seconds,int(k_length_seconds/time_step) ) 659 | ix_f=np.where((self.f>=f_lim[0]) & (self.f<=f_lim[1]))[0] 660 | k_f=self.f[ix_f[0]:ix_f[-1]] 661 | # k_f=np.linspace(f_lim[0],f_lim[1], int( (f_lim[1]-f_lim[0]) /f_step) ) 662 | 663 | kk_t,kk_f=np.meshgrid(k_t,k_f) 664 | kernel_background_db=0 665 | kernel_signal_db=1 666 | kernel=np.ones( [ k_f.shape[0] ,k_t.shape[0] ] ) * kernel_background_db 667 | # find wich grid points are inside the shape 668 | x, y = kk_t.flatten(), kk_f.flatten() 669 | points = np.vstack((x,y)).T 670 | # p = Path(list(zip(shape_t, shape_f))) # make a polygon 671 | # grid = p.contains_points(points) 672 | # mask = grid.reshape(kk_t.shape) # now you have a mask with points inside a polygon 673 | # kernel[mask]=kernel_signal_db 674 | for template in templates: 675 | shf= template['Frequency_in_Hz'].values 676 | st= template['Time_in_s'].values 677 | st=st-shape_t_raw.min() 678 | p = Path(list(zip(st, shf))) # make a polygon 679 | grid = p.contains_points(points) 680 | kern = grid.reshape(kk_t.shape) # now you have a mask with points inside a polygon 681 | kernel[kern>0]=kernel_signal_db 682 | 683 | # print(kernel) 684 | 685 | 686 | ix_f=np.where((self.f>=f_lim[0]) & (self.f<=f_lim[1]))[0] 687 | spectrog =10*np.log10( self.Sxx[ ix_f[0]:ix_f[-1],: ] ) 688 | 689 | result = match_template(spectrog, kernel) 690 | corr_score=result[0,:] 691 | t_score=np.linspace( self.t[int(kernel.shape[1]/2)] , self.t[-int(kernel.shape[1]/2)], corr_score.shape[0] ) 692 | 693 | peaks_indices = find_peaks(corr_score, height=corrscore_threshold)[0] 694 | 695 | 696 | t1=[] 697 | t2=[] 698 | f1=[] 699 | f2=[] 700 | score=[] 701 | 702 | if len(peaks_indices)>0: 703 | t2_old=0 704 | for ixpeak in peaks_indices: 705 | tstar=t_score[ixpeak] - k_length_seconds/2 - offset_t 706 | tend=t_score[ixpeak] + k_length_seconds/2 - offset_t 707 | # if tstar>t2_old: 708 | t1.append(tstar) 709 | t2.append(tend) 710 | f1.append(f_lim[0]+offset_f) 711 | f2.append(f_lim[1]-offset_f) 712 | score.append(corr_score[ixpeak]) 713 | # t2_old=tend 714 | df=pd.DataFrame() 715 | df['t-1']=t1 716 | df['t-2']=t2 717 | df['f-1']=f1 718 | df['f-2']=f2 719 | df['score']=score 720 | 721 | self.detectiondf = df.copy() 722 | self.detectiondf['audiofilename']= self.current_audiopath 723 | self.detectiondf['threshold']= corrscore_threshold 724 | 725 | self.plot_spectrogram() 726 | else: # image kernel 727 | 728 | template=templates[0] 729 | 730 | k_length_seconds= float(template.columns[-1]) -float(template.columns[0]) 731 | 732 | f_lim=[ int(template.index[0]) , int( template.index[-1] )] 733 | ix_f=np.where((self.f>=f_lim[0]) & (self.f<=f_lim[1]))[0] 734 | spectrog =10*np.log10( self.Sxx[ ix_f[0]:ix_f[-1],: ] ) 735 | specgram_t_step= self.t[1] - self.t[0] 736 | n_f=spectrog.shape[0] 737 | n_t= int(k_length_seconds/ specgram_t_step) 738 | 739 | kernel= resize( template.values , [ n_f,n_t] ) 740 | 741 | 742 | result = match_template(spectrog, kernel) 743 | corr_score=result[0,:] 744 | t_score=np.linspace( self.t[int(kernel.shape[1]/2)] , self.t[-int(kernel.shape[1]/2)], corr_score.shape[0] ) 745 | 746 | peaks_indices = find_peaks(corr_score, height=corrscore_threshold)[0] 747 | 748 | # print(corr_score) 749 | 750 | t1=[] 751 | t2=[] 752 | f1=[] 753 | f2=[] 754 | score=[] 755 | 756 | if len(peaks_indices)>0: 757 | t2_old=0 758 | for ixpeak in peaks_indices: 759 | tstar=t_score[ixpeak] - k_length_seconds/2 760 | tend=t_score[ixpeak] + k_length_seconds/2 761 | # if tstar>t2_old: 762 | t1.append(tstar) 763 | t2.append(tend) 764 | f1.append(f_lim[0]) 765 | f2.append(f_lim[1]) 766 | score.append(corr_score[ixpeak]) 767 | t2_old=tend 768 | df=pd.DataFrame() 769 | df['t-1']=t1 770 | df['t-2']=t2 771 | df['f-1']=f1 772 | df['f-2']=f2 773 | df['score']=score 774 | 775 | self.detectiondf = df.copy() 776 | self.detectiondf['audiofilename']= self.current_audiopath 777 | self.detectiondf['threshold']= corrscore_threshold 778 | 779 | print(self.detectiondf) 780 | 781 | print('done!!!') 782 | 783 | self.plot_spectrogram() 784 | 785 | def automatic_detector_specgram_corr_allfiles(self): 786 | msg = QtWidgets.QMessageBox() 787 | msg.setIcon(QtWidgets.QMessageBox.Information) 788 | msg.setText("Are you sure you want to run the detector over "+ str(self.file_blocks.shape[0]) +" ?") 789 | msg.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) 790 | returnValue = msg.exec() 791 | 792 | if returnValue == QtWidgets.QMessageBox.Yes: 793 | 794 | templatefiles, ok1 = QtWidgets.QFileDialog.getOpenFileNames(self,"QFileDialog.getOpenFileNames()", r"C:\Users","CSV file (*.csv)") 795 | if ok1: 796 | templates=[] 797 | for fnam in templatefiles: 798 | template=pd.read_csv(fnam,index_col=0) 799 | templates.append( template ) 800 | 801 | corrscore_threshold, ok = QtWidgets.QInputDialog.getDouble(self, 'Input Dialog', 802 | 'Enter correlation threshold in (0-1):',decimals=2) 803 | if corrscore_threshold>1: 804 | corrscore_threshold=1 805 | if corrscore_threshold<0: 806 | corrscore_threshold=0 807 | 808 | self.detectiondf_all=pd.DataFrame([]) 809 | 810 | for i_block in range(len(self.file_blocks)): 811 | 812 | audiopath=self.file_blocks.loc[i_block,'fname'] 813 | 814 | 815 | if self.filename_timekey.text()=='': 816 | self.time= dt.datetime(2000,1,1,0,0,0) 817 | else: 818 | try: 819 | self.time= dt.datetime.strptime( audiopath.split('/')[-1], self.filename_timekey.text() ) 820 | except: 821 | self.time= dt.datetime(2000,1,1,0,0,0) 822 | 823 | if self.file_blocks.loc[i_block,'start']>0: 824 | secoffset = self.file_blocks.loc[i_block,'start'] / self.fs 825 | self.time=self.time + pd.Timedelta(seconds=secoffset) 826 | 827 | 828 | self.x,self.fs = sf.read(audiopath,dtype='int16', start=self.file_blocks.loc[i_block,'start'] , stop=self.file_blocks.loc[i_block,'end']) 829 | print('open new file: '+audiopath) 830 | 831 | print('FS: '+str(self.fs) +' x: '+str(np.shape(self.x))) 832 | if len(self.x.shape)>1: 833 | if np.shape(self.x)[1]>1: 834 | self.x=self.x[:,0] 835 | 836 | 837 | db_saturation=float( self.db_saturation.text() ) 838 | x=self.x/32767 839 | p =np.power(10,(db_saturation/20))*x #convert data.signal to uPa 840 | 841 | fft_size=int( self.fft_size.currentText() ) 842 | fft_overlap=float( self.fft_overlap.currentText() ) 843 | # print(fft_size) 844 | # print(fft_overlap) 845 | 846 | self.f, self.t, self.Sxx = signal.spectrogram(p, self.fs, window='hamming',nperseg=fft_size,noverlap=int(fft_size*fft_overlap)) 847 | if self.file_blocks.loc[i_block,'start']>0: 848 | secoffset = self.file_blocks.loc[i_block,'start'] / self.fs 849 | self.t= self.t + secoffset 850 | 851 | # self.plotwindow_startsecond=0 852 | # self.plotwindow_length = self.t.max() 853 | 854 | if templates[0].columns[0]=='Time_in_s': 855 | 856 | # print(template) 857 | offset_f=10 858 | offset_t=0.5 859 | # shape_f=template['Frequency_in_Hz'].values 860 | # shape_t=template['Time_in_s'].values 861 | # shape_t=shape_t-shape_t.min() 862 | shape_f=np.array([]) 863 | shape_t_raw=np.array([]) 864 | for template in templates: 865 | shape_f=np.concatenate( [shape_f, template['Frequency_in_Hz'].values ] ) 866 | shape_t_raw=np.concatenate( [shape_t_raw, template['Time_in_s'].values ]) 867 | shape_t=shape_t_raw-shape_t_raw.min() 868 | 869 | f_lim=[ shape_f.min() - offset_f , shape_f.max() + offset_f ] 870 | k_length_seconds=shape_t.max()+offset_t*2 871 | 872 | # generate kernel 873 | time_step=np.diff(self.t)[0] 874 | 875 | k_t=np.linspace(0,k_length_seconds,int(k_length_seconds/time_step) ) 876 | ix_f=np.where((self.f>=f_lim[0]) & (self.f<=f_lim[1]))[0] 877 | k_f=self.f[ix_f[0]:ix_f[-1]] 878 | # k_f=np.linspace(f_lim[0],f_lim[1], int( (f_lim[1]-f_lim[0]) /f_step) ) 879 | 880 | kk_t,kk_f=np.meshgrid(k_t,k_f) 881 | kernel_background_db=0 882 | kernel_signal_db=1 883 | kernel=np.ones( [ k_f.shape[0] ,k_t.shape[0] ] ) * kernel_background_db 884 | # find wich grid points are inside the shape 885 | x, y = kk_t.flatten(), kk_f.flatten() 886 | points = np.vstack((x,y)).T 887 | # p = Path(list(zip(shape_t, shape_f))) # make a polygon 888 | # grid = p.contains_points(points) 889 | # mask = grid.reshape(kk_t.shape) # now you have a mask with points inside a polygon 890 | # kernel[mask]=kernel_signal_db 891 | for template in templates: 892 | shf= template['Frequency_in_Hz'].values 893 | st= template['Time_in_s'].values 894 | st=st-shape_t_raw.min() 895 | p = Path(list(zip(st, shf))) # make a polygon 896 | grid = p.contains_points(points) 897 | kern = grid.reshape(kk_t.shape) # now you have a mask with points inside a polygon 898 | kernel[kern>0]=kernel_signal_db 899 | 900 | ix_f=np.where((self.f>=f_lim[0]) & (self.f<=f_lim[1]))[0] 901 | spectrog =10*np.log10( self.Sxx[ ix_f[0]:ix_f[-1],: ] ) 902 | 903 | result = match_template(spectrog, kernel) 904 | corr_score=result[0,:] 905 | t_score=np.linspace( self.t[int(kernel.shape[1]/2)] , self.t[-int(kernel.shape[1]/2)], corr_score.shape[0] ) 906 | 907 | peaks_indices = find_peaks(corr_score, height=corrscore_threshold)[0] 908 | 909 | 910 | t1=[] 911 | t2=[] 912 | f1=[] 913 | f2=[] 914 | score=[] 915 | 916 | if len(peaks_indices)>0: 917 | t2_old=0 918 | for ixpeak in peaks_indices: 919 | tstar=t_score[ixpeak] - k_length_seconds/2 - offset_t 920 | tend=t_score[ixpeak] + k_length_seconds/2 - offset_t 921 | # if tstar>t2_old: 922 | t1.append(tstar) 923 | t2.append(tend) 924 | f1.append(f_lim[0]+offset_f) 925 | f2.append(f_lim[1]-offset_f) 926 | score.append(corr_score[ixpeak]) 927 | t2_old=tend 928 | df=pd.DataFrame() 929 | df['t-1']=t1 930 | df['t-2']=t2 931 | df['f-1']=f1 932 | df['f-2']=f2 933 | df['score']=score 934 | 935 | self.detectiondf = df.copy() 936 | self.detectiondf['audiofilename']= audiopath 937 | self.detectiondf['threshold']= corrscore_threshold 938 | else: # image kernel 939 | template=templates[0] 940 | 941 | k_length_seconds= float(template.columns[-1]) -float(template.columns[0]) 942 | 943 | f_lim=[ int(template.index[0]) , int( template.index[-1] )] 944 | ix_f=np.where((self.f>=f_lim[0]) & (self.f<=f_lim[1]))[0] 945 | spectrog =10*np.log10( self.Sxx[ ix_f[0]:ix_f[-1],: ] ) 946 | specgram_t_step= self.t[1] - self.t[0] 947 | n_f=spectrog.shape[0] 948 | n_t= int(k_length_seconds/ specgram_t_step) 949 | 950 | kernel= resize( template.values , [ n_f,n_t] ) 951 | 952 | 953 | result = match_template(spectrog, kernel) 954 | corr_score=result[0,:] 955 | t_score=np.linspace( self.t[int(kernel.shape[1]/2)] , self.t[-int(kernel.shape[1]/2)], corr_score.shape[0] ) 956 | 957 | peaks_indices = find_peaks(corr_score, height=corrscore_threshold)[0] 958 | 959 | # print(corr_score) 960 | 961 | t1=[] 962 | t2=[] 963 | f1=[] 964 | f2=[] 965 | score=[] 966 | 967 | if len(peaks_indices)>0: 968 | t2_old=0 969 | for ixpeak in peaks_indices: 970 | tstar=t_score[ixpeak] - k_length_seconds/2 971 | tend=t_score[ixpeak] + k_length_seconds/2 972 | # if tstar>t2_old: 973 | t1.append(tstar) 974 | t2.append(tend) 975 | f1.append(f_lim[0]) 976 | f2.append(f_lim[1]) 977 | score.append(corr_score[ixpeak]) 978 | t2_old=tend 979 | df=pd.DataFrame() 980 | df['t-1']=t1 981 | df['t-2']=t2 982 | df['f-1']=f1 983 | df['f-2']=f2 984 | df['score']=score 985 | 986 | self.detectiondf = df.copy() 987 | self.detectiondf['audiofilename']= audiopath 988 | self.detectiondf['threshold']= corrscore_threshold 989 | 990 | self.detectiondf_all=pd.concat([ self.detectiondf_all,self.detectiondf ]) 991 | self.detectiondf_all=self.detectiondf_all.reset_index(drop=True) 992 | 993 | print(self.detectiondf_all) 994 | 995 | 996 | self.detectiondf= self.detectiondf_all 997 | # self.detectiondf=self.detectiondf.reset_index(drop=True) 998 | self.read_wav() 999 | self.plot_spectrogram() 1000 | print('done!!!') 1001 | 1002 | 1003 | # def automatic_detector_shapematching(): 1004 | # # open template 1005 | # self.detectiondf=pd.DataFrame([]) 1006 | 1007 | # templatefile, ok1 = QtWidgets.QFileDialog.getOpenFileName(self,"QFileDialog.getOpenFileNames()", r"C:\Users","CSV file (*.csv)") 1008 | # if ok1: 1009 | # template=pd.read_csv(templatefile,index_col=0) 1010 | 1011 | # # print(template) 1012 | # # set db threshold 1013 | # db_threshold, ok = QtWidgets.QInputDialog.getInt(self, 'Input Dialog', 1014 | # 'Enter signal-to-noise threshold in dB:') 1015 | # if ok: 1016 | # print(db_threshold) 1017 | # self.detectiondf=pd.DataFrame([]) 1018 | 1019 | # find_regions(db_threshold) 1020 | # self.detectiondf=match_bbox_and_iou(template) 1021 | # ixdel=np.where(self.detectiondf['score']<0.01)[0] 1022 | # self.detectiondf=self.detectiondf.drop(ixdel) 1023 | # self.detectiondf=self.detectiondf.reset_index(drop=True) 1024 | # self.detectiondf['audiofilename']= self.current_audiopath 1025 | # self.detectiondf['threshold']= db_threshold 1026 | 1027 | # print(self.detectiondf) 1028 | 1029 | # # plot results 1030 | # plot_spectrogram() 1031 | 1032 | def automatic_detector_shapematching(self): 1033 | # open template 1034 | self.detectiondf=pd.DataFrame([]) 1035 | 1036 | templatefiles, ok1 = QtWidgets.QFileDialog.getOpenFileNames(self,"QFileDialog.getOpenFileNames()", r"C:\Users","CSV file (*.csv)") 1037 | if ok1: 1038 | 1039 | # print(template) 1040 | # set db threshold 1041 | db_threshold, ok = QtWidgets.QInputDialog.getInt(self, 'Input Dialog', 1042 | 'Enter signal-to-noise threshold in dB:') 1043 | if ok: 1044 | print(db_threshold) 1045 | self.detectiondf=pd.DataFrame([]) 1046 | 1047 | self.find_regions(db_threshold) 1048 | self.detectiondf['score']=np.zeros(len( self.detectiondf )) 1049 | 1050 | for fnam in templatefiles: 1051 | template=pd.read_csv(fnam,index_col=0) 1052 | score_new = self.match_bbox_and_iou(template) 1053 | ix_better = score_new > self.detectiondf['score'].values 1054 | # print(score_new) 1055 | # print( self.detectiondf['score'].values) 1056 | # print( 'better:' ) 1057 | # print( ix_better ) 1058 | 1059 | self.detectiondf.loc[ix_better,'score'] = score_new[ix_better] 1060 | # print(self.detectiondf['score']) 1061 | 1062 | ixdel=np.where(self.detectiondf['score']<0.01)[0] 1063 | self.detectiondf=self.detectiondf.drop(ixdel) 1064 | self.detectiondf=self.detectiondf.reset_index(drop=True) 1065 | self.detectiondf['audiofilename']= self.current_audiopath 1066 | self.detectiondf['threshold']= db_threshold 1067 | 1068 | print(self.detectiondf) 1069 | 1070 | # plot results 1071 | self.plot_spectrogram() 1072 | 1073 | def automatic_detector_shapematching_allfiles(self): 1074 | msg = QtWidgets.QMessageBox() 1075 | msg.setIcon(QtWidgets.QMessageBox.Information) 1076 | msg.setText("Are you sure you want to run the detector over "+ str(self.filenames.shape[0]) +" ?") 1077 | msg.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) 1078 | returnValue = msg.exec() 1079 | 1080 | if returnValue == QtWidgets.QMessageBox.Yes: 1081 | templatefiles, ok1 = QtWidgets.QFileDialog.getOpenFileNames(self,"QFileDialog.getOpenFileNames()", r"C:\Users","CSV file (*.csv)") 1082 | # template=pd.read_csv(templatefile) 1083 | db_threshold, ok = QtWidgets.QInputDialog.getInt(self, 'Input Dialog', 1084 | 'Enter signal-to-noise threshold in dB:') 1085 | 1086 | self.detectiondf_all=pd.DataFrame([]) 1087 | 1088 | for i_block in range(len(self.file_blocks)): 1089 | 1090 | audiopath=self.file_blocks.loc[i_block,'fname'] 1091 | 1092 | 1093 | if self.filename_timekey.text()=='': 1094 | self.time= dt.datetime(2000,1,1,0,0,0) 1095 | else: 1096 | try: 1097 | self.time= dt.datetime.strptime( audiopath.split('/')[-1], self.filename_timekey.text() ) 1098 | except: 1099 | self.time= dt.datetime(2000,1,1,0,0,0) 1100 | 1101 | self.x,self.fs = sf.read(audiopath,dtype='int16', start=self.file_blocks.loc[i_block,'start'] , stop=self.file_blocks.loc[i_block,'end']) 1102 | print('open new file: '+audiopath) 1103 | print('FS: '+str(self.fs) +' x: '+str(np.shape(self.x))) 1104 | if len(self.x.shape)>1: 1105 | if np.shape(self.x)[1]>1: 1106 | self.x=self.x[:,0] 1107 | 1108 | 1109 | db_saturation=float( self.db_saturation.text() ) 1110 | x=self.x/32767 1111 | p =np.power(10,(db_saturation/20))*x #convert data.signal to uPa 1112 | 1113 | fft_size=int( self.fft_size.currentText() ) 1114 | fft_overlap=float( self.fft_overlap.currentText() ) 1115 | 1116 | self.f, self.t, self.Sxx = signal.spectrogram(p, self.fs, window='hamming',nperseg=fft_size,noverlap=fft_size*fft_overlap) 1117 | if self.file_blocks.loc[i_block,'start']>0: 1118 | secoffset = self.file_blocks.loc[i_block,'start'] / self.fs 1119 | self.t= self.t + secoffset 1120 | 1121 | self.plotwindow_startsecond=0 1122 | self.plotwindow_length = self.t.max() 1123 | 1124 | self.detectiondf=pd.DataFrame([]) 1125 | 1126 | self.find_regions(db_threshold) 1127 | # match_bbox_and_iou(template) 1128 | self.detectiondf['score']=np.zeros(len( self.detectiondf )) 1129 | for fnam in templatefiles: 1130 | template=pd.read_csv(fnam,index_col=0) 1131 | score_new = self.match_bbox_and_iou(template) 1132 | ix_better = score_new > self.detectiondf['score'].values 1133 | self.detectiondf.loc[ix_better,'score'] = score_new[ix_better] 1134 | 1135 | ixdel=np.where(self.detectiondf['score']<0.01)[0] 1136 | self.detectiondf=self.detectiondf.drop(ixdel) 1137 | self.detectiondf=self.detectiondf.reset_index(drop=True) 1138 | self.detectiondf['audiofilename']= audiopath 1139 | self.detectiondf['threshold']= db_threshold 1140 | 1141 | self.detectiondf_all=pd.concat([ self.detectiondf_all,self.detectiondf ]) 1142 | self.detectiondf_all=self.detectiondf_all.reset_index(drop=True) 1143 | 1144 | print(self.detectiondf_all) 1145 | 1146 | 1147 | self.detectiondf= self.detectiondf_all 1148 | # self.detectiondf=self.detectiondf.reset_index(drop=True) 1149 | print('done!!!') 1150 | 1151 | def export_automatic_detector(self): 1152 | if self.detectiondf.shape[0]>0: 1153 | savename = QtWidgets.QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()", r"C:\Users", "csv files (*.csv)") 1154 | print('location is:' + savename[0]) 1155 | if len(savename[0])>0: 1156 | self.detectiondf.to_csv(savename[0]) 1157 | 1158 | def openfilefunc(self): 1159 | fname_canidates, ok = QtWidgets.QFileDialog.getOpenFileNames(self,"QFileDialog.getOpenFileNames()",'',"Audio Files (*.wav *.aif *.aiff *.aifc *.ogg *.flac)") 1160 | 1161 | # print( fname_canidates) 1162 | 1163 | if len( fname_canidates ) >0: 1164 | 1165 | self.filenames = np.array( fname_canidates ) 1166 | 1167 | self.filecounter=-1 1168 | self.plotwindow_startsecond= float( self.t_length.text() ) 1169 | 1170 | self.annotation= pd.DataFrame({'t1': pd.Series(dtype='datetime64[ns]'), 1171 | 't2': pd.Series(dtype='datetime64[ns]'), 1172 | 'f1': pd.Series(dtype='float'), 1173 | 'f2': pd.Series(dtype='float'), 1174 | 'label': pd.Series(dtype='object'), 1175 | 'audiofilename': pd.Series(dtype='object')}) 1176 | self.detectiondf=pd.DataFrame([]) 1177 | 1178 | # self.annotation=pd.DataFrame(columns=['t1','t2','f1','f2','label'],dtype=[("t1", "datetime64[ns]"), ("t2", "datetime64[ns]"), ("f1", "float"), ("f2", "float"), ("label", "object")] ) 1179 | 1180 | # annotation=pd.DataFrame(dtype=[("t1", "datetime64[ns]"), ("t2", "datetime64[ns]"), ("f1", "float"), ("f2", "float"), ("label", "object")] ) 1181 | 1182 | # ,dtype=('datetime64[ns]','datetime64[ns]','float','float','object')) 1183 | # self.call_t_1=pd.Series(dtype='datetime64[ns]') 1184 | # self.call_f_1=pd.Series(dtype='float') 1185 | # self.call_t_2=pd.Series(dtype='datetime64[ns]') 1186 | # self.call_f_2=pd.Series(dtype='float') 1187 | # self.call_label=pd.Series(dtype='object') 1188 | 1189 | # options = QtWidgets.QFileDialog.Options() 1190 | # options |= QtWidgets.QFileDialog.DontUseNativeDialog 1191 | 1192 | 1193 | if len(self.filenames)>0: 1194 | fid_names=[] 1195 | fid_start=[] 1196 | fid_end=[] 1197 | 1198 | max_element_length_sec=60*10 1199 | 1200 | for fname in self.filenames: 1201 | a = sf.info( fname ) 1202 | 1203 | if a.duration < max_element_length_sec: 1204 | fid_names.append( fname ) 1205 | fid_start.append( 0 ) 1206 | fid_end.append(a.frames ) 1207 | else: 1208 | 1209 | s=0 1210 | while s < a.frames: 1211 | fid_names.append( fname ) 1212 | fid_start.append( s ) 1213 | e = s + max_element_length_sec * a.samplerate 1214 | fid_end.append( e ) 1215 | s=s+max_element_length_sec * a.samplerate 1216 | 1217 | self.file_blocks = pd.DataFrame([]) 1218 | self.file_blocks['fname']=fid_names 1219 | self.file_blocks['start']=fid_start 1220 | self.file_blocks['end']=fid_end 1221 | 1222 | print( self.file_blocks) 1223 | self.plotwindow_startsecond=0 1224 | 1225 | self.plot_next_spectro() 1226 | 1227 | # openfilebutton.clicked.connect(openfilefunc) 1228 | 1229 | 1230 | def read_wav(self): 1231 | if self.filecounter>=0: 1232 | self.current_audiopath=self.file_blocks.loc[self.filecounter,'fname'] 1233 | 1234 | # if self.filename_timekey.text()=='': 1235 | # self.time= dt.datetime(1,1,1,0,0,0) 1236 | # else: 1237 | # self.time= dt.datetime.strptime( audiopath.split('/')[-1], self.filename_timekey.text() ) 1238 | 1239 | 1240 | 1241 | # if audiopath[-4:]=='.wav': 1242 | 1243 | 1244 | self.x,self.fs = sf.read(self.current_audiopath, start=self.file_blocks.loc[self.filecounter,'start'] , stop=self.file_blocks.loc[self.filecounter,'end'], dtype='int16') 1245 | 1246 | if self.filename_timekey.text()=='': 1247 | self.time= dt.datetime(2000,1,1,0,0,0) 1248 | #self.time= dt.datetime.now() 1249 | else: 1250 | try: 1251 | self.time= dt.datetime.strptime( self.current_audiopath.split('/')[-1], self.filename_timekey.text() ) 1252 | except: 1253 | print('wrongfilename') 1254 | 1255 | if self.file_blocks.loc[self.filecounter,'start']>0: 1256 | secoffset = self.file_blocks.loc[self.filecounter,'start'] / self.fs 1257 | self.time=self.time + pd.Timedelta(seconds=secoffset) 1258 | 1259 | # if audiopath[-4:]=='.aif' | audiopath[-4:]=='.aiff' | audiopath[-4:]=='.aifc': 1260 | # obj = aifc.open(audiopath,'r') 1261 | # self.fs, self.x = wav.read(audiopath) 1262 | print('open new file: '+self.current_audiopath) 1263 | print('FS: '+str(self.fs) +' x: '+str(np.shape(self.x))) 1264 | if len(self.x.shape)>1: 1265 | if np.shape(self.x)[1]>1: 1266 | self.x=self.x[:,0] 1267 | 1268 | # factor=60 1269 | # x=signal.decimate(x,factor,ftype='fir') 1270 | 1271 | db_saturation=float( self.db_saturation.text() ) 1272 | x=self.x/32767 1273 | p =np.power(10,(db_saturation/20))*x #convert data.signal to uPa 1274 | 1275 | fft_size=int( self.fft_size.currentText() ) 1276 | fft_overlap=float( self.fft_overlap.currentText() ) 1277 | self.f, self.t, self.Sxx = signal.spectrogram(p, self.fs, window='hamming',nperseg=fft_size,noverlap=int(fft_size*fft_overlap)) 1278 | # self.t=self.time + pd.to_timedelta( t , unit='s') 1279 | if self.file_blocks.loc[self.filecounter,'start']>0: 1280 | secoffset = self.file_blocks.loc[self.filecounter,'start'] / self.fs 1281 | self.t= self.t + secoffset 1282 | # print(self.t) 1283 | 1284 | def plot_annotation_box(self,annotation_row): 1285 | print('row:') 1286 | # print(annotation_row.dtypes) 1287 | x1=annotation_row.iloc[0,0] 1288 | x2=annotation_row.iloc[0,1] 1289 | 1290 | xt=pd.Series([x1,x2]) 1291 | print(xt) 1292 | print(xt.dtype) 1293 | 1294 | # print(np.dtype(np.array(self.time).astype('datetime64[ns]') )) 1295 | tt=xt - np.array(self.time).astype('datetime64[ns]') 1296 | xt=tt.dt.seconds + tt.dt.microseconds/10**6 1297 | x1=xt[0] 1298 | x2=xt[1] 1299 | 1300 | # tt=x1 - np.array(self.time).astype('datetime64[ns]') 1301 | # x1=tt.dt.seconds + tt.dt.microseconds/10**6 1302 | # tt=x2 - np.array(self.time).astype('datetime64[ns]') 1303 | # x2=tt.dt.seconds + tt.dt.microseconds/10**6 1304 | 1305 | y1=annotation_row.iloc[0,2] 1306 | y2=annotation_row.iloc[0,3] 1307 | c_label=annotation_row.iloc[0,4] 1308 | 1309 | line_x=[x2,x1,x1,x2,x2] 1310 | line_y=[y1,y1,y2,y2,y1] 1311 | 1312 | xmin=np.min([x1,x2]) 1313 | ymax=np.max([y1,y2]) 1314 | 1315 | self.canvas.axes.plot(line_x,line_y,'-b',linewidth=.75) 1316 | self.canvas.axes.text(xmin,ymax,c_label,size=8) 1317 | 1318 | 1319 | 1320 | def plot_spectrogram(self): 1321 | if self.filecounter>=0: 1322 | # self.canvas = MplCanvas(self, width=5, height=4, dpi=100) 1323 | # self.setCentralWidget(self.canvas) 1324 | self.canvas.fig.clf() 1325 | self.canvas.axes = self.canvas.fig.add_subplot(111) 1326 | # self.canvas.axes.cla() 1327 | 1328 | if self.t_length.text()=='': 1329 | self.plotwindow_length= self.t[-1] 1330 | self.plotwindow_startsecond=self.t[0] 1331 | else: 1332 | self.plotwindow_length=float( self.t_length.text() ) 1333 | if self.t[-1](self.fs/2): 1340 | y2=(self.fs/2) 1341 | t1=self.plotwindow_startsecond 1342 | t2=self.plotwindow_startsecond+self.plotwindow_length 1343 | 1344 | # if self.t_length.text=='': 1345 | # t2=self.t[-1] 1346 | # else: 1347 | # if self.t[-1]=self.plotwindow_startsecond) & (tt<(self.plotwindow_startsecond+self.plotwindow_length)) 1354 | # ix_f=(ff>=y1) & (ff=t1) & (self.t=y1) & (self.f0: 1409 | ix=(self.annotation['t1'] > (np.array(self.time).astype('datetime64[ns]')+pd.Timedelta(self.plotwindow_startsecond, unit="s") ) ) & \ 1410 | (self.annotation['t1'] < (np.array(self.time).astype('datetime64[ns]')+pd.Timedelta(self.plotwindow_startsecond+self.plotwindow_length, unit="s") ) ) &\ 1411 | (self.annotation['audiofilename'] == self.current_audiopath ) 1412 | if np.sum(ix)>0: 1413 | ix=np.where(ix)[0] 1414 | print('ix is') 1415 | print(ix) 1416 | for ix_x in ix: 1417 | a= pd.DataFrame([self.annotation.iloc[ix_x,:] ]) 1418 | print(a) 1419 | self.plot_annotation_box(a) 1420 | 1421 | # plot detections 1422 | cmap = plt.cm.get_cmap('cool') 1423 | if self.detectiondf.shape[0]>0: 1424 | for i in range(self.detectiondf.shape[0]): 1425 | 1426 | insidewindow=(self.detectiondf.loc[i,'t-1'] > self.plotwindow_startsecond ) & (self.detectiondf.loc[i,'t-2'] < (self.plotwindow_startsecond+self.plotwindow_length) ) &\ 1427 | (self.detectiondf.loc[i,'audiofilename'] == self.current_audiopath ) 1428 | 1429 | scoremin=self.detectiondf['score'].min() 1430 | scoremax=self.detectiondf['score'].max() 1431 | 1432 | if (self.detectiondf.loc[i,'score']>=0.01) & insidewindow: 1433 | 1434 | xx1=self.detectiondf.loc[i,'t-1'] 1435 | xx2=self.detectiondf.loc[i,'t-2'] 1436 | yy1=self.detectiondf.loc[i,'f-1'] 1437 | yy2=self.detectiondf.loc[i,'f-2'] 1438 | scorelabel=str(np.round(self.detectiondf.loc[i,'score'],2)) 1439 | snorm=(self.detectiondf.loc[i,'score']-scoremin) / (scoremax-scoremin) 1440 | scorecolor = cmap(snorm) 1441 | 1442 | line_x=[xx2,xx1,xx1,xx2,xx2] 1443 | line_y=[yy1,yy1,yy2,yy2,yy1] 1444 | 1445 | xmin=np.min([xx1,xx2]) 1446 | ymax=np.max([yy1,yy2]) 1447 | self.canvas.axes.plot(line_x,line_y,'-',color=scorecolor,linewidth=.75) 1448 | self.canvas.axes.text(xmin,ymax,scorelabel,size=8,color=scorecolor) 1449 | 1450 | 1451 | 1452 | self.canvas.axes.set_ylim([y1,y2]) 1453 | self.canvas.axes.set_xlim([t1,t2]) 1454 | 1455 | 1456 | self.canvas.fig.tight_layout() 1457 | self.toggle_selector=RectangleSelector(self.canvas.axes, self.box_select_callback, 1458 | drawtype='box', useblit=False, 1459 | button=[1], # disable middle button 1460 | interactive=False,rectprops=dict(facecolor="blue", edgecolor="black", alpha=0.1, fill=True)) 1461 | 1462 | 1463 | self.canvas.draw() 1464 | self.cid1=self.canvas.fig.canvas.mpl_connect('button_press_event', self.onclick) 1465 | 1466 | 1467 | def plot_spectrogram_threshold(self): 1468 | if self.filecounter>=0: 1469 | 1470 | db_threshold, ok = QtWidgets.QInputDialog.getInt(self, 'Input Dialog', 1471 | 'Enter signal-to-noise threshold in dB:') 1472 | self.find_regions(db_threshold) 1473 | self.detectiondf=pd.DataFrame([]) 1474 | 1475 | # plot_spectrogram() 1476 | # self.canvas.axes2 = self.canvas.axes.twiny() 1477 | 1478 | # self.canvas.axes2.contour( self.region_labels>0 , [0.5] , color='g') 1479 | 1480 | self.canvas.fig.clf() 1481 | self.canvas.axes = self.canvas.fig.add_subplot(111) 1482 | 1483 | 1484 | self.canvas.axes.set_ylabel('Frequency [Hz]') 1485 | # self.canvas.axes.set_xlabel('Time [sec]') 1486 | if self.checkbox_logscale.isChecked(): 1487 | self.canvas.axes.set_yscale('log') 1488 | else: 1489 | self.canvas.axes.set_yscale('linear') 1490 | 1491 | 1492 | 1493 | img=self.canvas.axes.imshow( self.region_labels>0 , aspect='auto',cmap='gist_yarg',origin = 'lower') 1494 | 1495 | self.canvas.fig.colorbar(img) 1496 | 1497 | self.canvas.fig.tight_layout() 1498 | self.canvas.draw() 1499 | 1500 | 1501 | def export_zoomed_sgram_as_csv(self): 1502 | if self.filecounter>=0: 1503 | 1504 | 1505 | # filter out background 1506 | spectrog = 10*np.log10(self.Sxx ) 1507 | 1508 | msg = QtWidgets.QMessageBox() 1509 | msg.setIcon(QtWidgets.QMessageBox.Information) 1510 | msg.setText("Remove background?") 1511 | msg.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) 1512 | returnValue = msg.exec() 1513 | 1514 | if returnValue == QtWidgets.QMessageBox.Yes: 1515 | rectime= pd.to_timedelta( self.t ,'s') 1516 | spg=pd.DataFrame(np.transpose(spectrog),index=rectime) 1517 | bg=spg.resample('3min').mean().copy() 1518 | bg=bg.resample('1s').interpolate(method='time') 1519 | bg= bg.reindex(rectime,method='nearest') 1520 | background=np.transpose(bg.values) 1521 | z=spectrog-background 1522 | else: 1523 | z=spectrog 1524 | 1525 | self.f_limits=self.canvas.axes.get_ylim() 1526 | self.t_limits=self.canvas.axes.get_xlim() 1527 | y1=int(self.f_limits[0]) 1528 | y2=int(self.f_limits[1]) 1529 | t1=self.t_limits[0] 1530 | t2=self.t_limits[1] 1531 | 1532 | 1533 | ix_time=np.where( (self.t>=t1) & (self.t=y1) & (self.f0: 1544 | if savename[-4:]!='.csv': 1545 | savename=savename[0]+'.csv' 1546 | sgram.to_csv(savename) 1547 | 1548 | 1549 | 1550 | 1551 | 1552 | def box_select_callback(self,eclick, erelease): 1553 | 1554 | x1, y1 = eclick.xdata, eclick.ydata 1555 | x2, y2 = erelease.xdata, erelease.ydata 1556 | 1557 | x1 =self.time + pd.to_timedelta( x1 , unit='s') 1558 | x2 =self.time + pd.to_timedelta( x2 , unit='s') 1559 | 1560 | # sort to increasing values 1561 | t1=np.min([x1,x2]) 1562 | t2=np.max([x1,x2]) 1563 | f1=np.min([y1,y2]) 1564 | f2=np.max([y1,y2]) 1565 | 1566 | if self.bg.checkedId()==-1: 1567 | c_label='' 1568 | else: 1569 | c_label=eval( 'self.an_'+str(self.bg.checkedId())+'.text()' ) 1570 | 1571 | # a=pd.DataFrame(columns=['t1','t2','f1','f2','label']) 1572 | # a.iloc[0,:]=np.array([x1,x2,y1,y2,c_label ]) 1573 | a=pd.DataFrame({'t1': pd.Series(t1,dtype='datetime64[ns]'), 1574 | 't2': pd.Series(t2,dtype='datetime64[ns]'), 1575 | 'f1': pd.Series(f1,dtype='float'), 1576 | 'f2': pd.Series(f2,dtype='float'), 1577 | 'label': pd.Series(c_label,dtype='object') , 1578 | 'audiofilename': self.current_audiopath }) 1579 | 1580 | # a=pd.DataFrame(data=[ [x1,x2,y1,y2,c_label ] ],columns=['t1','t2','f1','f2','label']) 1581 | # print('a:') 1582 | # print(a.dtypes) 1583 | # self.annotation.append(a, ignore_index = True) 1584 | self.annotation=pd.concat([ self.annotation ,a ] , ignore_index = True) 1585 | 1586 | # print(self.annotation.dtypes) 1587 | self.plot_annotation_box(a) 1588 | 1589 | def toggle_selector(self,event): 1590 | # toggle_selector.RS.set_active(True) 1591 | print('select') 1592 | # if event.key == 't': 1593 | # if toggle_selector.RS.active: 1594 | # print(' RectangleSelector deactivated.') 1595 | # toggle_selector.RS.set_active(False) 1596 | # else: 1597 | # print(' RectangleSelector activated.') 1598 | # toggle_selector.RS.set_active(True) 1599 | 1600 | 1601 | 1602 | def onclick(self,event): 1603 | if event.button==3: 1604 | self.annotation=self.annotation.head(-1) 1605 | # print(self.annotation) 1606 | self.plot_spectrogram() 1607 | 1608 | def end_of_filelist_warning(self): 1609 | msg_listend = QtWidgets.QMessageBox() 1610 | msg_listend.setIcon(QtWidgets.QMessageBox.Information) 1611 | msg_listend.setText("End of file list reached!") 1612 | msg_listend.exec_() 1613 | 1614 | def plot_next_spectro(self): 1615 | if len(self.filenames)>0: 1616 | print('old filecounter is: '+str(self.filecounter)) 1617 | 1618 | if self.t_length.text()=='' or ((self.filecounter>=0) & (self.t[-1]self.file_blocks.shape[0]-1: 1621 | self.filecounter=self.file_blocks.shape[0]-1 1622 | print('That was it') 1623 | self.end_of_filelist_warning() 1624 | self.plotwindow_length= self.t[-1] 1625 | self.plotwindow_startsecond=self.t[0] 1626 | # new file 1627 | # self.filecounter=self.filecounter+1 1628 | self.read_wav() 1629 | self.plot_spectrogram() 1630 | 1631 | # print('hello!!!!!') 1632 | 1633 | else: 1634 | self.plotwindow_length=float( self.t_length.text() ) 1635 | self.plotwindow_startsecond=self.plotwindow_startsecond + self.plotwindow_length 1636 | 1637 | print( [self.plotwindow_startsecond, self.t[0], self.t[-1] ] ) 1638 | 1639 | if self.plotwindow_startsecond > self.t[-1]: 1640 | #save log 1641 | if self.checkbox_log.isChecked(): 1642 | 1643 | tt= self.annotation['t1'] - self.time 1644 | t_in_seconds=np.array( tt.values*1e-9 ,dtype='float16') 1645 | reclength=np.array( self.t[-1] ,dtype='float16') 1646 | 1647 | ix=(t_in_seconds>0) & (t_in_seconds=self.file_blocks.shape[0]-1: 1658 | self.filecounter=self.file_blocks.shape[0]-1 1659 | print('That was it') 1660 | self.end_of_filelist_warning() 1661 | self.read_wav() 1662 | self.plotwindow_startsecond=self.t[0] 1663 | self.plot_spectrogram() 1664 | else: 1665 | self.plot_spectrogram() 1666 | 1667 | 1668 | 1669 | def plot_previous_spectro(self): 1670 | if len(self.filenames)>0: 1671 | print('old filecounter is: '+str(self.filecounter)) 1672 | 1673 | if self.t_length.text()=='' or ((self.filecounter>=0) & (self.t[-1]=self.filenames.shape[0]-1: 1708 | # print('That was it') 1709 | # self.canvas.fig.clf() 1710 | # self.canvas.axes = self.canvas.fig.add_subplot(111) 1711 | # self.canvas.axes.set_title('That was it') 1712 | # self.canvas.draw() 1713 | 1714 | self.read_wav() 1715 | # self.plotwindow_startsecond=0 1716 | self.plot_spectrogram() 1717 | else: 1718 | self.plot_spectrogram() 1719 | 1720 | 1721 | 1722 | def new_fft_size_selected(self): 1723 | self.read_wav() 1724 | self.plot_spectrogram() 1725 | 1726 | # self.fft_size.currentIndexChanged.connect(self.new_fft_size_selected) 1727 | # self.colormap_plot.currentIndexChanged.connect( self.plot_spectrogram) 1728 | # self.checkbox_background.stateChanged.connect(self.plot_spectrogram ) 1729 | # self.checkbox_logscale.stateChanged.connect(self.plot_spectrogram ) 1730 | 1731 | 1732 | # self.canvas.fig.canvas.mpl_connect('button_press_event', onclick) 1733 | # self.canvas.fig.canvas.mpl_connect('key_press_event', toggle_selector) 1734 | 1735 | 1736 | 1737 | # QtGui.QShortcut(QtCore.Qt.Key_Right, MainWindow, plot_next_spectro()) 1738 | # self.msgSc = QShortcut(QKeySequence(u"\u2192"), self) 1739 | # self.msgSc.activated.connect(plot_next_spectro) 1740 | # button_plot_spectro=QtWidgets.QPushButton('Next spectrogram-->') 1741 | # button_plot_spectro.clicked.connect(plot_next_spectro) 1742 | 1743 | # button_plot_prevspectro=QtWidgets.QPushButton('<--Previous spectrogram') 1744 | # button_plot_prevspectro.clicked.connect(plot_previous_spectro) 1745 | 1746 | def func_savecsv(self): 1747 | options = QtWidgets.QFileDialog.Options() 1748 | savename = QtWidgets.QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()", r"C:\Users\a5278\Documents\passive_acoustics\detector_delevopment\detector_validation_subset", "csv files (*.csv)",options=options) 1749 | print('location is:' + savename[0]) 1750 | if len(savename[0])>0: 1751 | self.annotation.to_csv(savename[0]) 1752 | 1753 | 1754 | def func_logging(self): 1755 | if self.checkbox_log.isChecked(): 1756 | print('logging') 1757 | msg = QtWidgets.QMessageBox() 1758 | msg.setIcon(QtWidgets.QMessageBox.Information) 1759 | msg.setText("Overwrite existing log files?") 1760 | msg.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) 1761 | returnValue = msg.exec() 1762 | if returnValue == QtWidgets.QMessageBox.No: 1763 | 1764 | ix_delete=[] 1765 | i=0 1766 | for fn in self.filenames: 1767 | logpath=fn[:-4]+'_log.csv' 1768 | # print(logpath) 1769 | if os.path.isfile( logpath): 1770 | ix_delete.append(i) 1771 | i=i+1 1772 | # print(ix_delete) 1773 | 1774 | self.filenames=np.delete(self.filenames,ix_delete) 1775 | print('Updated filelist:') 1776 | print(self.filenames) 1777 | 1778 | 1779 | 1780 | # checkbox_log=QtWidgets.QCheckBox('Real-time Logging') 1781 | # checkbox_log.toggled.connect(func_logging) 1782 | 1783 | # button_plot_all_spectrograms=QtWidgets.QPushButton('Plot all spectrograms') 1784 | def plot_all_spectrograms(self): 1785 | 1786 | msg = QtWidgets.QMessageBox() 1787 | msg.setIcon(QtWidgets.QMessageBox.Information) 1788 | msg.setText("Are you sure you want to plot "+ str(self.filenames.shape[0]) +" spectrograms?") 1789 | msg.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) 1790 | returnValue = msg.exec() 1791 | 1792 | if returnValue == QtWidgets.QMessageBox.Yes: 1793 | for audiopath in self.filenames: 1794 | 1795 | if self.filename_timekey.text()=='': 1796 | self.time= dt.datetime(2000,1,1,0,0,0) 1797 | else: 1798 | try: 1799 | self.time= dt.datetime.strptime( audiopath.split('/')[-1], self.filename_timekey.text() ) 1800 | except: 1801 | self.time= dt.datetime(2000,1,1,0,0,0) 1802 | 1803 | self.x,self.fs = sf.read(audiopath,dtype='int16') 1804 | print('open new file: '+audiopath) 1805 | 1806 | db_saturation=float( self.db_saturation.text() ) 1807 | x=self.x/32767 1808 | p =np.power(10,(db_saturation/20))*x #convert data.signal to uPa 1809 | 1810 | fft_size=int( self.fft_size.currentText() ) 1811 | fft_overlap=float( self.fft_overlap.currentText() ) 1812 | 1813 | 1814 | self.f, self.t, self.Sxx = signal.spectrogram(p, self.fs, window='hamming',nperseg=fft_size,noverlap=fft_size*fft_overlap) 1815 | 1816 | self.plotwindow_startsecond=0 1817 | 1818 | self.plot_spectrogram() 1819 | self.canvas.axes.set_title(audiopath.split('/')[-1]) 1820 | self.canvas.fig.savefig( audiopath[:-4]+'.jpg',dpi=150 ) 1821 | 1822 | 1823 | # button_plot_all_spectrograms.clicked.connect(plot_all_spectrograms) 1824 | 1825 | # button_draw_shape=QtWidgets.QPushButton('Draw shape') 1826 | def func_draw_shape_plot(self): 1827 | if self.filecounter>=0: 1828 | self.canvas.fig.clf() 1829 | self.canvas.axes = self.canvas.fig.add_subplot(111) 1830 | if self.t_length.text()=='': 1831 | self.plotwindow_length= self.t[-1] 1832 | self.plotwindow_startsecond=0 1833 | else: 1834 | self.plotwindow_length=float( self.t_length.text() ) 1835 | if self.t[-1](self.fs/2): 1842 | y2=(self.fs/2) 1843 | t1=self.plotwindow_startsecond 1844 | t2=self.plotwindow_startsecond+self.plotwindow_length 1845 | 1846 | ix_time=np.where( (self.t>=t1) & (self.t=y1) & (self.f0: 1885 | ix=(self.annotation['t1'] > (np.array(self.time).astype('datetime64[ns]')+pd.Timedelta(self.plotwindow_startsecond, unit="s") ) ) & (self.annotation['t1'] < (np.array(self.time).astype('datetime64[ns]')+pd.Timedelta(self.plotwindow_startsecond+self.plotwindow_length, unit="s") ) ) 1886 | if np.sum(ix)>0: 1887 | ix=np.where(ix)[0] 1888 | print('ix is') 1889 | print(ix) 1890 | for ix_x in ix: 1891 | a= pd.DataFrame([self.annotation.iloc[ix_x,:] ]) 1892 | print(a) 1893 | self.plot_annotation_box(a) 1894 | 1895 | if self.t_limits==None: 1896 | self.canvas.axes.set_ylim([y1,y2]) 1897 | self.canvas.axes.set_xlim([t1,t2]) 1898 | else: 1899 | self.canvas.axes.set_ylim(self.f_limits) 1900 | self.canvas.axes.set_xlim(self.t_limits) 1901 | 1902 | 1903 | 1904 | self.canvas.fig.tight_layout() 1905 | 1906 | self.canvas.axes.plot(self.draw_x,self.draw_y,'.-g') 1907 | 1908 | self.canvas.draw() 1909 | self.cid2=self.canvas.fig.canvas.mpl_connect('button_press_event', self.onclick_draw) 1910 | 1911 | def onclick_draw(self,event): 1912 | # print('%s click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % 1913 | # ('double' if event.dblclick else 'single', event.button, 1914 | # event.x, event.y, event.xdata, event.ydata)) 1915 | if event.button==1 & event.dblclick: 1916 | self.draw_x=self.draw_x.append( pd.Series(event.xdata) ,ignore_index=True ) 1917 | self.draw_y=self.draw_y.append( pd.Series(event.ydata) ,ignore_index=True ) 1918 | self.f_limits=self.canvas.axes.get_ylim() 1919 | self.t_limits=self.canvas.axes.get_xlim() 1920 | 1921 | line = self.line_2.pop(0) 1922 | line.remove() 1923 | self.line_2 =self.canvas.axes.plot(self.draw_x,self.draw_y,'.-g') 1924 | self.canvas.draw() 1925 | 1926 | # func_draw_shape_plot() 1927 | 1928 | if event.button==3: 1929 | self.draw_x=self.draw_x.head(-1) 1930 | self.draw_y=self.draw_y.head(-1) 1931 | self.f_limits=self.canvas.axes.get_ylim() 1932 | self.t_limits=self.canvas.axes.get_xlim() 1933 | # func_draw_shape_plot() 1934 | line = self.line_2.pop(0) 1935 | line.remove() 1936 | self.line_2 =self.canvas.axes.plot(self.draw_x,self.draw_y,'.-g') 1937 | self.canvas.draw() 1938 | 1939 | # func_draw_shape_plot() 1940 | 1941 | def func_draw_shape_exit(self): 1942 | print('save shape' + str(self.draw_x.shape)) 1943 | self.canvas.fig.canvas.mpl_disconnect(self.cid2) 1944 | self.plot_spectrogram() 1945 | print('back to boxes') 1946 | ## deactive shortcut 1947 | self.drawexitm.setEnabled(False) 1948 | 1949 | if self.draw_x.shape[0]>0: 1950 | options = QtWidgets.QFileDialog.Options() 1951 | savename = QtWidgets.QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()", "csv files (*.csv)",options=options) 1952 | if len(savename[0])>0: 1953 | if savename[-4:]!='.csv': 1954 | savename=savename[0]+'.csv' 1955 | # drawcsv=pd.concat([self.draw_x,self.draw_y],axis=1) 1956 | drawcsv=pd.DataFrame(columns=['Time_in_s','Frequency_in_Hz']) 1957 | drawcsv['Time_in_s']=self.draw_x 1958 | drawcsv['Frequency_in_Hz']=self.draw_y 1959 | drawcsv.to_csv(savename) 1960 | 1961 | 1962 | def func_draw_shape(self): 1963 | msg = QtWidgets.QMessageBox() 1964 | msg.setIcon(QtWidgets.QMessageBox.Information) 1965 | msg.setText("Add points with double left click.\nRemove latest point with single right click. \nExit draw mode and save CSV by pushing enter") 1966 | msg.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) 1967 | returnValue = msg.exec() 1968 | if returnValue == QtWidgets.QMessageBox.Ok: 1969 | print('drawing') 1970 | self.draw_x=pd.Series(dtype='float') 1971 | self.draw_y=pd.Series(dtype='float') 1972 | self.f_limits=self.canvas.axes.get_ylim() 1973 | self.t_limits=self.canvas.axes.get_xlim() 1974 | self.canvas.fig.canvas.mpl_disconnect(self.cid1) 1975 | self.cid2=self.canvas.fig.canvas.mpl_connect('button_press_event', self.onclick_draw) 1976 | self.line_2 =self.canvas.axes.plot(self.draw_x,self.draw_y,'.-g') 1977 | self.func_draw_shape_plot() 1978 | self.drawexitm = QtWidgets.QShortcut(QtCore.Qt.Key_Return, self) 1979 | self.drawexitm.activated.connect(self.func_draw_shape_exit) 1980 | 1981 | # button_draw_shape.clicked.connect(func_draw_shape) 1982 | 1983 | # ####### play audio 1984 | # button_play_audio=QtWidgets.QPushButton('Play/Stop [spacebar]') 1985 | def func_playaudio(self): 1986 | if self.filecounter>=0: 1987 | if not hasattr(self, "play_obj"): 1988 | new_rate = 32000 1989 | 1990 | t_limits=list(self.canvas.axes.get_xlim()) 1991 | t_limits=t_limits - self.file_blocks.loc[self.filecounter,'start']/self.fs 1992 | f_limits=list(self.canvas.axes.get_ylim()) 1993 | if f_limits[1]>=(self.fs/2): 1994 | f_limits[1]= self.fs/2-10 1995 | print(t_limits) 1996 | print(f_limits) 1997 | 1998 | x_select=self.x[int(t_limits[0]*self.fs) : int(t_limits[1]*self.fs) ] 1999 | 2000 | sos=signal.butter(8, f_limits, 'bandpass', fs=self.fs, output='sos') 2001 | x_select = signal.sosfilt(sos, x_select) 2002 | 2003 | number_of_samples = round(len(x_select) * (float(new_rate)/ float(self.playbackspeed.currentText())) / self.fs) 2004 | x_resampled = np.array(signal.resample(x_select, number_of_samples)).astype('int') 2005 | 2006 | #normalize sound level 2007 | maximum_x=32767*0.8 2008 | old_max=np.max(np.abs([x_resampled.min(),x_resampled.max()])) 2009 | x_resampled=x_resampled * (maximum_x/old_max) 2010 | x_resampled = x_resampled.astype(np.int16) 2011 | 2012 | print( [x_resampled.min(),x_resampled.max()] ) 2013 | wave_obj = sa.WaveObject(x_resampled, 1, 2, new_rate) 2014 | self.play_obj = wave_obj.play() 2015 | else: 2016 | if self.play_obj.is_playing(): 2017 | sa.stop_all() 2018 | else: 2019 | new_rate = 32000 2020 | t_limits=list(self.canvas.axes.get_xlim()) 2021 | t_limits=t_limits - self.file_blocks.loc[self.filecounter,'start']/self.fs 2022 | f_limits=list(self.canvas.axes.get_ylim()) 2023 | if f_limits[1]>=(self.fs/2): 2024 | f_limits[1]= self.fs/2-10 2025 | print(t_limits) 2026 | print(f_limits) 2027 | 2028 | x_select=self.x[int(t_limits[0]*self.fs) : int(t_limits[1]*self.fs) ] 2029 | sos=signal.butter(8, f_limits, 'bandpass', fs=self.fs, output='sos') 2030 | x_select = signal.sosfilt(sos, x_select) 2031 | 2032 | # number_of_samples = round(len(x_select) * float(new_rate) / self.fs) 2033 | number_of_samples = round(len(x_select) * (float(new_rate)/ float(self.playbackspeed.currentText())) / self.fs) 2034 | 2035 | x_resampled = np.array(signal.resample(x_select, number_of_samples)).astype('int') 2036 | #normalize sound level 2037 | maximum_x=32767*0.8 2038 | old_max=np.max(np.abs([x_resampled.min(),x_resampled.max()])) 2039 | x_resampled=x_resampled * (maximum_x/old_max) 2040 | x_resampled = x_resampled.astype(np.int16) 2041 | print( [x_resampled.min(),x_resampled.max()] ) 2042 | wave_obj = sa.WaveObject(x_resampled, 1, 2, new_rate) 2043 | self.play_obj = wave_obj.play() 2044 | 2045 | # button_play_audio.clicked.connect(self.func_playaudio) 2046 | 2047 | # button_save_audio=QtWidgets.QPushButton('Export selected audio') 2048 | def func_saveaudio(self): 2049 | if self.filecounter>=0: 2050 | options = QtWidgets.QFileDialog.Options() 2051 | savename = QtWidgets.QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()", "wav files (*.wav)",options=options) 2052 | if len(savename[0])>0: 2053 | savename=savename[0] 2054 | new_rate = 32000 2055 | 2056 | t_limits=self.canvas.axes.get_xlim() 2057 | f_limits=list(self.canvas.axes.get_ylim()) 2058 | if f_limits[1]>=(self.fs/2): 2059 | f_limits[1]= self.fs/2-10 2060 | print(t_limits) 2061 | print(f_limits) 2062 | x_select=self.x[int(t_limits[0]*self.fs) : int(t_limits[1]*self.fs) ] 2063 | 2064 | sos=signal.butter(8, f_limits, 'bandpass', fs=self.fs, output='sos') 2065 | x_select = signal.sosfilt(sos, x_select) 2066 | 2067 | number_of_samples = round(len(x_select) * (float(new_rate)/ float(self.playbackspeed.currentText())) / self.fs) 2068 | x_resampled = np.array(signal.resample(x_select, number_of_samples)).astype('int') 2069 | #normalize sound level 2070 | maximum_x=32767*0.8 2071 | old_max=np.max(np.abs([x_resampled.min(),x_resampled.max()])) 2072 | x_resampled=x_resampled * (maximum_x/old_max) 2073 | x_resampled = x_resampled.astype(np.int16) 2074 | 2075 | if savename[-4:]!='.wav': 2076 | savename=savename+'.wav' 2077 | wav.write(savename, new_rate, x_resampled) 2078 | # button_save_audio.clicked.connect(func_saveaudio) 2079 | 2080 | # button_save_video=QtWidgets.QPushButton('Export video') 2081 | def func_save_video(self): 2082 | if self.filecounter>=0: 2083 | savename = QtWidgets.QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()", "video files (*.mp4)") 2084 | if len(savename[0])>0: 2085 | savename=savename[0] 2086 | new_rate = 32000 2087 | 2088 | t_limits=self.canvas.axes.get_xlim() 2089 | f_limits=list(self.canvas.axes.get_ylim()) 2090 | if f_limits[1]>=(self.fs/2): 2091 | f_limits[1]= self.fs/2-10 2092 | print(t_limits) 2093 | print(f_limits) 2094 | x_select=self.x[int(t_limits[0]*self.fs) : int(t_limits[1]*self.fs) ] 2095 | 2096 | sos=signal.butter(8, f_limits, 'bandpass', fs=self.fs, output='sos') 2097 | x_select = signal.sosfilt(sos, x_select) 2098 | 2099 | number_of_samples = round(len(x_select) * (float(new_rate)/ float(self.playbackspeed.currentText())) / self.fs) 2100 | x_resampled = np.array(signal.resample(x_select, number_of_samples)).astype('int') 2101 | #normalize sound level 2102 | maximum_x=32767*0.8 2103 | old_max=np.max(np.abs([x_resampled.min(),x_resampled.max()])) 2104 | x_resampled=x_resampled * (maximum_x/old_max) 2105 | x_resampled = x_resampled.astype(np.int16) 2106 | 2107 | if savename[:-4]=='.wav': 2108 | savename=savename[:-4] 2109 | if savename[:-4]=='.mp4': 2110 | savename=savename[:-4] 2111 | wav.write(savename+'.wav', new_rate, x_resampled) 2112 | 2113 | # self.f_limits=self.canvas.axes.get_ylim() 2114 | # self.t_limits=self.canvas.axes.get_xlim() 2115 | 2116 | audioclip = AudioFileClip(savename+'.wav') 2117 | duration=audioclip.duration 2118 | # func_draw_shape_plot() 2119 | 2120 | self.canvas.axes.set_title(None) 2121 | # self.canvas.axes.set_ylim(f_limits) 2122 | # self.canvas.axes.set_xlim(t_limits) 2123 | self.line_2=self.canvas.axes.plot([t_limits[0] ,t_limits[0] ],f_limits,'-k') 2124 | def make_frame(x): 2125 | s=t_limits[1] - t_limits[0] 2126 | xx= x/duration * s + t_limits[0] 2127 | line = self.line_2.pop(0) 2128 | line.remove() 2129 | self.line_2=self.canvas.axes.plot([xx,xx],f_limits,'-k') 2130 | 2131 | 2132 | return mplfig_to_npimage(self.canvas.fig) 2133 | 2134 | animation = VideoClip(make_frame, duration = duration ) 2135 | animation = animation.set_audio(audioclip) 2136 | animation.write_videofile(savename+".mp4",fps=24,preset='fast') 2137 | 2138 | self.plot_spectrogram() 2139 | # self.canvas.fig.canvas.mpl_disconnect(self.cid2) 2140 | 2141 | def start(): 2142 | app = QtWidgets.QApplication(sys.argv) 2143 | app.setApplicationName("Python Audio Spectrogram Explorer") 2144 | # app.setStyleSheet(qdarktheme.load_stylesheet()) 2145 | # apply_stylesheet(app, theme='dark_teal.xml') 2146 | 2147 | w = gui() 2148 | sys.exit(app.exec_()) -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![pase_icon](screenshots/pase_icon.png) 2 | # Python Audio Spectrogram Explorer (PASE) 3 | [![DOI](https://zenodo.org/badge/403931857.svg)](https://zenodo.org/badge/latestdoi/403931857) 4 | 5 | ### What you can do with this program: 6 | 7 | - Visualize audio files as spectrograms 8 | 9 | - Navigate through the spectrograms and listen in to selected areas in the spectrogram (adjustable playback speeds) 10 | 11 | - Export selected area in the spectrogram as .wav file, .csv table or .mp4 video 12 | 13 | - Annotate areas in the spectrograms with custom labels and log each annotation's time-stamp and frequency 14 | 15 | - Export spectrograms as image files and automatically plot spectrograms for all selected files 16 | 17 | - Draw shapes in the spectrogram and save them as .csv file 18 | 19 | - Automatically detect signals using spectrogram correlation or shapematching 20 | 21 | ![screenshots/s1](screenshots/m1.JPG) 22 | 23 | ## How to install and start the program: 24 | You can either download the windows executable (found here under "Release" and "PASE") or start the program using the python source code. The windows executable is included in this release, it cannot be used to export videos but has all the other functions. 25 | 26 | A platform independent way to start the program is run the source code directly in python. To download PASE use this command: 27 | 28 | ```python 29 | pip install pase 30 | ``` 31 | 32 | Than open a python console and start PASE with these two commands: 33 | 34 | ```python 35 | import pase 36 | pase.pase.start() 37 | ``` 38 | 39 | This program uses PyQT5 as GUI framework and numpy, scipy, pandas and matplotlib to manipulate and visualize the data. The module `simpleaudio` is used to playback sound and `moviepy` to generated videos. In case you are getting an error message due to a missing module, simply copy the module's name and install it using pip, for example `pip install simpleaudio` and `pip install soundfile`. 40 | 41 | ## How to use it: 42 | 43 | ### Open files with or without timestamps 44 | 45 | The currently supported audio file types are: .wav .aif .aiff .aifc .ogg .flac 46 | 47 | To get started, you first have to decide if you want to use real time-stamps (year-month-day hour:minute:seconds) or not. For simply looking at the spectrograms and exploring your audio-files, you do not need the real time-stamps. But as soon as you want to annotate your data, the program needs to know when each .wav file started recording based on the file names. The default is using real time-stamps. 48 | 49 | **Without timestamps:** 50 | 51 | - Delete the content of the field "Timestamp:" 52 | - Press the "Open files" button in the Menu 53 | 54 | **With timestamps:** 55 | 56 | - The start date and time of each recoding should be contained in the audio file name 57 | 58 | - Adjust the "Timestamp:" field so that the program recognizes the correct time-stamp. For example: `aural_%Y_%m_%d_%H_%M_%S.wav` or `%y%m%d_%H%M%S_AU_SO02.wav` Where %Y is year, %m is month, %d is day and so on. Here is a list of the format strings: 59 | 60 | | **Directive** | **Meaning** | **Example** | 61 | | ------------- | ------------------------------------------------------------ | ------------------------ | 62 | | `%a` | Abbreviated weekday name. | Sun, Mon, ... | 63 | | `%A` | Full weekday name. | Sunday, Monday, ... | 64 | | `%w` | Weekday as a decimal number. | 0, 1, ..., 6 | 65 | | `%d` | Day of the month as a zero-padded decimal. | 01, 02, ..., 31 | 66 | | `%-d` | Day of the month as a decimal number. | 1, 2, ..., 30 | 67 | | `%b` | Abbreviated month name. | Jan, Feb, ..., Dec | 68 | | `%B` | Full month name. | January, February, ... | 69 | | `%m` | Month as a zero-padded decimal number. | 01, 02, ..., 12 | 70 | | `%-m` | Month as a decimal number. | 1, 2, ..., 12 | 71 | | `%y` | Year without century as a zero-padded decimal number. | 00, 01, ..., 99 | 72 | | `%-y` | Year without century as a decimal number. | 0, 1, ..., 99 | 73 | | `%Y` | Year with century as a decimal number. | 2013, 2019 etc. | 74 | | `%H` | Hour (24-hour clock) as a zero-padded decimal number. | 00, 01, ..., 23 | 75 | | `%-H` | Hour (24-hour clock) as a decimal number. | 0, 1, ..., 23 | 76 | | `%I` | Hour (12-hour clock) as a zero-padded decimal number. | 01, 02, ..., 12 | 77 | | `%-I` | Hour (12-hour clock) as a decimal number. | 1, 2, ... 12 | 78 | | `%p` | Locale’s AM or PM. | AM, PM | 79 | | `%M` | Minute as a zero-padded decimal number. | 00, 01, ..., 59 | 80 | | `%-M` | Minute as a decimal number. | 0, 1, ..., 59 | 81 | | `%S` | Second as a zero-padded decimal number. | 00, 01, ..., 59 | 82 | | `%-S` | Second as a decimal number. | 0, 1, ..., 59 | 83 | | `%j` | Day of the year as a zero-padded decimal number. | 001, 002, ..., 366 | 84 | | `%-j` | Day of the year as a decimal number. | 1, 2, ..., 366 | 85 | 86 | - Press the "Open files" button and select your audio files with the dialogue. 87 | 88 | ### Plot and browse spectrograms 89 | - Select the spectrogram setting of your choice: 90 | - Minimum and maximum frequency (y-axis) as f_min and f_max 91 | - Linear or logarithmic (default) frequency scale 92 | - The length (x-axis) of each spectrogram in seconds. If the field is left empty the spectrogram will be the length of the entire .wav file. 93 | - The FFT size determines the spectral resolution. The higher it is, the more detail you will see in the lower part of the spectrogram, with less detail in the upper part 94 | - The minimum and maximum dB values for the spectrogram color, will be determined automatically if left empty 95 | - The colormap from a dropdown menu, below are examples for the black and white colormap called "gist_yarg". 96 | 97 | - Press next spectrogram (The Shortkey for this is the right arrow button) 98 | - You can now navigate between the spectrograms using the "next/previous spectrogram" buttons or the left and right arrow keys. The time-stamp or filename of the current audio file is displayed as title. 99 | - You can zoom and pan using the magnifying glass symbol in the matplotlib toolbar, where you can also save the spectrogram as image file (square save button). 100 | - Once you have reached the final spectrogram, the program will display a warning 101 | 102 | ### Play audio and adjust playback speed, export the selected sound as .wav 103 | - Press the "Play/Stop" button or the spacebar to play the current selection of your audio file. 104 | - The program will only play what is visible in the current spectrogram (Sound above and below the frequency limits is filtered out) 105 | - To listen to specific sounds, zoom in using the magnifying glass 106 | - To listen to sound below or above the human hearing range, adjust the playback speed and press the "Play" button again. 107 | - To export the sound you selected as .wav file, press "Export" in the Menu and selected "Spectrogram as .wav file" 108 | 109 | ### Automatically plot spectrograms of multiple .wav files 110 | 111 | - Select your audio files with the "Open files" button 112 | - Select the spectrogram settings of your choice 113 | - Press "Export" in the Menu and select "All files as spectrogram images" 114 | - The spectrograms will be saved as .jpg files with the same filename and location as your .wav files. 115 | 116 | ### Annotate the spectrograms 117 | 118 | - Make sure the "filename key" field contains the correct time-stamp information 119 | 120 | - Now you can either choose to log you annotations in real time or save them later. I recommend using the "real-time logging" option. 121 | 122 | - Press the "real-time logging" check-box. Now the program will look if there are already log files existing for each audio file. Log files are named by adding "_log.csv" to the .wav filename, for example "aural_2017_02_12_22_40_00_log.csv". You can choose to overwrite these log files. If you do not choose to overwrite them, the program ignores audio files that already have an existing log file. This is useful if you want to work on a dataset over several sessions. 123 | 124 | - Now you can choose custom (or preset) labels for your annotations by changing the labels in the row "Annotation labels". If no label is selected (using the check-boxes) an empty string will be used as label. 125 | 126 | - To set an annotation, left-click at any location inside the spectrogram plot and draw a rectangle over the region of interest 127 | 128 | - To remove the last annotation, click the right mouse button. 129 | 130 | - Once a new .wav file is opened, the annotations for the previous .wav file are saved as .csv file, for example as "aural_2017_02_12_22_40_00_log.csv". If no annotations were set an empty table is saved. This indicates you have already screened this .wav file but found nothing to annotate. 131 | 132 | - The "...._log.csv" files are formated like this (t1,f1 and t2,f2 are the corners of the annotation box): 133 | 134 | | | t1 | t2 | f1 | f2 | Label | 135 | | ---- | ------------------ | ---------------|------- | ----|--- | 136 | | 0 | 2016-04-09 19:25:47.49 |2016-04-09 19:25:49.49 | 17.313 | 20.546 | FW_20_Hz | 137 | | 1 | 2016-05-10 17:36:13.94 | 2016-05-10 17:38:13.94 | 27.59109 | 34.57 | BW_Z_call | 138 | 139 | - If you want to save your annotations separately, press "Export" in the menu and choose "Annotations as .csv table" 140 | 141 | ### Remove the background from spectrogram 142 | 143 | This feature can be useful to detect sounds hidden in background noise. It subtracts the average spectrum from the current spectrogram, so that the horizontal noise lines and slope in background noise disappear. To active this function toggle the checkbox called "Remove background". For optimal use, test different dB minimum setting. Here is an example for the spectrogram shown above: 144 | 145 | ![screenshots/s2](screenshots/m2.JPG) 146 | 147 | ### Animated spectrogram videos 148 | 149 | You can use the python console based version of PASE to generate a video (.mp4 files) of the spectrogram with a moving bar. Here is an example: 150 | 151 | ![minke_video_example](screenshots/minke_video_example.gif) 152 | 153 | With sound: https://youtu.be/x3_kFesvw5I 154 | 155 | - zoom into the desired area of the spectrogram and press "Export" in the menu and select "Spectrogram as animated video" 156 | - Wait while the video is generated (might take a few minutes depending on the spectrogram size) 157 | 158 | ### Draw shape and export as .csv file 159 | 160 | ![screenshots/s4](screenshots/m3.JPG) 161 | 162 | - Press the "Draw" button in the menu 163 | 164 | - now you can draw a line by adding points with a double left click and removing them with a right click 165 | 166 | - to save the shape as .csv file and exit the drawing mode press ENTER 167 | 168 | - now you are back in the normal annotation mode 169 | 170 | - the .csv file is structured as follows: 171 | 172 | | | Time_in_s | Frequency_in_Hz | 173 | | ---- | --------- | --------------- | 174 | | 0 | 49.273229 | 171.060302 | 175 | | 1 | 49.224946 | 166.780047 | 176 | | 2 | 49.221929 | 147.346955 | 177 | 178 | ### Automatic signal detection 179 | 180 | You can use two automatic detections methods to detect sounds that are very similar to a selected template: 181 | 182 | **Shapematching** https://github.com/sebastianmenze/Marine-mammal-call-detection-using-spectrogram-shape-matching 183 | 184 | - Draw a template shape based on an example signal and save the .csv file (example for a minke whale calls shown above) 185 | 186 | - Press "Automatic detection" in the menu an select shapematching (on either the current or all audio files) 187 | - load the template (.csv file) of your choice 188 | - select a signal-to-noise dB threshold, usually between 3 and 10 dB, depending on the strength of the signal and amount of noise 189 | - The spectrogram will now display the bounding boxes of detected signals, with the score displayed in the upper left corners 190 | - You can export the automatic detections (including some more metadata) as .csv file in the "Export" menu under "Automatic detections as .csv file" 191 | - To clear the automatic detections, press any of the "Automatic detection" buttons but than press "Cancel" instead of opening a template file 192 | 193 | or **Spectrogram correlation** https://github.com/sebastianmenze/Spectrogram-correlation-tutorial 194 | 195 | - Here the template can either be a shape or a 2-D image table 196 | - To use a shape, use the "draw" mode and save the .csv file (example for a minke whale calls shown above) 197 | 198 | - To use a 2-D table, zoom into the spectrogram so your selection contains only the desired signal and press "Export" in the menu and select "Spectrogram as .csv table" 199 | - To start the automatic detections, press "Automatic detection" in the menu and select spectrogram correlation (on either the current or all audio files) 200 | - Select a template .csv file (shape or table) 201 | - Choose a detection threshold (correlation score r between 0 and 1) 202 | 203 | - The spectrogram will now display the bounding boxes of detected signals, with the score displayed in the upper left corners 204 | - You can export the automatic detections including some more metadata as .csv file using the "Export auto-detec" button 205 | - To clear the automatic detections, press any of the "Automatic detection" buttons but than press "Cancel" instead of opening a template file 206 | 207 | Here is an example for the Ant. Minke whale calls:![autodetect4](screenshots/m6.JPG) 208 | 209 | Example result for the shapematching: 210 | 211 | ![autodetect3](screenshots/m4.JPG) 212 | 213 | Example result for the spectrogram correlation: 214 | 215 | ![autodetect3](screenshots/m5.JPG) 216 | 217 | -------------------------------------------------------------------------------- /screenshots/m1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianmenze/Python-Audio-Spectrogram-Explorer/fe322c18cd2f29e709f55b2256ead655bfdee779/screenshots/m1.JPG -------------------------------------------------------------------------------- /screenshots/m2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianmenze/Python-Audio-Spectrogram-Explorer/fe322c18cd2f29e709f55b2256ead655bfdee779/screenshots/m2.JPG -------------------------------------------------------------------------------- /screenshots/m3.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianmenze/Python-Audio-Spectrogram-Explorer/fe322c18cd2f29e709f55b2256ead655bfdee779/screenshots/m3.JPG -------------------------------------------------------------------------------- /screenshots/m4.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianmenze/Python-Audio-Spectrogram-Explorer/fe322c18cd2f29e709f55b2256ead655bfdee779/screenshots/m4.JPG -------------------------------------------------------------------------------- /screenshots/m5.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianmenze/Python-Audio-Spectrogram-Explorer/fe322c18cd2f29e709f55b2256ead655bfdee779/screenshots/m5.JPG -------------------------------------------------------------------------------- /screenshots/m6.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianmenze/Python-Audio-Spectrogram-Explorer/fe322c18cd2f29e709f55b2256ead655bfdee779/screenshots/m6.JPG -------------------------------------------------------------------------------- /screenshots/minke_video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianmenze/Python-Audio-Spectrogram-Explorer/fe322c18cd2f29e709f55b2256ead655bfdee779/screenshots/minke_video.mp4 -------------------------------------------------------------------------------- /screenshots/minke_video_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianmenze/Python-Audio-Spectrogram-Explorer/fe322c18cd2f29e709f55b2256ead655bfdee779/screenshots/minke_video_example.gif -------------------------------------------------------------------------------- /screenshots/pase_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianmenze/Python-Audio-Spectrogram-Explorer/fe322c18cd2f29e709f55b2256ead655bfdee779/screenshots/pase_icon.ico -------------------------------------------------------------------------------- /screenshots/pase_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastianmenze/Python-Audio-Spectrogram-Explorer/fe322c18cd2f29e709f55b2256ead655bfdee779/screenshots/pase_icon.png -------------------------------------------------------------------------------- /to_compile_yourself/compilecommands.txt: -------------------------------------------------------------------------------- 1 | cd C:\GitHub\pase_complile 2 | 3 | pyinstaller pase_compile.py --onefile --ico pase_icon.ico 4 | 5 | # py pase_compile.py 6 | -------------------------------------------------------------------------------- /to_compile_yourself/pase_compile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Sep 6 17:28:37 2021 4 | 5 | @author: Sebastian Menze, sebastian.menze@gmail.com 6 | """ 7 | 8 | import sys 9 | import matplotlib 10 | # matplotlib.use('Qt5Agg') 11 | 12 | from PyQt5 import QtCore, QtGui, QtWidgets 13 | 14 | # from PyQt5.QtWidgets import QShortcut 15 | # from PyQt5.QtGui import QKeySequence 16 | 17 | from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg, NavigationToolbar2QT as NavigationToolbar 18 | from matplotlib.figure import Figure 19 | 20 | import scipy.io.wavfile as wav 21 | 22 | import soundfile as sf 23 | 24 | 25 | from scipy import signal 26 | import numpy as np 27 | from matplotlib import pyplot as plt 28 | import pandas as pd 29 | import datetime as dt 30 | import time 31 | import os 32 | 33 | from matplotlib.widgets import RectangleSelector 34 | 35 | 36 | # from pydub import AudioSegment 37 | # from pydub.playback import play 38 | # import threading 39 | 40 | import simpleaudio as sa 41 | 42 | from skimage import data, filters, measure, morphology 43 | from skimage.morphology import (erosion, dilation, opening, closing, # noqa 44 | white_tophat) 45 | from skimage.morphology import disk # noqa 46 | from matplotlib.path import Path 47 | from skimage.transform import rescale, resize, downscale_local_mean 48 | 49 | from scipy.signal import find_peaks 50 | from skimage.feature import match_template 51 | 52 | # from moviepy.editor import VideoClip, AudioFileClip 53 | # from moviepy.video.io.bindings import mplfig_to_npimage 54 | 55 | 56 | 57 | 58 | class MplCanvas(FigureCanvasQTAgg ): 59 | 60 | def __init__(self, parent=None, width=5, height=4, dpi=100): 61 | self.fig = Figure(figsize=(width, height), dpi=dpi) 62 | self.axes = self.fig.add_subplot(111) 63 | super(MplCanvas, self).__init__(self.fig) 64 | 65 | 66 | 67 | class MainWindow(QtWidgets.QMainWindow): 68 | 69 | 70 | def __init__(self, *args, **kwargs): 71 | super(MainWindow, self).__init__(*args, **kwargs) 72 | 73 | self.canvas = MplCanvas(self, width=5, height=4, dpi=150) 74 | 75 | # self.call_time=pd.Series() 76 | # self.call_frec=pd.Series() 77 | 78 | self.f_min = QtWidgets.QLineEdit(self) 79 | self.f_min.setText('10') 80 | self.f_max = QtWidgets.QLineEdit(self) 81 | self.f_max.setText('16000') 82 | self.t_length = QtWidgets.QLineEdit(self) 83 | self.t_length.setText('120') 84 | self.db_saturation=QtWidgets.QLineEdit(self) 85 | self.db_saturation.setText('155') 86 | self.db_vmin=QtWidgets.QLineEdit(self) 87 | self.db_vmin.setText('30') 88 | self.db_vmax=QtWidgets.QLineEdit(self) 89 | self.db_vmax.setText('') 90 | # self.fft_size = QtWidgets.QLineEdit(self) 91 | # self.fft_size.setText('32768') 92 | self.fft_size = QtWidgets.QComboBox(self) 93 | self.fft_size.addItem('1024') 94 | self.fft_size.addItem('2048') 95 | self.fft_size.addItem('4096') 96 | self.fft_size.addItem('8192') 97 | self.fft_size.addItem('16384') 98 | self.fft_size.addItem('32768') 99 | self.fft_size.addItem('65536') 100 | self.fft_size.addItem('131072') 101 | self.fft_size.setCurrentIndex(4) 102 | 103 | 104 | self.colormap_plot = QtWidgets.QComboBox(self) 105 | self.colormap_plot.addItem('plasma') 106 | self.colormap_plot.addItem('viridis') 107 | self.colormap_plot.addItem('inferno') 108 | self.colormap_plot.addItem('gist_gray') 109 | self.colormap_plot.addItem('gist_yarg') 110 | self.colormap_plot.setCurrentIndex(2) 111 | 112 | self.checkbox_logscale=QtWidgets.QCheckBox('Log. scale') 113 | self.checkbox_logscale.setChecked(True) 114 | self.checkbox_background=QtWidgets.QCheckBox('Remove background') 115 | self.checkbox_background.setChecked(False) 116 | 117 | # self.fft_overlap = QtWidgets.QLineEdit(self) 118 | # self.fft_overlap.setText('0.9') 119 | 120 | self.fft_overlap = QtWidgets.QComboBox(self) 121 | self.fft_overlap.addItem('0.2') 122 | self.fft_overlap.addItem('0.5') 123 | self.fft_overlap.addItem('0.7') 124 | self.fft_overlap.addItem('0.9') 125 | self.fft_overlap.setCurrentIndex(3) 126 | 127 | 128 | 129 | self.filename_timekey = QtWidgets.QLineEdit(self) 130 | self.filename_timekey.setText('aural_%Y_%m_%d_%H_%M_%S.wav') 131 | 132 | self.playbackspeed = QtWidgets.QComboBox(self) 133 | self.playbackspeed.addItem('0.5') 134 | self.playbackspeed.addItem('1') 135 | self.playbackspeed.addItem('2') 136 | self.playbackspeed.addItem('5') 137 | self.playbackspeed.addItem('10') 138 | self.playbackspeed.setCurrentIndex(1) 139 | 140 | 141 | self.time= dt.datetime(2000,1,1,0,0,0) 142 | self.f=None 143 | self.t=[-1,-1] 144 | self.Sxx=None 145 | self.draw_x=pd.Series(dtype='float') 146 | self.draw_y=pd.Series(dtype='float') 147 | self.cid1=None 148 | self.cid2=None 149 | 150 | self.plotwindow_startsecond=0 151 | # self.plotwindow_length=120 152 | self.filecounter=-1 153 | self.filenames=np.array( [] ) 154 | self.current_audiopath=None 155 | 156 | self.detectiondf=pd.DataFrame([]) 157 | 158 | 159 | def find_regions(db_threshold): 160 | 161 | y1=int(self.f_min.text()) 162 | y2=int(self.f_max.text()) 163 | if y2>(self.fs/2): 164 | y2=(self.fs/2) 165 | t1=self.plotwindow_startsecond 166 | t2=self.plotwindow_startsecond+self.plotwindow_length 167 | 168 | ix_time=np.where( (self.t>=t1) & (self.t=y1) & (self.f db_threshold 198 | mask = morphology.remove_small_objects(mask, 50,connectivity=30) 199 | mask = morphology.remove_small_holes(mask, 50,connectivity=30) 200 | 201 | mask = closing(mask, disk(3) ) 202 | # op_and_clo = opening(closed, disk(1) ) 203 | 204 | labels = measure.label(mask) 205 | 206 | probs=measure.regionprops_table(labels,spectrog,properties=['label','area','mean_intensity','orientation','major_axis_length','minor_axis_length','weighted_centroid','bbox']) 207 | df=pd.DataFrame(probs) 208 | 209 | # get corect f anf t 210 | ff=self.f[ ix_f[0]:ix_f[-1] ] 211 | ix=df['bbox-0']>len(ff)-1 212 | df.loc[ix,'bbox-0']=len(ff)-1 213 | ix=df['bbox-2']>len(ff)-1 214 | df.loc[ix,'bbox-2']=len(ff)-1 215 | 216 | df['f-1']=ff[df['bbox-0']] 217 | df['f-2']=ff[df['bbox-2']] 218 | df['f-width']=df['f-2']-df['f-1'] 219 | 220 | ix=df['bbox-1']>len(t)-1 221 | df.loc[ix,'bbox-1']=len(t)-1 222 | ix=df['bbox-3']>len(t)-1 223 | df.loc[ix,'bbox-3']=len(t)-1 224 | 225 | df['t-1']=t[df['bbox-1']] 226 | df['t-2']=t[df['bbox-3']] 227 | df['duration']=df['t-2']-df['t-1'] 228 | 229 | indices=np.where( (df['area']=spectrog.shape[1]: ix2=spectrog.shape[1]-1 266 | # sgram[ df['id'][ix] ] = spectrog[:,ix1:ix2] 267 | self.detectiondf = df 268 | self.patches = patches 269 | self.p_t_dict = p_t_dict 270 | self.p_f_dict = p_f_dict 271 | 272 | # return df, patches,p_t_dict,p_f_dict 273 | 274 | def match_bbox_and_iou(template): 275 | 276 | shape_f=template['Frequency_in_Hz'].values 277 | shape_t=template['Time_in_s'].values 278 | shape_t=shape_t-shape_t.min() 279 | 280 | df=self.detectiondf 281 | patches=self.patches 282 | p_t_dict=self.p_t_dict 283 | p_f_dict=self.p_f_dict 284 | 285 | # f_lim=[ shape_f.min()-10 ,shape_f.max()+10 ] 286 | 287 | # score_smc=[] 288 | score_ioubox=[] 289 | smc_rs=[] 290 | 291 | for ix in df.index: 292 | 293 | # breakpoint() 294 | patch=patches[ix] 295 | pf=p_f_dict[ix] 296 | pt=p_t_dict[ix] 297 | pt=pt-pt[0] 298 | 299 | 300 | if df.loc[ix,'f-1'] < shape_f.min(): 301 | f1= df.loc[ix,'f-1'] 302 | else: 303 | f1= shape_f.min() 304 | if df.loc[ix,'f-2'] > shape_f.max(): 305 | f2= df.loc[ix,'f-2'] 306 | else: 307 | f2= shape_f.max() 308 | 309 | # f_lim=[ f1,f2 ] 310 | 311 | time_step=np.diff(pt)[0] 312 | f_step=np.diff(pf)[0] 313 | k_f=np.arange(f1,f2,f_step ) 314 | 315 | if pt.max()>shape_t.max(): 316 | k_t=pt 317 | 318 | else: 319 | k_t=np.arange(0,shape_t.max(),time_step) 320 | 321 | 322 | ### iou bounding box 323 | 324 | iou_kernel=np.zeros( [ k_f.shape[0] ,k_t.shape[0] ] ) 325 | ixp2=np.where((k_t>=shape_t.min()) & (k_t<=shape_t.max()))[0] 326 | ixp1=np.where((k_f>=shape_f.min()) & (k_f<=shape_f.max()))[0] 327 | iou_kernel[ ixp1[0]:ixp1[-1] , ixp2[0]:ixp2[-1] ]=1 328 | 329 | iou_patch=np.zeros( [ k_f.shape[0] ,k_t.shape[0] ] ) 330 | ixp2=np.where((k_t>=pt[0]) & (k_t<=pt[-1]))[0] 331 | ixp1=np.where((k_f>=pf[0]) & (k_f<=pf[-1]))[0] 332 | iou_patch[ ixp1[0]:ixp1[-1] , ixp2[0]:ixp2[-1] ]=1 333 | 334 | intersection= iou_kernel.astype('bool') & iou_patch.astype('bool') 335 | union= iou_kernel.astype('bool') | iou_patch.astype('bool') 336 | iou_bbox = np.sum( intersection ) / np.sum( union ) 337 | score_ioubox.append(iou_bbox) 338 | 339 | patch_rs = resize(patch, (50,50)) 340 | n_resize=50 341 | k_t=np.linspace(0,shape_t.max(),n_resize ) 342 | k_f=np.linspace(shape_f.min(), shape_f.max(),n_resize ) 343 | kk_t,kk_f=np.meshgrid(k_t,k_f) 344 | # kernel=np.zeros( [ k_f.shape[0] ,k_t.shape[0] ] ) 345 | x, y = kk_t.flatten(), kk_f.flatten() 346 | points = np.vstack((x,y)).T 347 | p = Path(list(zip(shape_t, shape_f))) # make a polygon 348 | grid = p.contains_points(points) 349 | kernel_rs = grid.reshape(kk_t.shape) # now you have a mask with points inside a polygon 350 | smc_rs.append( np.sum( kernel_rs.astype('bool') == patch_rs.astype('bool') ) / len( patch_rs.flatten() ) ) 351 | 352 | smc_rs=np.array(smc_rs) 353 | score_ioubox=np.array(score_ioubox) 354 | 355 | df['score'] =score_ioubox * (smc_rs-.5)/.5 356 | 357 | self.detectiondf = df.copy() 358 | 359 | 360 | def automatic_detector_specgram_corr(): 361 | # open template 362 | self.detectiondf=pd.DataFrame([]) 363 | 364 | templatefile, ok1 = QtWidgets.QFileDialog.getOpenFileName(self,"QFileDialog.getOpenFileNames()", r"C:\Users","CSV file (*.csv)") 365 | if ok1: 366 | template=pd.read_csv(templatefile,index_col=0) 367 | 368 | corrscore_threshold, ok = QtWidgets.QInputDialog.getDouble(self, 'Input Dialog', 369 | 'Enter correlation threshold in (0-1):',decimals=2) 370 | if corrscore_threshold>1: 371 | corrscore_threshold=1 372 | if corrscore_threshold<0: 373 | corrscore_threshold=0 374 | 375 | if template.columns[0]=='Time_in_s': 376 | 377 | # print(template) 378 | offset_f=10 379 | offset_t=0.5 380 | shape_f=template['Frequency_in_Hz'].values 381 | shape_t=template['Time_in_s'].values 382 | shape_t=shape_t-shape_t.min() 383 | 384 | f_lim=[ shape_f.min() - offset_f , shape_f.max() + offset_f ] 385 | k_length_seconds=shape_t.max()+offset_t*2 386 | 387 | # generate kernel 388 | time_step=np.diff(self.t)[0] 389 | 390 | k_t=np.linspace(0,k_length_seconds,int(k_length_seconds/time_step) ) 391 | ix_f=np.where((self.f>=f_lim[0]) & (self.f<=f_lim[1]))[0] 392 | k_f=self.f[ix_f[0]:ix_f[-1]] 393 | # k_f=np.linspace(f_lim[0],f_lim[1], int( (f_lim[1]-f_lim[0]) /f_step) ) 394 | 395 | kk_t,kk_f=np.meshgrid(k_t,k_f) 396 | kernel_background_db=0 397 | kernel_signal_db=1 398 | kernel=np.ones( [ k_f.shape[0] ,k_t.shape[0] ] ) * kernel_background_db 399 | # find wich grid points are inside the shape 400 | x, y = kk_t.flatten(), kk_f.flatten() 401 | points = np.vstack((x,y)).T 402 | p = Path(list(zip(shape_t, shape_f))) # make a polygon 403 | grid = p.contains_points(points) 404 | mask = grid.reshape(kk_t.shape) # now you have a mask with points inside a polygon 405 | kernel[mask]=kernel_signal_db 406 | 407 | ix_f=np.where((self.f>=f_lim[0]) & (self.f<=f_lim[1]))[0] 408 | spectrog =10*np.log10( self.Sxx[ ix_f[0]:ix_f[-1],: ] ) 409 | 410 | result = match_template(spectrog, kernel) 411 | corr_score=result[0,:] 412 | t_score=np.linspace( self.t[int(kernel.shape[1]/2)] , self.t[-int(kernel.shape[1]/2)], corr_score.shape[0] ) 413 | 414 | peaks_indices = find_peaks(corr_score, height=corrscore_threshold)[0] 415 | 416 | 417 | t1=[] 418 | t2=[] 419 | f1=[] 420 | f2=[] 421 | score=[] 422 | 423 | if len(peaks_indices)>0: 424 | t2_old=0 425 | for ixpeak in peaks_indices: 426 | tstar=t_score[ixpeak] - k_length_seconds/2 - offset_t 427 | tend=t_score[ixpeak] + k_length_seconds/2 - offset_t 428 | # if tstar>t2_old: 429 | t1.append(tstar) 430 | t2.append(tend) 431 | f1.append(f_lim[0]+offset_f) 432 | f2.append(f_lim[1]-offset_f) 433 | score.append(corr_score[ixpeak]) 434 | # t2_old=tend 435 | df=pd.DataFrame() 436 | df['t-1']=t1 437 | df['t-2']=t2 438 | df['f-1']=f1 439 | df['f-2']=f2 440 | df['score']=score 441 | 442 | self.detectiondf = df.copy() 443 | self.detectiondf['audiofilename']= self.current_audiopath 444 | self.detectiondf['threshold']= corrscore_threshold 445 | 446 | plot_spectrogram() 447 | else: # image kernel 448 | 449 | 450 | k_length_seconds= float(template.columns[-1]) -float(template.columns[0]) 451 | 452 | f_lim=[ int(template.index[0]) , int( template.index[-1] )] 453 | ix_f=np.where((self.f>=f_lim[0]) & (self.f<=f_lim[1]))[0] 454 | spectrog =10*np.log10( self.Sxx[ ix_f[0]:ix_f[-1],: ] ) 455 | specgram_t_step= self.t[1] - self.t[0] 456 | n_f=spectrog.shape[0] 457 | n_t= int(k_length_seconds/ specgram_t_step) 458 | 459 | kernel= resize( template.values , [ n_f,n_t] ) 460 | 461 | 462 | result = match_template(spectrog, kernel) 463 | corr_score=result[0,:] 464 | t_score=np.linspace( self.t[int(kernel.shape[1]/2)] , self.t[-int(kernel.shape[1]/2)], corr_score.shape[0] ) 465 | 466 | peaks_indices = find_peaks(corr_score, height=corrscore_threshold)[0] 467 | 468 | # print(corr_score) 469 | 470 | t1=[] 471 | t2=[] 472 | f1=[] 473 | f2=[] 474 | score=[] 475 | 476 | if len(peaks_indices)>0: 477 | t2_old=0 478 | for ixpeak in peaks_indices: 479 | tstar=t_score[ixpeak] - k_length_seconds/2 480 | tend=t_score[ixpeak] + k_length_seconds/2 481 | # if tstar>t2_old: 482 | t1.append(tstar) 483 | t2.append(tend) 484 | f1.append(f_lim[0]) 485 | f2.append(f_lim[1]) 486 | score.append(corr_score[ixpeak]) 487 | t2_old=tend 488 | df=pd.DataFrame() 489 | df['t-1']=t1 490 | df['t-2']=t2 491 | df['f-1']=f1 492 | df['f-2']=f2 493 | df['score']=score 494 | 495 | self.detectiondf = df.copy() 496 | self.detectiondf['audiofilename']= self.current_audiopath 497 | self.detectiondf['threshold']= corrscore_threshold 498 | 499 | plot_spectrogram() 500 | 501 | def automatic_detector_specgram_corr_allfiles(): 502 | msg = QtWidgets.QMessageBox() 503 | msg.setIcon(QtWidgets.QMessageBox.Information) 504 | msg.setText("Are you sure you want to run the detector over "+ str(self.filenames.shape[0]) +" ?") 505 | msg.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) 506 | returnValue = msg.exec() 507 | 508 | if returnValue == QtWidgets.QMessageBox.Yes: 509 | 510 | templatefile, ok1 = QtWidgets.QFileDialog.getOpenFileName(self,"QFileDialog.getOpenFileNames()", r"C:\Users","CSV file (*.csv)") 511 | if ok1: 512 | template=pd.read_csv(templatefile,index_col=0) 513 | 514 | corrscore_threshold, ok = QtWidgets.QInputDialog.getDouble(self, 'Input Dialog', 515 | 'Enter correlation threshold in (0-1):',decimals=2) 516 | if corrscore_threshold>1: 517 | corrscore_threshold=1 518 | if corrscore_threshold<0: 519 | corrscore_threshold=0 520 | 521 | self.detectiondf_all=pd.DataFrame([]) 522 | 523 | for audiopath in self.filenames: 524 | 525 | if self.filename_timekey.text()=='': 526 | self.time= dt.datetime(2000,1,1,0,0,0) 527 | else: 528 | try: 529 | self.time= dt.datetime.strptime( audiopath.split('/')[-1], self.filename_timekey.text() ) 530 | except: 531 | self.time= dt.datetime(2000,1,1,0,0,0) 532 | 533 | self.x,self.fs = sf.read(audiopath,dtype='int16') 534 | print('open new file: '+audiopath) 535 | 536 | db_saturation=float( self.db_saturation.text() ) 537 | x=self.x/32767 538 | p =np.power(10,(db_saturation/20))*x #convert data.signal to uPa 539 | 540 | fft_size=int( self.fft_size.currentText() ) 541 | fft_overlap=float( self.fft_overlap.currentText() ) 542 | 543 | self.f, self.t, self.Sxx = signal.spectrogram(p, self.fs, window='hamming',nperseg=fft_size,noverlap=fft_size*fft_overlap) 544 | 545 | # self.plotwindow_startsecond=0 546 | # self.plotwindow_length = self.t.max() 547 | 548 | if template.columns[0]=='Time_in_s': 549 | 550 | # print(template) 551 | offset_f=10 552 | offset_t=0.5 553 | shape_f=template['Frequency_in_Hz'].values 554 | shape_t=template['Time_in_s'].values 555 | shape_t=shape_t-shape_t.min() 556 | 557 | f_lim=[ shape_f.min() - offset_f , shape_f.max() + offset_f ] 558 | k_length_seconds=shape_t.max()+offset_t*2 559 | 560 | # generate kernel 561 | time_step=np.diff(self.t)[0] 562 | 563 | k_t=np.linspace(0,k_length_seconds,int(k_length_seconds/time_step) ) 564 | ix_f=np.where((self.f>=f_lim[0]) & (self.f<=f_lim[1]))[0] 565 | k_f=self.f[ix_f[0]:ix_f[-1]] 566 | # k_f=np.linspace(f_lim[0],f_lim[1], int( (f_lim[1]-f_lim[0]) /f_step) ) 567 | 568 | kk_t,kk_f=np.meshgrid(k_t,k_f) 569 | kernel_background_db=0 570 | kernel_signal_db=1 571 | kernel=np.ones( [ k_f.shape[0] ,k_t.shape[0] ] ) * kernel_background_db 572 | # find wich grid points are inside the shape 573 | x, y = kk_t.flatten(), kk_f.flatten() 574 | points = np.vstack((x,y)).T 575 | p = Path(list(zip(shape_t, shape_f))) # make a polygon 576 | grid = p.contains_points(points) 577 | mask = grid.reshape(kk_t.shape) # now you have a mask with points inside a polygon 578 | kernel[mask]=kernel_signal_db 579 | 580 | ix_f=np.where((self.f>=f_lim[0]) & (self.f<=f_lim[1]))[0] 581 | spectrog =10*np.log10( self.Sxx[ ix_f[0]:ix_f[-1],: ] ) 582 | 583 | result = match_template(spectrog, kernel) 584 | corr_score=result[0,:] 585 | t_score=np.linspace( self.t[int(kernel.shape[1]/2)] , self.t[-int(kernel.shape[1]/2)], corr_score.shape[0] ) 586 | 587 | peaks_indices = find_peaks(corr_score, height=corrscore_threshold)[0] 588 | 589 | 590 | t1=[] 591 | t2=[] 592 | f1=[] 593 | f2=[] 594 | score=[] 595 | 596 | if len(peaks_indices)>0: 597 | t2_old=0 598 | for ixpeak in peaks_indices: 599 | tstar=t_score[ixpeak] - k_length_seconds/2 - offset_t 600 | tend=t_score[ixpeak] + k_length_seconds/2 - offset_t 601 | # if tstar>t2_old: 602 | t1.append(tstar) 603 | t2.append(tend) 604 | f1.append(f_lim[0]+offset_f) 605 | f2.append(f_lim[1]-offset_f) 606 | score.append(corr_score[ixpeak]) 607 | t2_old=tend 608 | df=pd.DataFrame() 609 | df['t-1']=t1 610 | df['t-2']=t2 611 | df['f-1']=f1 612 | df['f-2']=f2 613 | df['score']=score 614 | 615 | self.detectiondf = df.copy() 616 | self.detectiondf['audiofilename']= audiopath 617 | self.detectiondf['threshold']= corrscore_threshold 618 | else: # image kernel 619 | k_length_seconds= float(template.columns[-1]) -float(template.columns[0]) 620 | 621 | f_lim=[ int(template.index[0]) , int( template.index[-1] )] 622 | ix_f=np.where((self.f>=f_lim[0]) & (self.f<=f_lim[1]))[0] 623 | spectrog =10*np.log10( self.Sxx[ ix_f[0]:ix_f[-1],: ] ) 624 | specgram_t_step= self.t[1] - self.t[0] 625 | n_f=spectrog.shape[0] 626 | n_t= int(k_length_seconds/ specgram_t_step) 627 | 628 | kernel= resize( template.values , [ n_f,n_t] ) 629 | 630 | 631 | result = match_template(spectrog, kernel) 632 | corr_score=result[0,:] 633 | t_score=np.linspace( self.t[int(kernel.shape[1]/2)] , self.t[-int(kernel.shape[1]/2)], corr_score.shape[0] ) 634 | 635 | peaks_indices = find_peaks(corr_score, height=corrscore_threshold)[0] 636 | 637 | # print(corr_score) 638 | 639 | t1=[] 640 | t2=[] 641 | f1=[] 642 | f2=[] 643 | score=[] 644 | 645 | if len(peaks_indices)>0: 646 | t2_old=0 647 | for ixpeak in peaks_indices: 648 | tstar=t_score[ixpeak] - k_length_seconds/2 649 | tend=t_score[ixpeak] + k_length_seconds/2 650 | # if tstar>t2_old: 651 | t1.append(tstar) 652 | t2.append(tend) 653 | f1.append(f_lim[0]) 654 | f2.append(f_lim[1]) 655 | score.append(corr_score[ixpeak]) 656 | t2_old=tend 657 | df=pd.DataFrame() 658 | df['t-1']=t1 659 | df['t-2']=t2 660 | df['f-1']=f1 661 | df['f-2']=f2 662 | df['score']=score 663 | 664 | self.detectiondf = df.copy() 665 | self.detectiondf['audiofilename']= self.current_audiopath 666 | self.detectiondf['threshold']= corrscore_threshold 667 | 668 | self.detectiondf_all=pd.concat([ self.detectiondf_all,self.detectiondf ]) 669 | self.detectiondf_all=self.detectiondf_all.reset_index(drop=True) 670 | 671 | print(self.detectiondf_all) 672 | 673 | 674 | self.detectiondf= self.detectiondf_all 675 | # self.detectiondf=self.detectiondf.reset_index(drop=True) 676 | print('done!!!') 677 | 678 | 679 | def automatic_detector_shapematching(): 680 | # open template 681 | self.detectiondf=pd.DataFrame([]) 682 | 683 | templatefile, ok1 = QtWidgets.QFileDialog.getOpenFileName(self,"QFileDialog.getOpenFileNames()", r"C:\Users","CSV file (*.csv)") 684 | if ok1: 685 | template=pd.read_csv(templatefile,index_col=0) 686 | 687 | if template.columns[0]=='Time_in_s': 688 | # print(template) 689 | 690 | # set db threshold 691 | db_threshold, ok = QtWidgets.QInputDialog.getInt(self, 'Input Dialog', 692 | 'Enter signal-to-noise threshold in dB:') 693 | if ok: 694 | print(db_threshold) 695 | self.detectiondf=pd.DataFrame([]) 696 | 697 | find_regions(db_threshold) 698 | match_bbox_and_iou(template) 699 | ixdel=np.where(self.detectiondf['score']<0.01)[0] 700 | self.detectiondf=self.detectiondf.drop(ixdel) 701 | self.detectiondf=self.detectiondf.reset_index(drop=True) 702 | self.detectiondf['audiofilename']= self.current_audiopath 703 | self.detectiondf['threshold']= db_threshold 704 | 705 | print(self.detectiondf) 706 | 707 | # plot results 708 | plot_spectrogram() 709 | else: 710 | msg = QtWidgets.QMessageBox() 711 | msg.setIcon(QtWidgets.QMessageBox.Information) 712 | msg.setText("Wrong template format") 713 | msg.setStandardButtons(QtWidgets.QMessageBox.Ok) 714 | returnValue = msg.exec() 715 | 716 | 717 | def automatic_detector_shapematching_allfiles(): 718 | msg = QtWidgets.QMessageBox() 719 | msg.setIcon(QtWidgets.QMessageBox.Information) 720 | msg.setText("Are you sure you want to run the detector over "+ str(self.filenames.shape[0]) +"files ?") 721 | msg.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) 722 | returnValue = msg.exec() 723 | 724 | if returnValue == QtWidgets.QMessageBox.Yes: 725 | templatefile, ok1 = QtWidgets.QFileDialog.getOpenFileName(self,"QFileDialog.getOpenFileNames()", r"C:\Users","CSV file (*.csv)") 726 | template=pd.read_csv(templatefile,index_col=0) 727 | 728 | self.detectiondf_all=pd.DataFrame([]) 729 | 730 | 731 | if template.columns[0]=='Time_in_s': 732 | 733 | db_threshold, ok = QtWidgets.QInputDialog.getInt(self, 'Input Dialog', 734 | 'Enter signal-to-noise threshold in dB:') 735 | 736 | for audiopath in self.filenames: 737 | 738 | if self.filename_timekey.text()=='': 739 | self.time= dt.datetime(2000,1,1,0,0,0) 740 | else: 741 | try: 742 | self.time= dt.datetime.strptime( audiopath.split('/')[-1], self.filename_timekey.text() ) 743 | except: 744 | self.time= dt.datetime(2000,1,1,0,0,0) 745 | 746 | self.x,self.fs = sf.read(audiopath,dtype='int16') 747 | print('open new file: '+audiopath) 748 | 749 | db_saturation=float( self.db_saturation.text() ) 750 | x=self.x/32767 751 | p =np.power(10,(db_saturation/20))*x #convert data.signal to uPa 752 | 753 | fft_size=int( self.fft_size.currentText() ) 754 | fft_overlap=float( self.fft_overlap.currentText() ) 755 | 756 | self.f, self.t, self.Sxx = signal.spectrogram(p, self.fs, window='hamming',nperseg=fft_size,noverlap=fft_size*fft_overlap) 757 | 758 | self.plotwindow_startsecond=0 759 | self.plotwindow_length = self.t.max() 760 | 761 | self.detectiondf=pd.DataFrame([]) 762 | 763 | find_regions(db_threshold) 764 | match_bbox_and_iou(template) 765 | ixdel=np.where(self.detectiondf['score']<0.01)[0] 766 | self.detectiondf=self.detectiondf.drop(ixdel) 767 | self.detectiondf=self.detectiondf.reset_index(drop=True) 768 | self.detectiondf['audiofilename']= audiopath 769 | self.detectiondf['threshold']= db_threshold 770 | 771 | self.detectiondf_all=pd.concat([ self.detectiondf_all,self.detectiondf ]) 772 | self.detectiondf_all=self.detectiondf_all.reset_index(drop=True) 773 | 774 | print(self.detectiondf_all) 775 | else: 776 | msg = QtWidgets.QMessageBox() 777 | msg.setIcon(QtWidgets.QMessageBox.Information) 778 | msg.setText("Wrong template format") 779 | msg.setStandardButtons(QtWidgets.QMessageBox.Ok) 780 | returnValue = msg.exec() 781 | 782 | self.detectiondf= self.detectiondf_all 783 | # self.detectiondf=self.detectiondf.reset_index(drop=True) 784 | print('done!!!') 785 | 786 | def export_automatic_detector_shapematching(): 787 | if self.detectiondf.shape[0]>0: 788 | savename = QtWidgets.QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()", r"C:\Users", "csv files (*.csv)") 789 | print('location is:' + savename[0]) 790 | if len(savename[0])>0: 791 | self.detectiondf.to_csv(savename[0]) 792 | 793 | openfilebutton=QtWidgets.QPushButton('Open files') 794 | def openfilefunc(): 795 | self.filecounter=-1 796 | self.annotation= pd.DataFrame({'t1': pd.Series(dtype='datetime64[ns]'), 797 | 't2': pd.Series(dtype='datetime64[ns]'), 798 | 'f1': pd.Series(dtype='float'), 799 | 'f2': pd.Series(dtype='float'), 800 | 'label': pd.Series(dtype='object'), 801 | 'audiofilename': pd.Series(dtype='object')}) 802 | 803 | # self.annotation=pd.DataFrame(columns=['t1','t2','f1','f2','label'],dtype=[("t1", "datetime64[ns]"), ("t2", "datetime64[ns]"), ("f1", "float"), ("f2", "float"), ("label", "object")] ) 804 | 805 | # annotation=pd.DataFrame(dtype=[("t1", "datetime64[ns]"), ("t2", "datetime64[ns]"), ("f1", "float"), ("f2", "float"), ("label", "object")] ) 806 | 807 | # ,dtype=('datetime64[ns]','datetime64[ns]','float','float','object')) 808 | # self.call_t_1=pd.Series(dtype='datetime64[ns]') 809 | # self.call_f_1=pd.Series(dtype='float') 810 | # self.call_t_2=pd.Series(dtype='datetime64[ns]') 811 | # self.call_f_2=pd.Series(dtype='float') 812 | # self.call_label=pd.Series(dtype='object') 813 | 814 | options = QtWidgets.QFileDialog.Options() 815 | # options |= QtWidgets.QFileDialog.DontUseNativeDialog 816 | self.filenames, _ = QtWidgets.QFileDialog.getOpenFileNames(self,"QFileDialog.getOpenFileNames()", r"C:\Users\a5278\Documents\passive_acoustics\detector_delevopment\detector_validation_subset","Audio Files (*.wav *.aif *.aiff *.aifc *.ogg *.flac)", options=options) 817 | self.filenames = np.array( self.filenames ) 818 | print(self.filenames) 819 | plot_next_spectro() 820 | openfilebutton.clicked.connect(openfilefunc) 821 | 822 | 823 | def read_wav(): 824 | if self.filecounter>=0: 825 | self.current_audiopath=self.filenames[self.filecounter] 826 | 827 | # if self.filename_timekey.text()=='': 828 | # self.time= dt.datetime(1,1,1,0,0,0) 829 | # else: 830 | # self.time= dt.datetime.strptime( audiopath.split('/')[-1], self.filename_timekey.text() ) 831 | 832 | if self.filename_timekey.text()=='': 833 | self.time= dt.datetime(2000,1,1,0,0,0) 834 | #self.time= dt.datetime.now() 835 | else: 836 | try: 837 | self.time= dt.datetime.strptime( self.current_audiopath.split('/')[-1], self.filename_timekey.text() ) 838 | except: 839 | print('wrongfilename') 840 | 841 | # if audiopath[-4:]=='.wav': 842 | 843 | 844 | self.x,self.fs = sf.read(self.current_audiopath,dtype='int16') 845 | 846 | # if audiopath[-4:]=='.aif' | audiopath[-4:]=='.aiff' | audiopath[-4:]=='.aifc': 847 | # obj = aifc.open(audiopath,'r') 848 | # self.fs, self.x = wav.read(audiopath) 849 | print('open new file: '+self.current_audiopath) 850 | print('FS: '+str(self.fs) +' x: '+str(np.shape(self.x))) 851 | if len(self.x.shape)>1: 852 | if np.shape(self.x)[1]>1: 853 | self.x=self.x[:,0] 854 | 855 | # factor=60 856 | # x=signal.decimate(x,factor,ftype='fir') 857 | 858 | db_saturation=float( self.db_saturation.text() ) 859 | x=self.x/32767 860 | p =np.power(10,(db_saturation/20))*x #convert data.signal to uPa 861 | 862 | fft_size=int( self.fft_size.currentText() ) 863 | fft_overlap=float( self.fft_overlap.currentText() ) 864 | self.f, self.t, self.Sxx = signal.spectrogram(p, self.fs, window='hamming',nperseg=fft_size,noverlap=int(fft_size*fft_overlap)) 865 | # self.t=self.time + pd.to_timedelta( t , unit='s') 866 | 867 | def plot_annotation_box(annotation_row): 868 | print('row:') 869 | # print(annotation_row.dtypes) 870 | x1=annotation_row.iloc[0,0] 871 | x2=annotation_row.iloc[0,1] 872 | 873 | xt=pd.Series([x1,x2]) 874 | print(xt) 875 | print(xt.dtype) 876 | 877 | # print(np.dtype(np.array(self.time).astype('datetime64[ns]') )) 878 | tt=xt - np.array(self.time).astype('datetime64[ns]') 879 | xt=tt.dt.seconds + tt.dt.microseconds/10**6 880 | x1=xt[0] 881 | x2=xt[1] 882 | 883 | # tt=x1 - np.array(self.time).astype('datetime64[ns]') 884 | # x1=tt.dt.seconds + tt.dt.microseconds/10**6 885 | # tt=x2 - np.array(self.time).astype('datetime64[ns]') 886 | # x2=tt.dt.seconds + tt.dt.microseconds/10**6 887 | 888 | y1=annotation_row.iloc[0,2] 889 | y2=annotation_row.iloc[0,3] 890 | c_label=annotation_row.iloc[0,4] 891 | 892 | line_x=[x2,x1,x1,x2,x2] 893 | line_y=[y1,y1,y2,y2,y1] 894 | 895 | xmin=np.min([x1,x2]) 896 | ymax=np.max([y1,y2]) 897 | 898 | self.canvas.axes.plot(line_x,line_y,'-b',linewidth=.75) 899 | self.canvas.axes.text(xmin,ymax,c_label,size=5) 900 | 901 | 902 | 903 | def plot_spectrogram(): 904 | if self.filecounter>=0: 905 | # self.canvas = MplCanvas(self, width=5, height=4, dpi=100) 906 | # self.setCentralWidget(self.canvas) 907 | self.canvas.fig.clf() 908 | self.canvas.axes = self.canvas.fig.add_subplot(111) 909 | # self.canvas.axes.cla() 910 | 911 | if self.t_length.text()=='': 912 | self.plotwindow_length= self.t[-1] 913 | self.plotwindow_startsecond=0 914 | else: 915 | self.plotwindow_length=float( self.t_length.text() ) 916 | if self.t[-1](self.fs/2): 923 | y2=(self.fs/2) 924 | t1=self.plotwindow_startsecond 925 | t2=self.plotwindow_startsecond+self.plotwindow_length 926 | 927 | # if self.t_length.text=='': 928 | # t2=self.t[-1] 929 | # else: 930 | # if self.t[-1]=self.plotwindow_startsecond) & (tt<(self.plotwindow_startsecond+self.plotwindow_length)) 937 | # ix_f=(ff>=y1) & (ff=t1) & (self.t=y1) & (self.f0: 992 | ix=(self.annotation['t1'] > (np.array(self.time).astype('datetime64[ns]')+pd.Timedelta(self.plotwindow_startsecond, unit="s") ) ) & \ 993 | (self.annotation['t1'] < (np.array(self.time).astype('datetime64[ns]')+pd.Timedelta(self.plotwindow_startsecond+self.plotwindow_length, unit="s") ) ) &\ 994 | (self.annotation['audiofilename'] == self.current_audiopath ) 995 | if np.sum(ix)>0: 996 | ix=np.where(ix)[0] 997 | print('ix is') 998 | print(ix) 999 | for ix_x in ix: 1000 | a= pd.DataFrame([self.annotation.iloc[ix_x,:] ]) 1001 | print(a) 1002 | plot_annotation_box(a) 1003 | 1004 | # plot detections 1005 | cmap = plt.cm.get_cmap('cool') 1006 | if self.detectiondf.shape[0]>0: 1007 | for i in range(self.detectiondf.shape[0]): 1008 | 1009 | insidewindow=(self.detectiondf.loc[i,'t-1'] > self.plotwindow_startsecond ) & (self.detectiondf.loc[i,'t-2'] < (self.plotwindow_startsecond+self.plotwindow_length) ) &\ 1010 | (self.detectiondf.loc[i,'audiofilename'] == self.current_audiopath ) 1011 | 1012 | scoremin=self.detectiondf['score'].min() 1013 | scoremax=self.detectiondf['score'].max() 1014 | 1015 | if (self.detectiondf.loc[i,'score']>=0.01) & insidewindow: 1016 | 1017 | xx1=self.detectiondf.loc[i,'t-1'] 1018 | xx2=self.detectiondf.loc[i,'t-2'] 1019 | yy1=self.detectiondf.loc[i,'f-1'] 1020 | yy2=self.detectiondf.loc[i,'f-2'] 1021 | scorelabel=str(np.round(self.detectiondf.loc[i,'score'],2)) 1022 | snorm=(self.detectiondf.loc[i,'score']-scoremin) / (scoremax-scoremin) 1023 | scorecolor = cmap(snorm) 1024 | 1025 | line_x=[xx2,xx1,xx1,xx2,xx2] 1026 | line_y=[yy1,yy1,yy2,yy2,yy1] 1027 | 1028 | xmin=np.min([xx1,xx2]) 1029 | ymax=np.max([yy1,yy2]) 1030 | self.canvas.axes.plot(line_x,line_y,'-',color=scorecolor,linewidth=.75) 1031 | self.canvas.axes.text(xmin,ymax,scorelabel,size=5,color=scorecolor) 1032 | 1033 | 1034 | 1035 | self.canvas.axes.set_ylim([y1,y2]) 1036 | self.canvas.axes.set_xlim([t1,t2]) 1037 | 1038 | 1039 | self.canvas.fig.tight_layout() 1040 | toggle_selector.RS=RectangleSelector(self.canvas.axes, box_select_callback, 1041 | drawtype='box', useblit=False, 1042 | button=[1], # disable middle button 1043 | interactive=False,rectprops=dict(facecolor="blue", edgecolor="black", alpha=0.1, fill=True)) 1044 | 1045 | 1046 | self.canvas.draw() 1047 | self.cid1=self.canvas.fig.canvas.mpl_connect('button_press_event', onclick) 1048 | 1049 | 1050 | def export_zoomed_sgram_as_csv(): 1051 | if self.filecounter>=0: 1052 | 1053 | # filter out background 1054 | spectrog = 10*np.log10(self.Sxx ) 1055 | rectime= pd.to_timedelta( self.t ,'s') 1056 | spg=pd.DataFrame(np.transpose(spectrog),index=rectime) 1057 | bg=spg.resample('3min').mean().copy() 1058 | bg=bg.resample('1s').interpolate(method='time') 1059 | bg= bg.reindex(rectime,method='nearest') 1060 | background=np.transpose(bg.values) 1061 | z=spectrog-background 1062 | 1063 | 1064 | self.f_limits=self.canvas.axes.get_ylim() 1065 | self.t_limits=self.canvas.axes.get_xlim() 1066 | y1=int(self.f_limits[0]) 1067 | y2=int(self.f_limits[1]) 1068 | t1=self.t_limits[0] 1069 | t2=self.t_limits[1] 1070 | 1071 | 1072 | ix_time=np.where( (self.t>=t1) & (self.t=y1) & (self.f0: 1083 | if savename[-4:]!='.csv': 1084 | savename=savename[0]+'.csv' 1085 | sgram.to_csv(savename) 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | def box_select_callback(eclick, erelease): 1092 | 1093 | x1, y1 = eclick.xdata, eclick.ydata 1094 | x2, y2 = erelease.xdata, erelease.ydata 1095 | 1096 | x1 =self.time + pd.to_timedelta( x1 , unit='s') 1097 | x2 =self.time + pd.to_timedelta( x2 , unit='s') 1098 | 1099 | # sort to increasing values 1100 | t1=np.min([x1,x2]) 1101 | t2=np.max([x1,x2]) 1102 | f1=np.min([y1,y2]) 1103 | f2=np.max([y1,y2]) 1104 | 1105 | if self.bg.checkedId()==-1: 1106 | c_label='' 1107 | else: 1108 | c_label=eval( 'self.an_'+str(self.bg.checkedId())+'.text()' ) 1109 | 1110 | # a=pd.DataFrame(columns=['t1','t2','f1','f2','label']) 1111 | # a.iloc[0,:]=np.array([x1,x2,y1,y2,c_label ]) 1112 | a=pd.DataFrame({'t1': pd.Series(t1,dtype='datetime64[ns]'), 1113 | 't2': pd.Series(t2,dtype='datetime64[ns]'), 1114 | 'f1': pd.Series(f1,dtype='float'), 1115 | 'f2': pd.Series(f2,dtype='float'), 1116 | 'label': pd.Series(c_label,dtype='object') , 1117 | 'audiofilename': self.current_audiopath }) 1118 | 1119 | # a=pd.DataFrame(data=[ [x1,x2,y1,y2,c_label ] ],columns=['t1','t2','f1','f2','label']) 1120 | # print('a:') 1121 | # print(a.dtypes) 1122 | # self.annotation.append(a, ignore_index = True) 1123 | self.annotation=pd.concat([ self.annotation ,a ] , ignore_index = True) 1124 | 1125 | # print(self.annotation.dtypes) 1126 | plot_annotation_box(a) 1127 | 1128 | def toggle_selector(event): 1129 | # toggle_selector.RS.set_active(True) 1130 | print('select') 1131 | # if event.key == 't': 1132 | # if toggle_selector.RS.active: 1133 | # print(' RectangleSelector deactivated.') 1134 | # toggle_selector.RS.set_active(False) 1135 | # else: 1136 | # print(' RectangleSelector activated.') 1137 | # toggle_selector.RS.set_active(True) 1138 | 1139 | 1140 | 1141 | def onclick(event): 1142 | if event.button==3: 1143 | self.annotation=self.annotation.head(-1) 1144 | # print(self.annotation) 1145 | plot_spectrogram() 1146 | 1147 | def end_of_filelist_warning(): 1148 | msg_listend = QtWidgets.QMessageBox() 1149 | msg_listend.setIcon(QtWidgets.QMessageBox.Information) 1150 | msg_listend.setText("End of file list reached!") 1151 | msg_listend.exec_() 1152 | 1153 | def plot_next_spectro(): 1154 | if len(self.filenames)>0: 1155 | print('old filecounter is: '+str(self.filecounter)) 1156 | 1157 | if self.t_length.text()=='' or self.t[-1]self.filenames.shape[0]-1: 1160 | self.filecounter=self.filenames.shape[0]-1 1161 | print('That was it') 1162 | end_of_filelist_warning() 1163 | self.plotwindow_length= self.t[-1] 1164 | self.plotwindow_startsecond=0 1165 | # new file 1166 | # self.filecounter=self.filecounter+1 1167 | read_wav() 1168 | plot_spectrogram() 1169 | 1170 | else: 1171 | self.plotwindow_length=float( self.t_length.text() ) 1172 | self.plotwindow_startsecond=self.plotwindow_startsecond + self.plotwindow_length 1173 | 1174 | print( [self.plotwindow_startsecond, self.t[-1] ] ) 1175 | 1176 | if self.plotwindow_startsecond > self.t[-1]: 1177 | #save log 1178 | if checkbox_log.isChecked(): 1179 | 1180 | tt= self.annotation['t1'] - self.time 1181 | t_in_seconds=np.array( tt.values*1e-9 ,dtype='float16') 1182 | reclength=np.array( self.t[-1] ,dtype='float16') 1183 | 1184 | ix=(t_in_seconds>0) & (t_in_seconds=self.filenames.shape[0]-1: 1194 | self.filecounter=self.filenames.shape[0]-1 1195 | print('That was it') 1196 | end_of_filelist_warning() 1197 | read_wav() 1198 | self.plotwindow_startsecond=0 1199 | plot_spectrogram() 1200 | else: 1201 | plot_spectrogram() 1202 | 1203 | 1204 | 1205 | def plot_previous_spectro(): 1206 | if len(self.filenames)>0: 1207 | print('old filecounter is: '+str(self.filecounter)) 1208 | 1209 | if self.t_length.text()=='' or self.t[-1]=self.filenames.shape[0]-1: 1241 | # print('That was it') 1242 | # self.canvas.fig.clf() 1243 | # self.canvas.axes = self.canvas.fig.add_subplot(111) 1244 | # self.canvas.axes.set_title('That was it') 1245 | # self.canvas.draw() 1246 | 1247 | read_wav() 1248 | # self.plotwindow_startsecond=0 1249 | plot_spectrogram() 1250 | else: 1251 | plot_spectrogram() 1252 | 1253 | 1254 | 1255 | def new_fft_size_selected(): 1256 | read_wav() 1257 | plot_spectrogram() 1258 | self.fft_size.currentIndexChanged.connect(new_fft_size_selected) 1259 | 1260 | self.colormap_plot.currentIndexChanged.connect( plot_spectrogram) 1261 | self.checkbox_background.stateChanged.connect(plot_spectrogram ) 1262 | self.checkbox_logscale.stateChanged.connect(plot_spectrogram ) 1263 | 1264 | 1265 | # self.canvas.fig.canvas.mpl_connect('button_press_event', onclick) 1266 | # self.canvas.fig.canvas.mpl_connect('key_press_event', toggle_selector) 1267 | 1268 | 1269 | 1270 | # QtGui.QShortcut(QtCore.Qt.Key_Right, MainWindow, plot_next_spectro()) 1271 | # self.msgSc = QShortcut(QKeySequence(u"\u2192"), self) 1272 | # self.msgSc.activated.connect(plot_next_spectro) 1273 | # button_plot_spectro=QtWidgets.QPushButton('Next spectrogram-->') 1274 | # button_plot_spectro.clicked.connect(plot_next_spectro) 1275 | 1276 | # button_plot_prevspectro=QtWidgets.QPushButton('<--Previous spectrogram') 1277 | # button_plot_prevspectro.clicked.connect(plot_previous_spectro) 1278 | 1279 | button_save=QtWidgets.QPushButton('Save annotation csv') 1280 | def func_savecsv(): 1281 | options = QtWidgets.QFileDialog.Options() 1282 | savename = QtWidgets.QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()", r"C:\Users\a5278\Documents\passive_acoustics\detector_delevopment\detector_validation_subset", "csv files (*.csv)",options=options) 1283 | print('location is:' + savename[0]) 1284 | if len(savename[0])>0: 1285 | self.annotation.to_csv(savename[0]) 1286 | button_save.clicked.connect(func_savecsv) 1287 | 1288 | button_quit=QtWidgets.QPushButton('Quit') 1289 | button_quit.clicked.connect(QtWidgets.QApplication.instance().quit) 1290 | 1291 | 1292 | 1293 | def func_logging(): 1294 | if checkbox_log.isChecked(): 1295 | print('logging') 1296 | msg = QtWidgets.QMessageBox() 1297 | msg.setIcon(QtWidgets.QMessageBox.Information) 1298 | msg.setText("Overwrite existing log files?") 1299 | msg.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) 1300 | returnValue = msg.exec() 1301 | if returnValue == QtWidgets.QMessageBox.No: 1302 | 1303 | ix_delete=[] 1304 | i=0 1305 | for fn in self.filenames: 1306 | logpath=fn[:-4]+'_log.csv' 1307 | # print(logpath) 1308 | if os.path.isfile( logpath): 1309 | ix_delete.append(i) 1310 | i=i+1 1311 | # print(ix_delete) 1312 | 1313 | self.filenames=np.delete(self.filenames,ix_delete) 1314 | print('Updated filelist:') 1315 | print(self.filenames) 1316 | 1317 | 1318 | 1319 | checkbox_log=QtWidgets.QCheckBox('Real-time Logging') 1320 | checkbox_log.toggled.connect(func_logging) 1321 | 1322 | button_plot_all_spectrograms=QtWidgets.QPushButton('Plot all spectrograms') 1323 | def plot_all_spectrograms(): 1324 | 1325 | msg = QtWidgets.QMessageBox() 1326 | msg.setIcon(QtWidgets.QMessageBox.Information) 1327 | msg.setText("Are you sure you want to plot "+ str(self.filenames.shape[0]) +" spectrograms?") 1328 | msg.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) 1329 | returnValue = msg.exec() 1330 | 1331 | if returnValue == QtWidgets.QMessageBox.Yes: 1332 | for audiopath in self.filenames: 1333 | 1334 | if self.filename_timekey.text()=='': 1335 | self.time= dt.datetime(2000,1,1,0,0,0) 1336 | else: 1337 | try: 1338 | self.time= dt.datetime.strptime( audiopath.split('/')[-1], self.filename_timekey.text() ) 1339 | except: 1340 | self.time= dt.datetime(2000,1,1,0,0,0) 1341 | 1342 | self.x,self.fs = sf.read(audiopath,dtype='int16') 1343 | print('open new file: '+audiopath) 1344 | 1345 | db_saturation=float( self.db_saturation.text() ) 1346 | x=self.x/32767 1347 | p =np.power(10,(db_saturation/20))*x #convert data.signal to uPa 1348 | 1349 | fft_size=int( self.fft_size.currentText() ) 1350 | fft_overlap=float( self.fft_overlap.currentText() ) 1351 | 1352 | 1353 | self.f, self.t, self.Sxx = signal.spectrogram(p, self.fs, window='hamming',nperseg=fft_size,noverlap=fft_size*fft_overlap) 1354 | 1355 | self.plotwindow_startsecond=0 1356 | 1357 | plot_spectrogram() 1358 | self.canvas.axes.set_title(audiopath.split('/')[-1]) 1359 | self.canvas.fig.savefig( audiopath[:-4]+'.jpg',dpi=150 ) 1360 | 1361 | 1362 | button_plot_all_spectrograms.clicked.connect(plot_all_spectrograms) 1363 | 1364 | button_draw_shape=QtWidgets.QPushButton('Draw shape') 1365 | def func_draw_shape_plot(): 1366 | if self.filecounter>=0: 1367 | self.canvas.fig.clf() 1368 | self.canvas.axes = self.canvas.fig.add_subplot(111) 1369 | if self.t_length.text()=='': 1370 | self.plotwindow_length= self.t[-1] 1371 | self.plotwindow_startsecond=0 1372 | else: 1373 | self.plotwindow_length=float( self.t_length.text() ) 1374 | if self.t[-1](self.fs/2): 1381 | y2=(self.fs/2) 1382 | t1=self.plotwindow_startsecond 1383 | t2=self.plotwindow_startsecond+self.plotwindow_length 1384 | 1385 | ix_time=np.where( (self.t>=t1) & (self.t=y1) & (self.f0: 1424 | ix=(self.annotation['t1'] > (np.array(self.time).astype('datetime64[ns]')+pd.Timedelta(self.plotwindow_startsecond, unit="s") ) ) & (self.annotation['t1'] < (np.array(self.time).astype('datetime64[ns]')+pd.Timedelta(self.plotwindow_startsecond+self.plotwindow_length, unit="s") ) ) 1425 | if np.sum(ix)>0: 1426 | ix=np.where(ix)[0] 1427 | print('ix is') 1428 | print(ix) 1429 | for ix_x in ix: 1430 | a= pd.DataFrame([self.annotation.iloc[ix_x,:] ]) 1431 | print(a) 1432 | plot_annotation_box(a) 1433 | 1434 | if self.t_limits==None: 1435 | self.canvas.axes.set_ylim([y1,y2]) 1436 | self.canvas.axes.set_xlim([t1,t2]) 1437 | else: 1438 | self.canvas.axes.set_ylim(self.f_limits) 1439 | self.canvas.axes.set_xlim(self.t_limits) 1440 | 1441 | 1442 | 1443 | self.canvas.fig.tight_layout() 1444 | 1445 | self.canvas.axes.plot(self.draw_x,self.draw_y,'.-g') 1446 | 1447 | self.canvas.draw() 1448 | self.cid2=self.canvas.fig.canvas.mpl_connect('button_press_event', onclick_draw) 1449 | 1450 | def onclick_draw(event): 1451 | # print('%s click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % 1452 | # ('double' if event.dblclick else 'single', event.button, 1453 | # event.x, event.y, event.xdata, event.ydata)) 1454 | if event.button==1 & event.dblclick: 1455 | self.draw_x=self.draw_x.append( pd.Series(event.xdata) ,ignore_index=True ) 1456 | self.draw_y=self.draw_y.append( pd.Series(event.ydata) ,ignore_index=True ) 1457 | self.f_limits=self.canvas.axes.get_ylim() 1458 | self.t_limits=self.canvas.axes.get_xlim() 1459 | 1460 | line = self.line_2.pop(0) 1461 | line.remove() 1462 | self.line_2 =self.canvas.axes.plot(self.draw_x,self.draw_y,'.-g') 1463 | self.canvas.draw() 1464 | 1465 | # func_draw_shape_plot() 1466 | 1467 | if event.button==3: 1468 | self.draw_x=self.draw_x.head(-1) 1469 | self.draw_y=self.draw_y.head(-1) 1470 | self.f_limits=self.canvas.axes.get_ylim() 1471 | self.t_limits=self.canvas.axes.get_xlim() 1472 | # func_draw_shape_plot() 1473 | line = self.line_2.pop(0) 1474 | line.remove() 1475 | self.line_2 =self.canvas.axes.plot(self.draw_x,self.draw_y,'.-g') 1476 | self.canvas.draw() 1477 | 1478 | # func_draw_shape_plot() 1479 | 1480 | def func_draw_shape_exit(): 1481 | print('save shape' + str(self.draw_x.shape)) 1482 | self.canvas.fig.canvas.mpl_disconnect(self.cid2) 1483 | plot_spectrogram() 1484 | print('back to boxes') 1485 | ## deactive shortcut 1486 | self.drawexitm.setEnabled(False) 1487 | 1488 | if self.draw_x.shape[0]>0: 1489 | options = QtWidgets.QFileDialog.Options() 1490 | savename = QtWidgets.QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()", "csv files (*.csv)",options=options) 1491 | if len(savename[0])>0: 1492 | if savename[-4:]!='.csv': 1493 | savename=savename[0]+'.csv' 1494 | # drawcsv=pd.concat([self.draw_x,self.draw_y],axis=1) 1495 | drawcsv=pd.DataFrame(columns=['Time_in_s','Frequency_in_Hz']) 1496 | drawcsv['Time_in_s']=self.draw_x 1497 | drawcsv['Frequency_in_Hz']=self.draw_y 1498 | drawcsv.to_csv(savename) 1499 | 1500 | 1501 | def func_draw_shape(): 1502 | msg = QtWidgets.QMessageBox() 1503 | msg.setIcon(QtWidgets.QMessageBox.Information) 1504 | msg.setText("Add points with double left click.\nRemove latest point with single right click. \nExit draw mode and save CSV by pushing enter") 1505 | msg.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) 1506 | returnValue = msg.exec() 1507 | if returnValue == QtWidgets.QMessageBox.Ok: 1508 | print('drawing') 1509 | self.draw_x=pd.Series(dtype='float') 1510 | self.draw_y=pd.Series(dtype='float') 1511 | self.f_limits=self.canvas.axes.get_ylim() 1512 | self.t_limits=self.canvas.axes.get_xlim() 1513 | self.canvas.fig.canvas.mpl_disconnect(self.cid1) 1514 | self.cid2=self.canvas.fig.canvas.mpl_connect('button_press_event', onclick_draw) 1515 | self.line_2 =self.canvas.axes.plot(self.draw_x,self.draw_y,'.-g') 1516 | func_draw_shape_plot() 1517 | self.drawexitm = QtWidgets.QShortcut(QtCore.Qt.Key_Return, self) 1518 | self.drawexitm.activated.connect(func_draw_shape_exit) 1519 | 1520 | button_draw_shape.clicked.connect(func_draw_shape) 1521 | 1522 | ####### play audio 1523 | button_play_audio=QtWidgets.QPushButton('Play/Stop [spacebar]') 1524 | def func_playaudio(): 1525 | if self.filecounter>=0: 1526 | if not hasattr(self, "play_obj"): 1527 | new_rate = 32000 1528 | 1529 | t_limits=self.canvas.axes.get_xlim() 1530 | f_limits=list(self.canvas.axes.get_ylim()) 1531 | if f_limits[1]>=(self.fs/2): 1532 | f_limits[1]= self.fs/2-10 1533 | print(t_limits) 1534 | print(f_limits) 1535 | 1536 | x_select=self.x[int(t_limits[0]*self.fs) : int(t_limits[1]*self.fs) ] 1537 | 1538 | sos=signal.butter(8, f_limits, 'bandpass', fs=self.fs, output='sos') 1539 | x_select = signal.sosfilt(sos, x_select) 1540 | 1541 | number_of_samples = round(len(x_select) * (float(new_rate)/ float(self.playbackspeed.currentText())) / self.fs) 1542 | x_resampled = np.array(signal.resample(x_select, number_of_samples)).astype('int') 1543 | 1544 | #normalize sound level 1545 | maximum_x=32767*0.8 1546 | old_max=np.max(np.abs([x_resampled.min(),x_resampled.max()])) 1547 | x_resampled=x_resampled * (maximum_x/old_max) 1548 | x_resampled = x_resampled.astype(np.int16) 1549 | 1550 | print( [x_resampled.min(),x_resampled.max()] ) 1551 | wave_obj = sa.WaveObject(x_resampled, 1, 2, new_rate) 1552 | self.play_obj = wave_obj.play() 1553 | else: 1554 | if self.play_obj.is_playing(): 1555 | sa.stop_all() 1556 | else: 1557 | new_rate = 32000 1558 | t_limits=self.canvas.axes.get_xlim() 1559 | f_limits=list(self.canvas.axes.get_ylim()) 1560 | if f_limits[1]>=(self.fs/2): 1561 | f_limits[1]= self.fs/2-10 1562 | print(t_limits) 1563 | print(f_limits) 1564 | 1565 | x_select=self.x[int(t_limits[0]*self.fs) : int(t_limits[1]*self.fs) ] 1566 | sos=signal.butter(8, f_limits, 'bandpass', fs=self.fs, output='sos') 1567 | x_select = signal.sosfilt(sos, x_select) 1568 | 1569 | # number_of_samples = round(len(x_select) * float(new_rate) / self.fs) 1570 | number_of_samples = round(len(x_select) * (float(new_rate)/ float(self.playbackspeed.currentText())) / self.fs) 1571 | 1572 | x_resampled = np.array(signal.resample(x_select, number_of_samples)).astype('int') 1573 | #normalize sound level 1574 | maximum_x=32767*0.8 1575 | old_max=np.max(np.abs([x_resampled.min(),x_resampled.max()])) 1576 | x_resampled=x_resampled * (maximum_x/old_max) 1577 | x_resampled = x_resampled.astype(np.int16) 1578 | print( [x_resampled.min(),x_resampled.max()] ) 1579 | wave_obj = sa.WaveObject(x_resampled, 1, 2, new_rate) 1580 | self.play_obj = wave_obj.play() 1581 | 1582 | button_play_audio.clicked.connect(func_playaudio) 1583 | 1584 | button_save_audio=QtWidgets.QPushButton('Export selected audio') 1585 | def func_saveaudio(): 1586 | if self.filecounter>=0: 1587 | options = QtWidgets.QFileDialog.Options() 1588 | savename = QtWidgets.QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()", "wav files (*.wav)",options=options) 1589 | if len(savename[0])>0: 1590 | savename=savename[0] 1591 | new_rate = 32000 1592 | 1593 | t_limits=self.canvas.axes.get_xlim() 1594 | f_limits=list(self.canvas.axes.get_ylim()) 1595 | if f_limits[1]>=(self.fs/2): 1596 | f_limits[1]= self.fs/2-10 1597 | print(t_limits) 1598 | print(f_limits) 1599 | x_select=self.x[int(t_limits[0]*self.fs) : int(t_limits[1]*self.fs) ] 1600 | 1601 | sos=signal.butter(8, f_limits, 'bandpass', fs=self.fs, output='sos') 1602 | x_select = signal.sosfilt(sos, x_select) 1603 | 1604 | number_of_samples = round(len(x_select) * (float(new_rate)/ float(self.playbackspeed.currentText())) / self.fs) 1605 | x_resampled = np.array(signal.resample(x_select, number_of_samples)).astype('int') 1606 | #normalize sound level 1607 | maximum_x=32767*0.8 1608 | old_max=np.max(np.abs([x_resampled.min(),x_resampled.max()])) 1609 | x_resampled=x_resampled * (maximum_x/old_max) 1610 | x_resampled = x_resampled.astype(np.int16) 1611 | 1612 | if savename[-4:]!='.wav': 1613 | savename=savename+'.wav' 1614 | wav.write(savename, new_rate, x_resampled) 1615 | button_save_audio.clicked.connect(func_saveaudio) 1616 | 1617 | # button_save_video=QtWidgets.QPushButton('Export video') 1618 | # def func_save_video(): 1619 | # if self.filecounter>=0: 1620 | # savename = QtWidgets.QFileDialog.getSaveFileName(self,"QFileDialog.getSaveFileName()", "video files (*.mp4)") 1621 | # if len(savename[0])>0: 1622 | # savename=savename[0] 1623 | # new_rate = 32000 1624 | 1625 | # t_limits=self.canvas.axes.get_xlim() 1626 | # f_limits=list(self.canvas.axes.get_ylim()) 1627 | # if f_limits[1]>=(self.fs/2): 1628 | # f_limits[1]= self.fs/2-10 1629 | # print(t_limits) 1630 | # print(f_limits) 1631 | # x_select=self.x[int(t_limits[0]*self.fs) : int(t_limits[1]*self.fs) ] 1632 | 1633 | # sos=signal.butter(8, f_limits, 'bandpass', fs=self.fs, output='sos') 1634 | # x_select = signal.sosfilt(sos, x_select) 1635 | 1636 | # number_of_samples = round(len(x_select) * (float(new_rate)/ float(self.playbackspeed.currentText())) / self.fs) 1637 | # x_resampled = np.array(signal.resample(x_select, number_of_samples)).astype('int') 1638 | # #normalize sound level 1639 | # maximum_x=32767*0.8 1640 | # old_max=np.max(np.abs([x_resampled.min(),x_resampled.max()])) 1641 | # x_resampled=x_resampled * (maximum_x/old_max) 1642 | # x_resampled = x_resampled.astype(np.int16) 1643 | 1644 | # if savename[:-4]=='.wav': 1645 | # savename=savename[:-4] 1646 | # if savename[:-4]=='.mp4': 1647 | # savename=savename[:-4] 1648 | # wav.write(savename+'.wav', new_rate, x_resampled) 1649 | 1650 | # # self.f_limits=self.canvas.axes.get_ylim() 1651 | # # self.t_limits=self.canvas.axes.get_xlim() 1652 | 1653 | # audioclip = AudioFileClip(savename+'.wav') 1654 | # duration=audioclip.duration 1655 | # # func_draw_shape_plot() 1656 | 1657 | # self.canvas.axes.set_title(None) 1658 | # # self.canvas.axes.set_ylim(f_limits) 1659 | # # self.canvas.axes.set_xlim(t_limits) 1660 | # self.line_2=self.canvas.axes.plot([t_limits[0] ,t_limits[0] ],f_limits,'-k') 1661 | # def make_frame(x): 1662 | # s=t_limits[1] - t_limits[0] 1663 | # xx= x/duration * s + t_limits[0] 1664 | # line = self.line_2.pop(0) 1665 | # line.remove() 1666 | # self.line_2=self.canvas.axes.plot([xx,xx],f_limits,'-k') 1667 | 1668 | 1669 | # return mplfig_to_npimage(self.canvas.fig) 1670 | 1671 | # animation = VideoClip(make_frame, duration = duration ) 1672 | # animation = animation.set_audio(audioclip) 1673 | # animation.write_videofile(savename+".mp4",fps=24,preset='fast') 1674 | 1675 | # plot_spectrogram() 1676 | # # self.canvas.fig.canvas.mpl_disconnect(self.cid2) 1677 | 1678 | # button_save_video.clicked.connect(func_save_video) 1679 | 1680 | ############# menue 1681 | menuBar = self.menuBar() 1682 | 1683 | # Creating menus using a title 1684 | openMenu = menuBar.addAction("Open files") 1685 | openMenu.triggered.connect(openfilefunc) 1686 | 1687 | 1688 | exportMenu = menuBar.addMenu("Export") 1689 | e1 =exportMenu.addAction("Spectrogram as .wav file") 1690 | e1.triggered.connect(func_saveaudio) 1691 | # e2 =exportMenu.addAction("Spectrogram as animated video") 1692 | # e2.triggered.connect(func_save_video) 1693 | e3 =exportMenu.addAction("Spectrogram as .csv table") 1694 | e3.triggered.connect(export_zoomed_sgram_as_csv) 1695 | e4 =exportMenu.addAction("All files as spectrogram images") 1696 | e4.triggered.connect(plot_all_spectrograms) 1697 | e5 =exportMenu.addAction("Annotations as .csv table") 1698 | e5.triggered.connect(func_savecsv) 1699 | e6 =exportMenu.addAction("Automatic detections as .csv table") 1700 | e6.triggered.connect(export_automatic_detector_shapematching) 1701 | 1702 | drawMenu = menuBar.addAction("Draw") 1703 | drawMenu.triggered.connect(func_draw_shape) 1704 | 1705 | 1706 | autoMenu = menuBar.addMenu("Automatic detection") 1707 | a1 =autoMenu.addAction("Shapematching on current file") 1708 | a1.triggered.connect(automatic_detector_shapematching) 1709 | a2 =autoMenu.addAction("Spectrogram correlation on current file") 1710 | a2.triggered.connect(automatic_detector_specgram_corr) 1711 | a3 =autoMenu.addAction("Shapematching on all files") 1712 | a3.triggered.connect(automatic_detector_shapematching_allfiles) 1713 | a4 =autoMenu.addAction("Spectrogram correlation on all files") 1714 | a4.triggered.connect(automatic_detector_specgram_corr_allfiles) 1715 | 1716 | quitMenu = menuBar.addAction("Quit") 1717 | quitMenu.triggered.connect(QtWidgets.QApplication.instance().quit) 1718 | 1719 | ################# 1720 | 1721 | ######## layout 1722 | outer_layout = QtWidgets.QVBoxLayout() 1723 | 1724 | # top_layout = QtWidgets.QHBoxLayout() 1725 | 1726 | # top_layout.addWidget(openfilebutton) 1727 | 1728 | # top_layout.addWidget(button_plot_prevspectro) 1729 | # top_layout.addWidget(button_plot_spectro) 1730 | # # top_layout.addWidget(button_plot_all_spectrograms) 1731 | # top_layout.addWidget(button_play_audio) 1732 | # top_layout.addWidget(QtWidgets.QLabel('Playback speed:')) 1733 | # top_layout.addWidget(self.playbackspeed) 1734 | # # top_layout.addWidget(button_save_audio) 1735 | # # top_layout.addWidget(button_save_video) 1736 | # # top_layout.addWidget(button_draw_shape) 1737 | 1738 | # top_layout.addWidget(button_save) 1739 | # top_layout.addWidget(button_quit) 1740 | 1741 | top2_layout = QtWidgets.QHBoxLayout() 1742 | 1743 | # self.f_min = QtWidgets.QLineEdit(self) 1744 | # self.f_max = QtWidgets.QLineEdit(self) 1745 | # self.t_start = QtWidgets.QLineEdit(self) 1746 | # self.t_end = QtWidgets.QLineEdit(self) 1747 | # self.fft_size = QtWidgets.QLineEdit(self) 1748 | top2_layout.addWidget(checkbox_log) 1749 | top2_layout.addWidget(self.checkbox_logscale) 1750 | top2_layout.addWidget(self.checkbox_background) 1751 | 1752 | top2_layout.addWidget(QtWidgets.QLabel('Timestamp:')) 1753 | top2_layout.addWidget(self.filename_timekey) 1754 | top2_layout.addWidget(QtWidgets.QLabel('f_min[Hz]:')) 1755 | top2_layout.addWidget(self.f_min) 1756 | top2_layout.addWidget(QtWidgets.QLabel('f_max[Hz]:')) 1757 | top2_layout.addWidget(self.f_max) 1758 | top2_layout.addWidget(QtWidgets.QLabel('Spec. length [sec]:')) 1759 | top2_layout.addWidget(self.t_length) 1760 | 1761 | # top2_layout.addWidget(QtWidgets.QLabel('fft_size[bits]:')) 1762 | # top2_layout.addWidget(self.fft_size) 1763 | # top2_layout.addWidget(QtWidgets.QLabel('fft_overlap[0-1]:')) 1764 | # top2_layout.addWidget(self.fft_overlap) 1765 | 1766 | 1767 | 1768 | # top2_layout.addWidget(QtWidgets.QLabel('Colormap:')) 1769 | # top2_layout.addWidget( self.colormap_plot) 1770 | 1771 | 1772 | top2_layout.addWidget(QtWidgets.QLabel('Saturation dB:')) 1773 | top2_layout.addWidget(self.db_saturation) 1774 | 1775 | top2_layout.addWidget(QtWidgets.QLabel('dB min:')) 1776 | top2_layout.addWidget(self.db_vmin) 1777 | top2_layout.addWidget(QtWidgets.QLabel('dB max:')) 1778 | top2_layout.addWidget(self.db_vmax) 1779 | 1780 | 1781 | 1782 | # annotation label area 1783 | top3_layout = QtWidgets.QHBoxLayout() 1784 | top3_layout.addWidget(QtWidgets.QLabel('Annotation labels:')) 1785 | 1786 | 1787 | self.checkbox_an_1=QtWidgets.QCheckBox() 1788 | top3_layout.addWidget(self.checkbox_an_1) 1789 | self.an_1 = QtWidgets.QLineEdit(self) 1790 | top3_layout.addWidget(self.an_1) 1791 | self.an_1.setText('') 1792 | 1793 | self.checkbox_an_2=QtWidgets.QCheckBox() 1794 | top3_layout.addWidget(self.checkbox_an_2) 1795 | self.an_2 = QtWidgets.QLineEdit(self) 1796 | top3_layout.addWidget(self.an_2) 1797 | self.an_2.setText('') 1798 | 1799 | self.checkbox_an_3=QtWidgets.QCheckBox() 1800 | top3_layout.addWidget(self.checkbox_an_3) 1801 | self.an_3 = QtWidgets.QLineEdit(self) 1802 | top3_layout.addWidget(self.an_3) 1803 | self.an_3.setText('') 1804 | 1805 | self.checkbox_an_4=QtWidgets.QCheckBox() 1806 | top3_layout.addWidget(self.checkbox_an_4) 1807 | self.an_4 = QtWidgets.QLineEdit(self) 1808 | top3_layout.addWidget(self.an_4) 1809 | self.an_4.setText('') 1810 | 1811 | self.checkbox_an_5=QtWidgets.QCheckBox() 1812 | top3_layout.addWidget(self.checkbox_an_5) 1813 | self.an_5 = QtWidgets.QLineEdit(self) 1814 | top3_layout.addWidget(self.an_5) 1815 | self.an_5.setText('') 1816 | 1817 | self.checkbox_an_6=QtWidgets.QCheckBox() 1818 | top3_layout.addWidget(self.checkbox_an_6) 1819 | self.an_6 = QtWidgets.QLineEdit(self) 1820 | top3_layout.addWidget(self.an_6) 1821 | self.an_6.setText('') 1822 | 1823 | # self.checkbox_an_7=QtWidgets.QCheckBox() 1824 | # top3_layout.addWidget(self.checkbox_an_7) 1825 | # self.an_7 = QtWidgets.QLineEdit(self) 1826 | # top3_layout.addWidget(self.an_7) 1827 | # self.an_7.setText('') 1828 | 1829 | 1830 | # button_export_sgramcsv=QtWidgets.QPushButton('Export spectrog. csv') 1831 | # button_export_sgramcsv.clicked.connect(export_zoomed_sgram_as_csv) 1832 | # top3_layout.addWidget(button_export_sgramcsv) 1833 | 1834 | # button_autodetect_shape=QtWidgets.QPushButton('Shapematching') 1835 | # button_autodetect_shape.clicked.connect(automatic_detector_shapematching) 1836 | # top3_layout.addWidget(button_autodetect_shape) 1837 | 1838 | # button_autodetect_corr=QtWidgets.QPushButton('Spectrog. correlation') 1839 | # button_autodetect_corr.clicked.connect(automatic_detector_specgram_corr) 1840 | # top3_layout.addWidget(button_autodetect_corr) 1841 | 1842 | 1843 | # button_saveautodetect=QtWidgets.QPushButton('Export auto-detec.') 1844 | # button_saveautodetect.clicked.connect(export_automatic_detector_shapematching) 1845 | # top3_layout.addWidget(button_saveautodetect) 1846 | 1847 | 1848 | 1849 | # self.checkbox_an_8=QtWidgets.QCheckBox() 1850 | # top3_layout.addWidget(self.checkbox_an_8) 1851 | # self.an_8 = QtWidgets.QLineEdit(self) 1852 | # top3_layout.addWidget(self.an_8) 1853 | # self.an_8.setText('') 1854 | 1855 | self.bg = QtWidgets.QButtonGroup() 1856 | self.bg.addButton(self.checkbox_an_1,1) 1857 | self.bg.addButton(self.checkbox_an_2,2) 1858 | self.bg.addButton(self.checkbox_an_3,3) 1859 | self.bg.addButton(self.checkbox_an_4,4) 1860 | self.bg.addButton(self.checkbox_an_5,5) 1861 | self.bg.addButton(self.checkbox_an_6,6) 1862 | # self.bg.addButton(self.checkbox_an_7,7) 1863 | # self.bg.addButton(self.checkbox_an_8,8) 1864 | 1865 | 1866 | 1867 | 1868 | # combine layouts together 1869 | 1870 | plot_layout = QtWidgets.QVBoxLayout() 1871 | tnav = NavigationToolbar( self.canvas, self) 1872 | 1873 | toolbar = QtWidgets.QToolBar() 1874 | 1875 | 1876 | # toolbar.addAction('test') 1877 | # toolbar.addWidget(button_plot_prevspectro) 1878 | # toolbar.addWidget(button_plot_spectro) 1879 | 1880 | # b1=QtWidgets.QToolButton() 1881 | # b1.setText('<--Previous spectrogram') 1882 | # # b1.setStyleSheet("background-color: yellow; font-size: 18pt") 1883 | # b1.clicked.connect(plot_previous_spectro) 1884 | # toolbar.addWidget(b1) 1885 | 1886 | # b2=QtWidgets.QToolButton() 1887 | # b2.setText('Next spectrogram-->') 1888 | # b2.clicked.connect(plot_next_spectro) 1889 | # toolbar.addWidget(b2) 1890 | 1891 | # b3=QtWidgets.QToolButton() 1892 | # b3.setText('Play/Stop [spacebar]') 1893 | # b3.clicked.connect(func_playaudio) 1894 | # toolbar.addWidget(b3) 1895 | 1896 | button_plot_prevspectro=QtWidgets.QPushButton('<--Previous spectrogram') 1897 | button_plot_prevspectro.clicked.connect(plot_previous_spectro) 1898 | toolbar.addWidget(button_plot_prevspectro) 1899 | 1900 | ss=' ' 1901 | toolbar.addWidget(QtWidgets.QLabel(ss)) 1902 | 1903 | button_plot_spectro=QtWidgets.QPushButton('Next spectrogram-->') 1904 | button_plot_spectro.clicked.connect(plot_next_spectro) 1905 | toolbar.addWidget(button_plot_spectro) 1906 | 1907 | toolbar.addWidget(QtWidgets.QLabel(ss)) 1908 | 1909 | 1910 | toolbar.addWidget(button_play_audio) 1911 | toolbar.addWidget(QtWidgets.QLabel(ss)) 1912 | 1913 | toolbar.addWidget(QtWidgets.QLabel('Playback speed:')) 1914 | toolbar.addWidget(QtWidgets.QLabel(ss)) 1915 | toolbar.addWidget(self.playbackspeed) 1916 | toolbar.addWidget(QtWidgets.QLabel(ss)) 1917 | 1918 | toolbar.addSeparator() 1919 | toolbar.addWidget(QtWidgets.QLabel(ss)) 1920 | 1921 | 1922 | toolbar.addWidget(QtWidgets.QLabel('fft_size[bits]:')) 1923 | toolbar.addWidget(QtWidgets.QLabel(ss)) 1924 | toolbar.addWidget(self.fft_size) 1925 | toolbar.addWidget(QtWidgets.QLabel(ss)) 1926 | toolbar.addWidget(QtWidgets.QLabel('fft_overlap[0-1]:')) 1927 | toolbar.addWidget(QtWidgets.QLabel(ss)) 1928 | toolbar.addWidget(self.fft_overlap) 1929 | 1930 | toolbar.addWidget(QtWidgets.QLabel(ss)) 1931 | 1932 | 1933 | toolbar.addWidget(QtWidgets.QLabel('Colormap:')) 1934 | toolbar.addWidget(QtWidgets.QLabel(ss)) 1935 | toolbar.addWidget( self.colormap_plot) 1936 | toolbar.addWidget(QtWidgets.QLabel(ss)) 1937 | 1938 | toolbar.addSeparator() 1939 | 1940 | toolbar.addWidget(tnav) 1941 | 1942 | 1943 | plot_layout.addWidget(toolbar) 1944 | plot_layout.addWidget(self.canvas) 1945 | 1946 | # outer_layout.addLayout(top_layout) 1947 | outer_layout.addLayout(top2_layout) 1948 | outer_layout.addLayout(top3_layout) 1949 | 1950 | outer_layout.addLayout(plot_layout) 1951 | 1952 | # self.setLayout(outer_layout) 1953 | 1954 | # Create a placeholder widget to hold our toolbar and canvas. 1955 | widget = QtWidgets.QWidget() 1956 | widget.setLayout(outer_layout) 1957 | self.setCentralWidget(widget) 1958 | 1959 | #### hotkeys 1960 | self.msgSc1 = QtWidgets.QShortcut(QtCore.Qt.Key_Right, self) 1961 | self.msgSc1.activated.connect(plot_next_spectro) 1962 | self.msgSc2 = QtWidgets.QShortcut(QtCore.Qt.Key_Left, self) 1963 | self.msgSc2.activated.connect(plot_previous_spectro) 1964 | self.msgSc3 = QtWidgets.QShortcut(QtCore.Qt.Key_Space, self) 1965 | self.msgSc3.activated.connect(func_playaudio) 1966 | 1967 | #### 1968 | # layout = QtWidgets.QVBoxLayout() 1969 | # layout.addWidget(openfilebutton) 1970 | # layout.addWidget(button_plot_spectro) 1971 | # layout.addWidget(button_save) 1972 | # layout.addWidget(button_quit) 1973 | 1974 | 1975 | # layout.addWidget(toolbar) 1976 | # layout.addWidget(self.canvas) 1977 | 1978 | # # Create a placeholder widget to hold our toolbar and canvas. 1979 | # widget = QtWidgets.QWidget() 1980 | # widget.setLayout(layout) 1981 | # self.setCentralWidget(widget) 1982 | 1983 | self.show() 1984 | 1985 | app = QtWidgets.QApplication(sys.argv) 1986 | app.setApplicationName("Python Audio Spectrogram Explorer") 1987 | w = MainWindow() 1988 | sys.exit(app.exec_()) --------------------------------------------------------------------------------