code: def _permutation_cluster_test(
      X,
      threshold,
      n_permutations,
      tail,
      stat_fun,
      adjacency,
      n_jobs,
      seed,
      max_step,
      exclude,
      step_down_p,
      t_power,
      out_type,
      check_disjoint,
      buffer_size,
  ):
      """Aux Function.
  
      Note. X is required to be a list. Depending on the length of X
      either a 1 sample t-test or an F test / more sample permutation scheme
      is elicited.
      """
      _check_option("out_type", out_type, ["mask", "indices"])
      _check_option("tail", tail, [-1, 0, 1])
      if not isinstance(threshold, dict):
          threshold = float(threshold)
          if (
              tail < 0
              and threshold > 0
              or tail > 0
              and threshold < 0
              or tail == 0
              and threshold < 0
          ):
              raise ValueError(
                  "incompatible tail and threshold signs, got "
                  "%s and %s" % (tail, threshold)
              )
  
      # check dimensions for each group in X (a list at this stage).
      X = [x[:, np.newaxis] if x.ndim == 1 else x for x in X]
      n_samples = X[0].shape[0]
      n_times = X[0].shape[1]
  
      sample_shape = X[0].shape[1:]
      for x in X:
          if x.shape[1:] != sample_shape:
              raise ValueError("All samples mush have the same size")
  
      # flatten the last dimensions in case the data is high dimensional
      X = [np.reshape(x, (x.shape[0], -1)) for x in X]
      n_tests = X[0].shape[1]
  
      if adjacency is not None and adjacency is not False:
          adjacency = _setup_adjacency(adjacency, n_tests, n_times)
  
      if (exclude is not None) and not exclude.size == n_tests:
          raise ValueError("exclude must be the same shape as X[0]")
  
      # Step 1: Calculate t-stat for original data
      # -------------------------------------------------------------
      t_obs = stat_fun(*X)
      _validate_type(t_obs, np.ndarray, "return value of stat_fun")
      logger.info(
          "stat_fun(H1): min=%f max=%f" % (np.min(t_obs), np.max(t_obs))
      )
  
      # test if stat_fun treats variables independently
      if buffer_size is not None:
          t_obs_buffer = np.zeros_like(t_obs)
          for pos in range(0, n_tests, buffer_size):
              t_obs_buffer[pos : pos + buffer_size] = stat_fun(
                  *[x[:, pos : pos + buffer_size] for x in X]
              )
  
          if not np.all(t_obs == t_obs_buffer):
              warn(
                  "Provided stat_fun does not treat variables independently. "
                  "Setting buffer_size to None."
              )
              buffer_size = None
  
      # The stat should have the same shape as the samples for no adj.
      if t_obs.size != np.prod(sample_shape):
          raise ValueError(
              "t_obs.shape %s provided by stat_fun %s is not "
              "compatible with the sample shape %s"
              % (t_obs.shape, stat_fun, sample_shape)
          )
      if adjacency is None or adjacency is False:
          t_obs.shape = sample_shape
  
      if exclude is not None:
          include = np.logical_not(exclude)
      else:
          include = None
  
      # determine if adjacency itself can be separated into disjoint sets
      if check_disjoint is True and (
          adjacency is not None and adjacency is not False
      ):
          partitions = _get_partitions_from_adjacency(
              adjacency, n_times
          )
      else:
          partitions = None
      logger.info("Running initial clustering …")
      out = _find_clusters(
          t_obs,
          threshold,
          tail,
          adjacency,
          max_step=max_step,
          include=include,
          partitions=partitions,
          t_power=t_power,
          show_info=True,
      )
      clusters, cluster_stats = out
  
      # The stat should have the same shape as the samples
      t_obs.shape = sample_shape
  
      # For TFCE, return the "adjusted" statistic instead of raw scores
      if isinstance(threshold, dict):
          t_obs = cluster_stats.reshape(t_obs.shape) * np.sign(t_obs)
  
      logger.info(f"Found {len(clusters)} cluster{_pl(clusters)}")
  
      # convert clusters to old format
      if adjacency is not None and adjacency is not False:
          # our algorithms output lists of indices by default
          if out_type == "mask":
              clusters = _cluster_indices_to_mask(clusters, n_tests)
      else:
          # ndimage outputs slices or boolean masks by default
          if out_type == "indices":
              clusters = _cluster_mask_to_indices(clusters, t_obs.shape)
  
      # convert our seed to orders
      # check to see if we can do an exact test
      # (for a two-tailed test, we can exploit symmetry to just do half)
      extra = ""
      rng = check_random_state(seed)
      del seed
      if len(X) == 1:  # 1-sample test
          do_perm_func = _do_1samp_permutations
          X_full = X[0]
          slices = None
          orders, n_permutations, extra = _get_1samp_orders(
              n_samples, n_permutations, tail, rng
          )
      else:
          n_permutations = int(n_permutations)
          do_perm_func = _do_permutations
          X_full = np.concatenate(X, axis=0)
          n_samples_per_condition = [x.shape[0] for x in X]
          splits_idx = np.append(
              [0], np.cumsum(n_samples_per_condition)
          )
          slices = [
              slice(splits_idx[k], splits_idx[k + 1])
              for k in range(len(X))
          ]
          orders = [
              rng.permutation(len(X_full))
              for _ in range(n_permutations - 1)
          ]
      del rng
      parallel, my_do_perm_func, n_jobs = parallel_func(
          do_perm_func, n_jobs, verbose=False
      )
  
      if len(clusters) == 0:
          warn(
              "No clusters found, returning empty H0, clusters, and cluster_pv"
          )
          return t_obs, np.array([]), np.array([]), np.array([])
  
      # Step 2: If we have some clusters, repeat process on permuted data
      # -------------------------------------------------------------------
      # Step 3: repeat permutations for step-down-in-jumps procedure
      n_removed = 1  # number of new clusters added
      total_removed = 0
      step_down_include = None  # start out including all points
      n_step_downs = 0
  
      while n_removed > 0:
          # actually do the clustering for each partition
          if include is not None:
              if step_down_include is not None:
                  this_include = np.logical_and(
                      include, step_down_include
                  )
              else:
                  this_include = include
          else:
              this_include = step_down_include
  
          with ProgressBar(
              iterable=range(len(orders)), mesg=f"Permuting{extra}"
          ) as progress_bar:
              H0 = parallel(
                  my_do_perm_func(
                      X_full,
                      slices,
                      threshold,
                      tail,
                      adjacency,
                      stat_fun,
                      max_step,
                      this_include,
                      partitions,
                      t_power,
                      order,
                      sample_shape,
                      buffer_size,
                      progress_bar.subset(idx),
                  )
                  for idx, order in split_list(orders, n_jobs, idx=True)
              )
          # include original (true) ordering
          if tail == -1:  # up tail
              orig = cluster_stats.min()
          elif tail == 1:
              orig = cluster_stats.max()
          else:
              orig = abs(cluster_stats).max()
          H0.insert(0, [orig])
          H0 = np.concatenate(H0)
          logger.debug("Computing cluster p-values")
          cluster_pv = _pval_from_histogram(cluster_stats, H0, tail)
  
          # figure out how many new ones will be removed for step-down
          to_remove = np.where(cluster_pv < step_down_p)[0]
          n_removed = to_remove.size - total_removed
          total_removed = to_remove.size
          step_down_include = np.ones(n_tests, dtype=bool)
          for ti in to_remove:
              step_down_include[clusters[ti]] = False
          if adjacency is None and adjacency is not False:
              step_down_include.shape = sample_shape
          n_step_downs += 1
          if step_down_p > 0:
              a_text = "additional " if n_step_downs > 1 else ""
              logger.info(
                  "Step-down-in-jumps iteration #%i found %i %s"
                  "cluster%s to exclude from subsequent iterations"
                  % (n_step_downs, n_removed, a_text, _pl(n_removed))
              )
  
      # The clusters should have the same shape as the samples
      clusters = _reshape_clusters(clusters, sample_shape)
      return t_obs, clusters, cluster_pv, H0
  
imports: import numpy
  from scipy import ndimage, sparse
  from scipy.sparse.csgraph import connected_components
  from scipy.stats import f
  from scipy.stats import t
  from ..fixes import has_numba, jit
  from ..parallel import parallel_func
  from ..source_estimate import MixedSourceEstimate, SourceEstimate, VolSourceEstimate
  from ..source_space import SourceSpaces
  from ..utils import ProgressBar, _check_option, _pl, _validate_type, check_random_state, logger, split_list, verbose, warn
  from .parametric import f_oneway, ttest_1samp_no_p
label_desc: Use image node_id 24 for calling a global function with example usage: _permutation_cluster_test(X, threshold, n_permutations, tail, stat_fun, adjacency, n_jobs, seed, max_step, exclude, step_down_p, t_power, out_type, check_disjoint, buffer_size) and returns: t_obs,  clusters,  cluster_pv,  H0, t_obs,  np,  np,  np