code: def note_onset_evaluation(detections, annotations, window=WINDOW):
      """
      Determine the true/false positive/negative note onset detections.
  
      Parameters
      ----------
      detections : numpy array
          Detected notes.
      annotations : numpy array
          Annotated ground truth notes.
      window : float, optional
          Evaluation window [seconds].
  
      Returns
      -------
      tp : numpy array, shape (num_tp, 2)
          True positive detections.
      fp : numpy array, shape (num_fp, 2)
          False positive detections.
      tn : numpy array, shape (0, 2)
          True negative detections (empty, see notes).
      fn : numpy array, shape (num_fn, 2)
          False negative detections.
      errors : numpy array, shape (num_tp, 2)
          Errors of the true positive detections wrt. the annotations.
  
      Notes
      -----
      The expected note row format is:
  
      'note_time' 'MIDI_note' ['duration' ['MIDI_velocity']]
  
      The returned true negative array is empty, because we are not interested
      in this class, since it is magnitudes bigger than true positives array.
  
      """
      # make sure the arrays have the correct types and dimensions
      detections = np.asarray(detections, dtype=float)
      annotations = np.asarray(annotations, dtype=float)
      # check dimensions
      if detections.ndim != 2 or annotations.ndim != 2:
          raise ValueError(
              "detections and annotations must be 2D arrays"
          )
  
      # init TP, FP, TN and FN lists
      tp = np.zeros((0, 2))
      fp = np.zeros((0, 2))
      tn = np.zeros((0, 2))  # this will not be altered
      fn = np.zeros((0, 2))
      errors = np.zeros((0, 2))
      # if neither detections nor annotations are given
      if detections.size == 0 and annotations.size == 0:
          # return the arrays as is
          return tp, fp, tn, fn, errors
      # if only detections are given
      elif annotations.size == 0:
          # all detections are FP
          return tp, detections, tn, fn, errors
      # if only annotations are given
      elif detections.size == 0:
          # all annotations are FN
          return tp, tp, tn, annotations, errors
  
      # TODO: extend to also evaluate the duration and velocity of notes
      # for onset evaluation use only the onset time and midi note number
      detections = detections[:, :2]
      annotations = annotations[:, :2]
  
      # get a list of all notes detected / annotated
      notes = np.unique(
          np.concatenate((detections[:, 1], annotations[:, 1]))
      ).tolist()
      # iterate over all notes
      for note in notes:
          # perform normal onset detection on each note
          det = detections[detections[:, 1] == note]
          ann = annotations[annotations[:, 1] == note]
          tp_, fp_, _, fn_, err_ = onset_evaluation(
              det[:, 0], ann[:, 0], window
          )
          # convert returned arrays to lists and append the detections and
          # annotations to the correct lists
          tp = np.vstack((tp, det[np.in1d(det[:, 0], tp_)]))
          fp = np.vstack((fp, det[np.in1d(det[:, 0], fp_)]))
          fn = np.vstack((fn, ann[np.in1d(ann[:, 0], fn_)]))
          # append the note number to the errors
          err_ = np.vstack(
              (np.array(err_), np.repeat(np.asarray([note]), len(err_)))
          ).T
          errors = np.vstack((errors, err_))
      # check calculations
      if len(tp) + len(fp) != len(detections):
          raise AssertionError("bad TP / FP calculation")
      if len(tp) + len(fn) != len(annotations):
          raise AssertionError("bad FN calculation")
      if len(tp) != len(errors):
          raise AssertionError("bad errors calculation")
      # sort the arrays
      # Note: The errors must have the same sorting order as the TPs, so they
      #       must be done first (before the TPs get sorted)
      errors = errors[tp[:, 0].argsort()]
      tp = tp[tp[:, 0].argsort()]
      fp = fp[fp[:, 0].argsort()]
      fn = fn[fn[:, 0].argsort()]
      # return the arrays
      return tp, fp, tn, fn, errors
  
imports: from __future__ import absolute_import, division, print_function
  import warnings
  import numpy
  from .None import evaluation_io, MultiClassEvaluation, SumEvaluation, MeanEvaluation
  from .onsets import onset_evaluation, OnsetEvaluation
  from ..io import load_notes
label_desc: Use image node_id 2 for calling a global function with example usage: note_onset_evaluation(detections, annotations, window) and returns: tp,  fp,  tn,  fn,  errors, tp,  fp,  tn,  fn,  errors, tp,  detections,  tn,  fn,  errors, tp,  tp,  tn,  annotations,  errors