Instruction:
Create a Python 3 function for
detecting an event in a given model.

Response:
def detect_event(self, model, ms_per_input, transition_matrix, model_stats, event_length_s,
                     start_as_yes=False, prob_raw_yes=0.5):
        """
        A list of tuples of the form [('n', AudioSegment), ('y', AudioSegment), etc.] is returned, where tuples
        of the form ('n', AudioSegment) are the segments of sound where the event was not detected,
        while ('y', AudioSegment) tuples were the segments of sound where the event was detected.

        .. code-block:: python

            # Example usage
            import audiosegment
            import keras
            import keras.models
            import numpy as np
            import sys

            class Model:
                def __init__(self, modelpath):
                    self.model = keras.models.load_model(modelpath)

                def predict(self, seg):
                    _bins, fft_vals = seg.fft()
                    fft_vals = np.abs(fft_vals) / len(fft_vals)
                    predicted_np_form = self.model.predict(np.array([fft_vals]), batch_size=1)
                    prediction_as_int = int(round(predicted_np_form[0][0]))
                    return prediction_as_int

            modelpath = sys.argv[1]
            wavpath = sys.argv[2]
            model = Model(modelpath)
            seg = audiosegment.from_file(wavpath).resample(sample_rate_Hz=32000, sample_width=2, channels=1)
            pyes_to_no = 0.3  # The probability of one 30 ms sample being an event, and the next one not
            pno_to_yes = 0.2  # The probability of one 30 ms sample not being an event, and the next one yes
            ptrue_pos_rate = 0.8  # The true positive rate (probability of a predicted yes being right)
            pfalse_neg_rate = 0.3  # The false negative rate (probability of a predicted no being wrong)
            raw_prob = 0.7  # The raw probability of seeing the event in any random 30 ms slice of this file
            events = seg.detect_event(model, ms_per_input=30, transition_matrix=[pyes_to_no, pno_to_yes],
                                      model_stats=[ptrue_pos_rate, pfalse_neg_rate], event_length_s=0.25,
                                      prob_raw_yes=raw_prob)
            nos = [event[1] for event in events if event[0] == 'n']
            yeses = [event[1] for event in events if event[0] == 'y']
            if len(nos) > 1:
                notdetected = nos[0].reduce(nos[1:])
                notdetected.export("notdetected.wav", format="WAV")
            if len(yeses) > 1:
                detected = yeses[0].reduce(yeses[1:])
                detected.export("detected.wav", format="WAV")

        :param model:               The model. The model must have a predict() function which takes an AudioSegment
                                    of `ms_per_input` number of ms and which outputs 1 if the audio event is detected
                                    in that input, and 0 if not. Make sure to resample the AudioSegment to the right
                                    values before calling this function on it.

        :param ms_per_input:        The number of ms of AudioSegment to be fed into the model at a time. If this does not
                                    come out even, the last AudioSegment will be zero-padded.

        :param transition_matrix:   An iterable of the form: [p(yes->no), p(no->yes)]. That is, the probability of moving
                                    from a 'yes' state to a 'no' state and the probability of vice versa.

        :param model_stats:         An iterable of the form: [p(reality=1|output=1), p(reality=1|output=0)]. That is,
                                    the probability of the ground truth really being a 1, given that the model output a 1,
                                    and the probability of the ground truth being a 1, given that the model output a 0.

        :param event_length_s:      The typical duration of the event you are looking for in seconds (can be a float).

        :param start_as_yes:        If True, the first `ms_per_input` will be in the 'y' category. Otherwise it will be
                                    in the 'n' category.

        :param prob_raw_yes:        The raw probability of finding the event in any given `ms_per_input` vector.

        :returns:                   A list of tuples of the form [('n', AudioSegment), ('y', AudioSegment), etc.],
                                    where over the course of the list, the AudioSegment in tuple 3 picks up
                                    where the one in tuple 2 left off.

        :raises:                    ValueError if `ms_per_input` is negative or larger than the number of ms in this
                                    AudioSegment; if `transition_matrix` or `model_stats` do not have a __len__ attribute
                                    or are not length 2; if the values in `transition_matrix` or `model_stats` are not
                                    in the closed interval [0.0, 1.0].
        """
        if ms_per_input < 0 or ms_per_input / MS_PER_S > self.duration_seconds:
            raise ValueError("ms_per_input cannot be negative and cannot be longer than the duration of the AudioSegment."\
                             " The given value was " + str(ms_per_input))
        elif not hasattr(transition_matrix, "__len__") or len(transition_matrix) != 2:
            raise ValueError("transition_matrix must be an iterable of length 2.")
        elif not hasattr(model_stats, "__len__") or len(model_stats) != 2:
            raise ValueError("model_stats must be an iterable of length 2.")
        elif any([True for prob in transition_matrix if prob > 1.0 or prob < 0.0]):
            raise ValueError("Values in transition_matrix are probabilities, and so must be in the range [0.0, 1.0].")
        elif any([True for prob in model_stats if prob > 1.0 or prob < 0.0]):
            raise ValueError("Values in model_stats are probabilities, and so must be in the range [0.0, 1.0].")
        elif prob_raw_yes > 1.0 or prob_raw_yes < 0.0:
            raise ValueError("`prob_raw_yes` is a probability, and so must be in the range [0.0, 1.0]")

        # Get the yeses or nos for when the filter is triggered (when the event is on/off)
        filter_indices = [yes_or_no for yes_or_no in detect._get_filter_indices(self,
                                                                                start_as_yes,
                                                                                prob_raw_yes,
                                                                                ms_per_input,
                                                                                model,
                                                                                transition_matrix,
                                                                                model_stats)]

        # Run a homogeneity filter over the values to make local regions more self-similar (reduce noise)
        ret = detect._homogeneity_filter(filter_indices, window_size=int(round(0.25 * MS_PER_S / ms_per_input)))

        # Group the consecutive ones together
        ret = detect._group_filter_values(self, ret, ms_per_input)

        # Take the groups and turn them into AudioSegment objects
        real_ret = []
        for i, (this_yesno, next_timestamp) in enumerate(ret):
            if i > 0:
                _next_yesno, timestamp = ret[i - 1]
            else:
                timestamp = 0

            ms_per_s = 1000
            data = self[timestamp * ms_per_s:next_timestamp * ms_per_s].raw_data
            seg = AudioSegment(pydub.AudioSegment(data=data, sample_width=self.sample_width,
                                                    frame_rate=self.frame_rate, channels=self.channels), self.name)
            real_ret.append((this_yesno, seg))
        return real_ret


Instruction:
Explain what the following Python 3 code does
def _execute_sox_cmd(self, cmd, console_output=False):
        on_windows = platform.system().lower() == "windows"

        # On Windows, a temporary file cannot be shared outside the process that creates it
        # so we need to create a "permanent" file that we will use and delete afterwards
        def _get_random_tmp_file():
            if on_windows:
                rand_string = "".join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8))
                tmp = self.name + "_" + rand_string
                WinTempFile = collections.namedtuple("WinTempFile", "name")
                tmp = WinTempFile(tmp)
            else:
                tmp = tempfile.NamedTemporaryFile()
            return tmp

        # Get a temp file to put our data and a temp file to store the result
        tmp = _get_random_tmp_file()
        othertmp = _get_random_tmp_file()

        # Store our data in the temp file
        self.export(tmp.name, format="WAV")

        # Write the command to sox
        stdout = stderr = subprocess.PIPE if console_output else subprocess.DEVNULL
        command = cmd.format(inputfile=tmp.name, outputfile=othertmp.name)
        res = subprocess.call(command.split(' '), stdout=stdout, stderr=stderr)
        assert res == 0, "Sox did not work as intended, or perhaps you don't have Sox installed?"

        # Create a new AudioSegment from the other temp file (where Sox put the result)
        other = AudioSegment(pydub.AudioSegment.from_wav(othertmp.name), self.name)

        # Clean up the temp files
        if on_windows:
            os.remove(tmp.name)
            os.remove(othertmp.name)
        else:
            tmp.close()
            othertmp.close()

        return other

Response: Executes a command in a platform - independent manner.


Instruction:
Create a Python 3 function to
return a copy of this AudioSegment, but whose silence has been removed. .. note:: This method requires that you have the program 'sox' installed. .. warning:: This method uses the program 'sox' to perform the task. While this is very fast for a single function call, the IO may add up for large numbers of AudioSegment objects. :param duration_s: The number of seconds of "silence" that must be present in a row to be stripped. :param threshold_percentage: Silence is defined as any samples whose absolute value is below `threshold_percentage * max(abs(samples in this segment))`. :param console_output: If True, will pipe all sox output to the console. :returns: A copy of this AudioSegment, but whose silence has been removed.

Response:
def filter_silence(self, duration_s=1, threshold_percentage=1, console_output=False):
        """
        Returns a copy of this AudioSegment, but whose silence has been removed.

        .. note:: This method requires that you have the program 'sox' installed.

        .. warning:: This method uses the program 'sox' to perform the task. While this is very fast for a single
                     function call, the IO may add up for large numbers of AudioSegment objects.

        :param duration_s: The number of seconds of "silence" that must be present in a row to
                           be stripped.
        :param threshold_percentage: Silence is defined as any samples whose absolute value is below
                                     `threshold_percentage * max(abs(samples in this segment))`.
        :param console_output: If True, will pipe all sox output to the console.
        :returns: A copy of this AudioSegment, but whose silence has been removed.
        """
        command = "sox {inputfile} -t wav {outputfile} silence -l 1 0.1 "\
            + str(threshold_percentage) + "% -1 " + str(float(duration_s)) + " " + str(threshold_percentage) + "%"
        try:
            result = self._execute_sox_cmd(command)
        except pydub.exceptions.CouldntDecodeError:
            warnings.warn("After silence filtering, the resultant WAV file is corrupted, and so its data cannot be retrieved. Perhaps try a smaller threshold value.", stacklevel=2)
            # Return a copy of us
            result = AudioSegment(self.seg, self.name)
        return result


