algorithms/asa.py
def _update_segmentation_mask(segmentation_mask, onset_fronts, offset_fronts, onset_front_id, offset_front_id_most_overlap):
    """
    Returns an updated segmentation mask such that the input `segmentation_mask` has been updated by segmenting between
    `onset_front_id` and `offset_front_id`, as found in `onset_fronts` and `offset_fronts`, respectively.

    This function also returns the onset_fronts and offset_fronts matrices, updated so that any fronts that are of
    less than 3 channels wide are removed.

    This function also returns a boolean value indicating whether the onset channel went to completion.

    Specifically, segments by doing the following:

    - Going across frequencies in the onset_front,
    - add the segment mask ID (the onset front ID) to all samples between the onset_front and the offset_front,
      if the offset_front is in that frequency.

    Possible scenarios:

    Fronts line up completely:

    ::

        |   |       S S S
        |   |  =>   S S S
        |   |       S S S
        |   |       S S S

    Onset front starts before offset front:

    ::

        |           |
        |   |       S S S
        |   |  =>   S S S
        |   |       S S S

    Onset front ends after offset front:

    ::

        |   |       S S S
        |   |  =>   S S S
        |   |       S S S
        |           |

    Onset front starts before and ends after offset front:

    ::

        |           |
        |   |  =>   S S S
        |   |       S S S
        |           |

    The above three options in reverse:

    ::

            |       |S S|           |
        |S S|       |S S|       |S S|
        |S S|       |S S|       |S S|
        |S S|           |           |

    There is one last scenario:

    ::

        |   |
        \   /
         \ /
         / \
        |   |

    Where the offset and onset fronts cross one another. If this happens, we simply
    reverse the indices and accept:

    ::

        |sss|
        \sss/
         \s/
         /s\
        |sss|

    The other option would be to destroy the offset front from the crossover point on, and
    then search for a new offset front for the rest of the onset front.
    """
    # Get the portions of the onset and offset fronts that overlap and are consecutive
    onset_front_overlap, offset_front_overlap = _get_consecutive_and_overlapping_fronts(onset_fronts, offset_fronts, onset_front_id, offset_front_id_most_overlap)
    onset_front = _get_front_idxs_from_id(onset_fronts, onset_front_id)
    offset_front = _get_front_idxs_from_id(offset_fronts, offset_front_id_most_overlap)
    msg = "Onset front {} and offset front {} result in consecutive overlapping portions of (on) {} and (off) {}, one of which is empty".format(
        onset_front, offset_front, onset_front_overlap, offset_front_overlap
    )
    assert onset_front_overlap, msg
    assert offset_front_overlap, msg
    onset_front = onset_front_overlap
    offset_front = offset_front_overlap

    # Figure out which frequencies will go in the segment
    flow_on, _slow_on = onset_front[0]
    fhigh_on, _shigh_on = onset_front[-1]
    flow_off, _slow_off = offset_front[0]
    fhigh_off, _shigh_off = offset_front[-1]
    flow = max(flow_on, flow_off)
    fhigh = min(fhigh_on, fhigh_off)

    # Update all the masks with the segment
    for fidx, _freqchan in enumerate(segmentation_mask[flow:fhigh + 1, :], start=flow):
        assert fidx >= flow, "Frequency index is {}, but we should have started at {}".format(fidx, flow)
        assert (fidx - flow) < len(onset_front), "Frequency index {} minus starting frequency {} is too large for nfrequencies {} in onset front {}".format(
            fidx, flow, len(onset_front), onset_front
        )
        assert (fidx - flow) < len(offset_front), "Frequency index {} minus starting frequency {} is too large for nfrequencies {} in offset front {}".format(
            fidx, flow, len(offset_front), offset_front
        )
        _, beg = onset_front[fidx - flow]
        _, end = offset_front[fidx - flow]
        if beg > end:
            end, beg = beg, end
        assert end >= beg
        segmentation_mask[fidx, beg:end + 1] = onset_front_id
        onset_fronts[fidx, (beg + 1):(end + 1)] = 0
        offset_fronts[fidx, (beg + 1):(end + 1)] = 0
    nfreqs_used_in_onset_front = (fidx - flow) + 1

    # Update the other masks to delete fronts that have been used
    indexes = np.arange(flow, fhigh + 1, 1, dtype=np.int64)
    onset_front_sample_idxs_across_freqs = np.array([s for _, s in onset_front])
    onset_front_sample_idxs_across_freqs_up_to_break = onset_front_sample_idxs_across_freqs[:nfreqs_used_in_onset_front]
    offset_front_sample_idxs_across_freqs = np.array([s for _, s in offset_front])
    offset_front_sample_idxs_across_freqs_up_to_break = offset_front_sample_idxs_across_freqs[:nfreqs_used_in_onset_front]

    ## Remove the offset front from where we started to where we ended
    offset_fronts[indexes[:nfreqs_used_in_onset_front], offset_front_sample_idxs_across_freqs_up_to_break] = 0

    ## Remove the onset front from where we started to where we ended
    onset_fronts[indexes[:nfreqs_used_in_onset_front], onset_front_sample_idxs_across_freqs_up_to_break] = 0

    # Determine if we matched the entire onset front by checking if there is any more of this onset front in onset_fronts
    whole_onset_front_matched = onset_front_id not in np.unique(onset_fronts)

    return whole_onset_front_matched
