def single_restart(
        pvalues: np.ndarray,
        a_max: float,
        indices_of_seeds: np.ndarray,
        image_to_node: bool,
        score_function: Callable[[np.ndarray, np.ndarray, np.ndarray], np.ndarray],
    ) -> tuple[float, np.ndarray, np.ndarray, float]:
        """
        Here we control the iteration between images->nodes and nodes->images. It starts with a fixed subset of nodes by
        default.

        :param pvalues: pvalue ranges.
        :param a_max: Determines the significance level threshold.
        :param indices_of_seeds: Indices of initial sets of images or nodes to perform optimization.
        :param image_to_node: Informs the direction for optimization.
        :param score_function: Scoring function.
        :return: (best_score_so_far, best_sub_of_images, best_sub_of_nodes, best_alpha).
        """
        best_score_so_far = -100000.0
        count = 0

        while True:
            # These can be moved outside the while loop as only executed first time through??
            if count == 0:  # first time through, we need something initialized depending on direction.
                if image_to_node:
                    sub_of_images = indices_of_seeds
                else:
                    sub_of_nodes = indices_of_seeds

            if image_to_node:  # passed pvalues are only those belonging to fixed images, update nodes in return
                # only sending sub of images
                (
                    score_from_optimization,
                    sub_of_nodes,
                    optimal_alpha,
                ) = ScanningOps.optimize_in_single_dimension(
                    pvalues[sub_of_images, :, :], a_max, image_to_node, score_function
                )
            else:  # passed pvalues are only those belonging to fixed nodes, update images in return
                # only sending sub of nodes
                (
                    score_from_optimization,
                    sub_of_images,
                    optimal_alpha,
                ) = ScanningOps.optimize_in_single_dimension(
                    pvalues[:, sub_of_nodes, :], a_max, image_to_node, score_function
                )

            if score_from_optimization > best_score_so_far:  # haven't converged yet
                # update
                best_score_so_far = score_from_optimization
                best_sub_of_nodes = sub_of_nodes
                best_sub_of_images = sub_of_images
                best_alpha = optimal_alpha

                image_to_node = not image_to_node  # switch direction!
                count = count + 1  # for printing and
            else:  # converged!  Don't update from most recent optimization, return current best
                return (
                    best_score_so_far,
                    best_sub_of_images,
                    best_sub_of_nodes,
                    best_alpha,
                )

        Here we control the iteration between images->nodes and nodes->images. It starts with a fixed subset of nodes by
        default.

        :param pvalues: pvalue ranges.
        :param a_max: Determines the significance level threshold.
        :param indices_of_seeds: Indices of initial sets of images or nodes to perform optimization.
        :param image_to_node: Informs the direction for optimization.
        :param score_function: Scoring function.
        :return: (best_score_so_far, best_sub_of_images, best_sub_of_nodes, best_alpha).
        
art/defences/detector/evasion/subsetscanning/scanningops.py