Instruction:
Can you tell what is the following Python 3 function doing
def fft(self, start_s=None, duration_s=None, start_sample=None, num_samples=None, zero_pad=False):
        if start_s is not None and start_sample is not None:
            raise ValueError("Only one of start_s and start_sample can be specified.")
        if duration_s is not None and num_samples is not None:
            raise ValueError("Only one of duration_s and num_samples can be specified.")
        if start_s is None and start_sample is None:
            start_sample = 0
        if duration_s is None and num_samples is None:
            num_samples = len(self.get_array_of_samples()) - int(start_sample)

        if duration_s is not None:
            num_samples = int(round(duration_s * self.frame_rate))
        if start_s is not None:
            start_sample = int(round(start_s * self.frame_rate))

        end_sample = start_sample + num_samples  # end_sample is excluded
        if end_sample > len(self.get_array_of_samples()) and not zero_pad:
            raise ValueError("The combination of start and duration will run off the end of the AudioSegment object.")
        elif end_sample > len(self.get_array_of_samples()) and zero_pad:
            arr = np.array(self.get_array_of_samples())
            zeros = np.zeros(end_sample - len(arr))
            arr = np.append(arr, zeros)
        else:
            arr = np.array(self.get_array_of_samples())

        audioslice = np.array(arr[start_sample:end_sample])
        fft_result = np.fft.fft(audioslice)[range(int(round(num_samples/2)) + 1)]
        step_size = self.frame_rate / num_samples
        bins = np.arange(0, int(round(num_samples/2)) + 1, 1.0) * step_size
        return bins, fft_result

Response: This function transforms the audio segment into a frequency domain and returns the bins and values of the first 3 seconds of the audio segment.


Instruction:
How would you explain what the following Python 3 function does
def generate_frames(self, frame_duration_ms, zero_pad=True):
        Frame = collections.namedtuple("Frame", "bytes timestamp duration")

        # (samples/sec) * (seconds in a frame) * (bytes/sample)
        bytes_per_frame = int(self.frame_rate * (frame_duration_ms / 1000) * self.sample_width)
        offset = 0  # where we are so far in self's data (in bytes)
        timestamp = 0.0  # where we are so far in self (in seconds)
        # (bytes/frame) * (sample/bytes) * (sec/samples)
        frame_duration_s = (bytes_per_frame / self.frame_rate) / self.sample_width
        while offset + bytes_per_frame < len(self.raw_data):
            yield Frame(self.raw_data[offset:offset + bytes_per_frame], timestamp, frame_duration_s)
            timestamp += frame_duration_s
            offset += bytes_per_frame

        if zero_pad:
            rest = self.raw_data[offset:]
            zeros = bytes(bytes_per_frame - len(rest))
            yield Frame(rest + zeros, timestamp, frame_duration_s)

Response: Yields the audio data in chunks of frame_duration_ms.


Instruction:
Make a summary of the following Python 3 code
def generate_frames_as_segments(self, frame_duration_ms, zero_pad=True):
        for frame in self.generate_frames(frame_duration_ms, zero_pad=zero_pad):
            seg = AudioSegment(pydub.AudioSegment(data=frame.bytes, sample_width=self.sample_width,
                               frame_rate=self.frame_rate, channels=self.channels), self.name)
            yield seg, frame.timestamp

Response: Generates a sequence of audio segments from this instance.


Instruction:
Given the following Python 3 function, write the documentation
def human_audible(self):
        hist_bins, hist_vals = self.fft()
        hist_vals_real_normed = np.abs(hist_vals) / len(hist_vals)
        f_characteristic = hist_bins[np.argmax(hist_vals_real_normed)]

        threshold_fc = 40.11453 - (0.01683697 * f_characteristic) + (1.406211e-6 * f_characteristic ** 2) - (2.371512e-11 * f_characteristic ** 3)

        return self.spl >= threshold_fc

Response: Returns a boolean indicating whether this sound is mostly human audible or not.


Instruction:
Can you tell what is the following Python 3 function doing
def normalize_spl_by_average(self, db):
        arr = self.to_numpy_array().copy()
        if len(arr) == 0:
            raise ValueError("Cannot normalize the SPL of an empty AudioSegment")

        def rms(x):
            return np.sqrt(np.mean(np.square(x)))

        # Figure out what RMS we would like
        desired_rms = P_REF_PCM * ((10 ** (db/20.0)) - 1E-9)

        # Use successive approximation to solve
        ## Keep trying different multiplication factors until we get close enough or run out of time
        max_ntries = 50
        res_rms = 0.0
        ntries = 0
        factor = 0.1
        left = 0.0
        right = desired_rms
        while (ntries < max_ntries) and not util.isclose(res_rms, desired_rms, abs_tol=0.1):
            res_rms = rms(arr * factor)
            if res_rms < desired_rms:
                left = factor
            else:
                right = factor
            factor = 0.5 * (left + right)
            ntries += 1

        dtype_dict = {1: np.int8, 2: np.int16, 4: np.int32}
        dtype = dtype_dict[self.sample_width]
        new_seg = from_numpy_array(np.array(arr * factor, dtype=dtype), self.frame_rate)
        return new_seg

Response: Normalizes the values in the AudioSegment so that its spl property gives the given db.


Instruction:
How would you explain what the following Python 3 function does
def reduce(self, others):
        ret = AudioSegment(self.seg, self.name)
        selfdata = [self.seg._data]
        otherdata = [o.seg._data for o in others]
        ret.seg._data = b''.join(selfdata + otherdata)

        return ret

Response: Reduces this segment into the others and returns the result.


Instruction:
Make a summary of the following Python 3 code
def resample(self, sample_rate_Hz=None, sample_width=None, channels=None, console_output=False):
        if sample_rate_Hz is None:
            sample_rate_Hz = self.frame_rate
        if sample_width is None:
            sample_width = self.sample_width
        if channels is None:
            channels = self.channels

        # TODO: Replace this with librosa's implementation to remove SOX dependency here
        command = "sox {inputfile} -b " + str(sample_width * 8) + " -r " + str(sample_rate_Hz) \
            + " -t wav {outputfile} channels " + str(channels)

        return self._execute_sox_cmd(command, console_output=console_output)

Response: Resamples the audio file to the specified size.


Instruction:
Create a Python 3 function to
serialize into a bytestring.

Response:
def serialize(self):
        """
        Serializes into a bytestring.

        :returns: An object of type Bytes.
        """
        d = self.__getstate__()
        return pickle.dumps({
            'name': d['name'],
            'seg': pickle.dumps(d['seg'], protocol=-1),
        }, protocol=-1)


Instruction:
Write a Python 3 function that can
plot a series of FFTs from start_s duration_s and num_samples.

Response:
def spectrogram(self, start_s=None, duration_s=None, start_sample=None, num_samples=None,
                    window_length_s=None, window_length_samples=None, overlap=0.5, window=('tukey', 0.25)):
        """
        Does a series of FFTs from `start_s` or `start_sample` for `duration_s` or `num_samples`.
        Effectively, transforms a slice of the AudioSegment into the frequency domain across different
        time bins.

        .. code-block:: python

            # Example for plotting a spectrogram using this function
            import audiosegment
            import matplotlib.pyplot as plt

            #...
            seg = audiosegment.from_file("somebodytalking.wav")
            freqs, times, amplitudes = seg.spectrogram(window_length_s=0.03, overlap=0.5)
            amplitudes = 10 * np.log10(amplitudes + 1e-9)

            # Plot
            plt.pcolormesh(times, freqs, amplitudes)
            plt.xlabel("Time in Seconds")
            plt.ylabel("Frequency in Hz")
            plt.show()

        .. image:: images/spectrogram.png

        :param start_s: The start time. Starts at the beginning if neither this nor `start_sample` is specified.
        :param duration_s: The duration of the spectrogram in seconds. Goes to the end if neither this nor
                           `num_samples` is specified.
        :param start_sample: The index of the first sample to use. Starts at the beginning if neither this nor
                             `start_s` is specified.
        :param num_samples: The number of samples in the spectrogram. Goes to the end if neither this nor
                            `duration_s` is specified.
        :param window_length_s: The length of each FFT in seconds. If the total number of samples in the spectrogram
                                is not a multiple of the window length in samples, the last window will be zero-padded.
        :param window_length_samples: The length of each FFT in number of samples. If the total number of samples in the
                                spectrogram is not a multiple of the window length in samples, the last window will
                                be zero-padded.
        :param overlap: The fraction of each window to overlap.
        :param window: See Scipy's spectrogram-function_.
                       This parameter is passed as-is directly into the Scipy spectrogram function. It's documentation is reproduced here:
                       Desired window to use. If window is a string or tuple, it is passed to get_window to generate the window values,
                       which are DFT-even by default. See get_window for a list of windows and required parameters.
                       If window is array_like it will be used directly as the window and its length must be
                       `window_length_samples`.
                       Defaults to a Tukey window with shape parameter of 0.25.
        :returns: Three np.ndarrays: The frequency values in Hz (the y-axis in a spectrogram), the time values starting
                  at start time and then increasing by `duration_s` each step (the x-axis in a spectrogram), and
                  the dB of each time/frequency bin as a 2D array of shape [len(frequency values), len(duration)].
        :raises ValueError: If `start_s` and `start_sample` are both specified, if `duration_s` and `num_samples` are both
                            specified, if the first window's duration plus start time lead to running off the end
                            of the AudioSegment, or if `window_length_s` and `window_length_samples` are either
                            both specified or if they are both not specified.

        .. _spectrogram-function: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.spectrogram.html
        """
        if start_s is not None and start_sample is not None:
            raise ValueError("Only one of start_s and start_sample may be specified.")
        if duration_s is not None and num_samples is not None:
            raise ValueError("Only one of duration_s and num_samples may be specified.")
        if window_length_s is not None and window_length_samples is not None:
            raise ValueError("Only one of window_length_s and window_length_samples may be specified.")
        if window_length_s is None and window_length_samples is None:
            raise ValueError("You must specify a window length, either in window_length_s or in window_length_samples.")

        # Determine the start sample
        if start_s is None and start_sample is None:
            start_sample = 0
        elif start_s is not None:
            start_sample = int(round(start_s * self.frame_rate))

        # Determine the number of samples
        if duration_s is None and num_samples is None:
            num_samples = len(self.get_array_of_samples()) - int(start_sample)
        elif duration_s is not None:
            num_samples = int(round(duration_s * self.frame_rate))

        # Determine the number of samples per window
        if window_length_s is not None:
            window_length_samples = int(round(window_length_s * self.frame_rate))

        # Check validity of number of samples
        if start_sample + num_samples > len(self.get_array_of_samples()):
            raise ValueError("The combination of start and duration will run off the end of the AudioSegment object.")

        # Create a Numpy Array out of the correct samples
        arr = self.to_numpy_array()[start_sample:start_sample+num_samples]

        # Use Scipy spectrogram and return
        fs, ts, sxx = signal.spectrogram(arr, self.frame_rate, scaling='spectrum', nperseg=window_length_samples,
                                             noverlap=int(round(overlap * window_length_samples)),
                                             mode='magnitude', window=window)
        return fs, ts, sxx


Instruction:
Write a Python 3 script to
convert the object to a numpy array.

