def poison(
        self, x: np.ndarray, y: np.ndarray | None = None, broadcast: bool = True, **kwargs
    ) -> tuple[np.ndarray, np.ndarray]:
        """
        Calls perturbation function on input x and returns the perturbed input and poison labels for the data.

        :param x: An array with the points that initialize attack points.
        :param y: The target labels for the attack.
        :param broadcast: whether to broadcast single target label
        :return: An tuple holding the `(poisoning_examples, poisoning_labels)`.
        """
        data = np.copy(x)
        estimated_labels = self.proxy_classifier.predict(data) if y is None else np.copy(y)

        # Selected target indices to poison
        all_indices = np.arange(len(data))
        target_indices = all_indices[np.all(estimated_labels == self.target, axis=1)]
        num_poison = int(self.pp_poison * len(target_indices))
        selected_indices = np.random.choice(target_indices, num_poison)

        # Run untargeted PGD on selected points, making it hard to classify correctly
        perturbed_input = self.attack.generate(data[selected_indices])
        no_change_detected = np.array(
            [
                np.all(data[selected_indices][poison_idx] == perturbed_input[poison_idx])
                for poison_idx in range(len(perturbed_input))
            ]
        )

        if any(no_change_detected):  # pragma: no cover
            logger.warning("Perturbed input is the same as original data after PGD. Check params.")
            idx_no_change = np.arange(len(no_change_detected))[no_change_detected]
            logger.warning("%d indices without change: %s", len(idx_no_change), idx_no_change)

        # Add backdoor and poison with the same label
        poisoned_input, _ = self.backdoor.poison(perturbed_input, self.target, broadcast=broadcast)
        data[selected_indices] = poisoned_input

        return data, estimated_labels

        Calls perturbation function on input x and returns the perturbed input and poison labels for the data.

        :param x: An array with the points that initialize attack points.
        :param y: The target labels for the attack.
        :param broadcast: whether to broadcast single target label
        :return: An tuple holding the `(poisoning_examples, poisoning_labels)`.
        
art/attacks/poisoning/clean_label_backdoor_attack.py