code: def affine_registration(
      moving,
      static,
      moving_affine=None,
      static_affine=None,
      pipeline=None,
      starting_affine=None,
      metric="MI",
      level_iters=None,
      sigmas=None,
      factors=None,
      ret_metric=False,
      moving_mask=None,
      static_mask=None,
      **metric_kwargs,
  ):
      """
      Find the affine transformation between two 3D images. Alternatively, find
      the combination of several linear transformations.
  
      Parameters
      ----------
      moving : array, nifti image or str
          Containing the data for the moving object, or full path to a nifti file
          with the moving data.
  
      static : array, nifti image or str
          Containing the data for the static object, or full path to a nifti file
          with the moving data.
  
      moving_affine : 4x4 array, optional
          An affine transformation associated with the moving object. Required if
          data is provided as an array. If provided together with nifti/path,
          will over-ride the affine that is in the nifti.
  
      static_affine : 4x4 array, optional
          An affine transformation associated with the static object. Required if
          data is provided as an array. If provided together with nifti/path,
          will over-ride the affine that is in the nifti.
  
      pipeline : list of str, optional
          Sequence of transforms to use in the gradual fitting. Default: gradual
          fit of the full affine (executed from left to right):
          ``["center_of_mass", "translation", "rigid", "affine"]``
          Alternatively, any other combination of the following registration
          methods might be used: center_of_mass, translation, rigid,
          rigid_isoscaling, rigid_scaling and affine.
  
      starting_affine: 4x4 array, optional
          Initial guess for the transformation between the spaces.
          Default: identity.
  
      metric : str, optional.
          Currently only supports 'MI' for MutualInformationMetric.
  
      level_iters : sequence, optional
          AffineRegistration key-word argument: the number of iterations at each
          scale of the scale space. `level_iters[0]` corresponds to the coarsest
          scale, `level_iters[-1]` the finest, where n is the length of the
          sequence. By default, a 3-level scale space with iterations
          sequence equal to [10000, 1000, 100] will be used.
  
      sigmas : sequence of floats, optional
          AffineRegistration key-word argument: custom smoothing parameter to
          build the scale space (one parameter for each scale). By default,
          the sequence of sigmas will be [3, 1, 0].
  
      factors : sequence of floats, optional
          AffineRegistration key-word argument: custom scale factors to build the
          scale space (one factor for each scale). By default, the sequence of
          factors will be [4, 2, 1].
  
      ret_metric : boolean, optional
          Set it to True to return the value of the optimized coefficients and
          the optimization quality metric.
  
      moving_mask : array, shape (S', R', C') or (R', C'), optional
          moving image mask that defines which pixels in the moving image
          are used to calculate the mutual information.
  
      static_mask : array, shape (S, R, C) or (R, C), optional
          static image mask that defines which pixels in the static image
          are used to calculate the mutual information.
  
      nbins : int, optional
          MutualInformationMetric key-word argument: the number of bins to be
          used for computing the intensity histograms. The default is 32.
  
      sampling_proportion : None or float in interval (0, 1], optional
          MutualInformationMetric key-word argument: There are two types of
          sampling: dense and sparse. Dense sampling uses all voxels for
          estimating the (joint and marginal) intensity histograms, while
          sparse sampling uses a subset of them. If `sampling_proportion` is
          None, then dense sampling is used. If `sampling_proportion` is a
          floating point value in (0,1] then sparse sampling is used,
          where `sampling_proportion` specifies the proportion of voxels to
          be used. The default is None (dense sampling).
  
      Returns
      -------
      transformed : array with moving data resampled to the static space
      after computing the affine transformation
      affine : the affine 4x4 associated with the transformation.
      xopt : the value of the optimized coefficients.
      fopt : the value of the optimization quality metric.
  
      Notes
      -----
      Performs a gradual registration between the two inputs, using a pipeline
      that gradually approximates the final registration. If the final default
      step (`affine`) is omitted, the resulting affine may not have all 12
      degrees of freedom adjusted.
  
      """
      pipeline = pipeline or [
          "center_of_mass",
          "translation",
          "rigid",
          "affine",
      ]
      level_iters = level_iters or [10000, 1000, 100]
      sigmas = sigmas or [3, 1, 0.0]
      factors = factors or [4, 2, 1]
  
      starting_was_supplied = starting_affine is not None
      (
          static,
          static_affine,
          moving,
          moving_affine,
          starting_affine,
      ) = _handle_pipeline_inputs(
          moving,
          static,
          moving_affine=moving_affine,
          static_affine=static_affine,
          starting_affine=starting_affine,
      )
  
      # Define the Affine registration object we'll use with the chosen metric.
      # For now, there is only one metric (mutual information)
      use_metric = affine_metric_dict[metric](**metric_kwargs)
  
      affreg = AffineRegistration(
          metric=use_metric,
          level_iters=level_iters,
          sigmas=sigmas,
          factors=factors,
      )
  
      # Convert pipeline to sanitized list of str
      pipeline = list(pipeline)
      for fi, func in enumerate(pipeline):
          if callable(func):
              for key, val in _METHOD_DICT.items():
                  if (
                      func is val[0]
                  ):  # if they passed the callable equiv.
                      pipeline[fi] = func = key
                      break
          if not isinstance(func, str) or func not in _METHOD_DICT:
              raise ValueError(
                  f"pipeline[{fi}] must be one of "
                  f"{list(_METHOD_DICT)}, got {func!r}"
              )
  
      if pipeline == ["center_of_mass"] and ret_metric:
          raise ValueError(
              "center of mass registration cannot return any "
              "quality metric."
          )
  
      # Go through the selected transformation:
      for func in pipeline:
          if func == "center_of_mass":
              if starting_affine is not None and starting_was_supplied:
                  wm = "starting_affine overwritten by center_of_mass transform"
                  warn(wm, UserWarning, stacklevel=2)
  
              # multiply images by masks for transform_centers_of_mass
              static_masked, moving_masked = static, moving
              if static_mask is not None:
                  static_masked = static * static_mask
              if moving_mask is not None:
                  moving_masked = moving * moving_mask
  
              transform = transform_centers_of_mass(
                  static_masked,
                  static_affine,
                  moving_masked,
                  moving_affine,
              )
              starting_affine = transform.affine
  
          else:
              transform = _METHOD_DICT[func][1]()
              xform, xopt, fopt = affreg.optimize(
                  static,
                  moving,
                  transform,
                  None,
                  static_affine,
                  moving_affine,
                  starting_affine=starting_affine,
                  ret_metric=True,
                  static_mask=static_mask,
                  moving_mask=moving_mask,
              )
              starting_affine = xform.affine
  
      # After doing all that, resample once at the end:
      affine_map = AffineMap(
          starting_affine,
          static.shape,
          static_affine,
          moving.shape,
          moving_affine,
      )
  
      resampled = affine_map.transform(moving)
  
      # Return the optimization metric only if requested
      if ret_metric:
          return resampled, starting_affine, xopt, fopt
      return resampled, starting_affine
  