Response:
def to_numpy_array(self):
        """
        Convenience function for `np.array(self.get_array_of_samples())` while
        keeping the appropriate dtype.
        """
        dtype_dict = {
                        1: np.int8,
                        2: np.int16,
                        4: np.int32
                     }
        dtype = dtype_dict[self.sample_width]
        return np.array(self.get_array_of_samples(), dtype=dtype)


Instruction:
How would you code a function in Python 3 to
add a number of zeros to the AudioSegment.

Response:
def zero_extend(self, duration_s=None, num_samples=None):
        """
        Adds a number of zeros (digital silence) to the AudioSegment (returning a new one).

        :param duration_s: The number of seconds of zeros to add. If this is specified, `num_samples` must be None.
        :param num_samples: The number of zeros to add. If this is specified, `duration_s` must be None.
        :returns: A new AudioSegment object that has been zero extended.
        :raises: ValueError if duration_s and num_samples are both specified.
        """
        if duration_s is not None and num_samples is not None:
            raise ValueError("`duration_s` and `num_samples` cannot both be specified.")
        elif duration_s is not None:
            num_samples = self.frame_rate * duration_s
        seg = AudioSegment(self.seg, self.name)
        zeros = silent(duration=num_samples / self.frame_rate, frame_rate=self.frame_rate)
        return zeros.overlay(seg)


Instruction:
Write a Python 3 script to
compute the peak or valley of the first derivative of a time bin in a spectrogram.

Response:
def _compute_peaks_or_valleys_of_first_derivative(s, do_peaks=True):
    """
    Takes a spectrogram and returns a 2D array of the form:

    0 0 0 1 0 0 1 0 0 0 1   <-- Frequency 0
    0 0 1 0 0 0 0 0 0 1 0   <-- Frequency 1
    0 0 0 0 0 0 1 0 1 0 0   <-- Frequency 2
    *** Time axis *******

    Where a 1 means that the value in that time bin in the spectrogram corresponds to
    a peak/valley in the first derivative.

    This function is used as part of the ASA algorithm and is not meant to be used publicly.
    """
    # Get the first derivative of each frequency in the time domain
    gradient = np.nan_to_num(np.apply_along_axis(np.gradient, 1, s), copy=False)

    # Calculate the value we will use for determinig whether something is an event or not
    threshold = np.squeeze(np.nanmean(gradient, axis=1) + np.nanstd(gradient, axis=1))

    # Look for relative extrema along the time dimension
    half_window = 4
    if do_peaks:
        indexes = [signal.argrelextrema(gradient[i, :], np.greater, order=half_window)[0] for i in range(gradient.shape[0])]
    else:
        indexes = [signal.argrelextrema(gradient[i, :], np.less, order=half_window)[0] for i in range(gradient.shape[0])]

    # indexes should now contain the indexes of possible extrema
    # But we need to filter out values that are not large enough, and we want the end result
    # to be a 1 or 0 mask corresponding to locations of extrema
    extrema = np.zeros(s.shape)
    for row_index, index_array in enumerate(indexes):
        # Each index_array is a list of indexes corresponding to all the extrema in a given row
        for col_index in index_array:
            if do_peaks and (gradient[row_index, col_index] > threshold[row_index]):
                extrema[row_index, col_index] = 1
            elif not do_peaks:
                # Note that we do not remove under-threshold values from the offsets - these will be taken care of later in the algo
                extrema[row_index, col_index] = 1
    return extrema, gradient


Instruction:
Create a Python 3 function for
taking an array of onsets and an array of offsets, of the shape [nfrequencies, nsamples], where each item in these arrays is either a 0 (not an on/offset) or a 1 (a possible on/offset). This function returns a new offsets array, where there is a one-to-one correlation between onsets and offsets, such that each onset has exactly one offset that occurs after it in the time domain (the second dimension of the array). The gradients array is used to decide which offset to use in the case of multiple possibilities.

Response:
def _correlate_onsets_and_offsets(onsets, offsets, gradients):
    """
    Takes an array of onsets and an array of offsets, of the shape [nfrequencies, nsamples], where
    each item in these arrays is either a 0 (not an on/offset) or a 1 (a possible on/offset).

    This function returns a new offsets array, where there is a one-to-one correlation between
    onsets and offsets, such that each onset has exactly one offset that occurs after it in
    the time domain (the second dimension of the array).

    The gradients array is used to decide which offset to use in the case of multiple possibilities.
    """
    # For each freq channel:
    for freq_index, (ons, offs) in enumerate(zip(onsets[:, :], offsets[:, :])):
        # Scan along onsets[f, :] until we find the first 1
        indexes_of_all_ones = np.reshape(np.where(ons == 1), (-1,))

        # Zero out anything in the offsets up to (and including) this point
        # since we can't have an offset before the first onset
        last_idx = indexes_of_all_ones[0]
        offs[0:last_idx + 1] = 0

        if len(indexes_of_all_ones > 1):
            # Do the rest of this only if we have more than one onset in this frequency band
            for next_idx in indexes_of_all_ones[1:]:
                # Get all the indexes of possible offsets from onset index to next onset index
                offset_choices = offs[last_idx:next_idx]
                offset_choice_indexes = np.where(offset_choices == 1)

                # Assert that there is at least one offset choice
                if not np.any(offset_choices):
                    continue
                assert np.any(offset_choices), "Offsets from {} to {} only include zeros".format(last_idx, next_idx)

                # If we have more than one choice, the offset index is the one that corresponds to the most negative gradient value
                # Convert the offset_choice_indexes to indexes in the whole offset array, rather than just the offset_choices array
                offset_choice_indexes = np.reshape(last_idx + offset_choice_indexes, (-1,))
                assert np.all(offsets[freq_index, offset_choice_indexes])
                gradient_values = gradients[freq_index, offset_choice_indexes]
                index_of_largest_from_gradient_values = np.where(gradient_values == np.min(gradient_values))[0]
                index_of_largest_offset_choice = offset_choice_indexes[index_of_largest_from_gradient_values]
                assert offsets[freq_index, index_of_largest_offset_choice] == 1

                # Zero the others
                offsets[freq_index, offset_choice_indexes] = 0
                offsets[freq_index, index_of_largest_offset_choice] = 1
                last_idx = next_idx
        else:
            # We only have one onset in this frequency band, so the offset will be the very last sample
            offsets[freq_index, :] = 0
            offsets[freq_index, -1] = 1
    return offsets


Instruction:
Implement a Python 3 function for
forming the onsets or offsets of a single onset or offset.

Response:
def _form_onset_offset_fronts(ons_or_offs, sample_rate_hz, threshold_ms=20):
    """
    Takes an array of onsets or offsets (shape = [nfrequencies, nsamples], where a 1 corresponds to an on/offset,
    and samples are 0 otherwise), and returns a new array of the same shape, where each 1 has been replaced by
    either a 0, if the on/offset has been discarded, or a non-zero positive integer, such that
    each front within the array has a unique ID - for example, all 2s in the array will be the front for on/offset
    front 2, and all the 15s will be the front for on/offset front 15, etc.

    Due to implementation details, there will be no 1 IDs.
    """
    threshold_s = threshold_ms / 1000
    threshold_samples = sample_rate_hz * threshold_s

    ons_or_offs = np.copy(ons_or_offs)

    claimed = []
    this_id = 2
    # For each frequency,
    for frequency_index, row in enumerate(ons_or_offs[:, :]):
        ones = np.reshape(np.where(row == 1), (-1,))

        # for each 1 in that frequency,
        for top_level_frequency_one_index in ones:
            claimed.append((frequency_index, top_level_frequency_one_index))

            found_a_front = False
            # for each frequencies[i:],
            for other_frequency_index, other_row in enumerate(ons_or_offs[frequency_index + 1:, :], start=frequency_index + 1):

                # for each non-claimed 1 which is less than theshold_ms away in time,
                upper_limit_index = top_level_frequency_one_index + threshold_samples
                lower_limit_index = top_level_frequency_one_index - threshold_samples
                other_ones = np.reshape(np.where(other_row == 1), (-1,))  # Get the indexes of all the 1s in row
                tmp = np.reshape(np.where((other_ones >= lower_limit_index)  # Get the indexes in the other_ones array of all items in bounds
                                        & (other_ones <= upper_limit_index)), (-1,))
                other_ones = other_ones[tmp]  # Get the indexes of all the 1s in the row that are in bounds
                if len(other_ones) > 0:
                    unclaimed_idx = other_ones[0]  # Take the first one
                    claimed.append((other_frequency_index, unclaimed_idx))
                elif len(claimed) < 3:
                    # revert the top-most 1 to 0
                    ons_or_offs[frequency_index, top_level_frequency_one_index] = 0
                    claimed = []
                    break  # Break from the for-each-frequencies[i:] loop so we can move on to the next item in the top-most freq
                elif len(claimed) >= 3:
                    found_a_front = True
                    # this group of so-far-claimed forms a front
                    claimed_as_indexes = tuple(np.array(claimed).T)
                    ons_or_offs[claimed_as_indexes] = this_id
                    this_id += 1
                    claimed = []
                    break  # Move on to the next item in the top-most array
            # If we never found a frequency that did not have a matching offset, handle that case here
            if len(claimed) >= 3:
                claimed_as_indexes = tuple(np.array(claimed).T)
                ons_or_offs[claimed_as_indexes] = this_id
                this_id += 1
                claimed = []
            elif found_a_front:
                this_id += 1
            else:
                ons_or_offs[frequency_index, top_level_frequency_one_index] = 0
                claimed = []

    return ons_or_offs


Instruction:
How would you explain what the following Python 3 function does
def _lookup_offset_by_onset_idx(onset_idx, onsets, offsets):
    assert len(onset_idx) == 2, "Onset_idx must be a tuple of the form (freq_idx, sample_idx)"
    frequency_idx, sample_idx = onset_idx
    offset_sample_idxs = np.reshape(np.where(offsets[frequency_idx, :] == 1), (-1,))
    # get the offsets which occur after onset
    offset_sample_idxs = offset_sample_idxs[offset_sample_idxs > sample_idx]
    if len(offset_sample_idxs) == 0:
        # There is no offset in this frequency that occurs after the onset, just return the last sample
        chosen_offset_sample_idx = offsets.shape[1] - 1
        assert offsets[frequency_idx, chosen_offset_sample_idx] == 0
    else:
        # Return the closest offset to the onset
        chosen_offset_sample_idx = offset_sample_idxs[0]
        assert offsets[frequency_idx, chosen_offset_sample_idx] != 0
    return frequency_idx, chosen_offset_sample_idx

Response: Given a frequency index and a list of onsets and offsets returns the offset index that occurs after the given onset.


