예제 #1
0
def test_one_hot_labels_like_overlap(boxed_annotations):
    clip_df = generate_clip_times_df(3, clip_duration=1.0, clip_overlap=0.5)
    labels = boxed_annotations.one_hot_labels_like(clip_df,
                                                   classes=["a"],
                                                   min_label_overlap=0.25)
    assert np.array_equal(labels.values,
                          np.array([[1, 1, 0, 0, 0]]).transpose())
예제 #2
0
def test_generate_clip_times_df_extend():
    clip_df = helpers.generate_clip_times_df(full_duration=10,
                                             clip_duration=6.0,
                                             final_clip="extend")
    assert clip_df.shape[0] == 2
    assert clip_df.iloc[0]["start_time"] == 0.0
    assert clip_df.iloc[0]["end_time"] == 6.0
    assert clip_df.iloc[1]["start_time"] == 6.0
    assert clip_df.iloc[1]["end_time"] == 12.0
예제 #3
0
def test_generate_clip_times_df_default():
    """many corner cases / alternatives are tested for audio.split()"""
    clip_df = helpers.generate_clip_times_df(full_duration=10,
                                             clip_duration=5.0)
    assert clip_df.shape[0] == 2
    assert clip_df.iloc[0]["start_time"] == 0.0
    assert clip_df.iloc[0]["end_time"] == 5.0
    assert clip_df.iloc[1]["start_time"] == 5.0
    assert clip_df.iloc[1]["end_time"] == 10.0
예제 #4
0
def test_generate_clip_times_df_overlap():
    clip_df = helpers.generate_clip_times_df(full_duration=10,
                                             clip_duration=5,
                                             clip_overlap=2.5)
    assert clip_df.shape[0] == 3
    assert clip_df.iloc[0]["start_time"] == 0.0
    assert clip_df.iloc[0]["end_time"] == 5.0
    assert clip_df.iloc[1]["start_time"] == 2.5
    assert clip_df.iloc[1]["end_time"] == 7.5
예제 #5
0
def test_generate_clip_times_df_full():
    clip_df = helpers.generate_clip_times_df(full_duration=11,
                                             clip_duration=6.0,
                                             final_clip="full")
    assert clip_df.shape[0] == 2
    assert clip_df.iloc[0]["start_time"] == 0.0
    assert clip_df.iloc[0]["end_time"] == 6.0
    assert clip_df.iloc[1]["start_time"] == 5.0
    assert clip_df.iloc[1]["end_time"] == 11.0
예제 #6
0
def ribbit(
    spectrogram,
    signal_band,
    pulse_rate_range,
    clip_duration,
    clip_overlap=0,
    final_clip=None,
    noise_bands=None,
    plot=False,
):
    """Run RIBBIT detector to search for periodic calls in audio

    This tool searches for periodic energy fluctuations at specific repetition rates and frequencies.


    Args:
        spectrogram: opensoundscape.Spectrogram object of an audio file
        signal_band: [min, max] frequency range of the target species, in Hz
        pulse_rate_range: [min,max] pulses per second for the target species
        clip_duration: the length of audio (in seconds) to analyze at one time
            - each clip is analyzed independently and recieves a ribbit score
        clip_overlap (float):   overlap between consecutive clips (sec)
        final_clip (str):       behavior if final clip is less than clip_duration
            seconds long. By default, discards remaining audio if less than
            clip_duration seconds long [default: None].
            Options:
            - None:         Discard the remainder (do not make a clip)
            - "remainder":  Use only remainder of Audio (final clip will be shorter than clip_duration)
            - "full":       Increase overlap with previous clip to yield a clip with clip_duration length
            Note that the "extend" option is not supported for RIBBIT.

        noise_bands: list of frequency ranges to subtract from the signal_band
            For instance: [ [min1,max1] , [min2,max2] ]
            - if `None`, no noise bands are used
            - default: None
        plot=False: if True, plot the power spectral density for each clip

    Returns:
        DataFrame of index=('start_time','end_time'), columns=['score'],
        with a row for each clip.

    Notes
    -----

    __PARAMETERS__
    RIBBIT requires the user to select a set of parameters that describe the target vocalization. Here is some detailed advice on how to use these parameters.

    **Signal Band:** The signal band is the frequency range where RIBBIT looks for the target species. It is best to pick a narrow signal band if possible, so that the model focuses on a specific part of the spectrogram and has less potential to include erronious sounds.

    **Noise Bands:** Optionally, users can specify other frequency ranges called noise bands. Sounds in the `noise_bands` are _subtracted_ from the `signal_band`. Noise bands help the model filter out erronious sounds from the recordings, which could include confusion species, background noise, and popping/clicking of the microphone due to rain, wind, or digital errors. It's usually good to include one noise band for very low frequencies -- this specifically eliminates popping and clicking from being registered as a vocalization. It's also good to specify noise bands that target confusion species. Another approach is to specify two narrow `noise_bands` that are directly above and below the `signal_band`.

    **Pulse Rate Range:** This parameters specifies the minimum and maximum pulse rate (the number of pulses per second, also known as pulse repetition rate) RIBBIT should look for to find the focal species. For example, choosing `pulse_rate_range = [10, 20]` means that RIBBIT should look for pulses no slower than 10 pulses per second and no faster than 20 pulses per second.

    **Clip Duration:** The `clip_duration` parameter tells RIBBIT how many seconds of audio to analyze at one time. Generally, you should choose a `clip_length` that is similar to the length of the target species vocalization, or a little bit longer. For very slowly pulsing vocalizations, choose a longer window so that at least 5 pulses can occur in one window (0.5 pulses per second -> 10 second window). Typical values for are 0.3 to 10 seconds.
    Also, `clip_overlap` can be used for overlap between sequential clips. This
    is more computationally expensive but will be more likely to center a target
    sound in the clip (with zero overlap, the target sound may be split up between
    adjacent clips).

    **Plot:** We can choose to show the power spectrum of pulse repetition rate for each window by setting `plot=True`. The default is not to show these plots (`plot=False`).

    __ALGORITHM__
    This is the procedure RIBBIT follows:
    divide the audio into segments of length clip_duration
    for each clip:
        calculate time series of energy in signal band (signal_band) and subtract noise band energies (noise_bands)
        calculate power spectral density of the amplitude time series
        score the file based on the maximum value of power spectral density in the pulse rate range

    """
    if final_clip == "extend":
        raise ValuseError("final_clip='extend' is not supported for RIBBIT. "
                          "consider using 'remainder'.")

    # Make a 1d amplitude signal from signal_band & subtract amplitude from noise bands
    amplitude = np.array(spectrogram.net_amplitude(signal_band, noise_bands))
    time = spectrogram.times
    # we calculate the sample rate of the amplitude signal using the difference
    # in time between columns of the Spectrogram
    sample_rate = 1 / spectrogram.window_step()

    # determine the start and end times of each clip to analyze
    clip_df = generate_clip_times_df(
        full_duration=spectrogram.duration(),
        clip_duration=clip_duration,
        clip_overlap=clip_overlap,
        final_clip=final_clip,
    )
    clip_df["score"] = np.nan

    # analyze each clip and save scores in the clip_df
    for i, row in clip_df.iterrows():

        # extract the amplitude signal for this clip
        window = amplitude[(time >= row["start_time"])
                           & (time < row["end_time"])]

        if plot:
            print(f"window: {row['start_time']} to {row['end_time']} sec")

        # calculate score for this clip:
        # - make psd (Power spectral density or power spectrum of amplitude)
        # - find max value in the pulse_rate_range
        clip_df.at[i, "score"] = calculate_pulse_score(window,
                                                       sample_rate,
                                                       pulse_rate_range,
                                                       plot=plot)

    return clip_df