imports: from warnings import warn
  import re
  import collections.abc
  from functools import partial
  import numbers
  import numpy
  import nibabel
  from dipy.align.metrics import CCMetric, EMMetric, SSDMetric
  from dipy.align.imwarp import SymmetricDiffeomorphicRegistration, DiffeomorphicMap
  from dipy.align.imaffine import transform_centers_of_mass, AffineMap, MutualInformationMetric, AffineRegistration
  from dipy.align.transforms import TranslationTransform3D, RigidTransform3D, RigidScalingTransform3D, RigidIsoScalingTransform3D, AffineTransform3D
  import dipy.core.gradients
  import dipy.data
  from dipy.align.streamlinear import StreamlineLinearRegistration
  from dipy.tracking.streamline import set_number_of_points
  from dipy.tracking.utils import transform_tracking_output
  from dipy.io.streamline import load_trk
  from dipy.io.utils import read_img_arr_or_path
  from dipy.io.image import load_nifti, save_nifti
label_desc: Use image node_id 7 for calling a global function with example usage: affine_registration(moving, static, moving_affine, static_affine, pipeline, starting_affine, metric, level_iters, sigmas, factors, ret_metric, moving_mask, static_mask) and returns: resampled,  starting_affine, resampled,  starting_affine,  xopt,  fopt