Skip to content
Snippets Groups Projects
Select Git revision
  • cdfbad8afa4c055df90c7ab689e5b8bc7b3af91a
  • master default
  • object
  • develop protected
  • private_algos
  • cuisine
  • SMOTE
  • revert-76c4cca5
  • archive protected
  • no_graphviz
  • 0.0.1
11 results

multiview_platform.tests.test_multiview_classifiers.Test_Fusion.html

Blame
  • Detector-solorun.py 8.88 KiB
    #%% Importations
    import os
    import librosa
    import argparse
    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    from scipy.signal import find_peaks
    
    from ClickUtils import TeagerKaiser_operator, butter_pass_filter
    
    
    #%% Functions
    # Argparser
    def fetch_inputs():
        """
        A function to fetch inputs from cmd terminal. 
        Run `$python ARGS.py -h` for more information.
    
        ...
    
        Parameters
        ----------
        None : Inputs are fetched from cmd terminal as arguments.
    
        Return
        ------
        See arparser's help
        """
    
        # setting up arg parser
        parser = argparse.ArgumentParser(
            description=
            ("This script requires LIBRARIES."
            "\nAnd it does things, TO BE DESCRIBED.")
        )
    
        group1 = parser.add_argument_group('File informations')
        group1.add_argument(
            '-f', '--audio_file',
            type=str,
            nargs='?', 
            required=True,
            help=("Path to the audio file that the script will use. "
                "Supports all audio formats. Determines sampling_rate automatically.")
        )
        group1.add_argument(
            '-out', '--output', 
            type=str,
            nargs='?', 
            default=os.path.join(".","outputs"),
            required=False,
            help=("Path where the contours will be saved. "
            "Contours will be saved in a .json file, different for each wavefile. "
            "Default path is './outputs'.")
        )
        group1.add_argument(
            '-v', '--verbose',
            action='store_true',
            required=False,
            help=("If given, show messages about the progress of the script.")
        )
        group1.add_argument(
            '-show', '--show_plot',
            action='store_true',
            required=False,
            help=("If given, plots the results of the selection process.")
        )
    
        group2 = parser.add_argument_group('Detector settings')
        group2.add_argument(
            '-s', '--sound_threshold',
            type=float,
            default=0.001,
            nargs='?', 
            required=False,
            help=("Value of amplitude used after the TK filter to set a minimum "
                "value to consider a point in the detection of peaks."
                "\nDefault is 0.001.")
        )
        group2.add_argument(
            '-c', '--channel',
            type=int,
            default=0,
            nargs='?', 
            required=False,
            help=("The index of the audio channel on which "
                  "the detection will be done. Should be in [0, nchannels-1]."
                  "\nDefault is 0 (first channel).")
        )
        group2.add_argument(
            '-k', '--click_size_sec',
            type=float,
            default=0.001,
            nargs='?', 
            required=False,
            help=("Duration of a click in seconds. "
                  "For our data it is estimated to be ~1 ms"
                  "\nDefault is 0.001 sec.")
        )
        group2.add_argument(
            '-freq', '--min_freq',
            type=int,
            default=50_000,
            nargs='?', 
            required=False,
            help=("Cutoff frequency to apply with the highpass filter."
                  "Default is 50 kHz (quite a strict criteria).")
        )
    
    
        # fetching arguments
        args = parser.parse_args()
        audio_file = args.audio_file
        output = args.output
        sound_threshold = args.sound_threshold
        channel = args.channel
        click_size_sec = args.click_size_sec
        min_freq = args.min_freq
    
        # verifying arguments
        try:
            assert (os.path.exists(audio_file)), (
                f"\nInputError [--audio_file]: Could not find file '{audio_file}'.")
            assert (os.path.exists(output)), (
                f"\nInputError [--output]: Outputs directory '{output}' does not exist. "
                "\nChange the path for the output folder or create corresponding folder.")
            assert (sound_threshold > 0), (
                f"\nInputError [--sound_threshold]: sound_threshold must be positive.")
            assert (channel >= 0), (
                f"\nInputError [--channel]: channel must be positive.")
            assert (click_size_sec > 0), (
                f"\nInputError [--click_size_sec]: click_size_sec must be positive.")
            assert (min_freq > 0), (
                f"\nInputError [--min_freq]: min_freq must be positive.")
        except Exception as e:
            print(e)
            exit()
    
        return (audio_file, output, sound_threshold, channel, 
                click_size_sec, min_freq, args.verbose, args.show_plot) 
    
    # Main functions
    class BColors:
        HEADER = '\033[95m'
        OKBLUE = '\033[94m'
        OKCYAN = '\033[96m'
        OKGREEN = '\033[92m'
        WARNING = '\033[93m'
        FAIL = '\033[91m'
        ENDC = '\033[0m'
        BOLD = '\033[1m'
        UNDERLINE = '\033[4m'
    
    class SimpleAutoClicksDetector:
        """
        A class to detect clicks automatically in an audio recording.
    
        Parameters
        ----------
        wavefile_path : string
            Path to the wavefile in which clicks will be detected.
        sound_thresh : float
            Threshold for the detection of peaks in the audio signal
            filtered (high-pass + teager-kaiser).
            Default is 0.001.
        chan : integer
            In case the audio data is not mono-channel,
            index of the channel on which the detection of clicks will be made.
            Default is 0.
        click_size_sec : float
            Duration of a click in seconds.
            Default is 0.001 sec.
        highpass_freq : int
            Cutoff frequency for the highpass filter.
            Default is 50 kHz.
        verbose : boolean
            Wether to show prints about the process or not.
            Default is False.
    
        Methods
        -------
        create_peaks_map():
            A function to find peaks (supposedly clicks) in an audio recording.
            Generates map_peaks, of the same shape that the audio recording
            with 0 as a base and 1's showing the positions of detections.
        """
        def __init__(
        self,
        wavefile_path=None,
        sound_thresh=0.001,
        chan=0,
        click_size_sec=0.001,
        highpass_freq=50_000,
        verbose=False):
            # fetching arguments
            if (isinstance(wavefile_path, str) and
                    isinstance(sound_thresh, float) and
                    isinstance(chan, int) and
                    isinstance(click_size_sec, float) and
                    isinstance(highpass_freq, int) and
                    isinstance(verbose, bool)):
                self.wavefile_path = wavefile_path
                self.sound_thresh = sound_thresh
                self.chan = chan
                self.click_size_sec = click_size_sec
                self.highpass_freq = highpass_freq
                self.verbose = verbose
            
            else:
                print(f"{BColors.FAIL}Error in parameters.{BColors.ENDC}")
                return None
    
            # actual click detection
            if self.verbose:
                print("Loading audio data...")
            self.audio_data, self.sr = librosa.load(
                self.wavefile_path, sr=None, mono=False)
            if self.verbose:
                print("Finding clicks in waveform...")
            self.create_peaks_map()
            if self.verbose:
                print(f"\t{BColors.OKGREEN}Done.{BColors.FAIL}")
    
        def create_peaks_map(self):
            # assign parameters
            max_length = int(self.sr*self.click_size_sec)
            mini_space = int(self.sr*self.click_size_sec*2)
    
            # check if mono or stereo+
            if len(self.audio_data.shape) == 1:
                self.signal = np.copy(self.audio_data)
            else:
                self.signal = np.copy(self.audio_data[self.chan])
            # detect clicks
            self.signal_high = butter_pass_filter(self.signal, self.highpass_freq, self.sr, mode='high')
            self.tk_signal = TeagerKaiser_operator(self.signal_high)
            self.signal_peaks = find_peaks(self.tk_signal, 
                                    distance=mini_space,
                                    width=[0,max_length],
                                    prominence=self.sound_thresh)[0]
            map_peaks = np.zeros(len(self.tk_signal), dtype=int)
            map_peaks[self.signal_peaks] = 1
    
            self.map_peaks = map_peaks
    
    
    #%% Parameters
    # Audio parameters
    (file_path, output_path, sound_thresh, channel, 
     click_size_sec, fmin, verbose, do_plot) = fetch_inputs()
    
    
    #%% Main executions
    Detector = SimpleAutoClicksDetector(
        wavefile_path=file_path,
        sound_thresh=sound_thresh,
        chan=channel,
        click_size_sec=click_size_sec,
        highpass_freq=fmin,
        verbose = verbose)
    
    # save peak locations
    arg_peaks = np.nonzero(Detector.map_peaks)[0]
    df = pd.DataFrame()
    df["time"] = arg_peaks/Detector.sr
    df["signal_amplitude"] = Detector.signal[arg_peaks]
    df["TK_amplitude"] = Detector.signal[arg_peaks]
    df.to_csv(
        os.path.join(output_path, os.path.basename(file_path)[:-4]+"_clicks-detection.csv"), 
        index=False)
    if verbose:
        print(f"\n{BColors.OKCYAN}File saved in '{output_path}'.{BColors.ENDC}")
    
    if do_plot:
        if verbose:
            print("Showing plot.")
        fig, ax = plt.subplots(1,1)
        ax.plot(
            np.arange(0, len(Detector.signal_high))/Detector.sr,
            Detector.signal_high,
            color='tab:blue', zorder=-1)
        ax.scatter(
            arg_peaks/Detector.sr,
            Detector.signal_high[arg_peaks],
            color='tab:orange', s=10, zorder=1)
        plt.show(block=True)