docs: def evaluate(self, reference_event_list, estimated_event_list):
          """Evaluate file pair (reference and estimated)
  
          Parameters
          ----------
  
          reference_event_list : event list
              Reference event list
  
          estimated_event_list : event list
              Estimated event list
  
          Returns
          -------
          self
  
          """
  
          # Make sure input is dcase_util.containers.MetaDataContainer
          if not isinstance(reference_event_list, dcase_util.containers.MetaDataContainer):
              reference_event_list = dcase_util.containers.MetaDataContainer(reference_event_list)
  
          if not isinstance(estimated_event_list, dcase_util.containers.MetaDataContainer):
              estimated_event_list = dcase_util.containers.MetaDataContainer(estimated_event_list)
  
          # Check that input event list have event only from one file
          reference_files = reference_event_list.unique_files
          if len(reference_files) > 1:
              raise ValueError(
                  "reference_event_list contains events from multiple files. Evaluate only file by file."
              )
  
          estimated_files = estimated_event_list.unique_files
          if len(estimated_files) > 1:
              raise ValueError(
                  "estimated_event_list contains events from multiple files. Evaluate only file by file."
              )
  
          # Evaluate only valid events
          valid_reference_event_list = dcase_util.containers.MetaDataContainer()
          for item in reference_event_list:
              if 'event_onset' in item and 'event_offset' in item and 'event_label' in item:
                  valid_reference_event_list.append(item)
  
              elif 'onset' in item and 'offset' in item and 'event_label' in item:
                  valid_reference_event_list.append(item)
  
          reference_event_list = valid_reference_event_list
  
          valid_estimated_event_list = dcase_util.containers.MetaDataContainer()
          for item in estimated_event_list:
              if 'event_onset' in item and 'event_offset' in item and 'event_label' in item:
                  valid_estimated_event_list.append(item)
  
              elif 'onset' in item and 'offset' in item and 'event_label' in item:
                  valid_estimated_event_list.append(item)
  
          estimated_event_list = valid_estimated_event_list
  
          self.evaluated_length += reference_event_list.max_offset
          self.evaluated_files += 1
  
          # Overall metrics
  
          # Total number of detected and reference events
          Nsys = len(estimated_event_list)
          Nref = len(reference_event_list)
  
          if self.event_matching_type == 'optimal':
              label_hit_matrix = numpy.zeros((len(reference_event_list), len(estimated_event_list)), dtype=bool)
              for j in range(0, len(reference_event_list)):
                  for i in range(0, len(estimated_event_list)):
                      label_hit_matrix[j, i] = reference_event_list[j]['event_label'] == estimated_event_list[i]['event_label']
  
              hit_matrix = label_hit_matrix
              if self.evaluate_onset:
                  onset_hit_matrix = numpy.zeros((len(reference_event_list), len(estimated_event_list)), dtype=bool)
                  for j in range(0, len(reference_event_list)):
                      for i in range(0, len(estimated_event_list)):
                          onset_hit_matrix[j, i] = self.validate_onset(
                              reference_event=reference_event_list[j],
                              estimated_event=estimated_event_list[i],
                              t_collar=self.t_collar
                          )
  
                  hit_matrix *= onset_hit_matrix
  
              if self.evaluate_offset:
                  offset_hit_matrix = numpy.zeros((len(reference_event_list), len(estimated_event_list)), dtype=bool)
                  for j in range(0, len(reference_event_list)):
                      for i in range(0, len(estimated_event_list)):
                          offset_hit_matrix[j, i] = self.validate_offset(
                              reference_event=reference_event_list[j],
                              estimated_event=estimated_event_list[i],
                              t_collar=self.t_collar,
                              percentage_of_length=self.percentage_of_length
                          )
  
                  hit_matrix *= offset_hit_matrix
  
              hits = numpy.where(hit_matrix)
              G = {}
              for ref_i, est_i in zip(*hits):
                  if est_i not in G:
                      G[est_i] = []
  
                  G[est_i].append(ref_i)
  
              matching = sorted(util.bipartite_match(G).items())
  
              ref_correct = numpy.zeros(Nref, dtype=bool)
              sys_correct = numpy.zeros(Nsys, dtype=bool)
              for item in matching:
                  ref_correct[item[0]] = True
                  sys_correct[item[1]] = True
  
              Ntp = len(matching)
              # Substitutions
              Nsubs = 0
  
              ref_leftover = numpy.nonzero(numpy.logical_not(ref_correct))[0]
              sys_leftover = numpy.nonzero(numpy.logical_not(sys_correct))[0]
              sys_counted = numpy.zeros(Nsys, dtype=bool)
              for j in ref_leftover:
                  for i in sys_leftover:
                      if not sys_counted[i]:
                          if self.evaluate_onset:
                              onset_condition = self.validate_onset(
                                  reference_event=reference_event_list[j],
                                  estimated_event=estimated_event_list[i],
                                  t_collar=self.t_collar
                              )
  
                          else:
                              onset_condition = True
  
                          if self.evaluate_offset:
                              offset_condition = self.validate_offset(
                                  reference_event=reference_event_list[j],
                                  estimated_event=estimated_event_list[i],
                                  t_collar=self.t_collar,
                                  percentage_of_length=self.percentage_of_length
                              )
  
                          else:
                              offset_condition = True
  
                          if onset_condition and offset_condition:
                              sys_counted[i] = True
                              Nsubs += 1
                              break
  
          elif self.event_matching_type == 'greedy':
              sys_correct = numpy.zeros(Nsys, dtype=bool)
              ref_correct = numpy.zeros(Nref, dtype=bool)
  
              # Number of correctly detected events
              for j in range(0, len(reference_event_list)):
                  for i in range(0, len(estimated_event_list)):
                      if not sys_correct[i]:  # skip already matched events
                          label_condition = reference_event_list[j]['event_label'] == estimated_event_list[i]['event_label']
  
                          if self.evaluate_onset:
                              onset_condition = self.validate_onset(
                                  reference_event=reference_event_list[j],
                                  estimated_event=estimated_event_list[i],
                                  t_collar=self.t_collar
                              )
  
                          else:
                              onset_condition = True
  
                          if self.evaluate_offset:
                              offset_condition = self.validate_offset(
                                  reference_event=reference_event_list[j],
                                  estimated_event=estimated_event_list[i],
                                  t_collar=self.t_collar,
                                  percentage_of_length=self.percentage_of_length
                              )
  
                          else:
                              offset_condition = True
  
                          if label_condition and onset_condition and offset_condition:
                              ref_correct[j] = True
                              sys_correct[i] = True
                              break
  
              Ntp = numpy.sum(sys_correct)
  
              ref_leftover = numpy.nonzero(numpy.logical_not(ref_correct))[0]
              sys_leftover = numpy.nonzero(numpy.logical_not(sys_correct))[0]
  
              # Substitutions
              Nsubs = 0
              sys_counted = numpy.zeros(Nsys, dtype=bool)
              for j in ref_leftover:
                  for i in sys_leftover:
                      if not sys_counted[i]:
                          if self.evaluate_onset:
                              onset_condition = self.validate_onset(
                                  reference_event=reference_event_list[j],
                                  estimated_event=estimated_event_list[i],
                                  t_collar=self.t_collar
                              )
  
                          else:
                              onset_condition = True
  
                          if self.evaluate_offset:
                              offset_condition = self.validate_offset(
                                  reference_event=reference_event_list[j],
                                  estimated_event=estimated_event_list[i],
                                  t_collar=self.t_collar,
                                  percentage_of_length=self.percentage_of_length
                              )
  
                          else:
                              offset_condition = True
  
                          if onset_condition and offset_condition:
                              sys_counted[i] = True
                              Nsubs += 1
                              break
  
          Nfp = Nsys - Ntp - Nsubs
          Nfn = Nref - Ntp - Nsubs
  
          self.overall['Nref'] += Nref
          self.overall['Nsys'] += Nsys
          self.overall['Ntp'] += Ntp
          self.overall['Nsubs'] += Nsubs
          self.overall['Nfp'] += Nfp
          self.overall['Nfn'] += Nfn
  
          # Class-wise metrics
          for class_id, class_label in enumerate(self.event_label_list):
              Nref = 0.0
              Nsys = 0.0
              Ntp = 0.0
  
              # Count event frequencies in the ground truth
              for i in range(0, len(reference_event_list)):
                  if reference_event_list[i]['event_label'] == class_label:
                      Nref += 1
  
              # Count event frequencies in the system output
              for i in range(0, len(estimated_event_list)):
                  if estimated_event_list[i]['event_label'] == class_label:
                      Nsys += 1
  
              if self.event_matching_type == 'optimal':
                  class_reference_event_list = reference_event_list.filter(event_label=class_label)
                  class_estimated_event_list = estimated_event_list.filter(event_label=class_label)
  
                  hit_matrix = numpy.ones((len(class_reference_event_list), len(class_estimated_event_list)), dtype=bool)
                  if self.evaluate_onset:
                      onset_hit_matrix = numpy.zeros((len(class_reference_event_list), len(class_estimated_event_list)), dtype=bool)
                      for j in range(0, len(class_reference_event_list)):
                          for i in range(0, len(class_estimated_event_list)):
                              onset_hit_matrix[j, i] = self.validate_onset(
                                  reference_event=class_reference_event_list[j],
                                  estimated_event=class_estimated_event_list[i],
                                  t_collar=self.t_collar
                              )
  
                      hit_matrix *= onset_hit_matrix
  
                  if self.evaluate_offset:
                      offset_hit_matrix = numpy.zeros((len(class_reference_event_list), len(class_estimated_event_list)), dtype=bool)
                      for j in range(0, len(class_reference_event_list)):
                          for i in range(0, len(class_estimated_event_list)):
                              offset_hit_matrix[j, i] = self.validate_offset(
                                  reference_event=class_reference_event_list[j],
                                  estimated_event=class_estimated_event_list[i],
                                  t_collar=self.t_collar,
                                  percentage_of_length=self.percentage_of_length
                              )
  
                      hit_matrix *= offset_hit_matrix
  
                  hits = numpy.where(hit_matrix)
                  G = {}
                  for ref_i, est_i in zip(*hits):
                      if est_i not in G:
                          G[est_i] = []
  
                      G[est_i].append(ref_i)
  
                  matching = sorted(util.bipartite_match(G).items())
  
                  ref_correct = numpy.zeros(int(Nref), dtype=bool)
                  sys_correct = numpy.zeros(int(Nsys), dtype=bool)
  
                  for item in matching:
                      ref_correct[item[0]] = True
                      sys_correct[item[1]] = True
  
                  Ntp = len(matching)
  
              elif self.event_matching_type == 'greedy':
                  sys_counted = numpy.zeros(len(estimated_event_list), dtype=bool)
                  for j in range(0, len(reference_event_list)):
                      if reference_event_list[j]['event_label'] == class_label:
                          for i in range(0, len(estimated_event_list)):
                              if estimated_event_list[i]['event_label'] == class_label and not sys_counted[i]:
                                  if self.evaluate_onset:
                                      onset_condition = self.validate_onset(
                                          reference_event=reference_event_list[j],
                                          estimated_event=estimated_event_list[i],
                                          t_collar=self.t_collar
                                      )
  
                                  else:
                                      onset_condition = True
  
                                  if self.evaluate_offset:
                                      offset_condition = self.validate_offset(
                                          reference_event=reference_event_list[j],
                                          estimated_event=estimated_event_list[i],
                                          t_collar=self.t_collar,
                                          percentage_of_length=self.percentage_of_length
                                      )
  
                                  else:
                                      offset_condition = True
  
                                  if onset_condition and offset_condition:
                                      sys_counted[i] = True
                                      Ntp += 1
                                      break
  
              Nfp = Nsys - Ntp
              Nfn = Nref - Ntp
  
              self.class_wise[class_label]['Nref'] += Nref
              self.class_wise[class_label]['Nsys'] += Nsys
  
              self.class_wise[class_label]['Ntp'] += Ntp
              self.class_wise[class_label]['Nfp'] += Nfp
              self.class_wise[class_label]['Nfn'] += Nfn
  
          return self