def generate_mask(
        self,
        x: np.ndarray,
        y: np.ndarray,
        sample_sizes: np.ndarray,
        perturbation_size: np.ndarray,
        perturb_sizes: list[list[int]] | None,
        perturb_starts: list[list[int]] | None,
    ) -> "tf.Tensor":
        """
        Makes a mask to apply to the gradients to control which samples in the batch are perturbed.

        :param x: Array with input data.
        :param y: Labels to make sure the benign files are zero masked.
        :param sample_sizes: The size of the original file, before it was padded to the input size required by MalConv
        :param perturbation_size: Size of the perturbations in L0 terms to put at end of file
        :param perturb_sizes: List of length batch size, each element is in itself a list containing the size
                              of the allowable perturbation region
        :param perturb_starts: List of length batch size, each element is in itself a list containing the start
                               of perturbation region.
        :return mask: Array with 1s on the features we will modify on this batch and 0s elsewhere.
        """
        import tensorflow as tf

        mask = np.zeros_like(x)
        for i in range(len(x)):
            if y[i] == 1:
                # if no section information was provided, append perturbation at the end of the file.
                if perturb_sizes is None and perturb_starts is None:
                    mask[i, sample_sizes[i] : sample_sizes[i] + perturbation_size[i]] = 1
                elif perturb_sizes is not None and perturb_starts is not None:
                    sample_perturb_sizes = perturb_sizes[i]
                    sample_perturb_starts = perturb_starts[i]

                    for size, start in zip(sample_perturb_sizes, sample_perturb_starts):
                        mask[i, start : start + size] = 1
                    mask[i, sample_sizes[i] : sample_sizes[i] + perturbation_size[i]] = 1
                else:  # pragma: no cover
                    raise ValueError(
                        "either both start and size of perturbation regions are supplied or neither is supplied"
                    )

                # sanity check that the total number of features marked on the mask
                # does not exceed the total perturbation budget
                assert np.sum(mask[i]) == self.total_perturbation[i]
        mask = np.expand_dims(mask, axis=-1)
        expanded_masks = []
        for _ in range(self.param_dic["embedding_size"]):
            expanded_masks.append(mask)
        # repeat it so that it matches the 8 dimensional embedding layer
        expanded_masks = np.concatenate(expanded_masks, axis=-1)
        expanded_masks = tf.convert_to_tensor(expanded_masks)
        expanded_masks = tf.cast(expanded_masks, dtype="float32")
        return expanded_masks

        Makes a mask to apply to the gradients to control which samples in the batch are perturbed.

        :param x: Array with input data.
        :param y: Labels to make sure the benign files are zero masked.
        :param sample_sizes: The size of the original file, before it was padded to the input size required by MalConv
        :param perturbation_size: Size of the perturbations in L0 terms to put at end of file
        :param perturb_sizes: List of length batch size, each element is in itself a list containing the size
                              of the allowable perturbation region
        :param perturb_starts: List of length batch size, each element is in itself a list containing the start
                               of perturbation region.
        :return mask: Array with 1s on the features we will modify on this batch and 0s elsewhere.
        
art/attacks/evasion/pe_malware_attack.py