예제 #7
0
    def one_hot_clip_labels(
        self,
        full_duration,
        clip_duration,
        clip_overlap,
        classes,
        min_label_overlap,
        min_label_fraction=1,
        final_clip=None,
    ):
        """Generate one-hot labels for clips of fixed duration

        wraps helpers.generate_clip_times_df() with self.one_hot_labels_like()
        - Clips are created in the same way as Audio.split()
        - Labels are applied based on overlap, using self.one_hot_labels_like()

        Args:
            full_duration: The amount of time (seconds) to split into clips
            clip_duration (float):  The duration in seconds of the clips
            clip_overlap (float):   The overlap of the clips in seconds [default: 0]
            classes: list of classes for one-hot labels. If None, classes will
                be all unique values of self.df['annotation']
            min_label_overlap: minimum duration (seconds) of annotation within the
                time interval for it to count as a label. Note that any annotation
                of length less than this value will be discarded.
                We recommend a value of 0.25 for typical bird songs, or shorter
                values for very short-duration events such as chip calls or
                nocturnal flight calls.
            min_label_fraction: [default: None] if >= this fraction of an annotation
                overlaps with the time window, it counts as a label regardless of
                its duration. Note that *if either* of the two
                criterea (overlap and fraction) is met, the label is 1.
                if None (default), this criterion is not used (i.e., only min_label_overlap
                is used). A value of 0.5 for ths parameter would ensure that all
                annotations result in at least one clip being labeled 1
                (if there are no gaps between clips).
            final_clip (str):       Behavior if final_clip is less than clip_duration
                seconds long. By default, discards remaining time if less than
                clip_duration seconds long [default: None].
                Options:
                    - None:         Discard the remainder (do not make a clip)
                    - "extend":     Extend the final clip beyond full_duration to reach clip_duration length
                    - "remainder":  Use only remainder of full_duration (final clip will be shorter than clip_duration)
                    - "full":       Increase overlap with previous clip to yield a clip with clip_duration length
        Returns:
            dataframe with index ['start_time','end_time'] and columns=classes
        """
        # generate list of start and end times for each clip
        clip_df = generate_clip_times_df(
            full_duration=full_duration,
            clip_duration=clip_duration,
            clip_overlap=clip_overlap,
            final_clip=final_clip,
        )
        # then create 0/1 labels for each clip and each class
        return self.one_hot_labels_like(
            clip_df=clip_df,
            classes=classes,
            min_label_overlap=min_label_overlap,
            min_label_fraction=min_label_fraction,
        )