Instruction:
How would you explain what the following Python 3 function does
def _get_front_idxs_from_id(fronts, id):
    if id == -1:
        # This is the only special case.
        # -1 is the index of the catch-all final column offset front.
        freq_idxs = np.arange(fronts.shape[0], dtype=np.int64)
        sample_idxs = np.ones(len(freq_idxs), dtype=np.int64) * (fronts.shape[1] - 1)
    else:
        freq_idxs, sample_idxs = np.where(fronts == id)
    return [(f, i) for f, i in zip(freq_idxs, sample_idxs)]

Response: Return a list of tuples of the form frequency_idx sample_idx corresponding to all the indexes of the given front.


Instruction:
Can you tell what is the following Python 3 function doing
def _choose_front_id_from_candidates(candidate_offset_front_ids, offset_fronts, offsets_corresponding_to_onsets):
    noverlaps = []  # will contain tuples of the form (number_overlapping, offset_front_id)
    for offset_front_id in candidate_offset_front_ids:
        offset_front_f_idxs, offset_front_s_idxs = np.where(offset_fronts == offset_front_id)
        offset_front_idxs = [(f, i) for f, i in zip(offset_front_f_idxs, offset_front_s_idxs)]
        noverlap_this_id = len(set(offset_front_idxs).symmetric_difference(set(offsets_corresponding_to_onsets)))
        noverlaps.append((noverlap_this_id, offset_front_id))
    _overlapped, chosen_offset_front_id = max(noverlaps, key=lambda t: t[0])
    return int(chosen_offset_front_id)

Response:
Returns a front ID that is the id of the most overlap
    with the given onset front ID.


Instruction:
Make a summary of the following Python 3 code
def _get_offset_front_id_after_onset_sample_idx(onset_sample_idx, offset_fronts):
    # get all the offset_front_ids
    offset_front_ids = [i for i in np.unique(offset_fronts) if i != 0]

    best_id_so_far = -1
    closest_offset_sample_idx = sys.maxsize
    for offset_front_id in offset_front_ids:
        # get all that offset front's indexes
        offset_front_idxs = _get_front_idxs_from_id(offset_fronts, offset_front_id)

        # get the sample indexes
        offset_front_sample_idxs = [s for _f, s in offset_front_idxs]

        # if each sample index is greater than onset_sample_idx, keep this offset front if it is the best one so far
        min_sample_idx = min(offset_front_sample_idxs)
        if min_sample_idx > onset_sample_idx and min_sample_idx < closest_offset_sample_idx:
            closest_offset_sample_idx = min_sample_idx
            best_id_so_far = offset_front_id

    assert best_id_so_far > 1 or best_id_so_far == -1
    return best_id_so_far

Response: Returns the offset_front_id which corresponds to the offset front after the given onset sample_idx.


Instruction:
Can you generate the documentation for the following Python 3 function
def _get_offset_front_id_after_onset_front(onset_front_id, onset_fronts, offset_fronts):
    # get the onset idxs for this front
    onset_idxs = _get_front_idxs_from_id(onset_fronts, onset_front_id)

    # get the sample idxs for this front
    onset_sample_idxs = [s for _f, s in onset_idxs]

    # get the latest onset in this onset front
    latest_onset_in_front = max(onset_sample_idxs)

    offset_front_id_after_this_onset_front = _get_offset_front_id_after_onset_sample_idx(latest_onset_in_front, offset_fronts)

    return int(offset_front_id_after_this_onset_front)

Response: Get the ID corresponding to the offset after the given onset front.


Instruction:
Can you write a function in Python 3 where it
gives a onset_front_id and a list of offsets and a list of onsets and offsets return the first matching onset_front_id.

Response:
def _match_offset_front_id_to_onset_front_id(onset_front_id, onset_fronts, offset_fronts, onsets, offsets):
    """
    Find all offset fronts which are composed of at least one offset which corresponds to one of the onsets in the
    given onset front.
    The offset front which contains the most of such offsets is the match.
    If there are no such offset fronts, return -1.
    """
    # find all offset fronts which are composed of at least one offset which corresponds to one of the onsets in the onset front
    # the offset front which contains the most of such offsets is the match

    # get the onsets that make up front_id
    onset_idxs = _get_front_idxs_from_id(onset_fronts, onset_front_id)

    # get the offsets that match the onsets in front_id
    offset_idxs = [_lookup_offset_by_onset_idx(i, onsets, offsets) for i in onset_idxs]

    # get all offset_fronts which contain at least one of these offsets
    candidate_offset_front_ids = set([int(offset_fronts[f, i]) for f, i in offset_idxs])

    # It is possible that offset_idxs contains offset indexes that correspond to offsets that did not
    # get formed into a front - those will have a front ID of 0. Remove them.
    candidate_offset_front_ids = [id for id in candidate_offset_front_ids if id != 0]

    if candidate_offset_front_ids:
        chosen_offset_front_id = _choose_front_id_from_candidates(candidate_offset_front_ids, offset_fronts, offset_idxs)
    else:
        chosen_offset_front_id = _get_offset_front_id_after_onset_front(onset_front_id, onset_fronts, offset_fronts)

    return chosen_offset_front_id


Instruction:
How would you implement a function in Python 3 that
yields lists of the form [ f s ) where f is the first element of front and s is the second element of front.

Response:
def _get_consecutive_portions_of_front(front):
    """
    Yields lists of the form [(f, s), (f, s)], one at a time from the given front (which is a list of the same form),
    such that each list yielded is consecutive in frequency.
    """
    last_f = None
    ls = []
    for f, s in front:
        if last_f is not None and f != last_f + 1:
            yield ls
            ls = []
        ls.append((f, s))
        last_f = f
    yield ls


Instruction:
Can you write a function in Python 3 where it
gets the consecutive portion of the onset_front and offset_front of the same order.

Response:
def _get_consecutive_and_overlapping_fronts(onset_fronts, offset_fronts, onset_front_id, offset_front_id):
    """
    Gets an onset_front and an offset_front such that they both occupy at least some of the same
    frequency channels, then returns the portion of each that overlaps with the other.
    """
    # Get the onset front of interest
    onset_front = _get_front_idxs_from_id(onset_fronts, onset_front_id)

    # Get the offset front of interest
    offset_front = _get_front_idxs_from_id(offset_fronts, offset_front_id)

    # Keep trying consecutive portions of this onset front until we find a consecutive portion
    # that overlaps with part of the offset front
    consecutive_portions_of_onset_front = [c for c in _get_consecutive_portions_of_front(onset_front)]
    for consecutive_portion_of_onset_front in consecutive_portions_of_onset_front:
        # Only get the segment of this front that overlaps in frequencies with the onset front of interest
        onset_front_frequency_indexes = [f for f, _ in consecutive_portion_of_onset_front]
        overlapping_offset_front = [(f, s) for f, s in offset_front if f in onset_front_frequency_indexes]

        # Only get as much of this overlapping portion as is actually consecutive
        for consecutive_portion_of_offset_front in _get_consecutive_portions_of_front(overlapping_offset_front):
            if consecutive_portion_of_offset_front:
                # Just return the first one we get - if we get any it means we found a portion of overlap
                return consecutive_portion_of_onset_front, consecutive_portion_of_offset_front
    return [], []


Instruction:
Create a Python 3 function for
returning 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.

Response:
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


Instruction:
Can you generate a brief explanation for the following Python 3 code
def _front_id_from_idx(front, index):
    fidx, sidx = index
    id = front[fidx, sidx]
    if id == 0:
        return -1
    else:
        return id

Response: Returns the front ID found in front at the given index.


Instruction:
Can you generate the documentation for the following Python 3 function
def _get_front_ids_one_at_a_time(onset_fronts):
    yielded_so_far = set()
    for row in onset_fronts:
        for id in row:
            if id != 0 and id not in yielded_so_far:
                yield id
                yielded_so_far.add(id)

Response: Yields one onset front ID at a time until they are gone.


Instruction:
Create a Python 3 function to
get the offsets that occur as close as possible to the onsets in the given onset - front.

Response:
def _get_corresponding_offsets(onset_fronts, onset_front_id, onsets, offsets):
    """
    Gets the offsets that occur as close as possible to the onsets in the given onset-front.
    """
    corresponding_offsets = []
    for index in _get_front_idxs_from_id(onset_fronts, onset_front_id):
        offset_fidx, offset_sidx = _lookup_offset_by_onset_idx(index, onsets, offsets)
        corresponding_offsets.append((offset_fidx, offset_sidx))
    return corresponding_offsets


Instruction:
How would you implement a function in Python 3 that
returns all the offset fronts that are composed of at least one of the given offset indexes.

Response:
def _get_all_offset_fronts_from_offsets(offset_fronts, corresponding_offsets):
    """
    Returns all the offset fronts that are composed of at least one of the given offset indexes.
    Also returns a dict of the form {offset_front_id: ntimes saw}
    """
    all_offset_fronts_of_interest = []
    ids_ntimes_seen = {}
    for offset_index in corresponding_offsets:
        offset_id = _front_id_from_idx(offset_fronts, offset_index)
        if offset_id not in ids_ntimes_seen:
            offset_front_idxs = _get_front_idxs_from_id(offset_fronts, offset_id)
            all_offset_fronts_of_interest.append(offset_front_idxs)
            ids_ntimes_seen[offset_id] = 1
        else:
            ids_ntimes_seen[offset_id] += 1
    return all_offset_fronts_of_interest, ids_ntimes_seen


Instruction:
Here you have a function in Python 3, explain what it does
def _remove_overlaps(segmentation_mask, fronts):
    fidxs, sidxs = np.where((segmentation_mask != fronts) & (segmentation_mask != 0) & (fronts != 0))
    fronts[fidxs, sidxs] = 0

Response: Removes all points in the segmented fronts that overlap with the segmentation mask.


Instruction:
Can you write a function in Python 3 where it
returns segmentation mask for the frequency - band of the onset - fronts.

