code: def groupwise_slr(
      bundles,
      x0="affine",
      tol=0,
      max_iter=20,
      qbx_thr=[4],
      nb_pts=20,
      select_random=10000,
      verbose=False,
      rng=None,
  ):
      """Function to perform unbiased groupwise bundle registration.
  
      All bundles are moved to the same space by iteratively applying halfway
      streamline linear registration in pairs. With each iteration, bundles get
      closer to each other until the procedure converges and there is no more
      improvement.
  
      Parameters
      ----------
      bundles : list
          List with streamlines of the bundles to be registered.
  
      x0 : str, optional
          rigid, similarity or affine transformation model. Default: affine.
  
      tol : float, optional
          Tolerance value to be used to assume convergence. Default: 0.
  
      max_iter : int, optional
          Maximum number of iterations. Depending on the number of bundles to be
          registered this may need to be larger. Default: 20.
  
      qbx_thr : variable int, optional
          Thresholds for Quickbundles used for clustering streamlines and reduce
          computational time. If None, no clustering is performed. Higher values
          cluster streamlines into a smaller number of centroids. Default: [4].
  
      nb_pts : int, optional
          Number of points for discretizing each streamline. Default: 20.
  
      select_random : int, optional
          Maximum number of streamlines for each bundle. If None, all the
          streamlines are used. Default: 10000.
  
      verbose : bool, optional
          If True, logs information. Default: False.
  
      rng : np.random.Generator
          If None, creates random generator in function. Default: None.
  
      References
      ----------
      .. [Garyfallidis15] Garyfallidis et al. "Robust and efficient linear
      registration of white-matter fascicles in the space of streamlines",
      NeuroImage, 117, 124--140, 2015
      .. [Garyfallidis14] Garyfallidis et al., "Direct native-space fiber
              bundle alignment for group comparisons", ISMRM, 2014.
      .. [Garyfallidis17] Garyfallidis et al. Recognition of white matter
      bundles using local and global streamline-based registration and
      clustering, Neuroimage, 2017.
  
      """
  
      def group_distance(bundles, n_bundle):
          all_pairs = list(combinations(np.arange(n_bundle), 2))
          d = np.zeros(len(all_pairs))
          for i, ind in enumerate(all_pairs):
              mdf = distance_matrix_mdf(
                  bundles[ind[0]], bundles[ind[1]]
              )
              rows, cols = mdf.shape
              d[i] = (
                  0.25
                  * (
                      np.sum(np.min(mdf, axis=0)) / float(cols)
                      + np.sum(np.min(mdf, axis=1)) / float(rows)
                  )
                  ** 2
              )
          return d
  
      if rng is None:
          rng = np.random.default_rng()
  
      metric = JointBundleMinDistanceMetric()
  
      bundles = bundles.copy()
      n_bundle = len(bundles)
  
      if verbose:
          logging.info("Groupwise bundle registration running.")
          logging.info(f"Number of bundles found: {n_bundle}.")
  
      # Preprocess bundles: streamline selection, centering and clustering
      centroids = []
      aff_list = []
      for i in range(n_bundle):
          if verbose:
              logging.info(
                  f"Preprocessing: bundle {i}/{n_bundle}: "
                  + f"{len(bundles[i])} streamlines found."
              )
  
          if select_random is not None:
              bundles[i] = select_random_set_of_streamlines(
                  bundles[i], select_random, rng
              )
  
          bundles[i] = set_number_of_points(bundles[i], nb_pts)
  
          bundle, shift = center_streamlines(bundles[i])
          aff_list.append(compose_matrix44(-shift))
  
          if qbx_thr is not None:
              cluster_map = qbx_and_merge(
                  bundle, thresholds=qbx_thr, rng=rng
              )
              bundle = remove_clusters_by_size(cluster_map, 1)
  
          centroids.append(bundle)
  
      # Compute initial group distance (mean distance between all bundle pairs)
      d = group_distance(centroids, n_bundle)
  
      if verbose:
          logging.info(f"Initial group distance: {np.mean(d)}.")
  
      # Make pairs and start iterating
      pairs, excluded = get_unique_pairs(n_bundle)
      n_pair = n_bundle // 2
  
      for i_iter in range(1, max_iter + 1):
          for i_pair, pair in enumerate(pairs):
              ind1 = pair[0]
              ind2 = pair[1]
  
              centroids1 = centroids[ind1]
              centroids2 = centroids[ind2]
  
              hslr = StreamlineLinearRegistration(x0=x0, metric=metric)
              hsrm = hslr.optimize(
                  static=centroids1, moving=centroids2, mat=np.eye(4)
              )
  
              # Update transformation matrices
              aff_list[ind1] = np.dot(hsrm.matrix1, aff_list[ind1])
              aff_list[ind2] = np.dot(hsrm.matrix2, aff_list[ind2])
  
              centroids1, centroids2 = hsrm.transform(
                  centroids1, centroids2
              )
  
              centroids[ind1] = centroids1
              centroids[ind2] = centroids2
  
              if verbose:
                  logging.info(
                      f"Iteration: {i_iter} pair: {i_pair+1}/{n_pair}."
                  )
  
          d = np.vstack((d, group_distance(centroids, n_bundle)))
  
          # Use as reference the distance 3 iterations ago
          prev_iter = np.max([0, i_iter - 3])
          d_improve = np.mean(d[prev_iter, :]) - np.mean(d[i_iter, :])
  
          if verbose:
              logging.info(
                  f"Iteration {i_iter} group distance: "
                  + f"{np.mean(d[i_iter, :])}"
              )
              logging.info(
                  f"Iteration {i_iter} improvement previous 3: "
                  + f"{d_improve}"
              )
  
          if d_improve < tol:
              if verbose:
                  logging.info(
                      "Registration converged " + f"{d_improve} < {tol}"
                  )
              break
  
          pairs, excluded = get_unique_pairs(n_bundle, pairs)
  
      # Move bundles just once at the end
      for i, aff in enumerate(aff_list):
          bundles[i] = transform_streamlines(bundles[i], aff)
  
      return bundles, aff_list, d
  
imports: import logging
  import abc
  from itertools import combinations
  import numpy
  from dipy.core.optimize import Optimizer
  from dipy.align.bundlemin import _bundle_minimum_distance, _bundle_minimum_distance_asymmetric, distance_matrix_mdf
  from dipy.tracking.streamline import transform_streamlines, unlist_streamlines, center_streamlines, set_number_of_points, select_random_set_of_streamlines, length, Streamlines
  from dipy.segment.clustering import qbx_and_merge
  from dipy.core.geometry import compose_transformations, compose_matrix, decompose_matrix
  from time import time
label_desc: Use image node_id 8 for calling a global function with example usage: groupwise_slr(bundles, x0, tol, max_iter, qbx_thr, nb_pts, select_random, verbose, rng) and returns: bundles,  aff_list,  d, d