def _update_segmentation_mask(segmentation_mask, onset_fronts, offset_fronts, onset_front_id, offset_front_id_most_overlap):
    """
    Returns an updated segmentation mask such that the input `segmentation_mask` has been updated by segmenting between
    `onset_front_id` and `offset_front_id`, as found in `onset_fronts` and `offset_fronts`, respectively.

    This function also returns the onset_fronts and offset_fronts matrices, updated so that any fronts that are of
    less than 3 channels wide are removed.

    This function also returns a boolean value indicating whether the onset channel went to completion.

    Specifically, segments by doing the following:

    - Going across frequencies in the onset_front,
    - add the segment mask ID (the onset front ID) to all samples between the onset_front and the offset_front,
      if the offset_front is in that frequency.

    Possible scenarios:

    Fronts line up completely:

    ::

        |   |       S S S
        |   |  =>   S S S
        |   |       S S S
        |   |       S S S

    Onset front starts before offset front:

    ::

        |           |
        |   |       S S S
        |   |  =>   S S S
        |   |       S S S

    Onset front ends after offset front:

    ::

        |   |       S S S
        |   |  =>   S S S
        |   |       S S S
        |           |

    Onset front starts before and ends after offset front:

    ::

        |           |
        |   |  =>   S S S
        |   |       S S S
        |           |

    The above three options in reverse:

    ::

            |       |S S|           |
        |S S|       |S S|       |S S|
        |S S|       |S S|       |S S|
        |S S|           |           |

    There is one last scenario:

    ::

        |   |
        \   /
         \ /
         / \
        |   |

    Where the offset and onset fronts cross one another. If this happens, we simply
    reverse the indices and accept:

    ::

        |sss|
        \sss/
         \s/
         /s\
        |sss|

    The other option would be to destroy the offset front from the crossover point on, and
    then search for a new offset front for the rest of the onset front.
    """
    # Get the portions of the onset and offset fronts that overlap and are consecutive
    onset_front_overlap, offset_front_overlap = _get_consecutive_and_overlapping_fronts(onset_fronts, offset_fronts, onset_front_id, offset_front_id_most_overlap)
    onset_front = _get_front_idxs_from_id(onset_fronts, onset_front_id)
    offset_front = _get_front_idxs_from_id(offset_fronts, offset_front_id_most_overlap)
    msg = "Onset front {} and offset front {} result in consecutive overlapping portions of (on) {} and (off) {}, one of which is empty".format(
        onset_front, offset_front, onset_front_overlap, offset_front_overlap
    )
    assert onset_front_overlap, msg
    assert offset_front_overlap, msg
    onset_front = onset_front_overlap
    offset_front = offset_front_overlap

    # Figure out which frequencies will go in the segment
    flow_on, _slow_on = onset_front[0]
    fhigh_on, _shigh_on = onset_front[-1]
    flow_off, _slow_off = offset_front[0]
    fhigh_off, _shigh_off = offset_front[-1]
    flow = max(flow_on, flow_off)
    fhigh = min(fhigh_on, fhigh_off)

    # Update all the masks with the segment
    for fidx, _freqchan in enumerate(segmentation_mask[flow:fhigh + 1, :], start=flow):
        assert fidx >= flow, "Frequency index is {}, but we should have started at {}".format(fidx, flow)
        assert (fidx - flow) < len(onset_front), "Frequency index {} minus starting frequency {} is too large for nfrequencies {} in onset front {}".format(
            fidx, flow, len(onset_front), onset_front
        )
        assert (fidx - flow) < len(offset_front), "Frequency index {} minus starting frequency {} is too large for nfrequencies {} in offset front {}".format(
            fidx, flow, len(offset_front), offset_front
        )
        _, beg = onset_front[fidx - flow]
        _, end = offset_front[fidx - flow]
        if beg > end:
            end, beg = beg, end
        assert end >= beg
        segmentation_mask[fidx, beg:end + 1] = onset_front_id
        onset_fronts[fidx, (beg + 1):(end + 1)] = 0
        offset_fronts[fidx, (beg + 1):(end + 1)] = 0
    nfreqs_used_in_onset_front = (fidx - flow) + 1

    # Update the other masks to delete fronts that have been used
    indexes = np.arange(flow, fhigh + 1, 1, dtype=np.int64)
    onset_front_sample_idxs_across_freqs = np.array([s for _, s in onset_front])
    onset_front_sample_idxs_across_freqs_up_to_break = onset_front_sample_idxs_across_freqs[:nfreqs_used_in_onset_front]
    offset_front_sample_idxs_across_freqs = np.array([s for _, s in offset_front])
    offset_front_sample_idxs_across_freqs_up_to_break = offset_front_sample_idxs_across_freqs[:nfreqs_used_in_onset_front]

    ## Remove the offset front from where we started to where we ended
    offset_fronts[indexes[:nfreqs_used_in_onset_front], offset_front_sample_idxs_across_freqs_up_to_break] = 0

    ## Remove the onset front from where we started to where we ended
    onset_fronts[indexes[:nfreqs_used_in_onset_front], onset_front_sample_idxs_across_freqs_up_to_break] = 0

    # Determine if we matched the entire onset front by checking if there is any more of this onset front in onset_fronts
    whole_onset_front_matched = onset_front_id not in np.unique(onset_fronts)

    return whole_onset_front_matched