Response:
def _match_fronts(onset_fronts, offset_fronts, onsets, offsets, debug=False):
    """
    Returns a segmentation mask, which looks like this:
    frequency 1: 0 0 4 4 4 4 4 0 0 5 5 5
    frequency 2: 0 4 4 4 4 4 0 0 0 0 5 5
    frequency 3: 0 4 4 4 4 4 4 4 5 5 5 5

    That is, each item in the array is either a 0 (not part of a segment) or a positive
    integer which indicates which segment the sample in that frequency band belongs to.
    """
    def printd(*args, **kwargs):
        if debug:
            print(*args, **kwargs)

    # Make copies of everything, so we can do whatever we want with them
    onset_fronts = np.copy(onset_fronts)
    offset_fronts = np.copy(offset_fronts)
    onsets = np.copy(onsets)
    offsets = np.copy(offsets)

    # This is what we will return
    segmentation_mask = np.zeros_like(onset_fronts)

    # - Take the first frequency in the onset_fronts matrix
    #     [ s s s s s s s s s] <-- This frequency
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]

    # - Follow it along in time like this:

    #     first sample    last sample
    #       v      -->      v
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]
    #     [ s s s s s s s s s]

    # until you get to the first onset front in that frequency

    #     Here it is!
    #         v
    #     [ . O . . . . . . .]
    #     [ . . O . . . . . .]
    #     [ . . O . . . . . .]
    #     [ . O . . . . . . .]
    #     [ O . . . . . . . .]

    resulting_onset_fronts = np.copy(onset_fronts)
    printd("    -> Dealing with onset fronts...")
    for onset_front_id in _get_front_ids_one_at_a_time(onset_fronts):
        printd("      -> Dealing with onset front", int(onset_front_id))
        front_is_complete = False
        while not front_is_complete:
            # - Now, starting at this onset front in each frequency, find that onset's corresponding offset

            #     [ . O . . . . F . .]
            #     [ . . O . . . F . .]
            #     [ . . O . F . . . .]
            #     [ . O . F . . . . .]
            #     [ O F . . . . . . .]

            corresponding_offsets = _get_corresponding_offsets(resulting_onset_fronts, onset_front_id, onsets, offsets)

            # It is possible that onset_front_id has been removed from resulting_onset_fronts,
            # if so, skip it and move on to the next onset front (we are iterating over the original
            # to keep the iterator valid)
            if not corresponding_offsets:
                break

            # - Get all the offset fronts that are composed of at least one of these offset times

            #     [ . O . . . . 1 . .]
            #     [ . . O 3 . . 1 . .]
            #     [ . . O 3 F . 1 . .]
            #     [ . O . 3 . . . 1 .]
            #     [ O F 3 . . . . . .]

            _all_offset_fronts_of_interest, ids_ntimes_seen = _get_all_offset_fronts_from_offsets(offset_fronts, corresponding_offsets)

            # - Check how many of these offset times each of the offset fronts are composed of:

            #     [ . O . . . . Y . .]
            #     [ . . O 3 . . Y . .]
            #     [ . . O 3 F . 1 . .]
            #     [ . O . X . . . 1 .]
            #     [ O F 3 . . . . . .]

            # In this example, offset front 1 is made up of 4 offset times, 2 of which (the Y's) are offset times
            # that correspond to onsets in the onset front we are currently dealing with. Meanwhile, offset
            # front 3 is made up of 4 offset times, only one of which (the X) is one of the offsets that corresponds
            # to the onset front.

            # - Choose the offset front which matches the most offset time candidates. In this example, offset front 1
            #   is chosen because it has 2 of these offset times.
            #   If there is a tie, we choose the ID with the lower number
            ntimes_seen_sorted = sorted([(k, v) for k, v in ids_ntimes_seen.items()], key=lambda tup: (-1 * tup[1], tup[0]))
            assert len(ntimes_seen_sorted) > 0, "We somehow got an empty dict of offset front IDs"

            # Only use the special final front (the -1, catch-all front composed of final samples in each frequency) if necessary
            offset_front_id, _ntimes_seen = ntimes_seen_sorted[0]
            if offset_front_id == -1 and len(ntimes_seen_sorted) > 1:
                offset_front_id, _ntimes_seen = ntimes_seen_sorted[1]
            offset_front_id_most_overlap = offset_front_id

            # - Finally, update the segmentation mask to follow the offset
            #   front from where it first overlaps in frequency with the onset front to where it ends or to where
            #   the onset front ends, whichever happens first.

            #     [ . S S S S S S . .]
            #     [ . . S S S S S . .]
            #     [ . . S S S S S . .]
            #     [ . S S S S S S S .]
            #     [ O F 3 . . . . . .]  <-- This frequency has not yet been matched with an offset front
            front_is_complete = _update_segmentation_mask(segmentation_mask,
                                                            resulting_onset_fronts,
                                                            offset_fronts,
                                                            onset_front_id,
                                                            offset_front_id_most_overlap)

            # Remove any onsets that are covered by the new segmentation mask
            _remove_overlaps(segmentation_mask, resulting_onset_fronts)

            # Remove any offsets that are covered by the new segmentaion mask
            _remove_overlaps(segmentation_mask, offset_fronts)

            # - Repeat this algorithm, restarting in the first frequency channel that did not match (the last frequency in
            #   the above example). Do this until you have finished with this onset front.

        # - Repeat for each onset front in the rest of this frequency
        # - Repeat for each frequency

    return segmentation_mask


Instruction:
Can you create a Python 3 function that
removes all fronts from the given list that are strictly smaller than size consecutive frequencies in length.

Response:
def _remove_fronts_that_are_too_small(fronts, size):
    """
    Removes all fronts from `fronts` which are strictly smaller than
    `size` consecutive frequencies in length.
    """
    ids = np.unique(fronts)
    for id in ids:
        if id == 0 or id == -1:
            continue
        front = _get_front_idxs_from_id(fronts, id)
        if len(front) < size:
            indexes = ([f for f, _ in front], [s for _, s in front])
            fronts[indexes] = 0


Instruction:
How would you explain what the following Python 3 function does
def _break_poorly_matched_fronts(fronts, threshold=0.1, threshold_overlap_samples=3):
    assert threshold_overlap_samples > 0, "Number of samples of overlap must be greater than zero"
    breaks_after = {}
    for front_id in _get_front_ids_one_at_a_time(fronts):
        front = _get_front_idxs_from_id(fronts, front_id)
        for i, (f, s) in enumerate(front):
            if i < len(front) - 1:
                # Get the signal from f, s to f, s+1 and the signal from f+1, s to f+1, s+1
                next_f, next_s = front[i + 1]
                low_s = min(s, next_s)
                high_s = max(s, next_s)
                sig_this_f = fronts[f, low_s:high_s]
                sig_next_f = fronts[next_f, low_s:high_s]
                assert len(sig_next_f) == len(sig_this_f)

                if len(sig_next_f) > threshold_overlap_samples:
                    # If these two signals are not sufficiently close in form, this front should be broken up
                    correlation = signal.correlate(sig_this_f, sig_next_f, mode='same')
                    assert len(correlation) > 0
                    correlation = correlation / max(correlation + 1E-9)
                    similarity = np.sum(correlation) / len(correlation)
                    # TODO: the above stuff probably needs to be figured out
                    if similarity < threshold:
                        if front_id in breaks_after:
                            breaks_after[front_id].append((f, s))
                        else:
                            breaks_after[front_id] = []

    # Now update the fronts matrix by breaking up any fronts at the points we just identified
    # and assign the newly created fronts new IDs
    taken_ids = sorted(np.unique(fronts))
    next_id = taken_ids[-1] + 1
    for id in breaks_after.keys():
        for f, s in breaks_after[id]:
            fidxs, sidxs = np.where(fronts == id)
            idxs_greater_than_f = [fidx for fidx in fidxs if fidx > f]
            start = len(sidxs) - len(idxs_greater_than_f)
            indexes = (idxs_greater_than_f, sidxs[start:])
            fronts[indexes] = next_id
            next_id += 1

    _remove_fronts_that_are_too_small(fronts, 3)

Response: Break poorly matched fronts into two onset fronts.


Instruction:
Implement a Python 3 function for
updating the segmentation mask if there is any overlap.

Response:
def _update_segmentation_mask_if_overlap(toupdate, other, id, otherid):
    """
    Merges the segments specified by `id` (found in `toupdate`) and `otherid`
    (found in `other`) if they overlap at all. Updates `toupdate` accordingly.
    """
    # If there is any overlap or touching, merge the two, otherwise just return
    yourmask = other == otherid
    mymask = toupdate == id
    overlap_exists = np.any(yourmask & mymask)
    if not overlap_exists:
        return

    yourfidxs, yoursidxs = np.where(other == otherid)
    toupdate[yourfidxs, yoursidxs] = id


Instruction:
Implement a function in Python 3 to
check if two segments are adjacent at any point.

Response:
def _segments_are_adjacent(seg1, seg2):
    """
    Checks if seg1 and seg2 are adjacent at any point. Each is a tuple of the form
    (fidxs, sidxs).
    """
    # TODO: This is unnacceptably slow
    lsf1, lss1 = seg1
    lsf2, lss2 = seg2
    for i, f1 in enumerate(lsf1):
        for j, f2 in enumerate(lsf2):
            if f1 <= f2 + 1 and f1 >= f2 - 1:
                # Frequencies are a match, are samples?
                if lss1[i] <= lss2[j] + 1 and lss1[i] >= lss2[j] - 1:
                    return True
    return False


Instruction:
Can you tell what is the following Python 3 function doing
def _merge_adjacent_segments(mask):
    mask_ids = [id for id in np.unique(mask) if id != 0]
    for id in mask_ids:
        myfidxs, mysidxs = np.where(mask == id)
        for other in mask_ids:  # Ugh, brute force O(N^2) algorithm.. gross..
            if id == other:
                continue
            else:
                other_fidxs, other_sidxs = np.where(mask == other)
                if _segments_are_adjacent((myfidxs, mysidxs), (other_fidxs, other_sidxs)):
                    mask[other_fidxs, other_sidxs] = id

Response: Merges all segments in mask which are touching.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def _integrate_segmentation_masks(segmasks):
    if len(segmasks) == 1:
        return segmasks

    assert len(segmasks) > 0, "Passed in empty list of segmentation masks"
    coarse_mask = np.copy(segmasks[0])
    mask_ids = [id for id in np.unique(coarse_mask) if id != 0]
    for id in mask_ids:
        for mask in segmasks[1:]:
            finer_ids = [i for i in np.unique(mask) if i != 0]
            for finer_id in finer_ids:
                _update_segmentation_mask_if_overlap(coarse_mask, mask, id, finer_id)

    # Lastly, merge all adjacent blocks, but just kidding, since this algorithm is waaaay to slow
    #_merge_adjacent_segments(coarse_mask)
    return coarse_mask

Response: Integrates the given list of segmentation masks together to form one segmentation mask.


Instruction:
How would you code a function in Python 3 to
return a list of segmentation masks each of the same dimension as the input one and where each segment in each of the same dimension is zeroed.

