docs: def sample(self, model = None, maximum_number_of_positives = None, maximum_number_of_negatives = None, positive_indices = None, negative_indices = None):
      """sample([model], [maximum_number_of_positives], [maximum_number_of_negatives], [positive_indices], [negative_indices]) -> positives, negatives
  
      Returns positive and negative samples from the set of positives and negatives.
  
      This reads the previously extracted feature file (or all of them, in case features were extracted in parallel) and returns features.
      If the ``model`` is not specified, a random sub-selection of positive and negative features is returned.
      When the ``model`` is given, all patches are first classified with the given ``model``, and the ones that are mis-classified most are returned.
      The number of returned positives and negatives can be limited by specifying the ``maximum_number_of_positives`` and ``maximum_number_of_negatives``.
  
      This function keeps track of the positives and negatives that it once has returned, so it does not return the same positive or negative feature twice.
      However, when you have to restart training from a given point, you can set the ``positive_indices`` and ``negative_indices`` parameters, to retrieve the features for the given indices.
      In this case, no additional features are selected, but the given sets of indices are stored internally.
  
      .. note::
         The ``positive_indices`` and ``negative_indices`` only have an effect, when ``model`` is ``None``.
  
      **Parameters:**
  
      ``model`` : :py:class:`bob.learn.boosting.BoostedMachine` or ``None``
        If given, the ``model`` is used to predict the training features, and the highest mis-predicted features are returned
  
      ``maximum_number_of_positives, maximum_number_of_negatives`` : int
        The maximum number of positive and negative features to be returned
  
      ``positive_indices, negative_indices`` : set(int) or ``None``
        The set of positive and negative indices to extract features for, instead of randomly choosing indices; only considered when ``model = None``
  
      **Returns:**
  
      ``positives, negatives`` : array_like(2D, uint16)
        The new set of training features for the positive class (faces) and negative class (background).
      """
  
      # get all existing feature files
      feature_file = self._feature_file(index = 0)
      if os.path.exists(feature_file):
        feature_files = [feature_file]
      else:
        feature_files = []
        i = 1
        while True:
          feature_file = self._feature_file(index = i)
          if not os.path.exists(feature_file):
            break
          feature_files.append(feature_file)
          i += 1
  
      features = []
      labels = []
  
      # make a first iteration through the feature files and count the number of positives and negatives
      positive_count, negative_count = 0, 0
      logger.info("Reading %d feature files", len(feature_files))
      for feature_file in feature_files:
        logger.debug(".. Loading file %s", feature_file)
        hdf5 = bob.io.base.HDF5File(feature_file)
        positive_count += hdf5.get("TotalPositives")
        negative_count += hdf5.get("TotalNegatives")
        del hdf5
  
      if model is None:
        # get a list of indices and store them, so that we don't re-use them next time
        if positive_indices is None:
          positive_indices = set(quasi_random_indices(positive_count, maximum_number_of_positives))
        if negative_indices is None:
          negative_indices = set(quasi_random_indices(negative_count, maximum_number_of_negatives))
        self.positive_indices |= positive_indices
        self.negative_indices |= negative_indices
  
        # now, iterate through the files again and sample
        positive_indices = collections.deque(sorted(positive_indices))
        negative_indices = collections.deque(sorted(negative_indices))
  
        logger.info("Extracting %d of %d positive and %d of %d negative samples" % (len(positive_indices), positive_count, len(negative_indices), negative_count))
  
        positive_count, negative_count = 0, 0
        for feature_file in feature_files:
          hdf5 = bob.io.base.HDF5File(feature_file)
          for image in sorted(hdf5.sub_groups(recursive=False, relative=True)):
            hdf5.cd(image)
            for scale in sorted(hdf5.keys(relative=True)):
              read = hdf5.get(scale)
              size = read.shape[0]
              if scale.startswith("Positives"):
                # copy positive data
                while positive_indices and positive_count <= positive_indices[0] and positive_count + size > positive_indices[0]:
                  assert positive_indices[0] >= positive_count
                  features.append(read[positive_indices.popleft() - positive_count, :])
                  labels.append(1)
                positive_count += size
              else:
                # copy negative data
                while negative_indices and negative_count <= negative_indices[0] and negative_count + size > negative_indices[0]:
                  assert negative_indices[0] >= negative_count
                  features.append(read[negative_indices.popleft() - negative_count, :])
                  labels.append(-1)
                negative_count += size
            hdf5.cd("..")
        # return features and labels
        return numpy.array(features), numpy.array(labels)
  
      else:
        positive_count -= len(self.positive_indices)
        negative_count -= len(self.negative_indices)
        logger.info("Getting worst %d of %d positive and worst %d of %d negative examples", min(maximum_number_of_positives, positive_count), positive_count, min(maximum_number_of_negatives, negative_count), negative_count)
  
        # compute the worst features based on the current model
        worst_positives, worst_negatives = [], []
        positive_count, negative_count = 0, 0
  
        for feature_file in feature_files:
          hdf5 = bob.io.base.HDF5File(feature_file)
          for image in sorted(hdf5.sub_groups(recursive=False, relative=True)):
            hdf5.cd(image)
            for scale in sorted(hdf5.keys(relative=True)):
              read = hdf5.get(scale)
              size = read.shape[0]
              prediction = bob.blitz.array((size,), numpy.float64)
              # forward features through the model
              result = model.forward(read, prediction)
              if scale.startswith("Positives"):
                indices = [i for i in range(size) if positive_count + i not in self.positive_indices]
                worst_positives.extend([(prediction[i], positive_count + i, read[i]) for i in indices if prediction[i] <= 0])
                positive_count += size
              else:
                indices = [i for i in range(size) if negative_count + i not in self.negative_indices]
                worst_negatives.extend([(prediction[i], negative_count + i, read[i]) for i in indices if prediction[i] >= 0])
                negative_count += size
            hdf5.cd("..")
  
          # cut off good results
          if maximum_number_of_positives is not None and len(worst_positives) > maximum_number_of_positives:
            # keep only the positives with the low predictions (i.e., the worst)
            worst_positives = sorted(worst_positives, key=lambda k: k[0])[:maximum_number_of_positives]
          if maximum_number_of_negatives is not None and len(worst_negatives) > maximum_number_of_negatives:
            # keep only the negatives with the high predictions (i.e., the worst)
            worst_negatives = sorted(worst_negatives, reverse=True, key=lambda k: k[0])[:maximum_number_of_negatives]
  
        # mark all indices to be used
        self.positive_indices |= set(k[1] for k in worst_positives)
        self.negative_indices |= set(k[1] for k in worst_negatives)
  
        # finally, collect features and labels
        return numpy.array([f[2] for f in worst_positives] + [f[2] for f in worst_negatives]), numpy.array([1]*len(worst_positives) + [-1]*len(worst_negatives))