Returns an updated segmentation mask such that the input `segmentation_mask` has been updated by segmenting between
    `onset_front_id` and `offset_front_id`, as found in `onset_fronts` and `offset_fronts`, respectively.

    This function also returns the onset_fronts and offset_fronts matrices, updated so that any fronts that are of
    less than 3 channels wide are removed.

    This function also returns a boolean value indicating whether the onset channel went to completion.

    Specifically, segments by doing the following:

    - Going across frequencies in the onset_front,
    - add the segment mask ID (the onset front ID) to all samples between the onset_front and the offset_front,
      if the offset_front is in that frequency.

    Possible scenarios:

    Fronts line up completely:

    ::

        |   |       S S S
        |   |  =>   S S S
        |   |       S S S
        |   |       S S S

    Onset front starts before offset front:

    ::

        |           |
        |   |       S S S
        |   |  =>   S S S
        |   |       S S S

    Onset front ends after offset front:

    ::

        |   |       S S S
        |   |  =>   S S S
        |   |       S S S
        |           |

    Onset front starts before and ends after offset front:

    ::

        |           |
        |   |  =>   S S S
        |   |       S S S
        |           |

    The above three options in reverse:

    ::

            |       |S S|           |
        |S S|       |S S|       |S S|
        |S S|       |S S|       |S S|
        |S S|           |           |

    There is one last scenario:

    ::

        |   |
        \   /
         \ /
         / \
        |   |

    Where the offset and onset fronts cross one another. If this happens, we simply
    reverse the indices and accept:

    ::

        |sss|
        \sss/
         \s/
         /s\
        |sss|

    The other option would be to destroy the offset front from the crossover point on, and
    then search for a new offset front for the rest of the onset front.