Response:
def _separate_masks(mask, threshold=0.025):
    """
    Returns a list of segmentation masks each of the same dimension as the input one,
    but where they each have exactly one segment in them and all other samples in them
    are zeroed.

    Only bothers to return segments that are larger in total area than `threshold * mask.size`.
    """
    try:
        ncpus = multiprocessing.cpu_count()
    except NotImplementedError:
        ncpus = 2

    with multiprocessing.Pool(processes=ncpus) as pool:
        mask_ids = [id for id in np.unique(mask) if id != 0]
        thresholds = [threshold * mask.size for _ in range(len(mask_ids))]
        masks = [mask for _ in range(len(mask_ids))]
        ms = pool.starmap(_separate_masks_task, zip(mask_ids, thresholds, masks))
    return [m for m in ms if m is not None]


Instruction:
Write a Python 3 function that can
take the given `mask` and `stft`, which must be matrices of shape `frequencies, times` and downsamples one of them into the other one's times, so that the time dimensions are equal. Leaves the frequency dimension untouched.

Response:
def _downsample_one_or_the_other(mask, mask_indexes, stft, stft_indexes):
    """
    Takes the given `mask` and `stft`, which must be matrices of shape `frequencies, times`
    and downsamples one of them into the other one's times, so that the time dimensions
    are equal. Leaves the frequency dimension untouched.
    """
    assert len(mask.shape) == 2, "Expected a two-dimensional `mask`, but got one of {} dimensions.".format(len(mask.shape))
    assert len(stft.shape) == 2, "Expected a two-dimensional `stft`, but got one of {} dimensions.".format(len(stft.shape))

    if mask.shape[1] > stft.shape[1]:
        downsample_factor = mask.shape[1] / stft.shape[1]
        indexes = _get_downsampled_indexes(mask, downsample_factor)
        mask = mask[:, indexes]
        mask_indexes = np.array(indexes)
    elif mask.shape[1] < stft.shape[1]:
        downsample_factor = stft.shape[1] / mask.shape[1]
        indexes = _get_downsampled_indexes(stft, downsample_factor)
        stft = stft[:, indexes]
        stft_indexes = np.array(indexes)

    return mask, mask_indexes, stft, stft_indexes


Instruction:
Make a summary of the following Python 3 code
def _map_segmentation_mask_to_stft_domain(mask, times, frequencies, stft_times, stft_frequencies):
    assert mask.shape == (frequencies.shape[0], times.shape[0]), "Times is shape {} and frequencies is shape {}, but mask is shaped {}".format(
        times.shape, frequencies.shape, mask.shape
    )
    result = np.zeros((stft_frequencies.shape[0], stft_times.shape[0]))

    if len(stft_times) > len(times):
        all_j = [j for j in range(len(stft_times))]
        idxs  = [int(i) for i in np.linspace(0, len(times) - 1, num=len(stft_times))]
        all_i = [all_j[idx] for idx in idxs]
    else:
        all_i = [i for i in range(len(times))]
        idxs  = [int(i) for i in np.linspace(0, len(stft_times) - 1, num=len(times))]
        all_j = [all_i[idx] for idx in idxs]

    for i, j in zip(all_i, all_j):
        result[:, j] = np.interp(stft_frequencies, frequencies, mask[:, i])

    return result

