def generate(self, x: np.ndarray, y: np.ndarray | None = None, **kwargs) -> np.ndarray:
        """
        Generate adversarial samples and return them in an array.

        :param x: An array with the original inputs.
        :param y: An array with the original labels to be predicted.
        :return: An array holding the adversarial examples.
        """
        logger.info("Computing spatial transformation based on grid search.")

        if len(x.shape) == 2:
            raise ValueError(
                "Feature vectors detected. The attack can only be applied to data with spatial" "dimensions."
            )

        if self.attack_trans_x is None or self.attack_trans_y is None or self.attack_rot is None:

            y_pred = self.estimator.predict(x, batch_size=1)
            if self.estimator.nb_classes == 2 and y_pred.shape[1] == 1:
                raise ValueError(
                    "This attack has not yet been tested for binary classification with a single output classifier."
                )

            y_pred_max = np.argmax(y_pred, axis=1)

            nb_instances = len(x)

            # Determine grids
            max_num_pixel_trans_x = int(round((x.shape[1] * self.max_translation / 100.0)))
            max_num_pixel_trans_y = int(round((x.shape[2] * self.max_translation / 100.0)))

            grid_trans_x = [
                int(round(g))
                for g in list(
                    np.linspace(
                        -max_num_pixel_trans_x,
                        max_num_pixel_trans_x,
                        num=self.num_translations,
                    )
                )
            ]
            grid_trans_y = [
                int(round(g))
                for g in list(
                    np.linspace(
                        -max_num_pixel_trans_y,
                        max_num_pixel_trans_y,
                        num=self.num_translations,
                    )
                )
            ]
            grid_rot = list(np.linspace(-self.max_rotation, self.max_rotation, num=self.num_rotations))

            # Remove duplicates
            grid_trans_x = list(set(grid_trans_x))
            grid_trans_y = list(set(grid_trans_y))
            grid_rot = list(set(grid_rot))

            grid_trans_x.sort()
            grid_trans_y.sort()
            grid_rot.sort()

            # Search for worst case
            fooling_rate = 0.0
            x_adv = np.copy(x)
            trans_x = 0
            trans_y = 0
            rot = 0.0

            # Initialize progress bar
            pbar = tqdm(
                total=len(grid_trans_x) * len(grid_trans_y) * len(grid_rot),
                desc="Spatial transformation",
                disable=not self.verbose,
            )

            for trans_x_i in grid_trans_x:
                for trans_y_i in grid_trans_y:
                    for rot_i in grid_rot:

                        # Generate the adversarial examples
                        x_adv_i = self._perturb(x, trans_x_i, trans_y_i, rot_i)

                        # Compute the error rate
                        y_adv_i = np.argmax(self.estimator.predict(x_adv_i, batch_size=1), axis=1)
                        fooling_rate_i = np.sum(y_pred_max != y_adv_i) / nb_instances

                        if fooling_rate_i > fooling_rate:
                            fooling_rate = fooling_rate_i
                            trans_x = trans_x_i
                            trans_y = trans_y_i
                            rot = rot_i
                            x_adv = np.copy(x_adv_i)
                        pbar.update(1)
            pbar.close()

            self.fooling_rate = fooling_rate
            self.attack_trans_x = trans_x
            self.attack_trans_y = trans_y
            self.attack_rot = rot

            logger.info(
                "Success rate of spatial transformation attack: %.2f%%",
                100 * self.fooling_rate,
            )
            logger.info("Attack-translation in x: %.2f%%", self.attack_trans_x)
            logger.info("Attack-translation in y: %.2f%%", self.attack_trans_y)
            logger.info("Attack-rotation: %.2f%%", self.attack_rot)

        else:
            x_adv = self._perturb(x, self.attack_trans_x, self.attack_trans_y, self.attack_rot)

        return x_adv

        Generate adversarial samples and return them in an array.

        :param x: An array with the original inputs.
        :param y: An array with the original labels to be predicted.
        :return: An array holding the adversarial examples.
        
art/attacks/evasion/spatial_transformation.py