text: import random
  from dataclasses import dataclass
  import pandas as pd
  import numpy as np
  import tensorflow as tf
  
  
  @dataclass
  class RamanDataGenerator(tf.keras.utils.Sequence):
      df: pd.DataFrame
      batch_size: int
      max_classes: int
  
      weighted_sum: bool = True
      roll: bool = True
      roll_factor: int = 12
  
      slope: bool = True
      slope_factor: float = 0.2
  
      noise: bool = True
      noise_range: tuple = (80, 100)
  
      sparse_labels: bool = False
  
      def __post_init__(self):
          # transform to numpy for performance reasons
          self.samples = self.df.iloc[:, 10:-1].to_numpy().astype("float32")
          self.labels = self.df.loc[:, "labels"].to_numpy().astype("uint32")
  
      def __len__(self):
          return int(len(self.df) // self.batch_size)
  
      def __getitem__(self, index):
          # selection of mini-batch
          BOTTOM = index * self.batch_size
          TOP = (index + 1) * self.batch_size
          batch_samples = self.samples[BOTTOM:TOP]
          batch_labels = self.labels[BOTTOM:TOP].reshape((self.batch_size, 1))
  
          batch_samples = self._augmentation(batch_samples, batch_labels)
          batch_labels = batch_labels.reshape((self.batch_size,))
  
          # in case of categorical crossentropy loss, labels are translated
          # form sparse to categorical
          if not self.sparse_labels:
              batch_labels = tf.keras.utils.to_categorical(
                  batch_labels, num_classes=self.max_classes
              )
  
          return (
              batch_samples,
              batch_labels,
          )
  
      def _augmentation(self, batch_samples, batch_labels):
          """Compute data augmentation on 'batch_samples', applying 
          weighted sum + roll(horizontal shift) + baseline noise + adittive white gaussian noise
  
          Args:
              batch_samples (np.array): Batch of spectra (1d array)
              batch_labels (np.array): Batch of label, number that are class identifier
  
          Returns:
              np.array: Batch of augmented data
          """
  
          if self.weighted_sum:
              alpha = np.random.rand(self.batch_size)
  
              other_samples = np.apply_along_axis(
                  self._get_random_sample_from_class, 1, batch_labels
              ).reshape(self.batch_size, batch_samples.shape[1])
  
              if self.roll:
                  other_samples = np.apply_along_axis(self._random_roll, 1, other_samples)
  
              batch_samples = (
                  np.multiply(
                      batch_samples - other_samples, alpha.reshape(self.batch_size, 1)
                  )
                  + other_samples
              )
  
          if self.slope:
              batch_samples = np.apply_along_axis(
                  lambda x: self._produce_background_baseline(x, batch_samples.shape[1]),
                  1,
                  batch_samples,
              )
  
          if self.noise:
              batch_samples = np.apply_along_axis(self._random_noise, 1, batch_samples)
  
          return batch_samples
  
      def _get_random_sample_from_class(self, label):
          """Extract a random sample from the datas marked as 'label'
  
          Args:
              label (int): Number that describe the class identifier of data to select
  
          Returns:
              np.array: Random sample of the 'label' class
          """
          class_indexes = np.where(self.labels == label)[0]
          CLASS_INDEX = np.random.choice(class_indexes, 1)[0]
  
          return self.samples[CLASS_INDEX : CLASS_INDEX + 1]
  
      def _random_noise(self, arr):
          """Apply adittive white gaussian noise to 'arr' of magnitued 'noise_range'
  
          Args:
              arr (np.array): Sample to wich apply the noise
  
          Returns:
              np.array: Noise 'arr'
          """
          rnd_snr = random.randint(self.noise_range[0], self.noise_range[1])
          NOISE_FACTOR = 1 / (10 ** (rnd_snr / 10))
  
          return arr + np.random.normal(0, NOISE_FACTOR, len(arr))
  
      def _random_roll(self, arr):
          """Apply random roll (numpy way to say horizontal shift) to 'arr' of magnitude 'roll_factor'
  
          Args:
              arr (np.array): Sample to wich apply the roll
  
          Returns:
              np.array: Random rolled sample
          """
  
          SHIFT_FACTOR = self.roll_factor
          random_shift = random.randint(-1 * SHIFT_FACTOR, SHIFT_FACTOR)
  
          rolled = np.roll(arr, random_shift)
          padded = (
              np.pad(rolled[random_shift:], (random_shift, 0), "edge")
              if random_shift >= 0
              else np.pad(rolled[:random_shift], (0, abs(random_shift)), "edge")
          )
  
          return padded
  
      def _produce_background_baseline(self, arr, steps):
          """Apply a random baseline noise to 'arr' of magnitude 'slope_factor' 
  
          Args:
              arr (np.array): Spectrum to wich apply the baseline noise
              steps (int): Length of the 'arr' argument
  
          Returns:
              np.array: Noised spectrum
          """
  
          SLOPE = random.triangular(-1 * self.slope_factor, self.slope_factor)
          line = (
              np.linspace(abs(SLOPE), 0, steps)
              if SLOPE < 0
              else np.linspace(0, SLOPE, steps)
          )
  
          alpha = random.random()
  
          return arr * alpha + line * (1 - alpha)
  