Response: Maps the given mask which is in domain ( frequencies times stft_times and stft_frequencies and returns the result.


Instruction:
Can you generate the documentation for the following Python 3 function
def _asa_task(q, masks, stft, sample_width, frame_rate, nsamples_for_each_fft):
    # Convert each mask to (1 or 0) rather than (ID or 0)
    for mask in masks:
        mask = np.where(mask > 0, 1, 0)

    # Multiply the masks against STFTs
    masks = [mask * stft for mask in masks]

    nparrs = []
    dtype_dict = {1: np.int8, 2: np.int16, 4: np.int32}
    dtype = dtype_dict[sample_width]
    for m in masks:
        _times, nparr = signal.istft(m, frame_rate, nperseg=nsamples_for_each_fft)
        nparr = nparr.astype(dtype)
        nparrs.append(nparr)

    for m in nparrs:
        q.put(m)
    q.put("DONE")

Response: A task that performs the ASA algorithm.


Instruction:
Implement a function in Python 3 to
run a Markov Decision Process over the given audio segment and returns a generator of integers.

Response:
def _get_filter_indices(seg, start_as_yes, prob_raw_yes, ms_per_input, model, transition_matrix, model_stats):
    """
    Runs a Markov Decision Process over the given `seg` in chunks of `ms_per_input`, yielding `True` if
    this `ms_per_input` chunk has been classified as positive (1) and `False` if this chunk has been
    classified as negative (0).

    :param seg:                 The AudioSegment to apply this algorithm to.
    :param start_as_yes:        If True, the first `ms_per_input` chunk will be classified as positive.
    :param prob_raw_yes:        The raw probability of finding the event in any given independently sampled `ms_per_input`.
    :param ms_per_input:        The number of ms of AudioSegment to be fed into the model at a time.
    :param model:               The model, which must hava predict() function, which takes an AudioSegment of `ms_per_input`
                                number of ms and which outputs 1 if the audio event is detected in that input, 0 if not.
    :param transition_matrix:   An iterable of the form: [p(yes->no), p(no->yes)].
    :param model_stats:         An iterable of the form: [p(reality=1|output=1), p(reality=1|output=0)].
    :yields:                    `True` if the event has been classified in this chunk, `False` otherwise.
    """
    filter_triggered = 1 if start_as_yes else 0
    prob_raw_no = 1.0 - prob_raw_yes
    for segment, _timestamp in seg.generate_frames_as_segments(ms_per_input):
        yield filter_triggered
        observation = int(round(model.predict(segment)))
        assert observation == 1 or observation == 0, "The given model did not output a 1 or a 0, output: "\
                + str(observation)
        prob_hyp_yes_given_last_hyp = 1.0 - transition_matrix[0] if filter_triggered else transition_matrix[1]
        prob_hyp_no_given_last_hyp  = transition_matrix[0] if filter_triggered else 1.0 - transition_matrix[1]
        prob_hyp_yes_given_data = model_stats[0] if observation == 1 else model_stats[1]
        prob_hyp_no_given_data = 1.0 - model_stats[0] if observation == 1 else 1.0 - model_stats[1]
        hypothesis_yes = prob_raw_yes * prob_hyp_yes_given_last_hyp * prob_hyp_yes_given_data
        hypothesis_no  = prob_raw_no * prob_hyp_no_given_last_hyp  * prob_hyp_no_given_data
        # make a list of ints - each is 0 or 1. The number of 1s is hypotheis_yes * 100
        # the number of 0s is hypothesis_no * 100
        distribution = [1 for i in range(int(round(hypothesis_yes * 100)))]
        distribution.extend([0 for i in range(int(round(hypothesis_no * 100)))])
        # shuffle
        random.shuffle(distribution)
        filter_triggered = random.choice(distribution)


Instruction:
Can you write a function in Python 3 where it
takes a list of 1s and 0s and returns a list of tuples of the form y n

Response:
def _group_filter_values(seg, filter_indices, ms_per_input):
    """
    Takes a list of 1s and 0s and returns a list of tuples of the form:
    ['y/n', timestamp].
    """
    ret = []
    for filter_value, (_segment, timestamp) in zip(filter_indices, seg.generate_frames_as_segments(ms_per_input)):
        if filter_value == 1:
            if len(ret) > 0 and ret[-1][0] == 'n':
                ret.append(['y', timestamp])  # The last one was different, so we create a new one
            elif len(ret) > 0 and ret[-1][0] == 'y':
                ret[-1][1] = timestamp  # The last one was the same as this one, so just update the timestamp
            else:
                ret.append(['y', timestamp])  # This is the first one
        else:
            if len(ret) > 0 and ret[-1][0] == 'n':
                ret[-1][1] = timestamp
            elif len(ret) > 0 and ret[-1][0] == 'y':
                ret.append(['n', timestamp])
            else:
                ret.append(['n', timestamp])
    return ret


Instruction:
Can you create a Python 3 function that
takes `ls` (a list of 1s and 0s) and smoothes it so that adjacent values are more likely to be the same. :param ls: A list of 1s and 0s to smooth. :param window_size: How large the smoothing kernel is. :returns: A list of 1s and 0s, but smoother.

Response:
def _homogeneity_filter(ls, window_size):
    """
    Takes `ls` (a list of 1s and 0s) and smoothes it so that adjacent values are more likely
    to be the same.

    :param ls:          A list of 1s and 0s to smooth.
    :param window_size: How large the smoothing kernel is.
    :returns:           A list of 1s and 0s, but smoother.
    """
    # TODO: This is fine way to do this, but it seems like it might be faster and better to do a Gaussian convolution followed by rounding
    k = window_size
    i = k
    while i <= len(ls) - k:
        # Get a window of k items
        window = [ls[i + j] for j in range(k)]
        # Change the items in the window to be more like the mode of that window
        mode = 1 if sum(window) >= k / 2 else 0
        for j in range(k):
            ls[i+j] = mode
        i += k
    return ls


Instruction:
Explain what the following Python 3 code does
def bandpass_filter(data, low, high, fs, order=5):
    nyq = 0.5 * fs
    low = low / nyq
    high = high / nyq
    b, a = signal.butter(order, [low, high], btype='band')
    y = signal.lfilter(b, a, data)
    return y

Response: Does a bandpass filter over the given data.


Instruction:
Write a Python 3 script to
do a lowpass filter over the given data.

Response:
def lowpass_filter(data, cutoff, fs, order=5):
    """
    Does a lowpass filter over the given data.

    :param data: The data (numpy array) to be filtered.
    :param cutoff: The high cutoff in Hz.
    :param fs: The sample rate in Hz of the data.
    :param order: The order of the filter. The higher the order, the tighter the roll-off.
    :returns: Filtered data (numpy array).
    """
    nyq = 0.5 * fs
    normal_cutoff = cutoff / nyq
    b, a = signal.butter(order, normal_cutoff, btype='low', analog=False)
    y = signal.lfilter(b, a, data)
    return y


Instruction:
Here you have a function in Python 3, explain what it does
def list_to_tf_input(data, response_index, num_outcomes):
  matrix = np.matrix([row[:response_index] + row[response_index+1:] for row in data])
  outcomes = np.asarray([row[response_index] for row in data], dtype=np.uint8)
  outcomes_onehot = (np.arange(num_outcomes) == outcomes[:, None]).astype(np.float32)

  return matrix, outcomes_onehot

Response: Converts a list of data into a tf. Input matrix and onehot vectors.


Instruction:
Implement a Python 3 function for
expanding continuous features and standardizes categorical features and expands continuous features.

Response:
def expand_and_standardize_dataset(response_index, response_header, data_set, col_vals, headers, standardizers, feats_to_ignore, columns_to_expand, outcome_trans_dict):
  """
  Standardizes continuous features and expands categorical features.
  """
  # expand and standardize
  modified_set = []
  for row_index, row in enumerate(data_set):
    new_row = []
    for col_index, val in enumerate(row):
      header = headers[col_index]

      # Outcome feature -> index outcome
      if col_index == response_index:
        new_outcome = outcome_trans_dict[val]
        new_row.append(new_outcome)

      # Ignored feature -> pass
      elif header in feats_to_ignore:
        pass

      # Categorical feature -> create new binary column for each possible value of the column
      elif header in columns_to_expand:
        for poss_val in col_vals[header]:
          if val == poss_val:
            new_cat_val = 1.0
          else:
            new_cat_val = -1.0
          new_row.append(new_cat_val)

      # Continuous feature -> standardize value with respect to its column
      else:
        new_cont_val = float((val - standardizers[header]['mean']) / standardizers[header]['std_dev'])
        new_row.append(new_cont_val)

    modified_set.append(new_row)

  # update headers to reflect column expansion
  expanded_headers = []
  for header in headers:
    if header in feats_to_ignore:
      pass
    elif (header in columns_to_expand) and (header is not response_header):
      for poss_val in col_vals[header]:
        new_header = '{}_{}'.format(header,poss_val)
        expanded_headers.append(new_header)
    else:
      expanded_headers.append(header)

  return modified_set, expanded_headers


Instruction:
Can you tell what is the following Python 3 function doing
def equal_ignore_order(a, b):
  unmatched = list(b)
  for element in a:
    try:
      unmatched.remove(element)
    except ValueError:
      return False
  return not unmatched

Response: Returns True if the two edge lists have the same elements.


Instruction:
Explain what the following Python 3 code does
def repair(self, data_to_repair):
    num_cols = len(data_to_repair[0])
    col_ids = range(num_cols)

    # Get column type information
    col_types = ["Y"]*len(col_ids)
    for i, col in enumerate(col_ids):
      if i in self.features_to_ignore:
        col_types[i] = "I"
      elif i == self.feature_to_repair:
        col_types[i] = "X"

    col_type_dict = {col_id: col_type for col_id, col_type in zip(col_ids, col_types)}

    not_I_col_ids = filter(lambda x: col_type_dict[x] != "I", col_ids)

    if self.kdd:
      cols_to_repair = filter(lambda x: col_type_dict[x] == "Y", col_ids)
    else:
      cols_to_repair = filter(lambda x: col_type_dict[x] in "YX", col_ids)

    # To prevent potential perils with user-provided column names, map them to safe column names
    safe_stratify_cols = [self.feature_to_repair]

    # Extract column values for each attribute in data
    # Begin byled code will usually be created in the same directory as the .py file. initializing keys and values in dictionary
    data_dict = {col_id: [] for col_id in col_ids}

    # Populate each attribute with its column values
    for row in data_to_repair:
      for i in col_ids:
        data_dict[i].append(row[i])

    repair_types = {}
    for col_id, values in data_dict.items():
      if all(isinstance(value, float) for value in values):
        repair_types[col_id] = float
      elif all(isinstance(value, int) for value in values):
        repair_types[col_id] = int
      else:
        repair_types[col_id] = str

    """
     Create unique value structures: When performing repairs, we choose median values. If repair is partial, then values will be modified to some intermediate value between the original and the median value. However, the partially repaired value will only be chosen out of values that exist in the data set.  This prevents choosing values that might not make any sense in the data's context.  To do this, for each column, we need to sort all unique values and create two data structures: a list of values, and a dict mapping values to their positions in that list. Example: There are unique_col_vals[col] = [1, 2, 5, 7, 10, 14, 20] in the column. A value 2 must be repaired to 14, but the user requests that data only be repaired by 50%. We do this by finding the value at the right index:
       index_lookup[col][2] = 1
       index_lookup[col][14] = 5
       this tells us that unique_col_vals[col][3] = 7 is 50% of the way from 2 to 14.
    """
    unique_col_vals = {}
    index_lookup = {}
    for col_id in not_I_col_ids:
      col_values = data_dict[col_id]
      # extract unique values from column and sort
      col_values = sorted(list(set(col_values)))
      unique_col_vals[col_id] = col_values
      # look up a value, get its position
      index_lookup[col_id] = {col_values[i]: i for i in range(len(col_values))}

    """
     Make a list of unique values per each stratified column.  Then make a list of combinations of stratified groups. Example: race and gender cols are stratified: [(white, female), (white, male), (black, female), (black, male)] The combinations are tuples because they can be hashed and used as dictionary keys.  From these, find the sizes of these groups.
    """
    unique_stratify_values = [unique_col_vals[i] for i in safe_stratify_cols]
    all_stratified_groups = list(product(*unique_stratify_values))
    # look up a stratified group, and get a list of indices corresponding to that group in the data
    stratified_group_indices = defaultdict(list)

    # Find the number of unique values for each strat-group, organized per column.
    val_sets = {group: {col_id:set() for col_id in cols_to_repair}
                                     for group in all_stratified_groups}
    for i, row in enumerate(data_to_repair):
      group = tuple(row[col] for col in safe_stratify_cols)
      for col_id in cols_to_repair:
        val_sets[group][col_id].add(row[col_id])

      # Also remember that this row pertains to this strat-group.
      stratified_group_indices[group].append(i)

    """
     Separate data by stratified group to perform repair on each Y column's values given that their corresponding protected attribute is a particular stratified group. We need to keep track of each Y column's values corresponding to each particular stratified group, as well as each value's index, so that when we repair the data, we can modify the correct value in the original data. Example: Supposing there is a Y column, "Score1", in which the 3rd and 5th scores, 70 and 90 respectively, belonged to black women, the data structure would look like: {("Black", "Woman"): {Score1: [(70,2),(90,4)]}}
    """
    stratified_group_data = {group: {} for group in all_stratified_groups}
    for group in all_stratified_groups:
      for col_id, col_dict in data_dict.items():
        # Get the indices at which each value occurs.
        indices = {}
        for i in stratified_group_indices[group]:
          value = col_dict[i]
          if value not in indices:
            indices[value] = []
          indices[value].append(i)

        stratified_col_values = [(occurs, val) for val, occurs in indices.items()]
        stratified_col_values.sort(key=lambda tup: tup[1])
        stratified_group_data[group][col_id] = stratified_col_values

    mode_feature_to_repair = get_mode(data_dict[self.feature_to_repair])

    # Repair Data and retrieve the results
    for col_id in cols_to_repair:
      # which bucket value we're repairing
      group_offsets = {group: 0 for group in all_stratified_groups}
      col = data_dict[col_id]

      num_quantiles = min(len(val_sets[group][col_id]) for group in all_stratified_groups)
      quantile_unit = 1.0/num_quantiles

      if repair_types[col_id] in {int, float}:
        for quantile in range(num_quantiles):
          median_at_quantiles = []
          indices_per_group = {}

          for group in all_stratified_groups:
            group_data_at_col = stratified_group_data[group][col_id]
            num_vals = len(group_data_at_col)
            offset = int(round(group_offsets[group]*num_vals))
            number_to_get = int(round((group_offsets[group] + quantile_unit)*num_vals) - offset)
            group_offsets[group] += quantile_unit

            if number_to_get > 0:

              # Get data at this quantile from this Y column such that stratified X = group
              offset_data = group_data_at_col[offset:offset+number_to_get]
              indices_per_group[group] = [i for val_indices, _ in offset_data for i in val_indices]
              values = sorted([float(val) for _, val in offset_data])

              # Find this group's median value at this quantile
              median_at_quantiles.append( get_median(values, self.kdd) )

          # Find the median value of all groups at this quantile (chosen from each group's medians)
          median = get_median(median_at_quantiles, self.kdd)
          median_val_pos = index_lookup[col_id][median]

          # Update values to repair the dataset.
          for group in all_stratified_groups:
            for index in indices_per_group[group]:
              original_value = col[index]

              current_val_pos = index_lookup[col_id][original_value]
              distance = median_val_pos - current_val_pos # distance between indices
              distance_to_repair = int(round(distance * self.repair_level))
              index_of_repair_value = current_val_pos + distance_to_repair
              repaired_value = unique_col_vals[col_id][index_of_repair_value]

              # Update data to repaired valued
              data_dict[col_id][index] = repaired_value

      #Categorical Repair is done below
      elif repair_types[col_id] in {str}:
        feature = CategoricalFeature(col)
        categories = feature.bin_index_dict.keys()

        group_features = get_group_data(all_stratified_groups, stratified_group_data, col_id)

        categories_count = get_categories_count(categories, all_stratified_groups, group_features)

        categories_count_norm = get_categories_count_norm(categories, all_stratified_groups, categories_count, group_features)

        median = get_median_per_category(categories, categories_count_norm)

        # Partially fill-out the generator functions to simplify later calls.
        dist_generator = lambda group_index, category : gen_desired_dist(group_index, category, col_id, median, self.repair_level, categories_count_norm, self.feature_to_repair, mode_feature_to_repair)

        count_generator = lambda group_index, group, category : gen_desired_count(group_index, group, category, median, group_features, self.repair_level, categories_count)

        group_features, overflow = flow_on_group_features(all_stratified_groups, group_features, count_generator)

        group_features, assigned_overflow, distribution = assign_overflow(all_stratified_groups, categories, overflow, group_features, dist_generator)

        # Return our repaired feature in the form of our original dataset
        for group in all_stratified_groups:
          indices = stratified_group_indices[group]
          for i, index in enumerate(indices):
            repaired_value = group_features[group].data[i]
            data_dict[col_id][index] = repaired_value

    # Replace stratified groups with their mode value, to remove it's information
    repaired_data = []
    for i, orig_row in enumerate(data_to_repair):
      new_row = [orig_row[j] if j not in cols_to_repair else data_dict[j][i] for j in col_ids]
      repaired_data.append(new_row)
    return repaired_data

Response: Repair the data in the archive.


Instruction:
How would you explain what the following Python 3 function does
def group_audit_ranks(filenames, measurer, similarity_bound=0.05):

  def _partition_groups(feature_scores):
    groups = []
    for feature, score in feature_scores:
      added_to_group = False

      # Check to see if the feature belongs in a group with any other features.
      for i, group in enumerate(groups):
        mean_score, group_feature_scores = group
        if abs(mean_score - score) < similarity_bound:
          groups[i][1].append( (feature, score) )

          # Recalculate the representative mean.
          groups[i][0] = sum([s for _, s in group_feature_scores])/len(group_feature_scores)
          added_to_group = True
          break

      # If this feature did not much with the current groups, create another group.
      if not added_to_group:
        groups.append( [score, [(feature,score)]] )

    # Return just the features.
    return [[feature for feature, score in group] for _, group in groups]

  score_dict = {}
  features = []
  for filename in filenames:
    with open(filename) as audit_file:
      header_line = audit_file.readline()[:-1] # Remove the trailing endline.
      feature = header_line[header_line.index(":")+1:]
      features.append(feature)

    confusion_matrices = load_audit_confusion_matrices(filename)
    for rep_level, matrix in confusion_matrices:
      score = measurer(matrix)
      if rep_level not in score_dict:
        score_dict[rep_level] = {}
      score_dict[rep_level][feature] = score

  # Sort by repair level increasing repair level.
  score_keys = sorted(score_dict.keys())

  groups = [features]
  while score_keys:
    key = score_keys.pop()
    new_groups = []
    for group in groups:
      group_features = [(f, score_dict[key][f]) for f in group]
      sub_groups = _partition_groups(group_features)
      new_groups.extend(sub_groups)
    groups = new_groups

  return groups

Response: Given a list of audit files and a measurer function and a similarity bound return the rank of each group.


Instruction:
Here you have a function in Python 3, explain what it does
def accuracy(conf_matrix):
  total, correct = 0.0, 0.0
  for true_response, guess_dict in conf_matrix.items():
    for guess, count in guess_dict.items():
      if true_response == guess:
        correct += count
      total += count
  return correct/total

Response: Calculates the accuracy of a confusion matrix.


Instruction:
Make a summary of the following Python 3 code
def BCR(conf_matrix):
  parts = []
  for true_response, guess_dict in conf_matrix.items():
    error = 0.0
    total = 0.0
    for guess, count in guess_dict.items():
      if true_response != guess:
        error += count
      total += count
    parts.append(error/total)
  BER = sum(parts)/len(parts)
  return 1 - BER

Response: Returns Balanced Error Rate.


Instruction:
How would you code a function in Python 3 to
give an unsorted list of numeric values return the median value.

Response:
def get_median(values, kdd):
  """
  Given an unsorted list of numeric values, return median value (as a float).
  Note that in the case of even-length lists of values, we apply the value to
  the left of the center to be the median (such that the median can only be
  a value from the list of values).
  Eg: get_median([1,2,3,4]) == 2, not 2.5.
  """

  if not values:
    raise Exception("Cannot calculate median of list with no values!")

  sorted_values = deepcopy(values)
  sorted_values.sort() # Not calling `sorted` b/c `sorted_values` may not be list.

  if kdd:
    return sorted_values[len(values)//2]
  else:
    if len(values) % 2 == 0:
      return sorted_values[len(values)//2-1]
    else:
      return sorted_values[len(values)//2]


Instruction:
Make a summary of the following Python 3 code
def expand_to_one_hot(data,expand = True,use_alternative=False):
    header_dict = {'ALCABUS':0,'PRIRCAT':1,'TMSRVC':2,'SEX1':3,'RACE':4,'RELTYP':5,'age_1st_arrest':6,'DRUGAB':7,'Class':8,'RLAGE':9,'NFRCTNS':10}

    new_data = []
    for entry in data:
	temp = {}
	if expand == True:
	    if entry[header_dict["SEX1"]] == "FEMALE":
		temp['female'] = 1
	    else:
		temp['female'] = 0

	    if entry[header_dict["ALCABUS"]] == 'INMATE IS AN ALCOHOL ABUSER':
		temp['prior_alcohol_abuse'] = 1
	    else:
		temp['prior_alcohol_abuse'] = 0

	    if entry[header_dict['DRUGAB']] == 'INMATE IS A DRUG ABUSER':
		temp['prior_drug_abuse'] = 1
	    else:
		temp['prior_drug_abuse'] = 0

	    if entry[header_dict['NFRCTNS']] == 'INMATE HAS RECORD':
		temp['infraction_in_prison'] = 1
	    else:
		temp['infraction_in_prison'] = 0

	    race_cats = ['WHITE','BLACK','AMERICAN INDIAN/ALEUTIAN','ASIAN/PACIFIC ISLANDER','OTHER','UNKNOWN']

	    for cat in race_cats:
		if entry[header_dict['RACE']] == cat:
		    temp['race_'+cat] = 1
		else:
		    temp['race_'+cat] = 0

	    release_age_cats = ['14 TO 17 YEARS OLD','18 TO 24 YEARS OLD', '25 TO 29 YEARS OLD', \
	    '30 TO 34 YEARS OLD','35 TO 39 YEARS OLD','40 TO 44 YEARS OLD','45 YEARS OLD AND OLDER']
	    for cat in release_age_cats:
		if entry[header_dict['RLAGE']] == cat:
		    temp['release_age_'+cat] = 1
		else:
		    temp['release_age_'+cat] = 0

	    time_served_cats = ['None','1 TO 6 MONTHS','13 TO 18 MONTHS','19 TO 24 MONTHS','25 TO 30 MONTHS', \
			'31 TO 36 MONTHS','37 TO 60 MONTHS','61 MONTHS AND HIGHER','7 TO 12 MONTHS']
	    for cat in time_served_cats:
		if entry[header_dict['TMSRVC']] == cat:
		    temp['time_served_'+cat] = 1
		else:
		    temp['time_served_'+cat] = 0

	    prior_arrest_cats = ['None','1 PRIOR ARREST','11 TO 15 PRIOR ARRESTS','16 TO HI PRIOR ARRESTS','2 PRIOR ARRESTS', \
		'3 PRIOR ARRESTS','4 PRIOR ARRESTS','5 PRIOR ARRESTS','6 PRIOR ARRESTS','7 TO 10 PRIOR ARRESTS']
	    for cat in prior_arrest_cats:
		if entry[header_dict['PRIRCAT']] == cat:
		    temp['prior_arrest_'+cat] = 1
		else:
		    temp['prior_arrest_'+cat] = 0

	    conditional_release =['PAROLE BOARD DECISION-SERVED NO MINIMUM','MANDATORY PAROLE RELEASE', 'PROBATION RELEASE-SHOCK PROBATION', \
			'OTHER CONDITIONAL RELEASE']
	    unconditional_release = ['EXPIRATION OF SENTENCE','COMMUTATION-PARDON','RELEASE TO CUSTODY, DETAINER, OR WARRANT', \
			'OTHER UNCONDITIONAL RELEASE']
	    other_release = ['NATURAL CAUSES','SUICIDE','HOMICIDE BY ANOTHER INMATE','OTHER HOMICIDE','EXECUTION','OTHER TYPE OF DEATH', \
		    'TRANSFER','RELEASE ON APPEAL OR BOND','OTHER TYPE OF RELEASE','ESCAPE','ACCIDENTAL INJURY TO SELF','UNKNOWN']
	    if entry[header_dict['RELTYP']] in conditional_release:
		temp['released_conditional'] = 1
		temp['released_unconditional'] = 0
		temp['released_other'] = 0
	    elif entry[header_dict['RELTYP']] in unconditional_release:
		temp['released_conditional'] = 0
		temp['released_unconditional'] = 1
		temp['released_other'] = 0
	    else:
		temp['released_conditional'] = 0
		temp['released_unconditional'] = 0
		temp['released_other'] = 1

	    first_arrest_cats = ['UNDER 17','BETWEEN 18 AND 24','BETWEEN 25 AND 29','BETWEEN 30 AND 39','OVER 40']
	    for cat in first_arrest_cats:
		if entry[header_dict['age_1st_arrest']] == cat:
		    temp['age_first_arrest_'+cat] = 1
		else:
		    temp['age_first_arrest_'+cat] = 0
	else:
	    temp['SEX1'] = entry['SEX1']
	    temp['RELTYP'] = entry['RELTYP']
	    temp['PRIRCAT'] = entry['PRIRCAT']
	    temp['ALCABUS'] = entry['ALCABUS']
	    temp['DRUGAB'] = entry['DRUGAB']
	    temp['RLAGE'] = entry['RLAGE']
	    temp['TMSRVC'] = entry['TMSRVC']
	    temp['NFRCTNS'] = entry['NFRCTNS']
	    temp['RACE'] = entry['RACE']
	    try:
		bdate = datetime.date(int(entry['YEAROB2']),int(entry['MNTHOB2']), int(entry['DAYOB2']))
		first_arrest = datetime.date(int(entry['A001YR']),int(entry['A001MO']),int(entry['A001DA']))
		first_arrest_age = first_arrest - bdate
		temp['age_1st_arrest'] = first_arrest_age.days
	    except:
		temp['age_1st_arrest'] = 0

	new_data.append(temp)

    # convert from dictionary to list of lists
    fin = [[int(entry[key]) for key in entry.keys()] for entry in new_data]
    """
    with open("brandon_testing/test_"+str(time.clock())+".csv","w") as f:
	writer = csv.writer(f,delimiter=",")
	for row in fin:
	    writer.writerow(row)
    """

    return fin

Response: Expand the data to one hot.


Instruction:
Can you write a function in Python 3 where it
loads a confusion matrix from an audit file.

Response:
def load_audit_confusion_matrices(filename):
  """
  Loads a confusion matrix in a two-level dictionary format.

  For example, the confusion matrix of a 75%-accurate model
  that predicted 15 values (and mis-classified 5) may look like:
  {"A": {"A":10, "B": 5}, "B": {"B":5}}

  Note that raw boolean values are translated into strings, such that
  a value that was the boolean True will be returned as the string "True".
  """

  with open(filename) as audit_file:
    audit_file.next() # Skip the first line.

    # Extract the confusion matrices and repair levels from the audit file.
    confusion_matrices = []
    for line in audit_file:
      separator = ":"
      separator_index = line.index(separator)

      comma_index = line.index(',')
      repair_level = float(line[separator_index+2:comma_index])
      raw_confusion_matrix = line[comma_index+2:-2]
      confusion_matrix = json.loads( raw_confusion_matrix.replace("'","\"") )
      confusion_matrices.append( (repair_level, confusion_matrix) )

  # Sort the repair levels in case they are out of order for whatever reason.
  confusion_matrices.sort(key = lambda pair: pair[0])
  return confusion_matrices


Instruction:
Write a Python 3 function that can
convert a list of data into a tf. Input matrix and the list of outcomes.

Response:
def list_to_tf_input(data, response_index, num_outcomes):
  """
  Separates the outcome feature from the data.
  """
  matrix = np.matrix([row[:response_index] + row[response_index+1:] for row in data])
  outcomes = np.asarray([row[response_index] for row in data], dtype=np.uint8)

  return matrix, outcomes