code: def _projection_matrix(
      mesh,
      affine,
      img_shape,
      kind="auto",
      radius=3.0,
      n_points=None,
      mask=None,
      inner_mesh=None,
      depth=None,
  ):
      """Get a sparse matrix that projects volume data onto a mesh.
  
      Parameters
      ----------
      mesh : :obj:`str` or :obj:`numpy.ndarray`
          Either a file containing surface mesh geometry (valid formats
          are .gii or Freesurfer specific files such as .orig, .pial,
          .sphere, .white, .inflated) or a list of two Numpy arrays,
          the first containing the x-y-z coordinates of the mesh
          vertices, the second containing the indices (into coords)
          of the mesh faces.
  
      affine : :obj:`numpy.ndarray` of shape (4, 4)
          Affine transformation from image voxels to the vertices' coordinate
          space.
  
      img_shape : 3-tuple of :obj:`int`
          The shape of the image to be projected.
  
      kind : {'auto', 'depth', 'line', 'ball'}, default='auto'
          The strategy used to sample image intensities around each vertex.
          Ignored if `inner_mesh` is not None.
  
          - 'auto':
              'depth' if `inner_mesh` is not `None`, otherwise 'line.
          - 'depth':
              Sampled at the specified cortical depths between corresponding
              nodes of `mesh` and `inner_mesh`.
          - 'line':
              Samples are placed along the normal to the mesh.
          - 'ball':
              Samples are regularly spaced inside a ball centered at the mesh
              vertex.
  
      radius : :obj:`float`, default=3.0
          The size (in mm) of the neighbourhood from which samples are drawn
          around each node. Ignored if `inner_mesh` is not `None`.
  
      n_points : :obj:`int` or None, optional
          How many samples are drawn around each vertex and averaged. If `None`,
          use a reasonable default for the chosen sampling strategy (20 for
          'ball' or 10 for lines ie using `line` or an `inner_mesh`).
          For performance reasons, if using kind="ball", choose `n_points` in
          [10, 20, 40, 80, 160] (default is 20), because cached positions are
          available.
  
      mask : :obj:`numpy.ndarray` of shape img_shape or `None`, optional
          Part of the image to be masked. If `None`, don't apply any mask.
  
      inner_mesh : :obj:`str` or :obj:`numpy.ndarray`, optional
          Either a file containing surface mesh or a pair of ndarrays
          (coordinates, triangles). If provided this is an inner surface that is
          nested inside the one represented by `mesh` -- e.g. `mesh` is a pial
          surface and `inner_mesh` a white matter surface. In this case nodes in
          both meshes must correspond: node i in `mesh` is just across the gray
          matter thickness from node i in `inner_mesh`. Image values for index i
          are then sampled along the line joining these two points (if `kind` is
          'auto' or 'depth').
  
      depth : sequence of :obj:`float` or `None`, optional
          Cortical depth, expressed as a fraction of segment_half_width.
          overrides n_points. Should be None if kind is 'ball'
  
      Returns
      -------
      proj : :obj:`scipy.sparse.csr_matrix`
         Shape (n_voxels, n_mesh_vertices). The dot product of this matrix with
         an image (represented as a column vector) gives the projection onto mesh
         vertices.
  
      See Also
      --------
      nilearn.surface.vol_to_surf
          Compute the projection for one or several images.
  
      """
      # A user might want to call this function directly so check mask size.
      if mask is not None and tuple(mask.shape) != img_shape:
          raise ValueError("mask should have shape img_shape")
      mesh = load_surf_mesh(mesh)
      sample_locations = _sample_locations(
          mesh,
          affine,
          kind=kind,
          radius=radius,
          n_points=n_points,
          inner_mesh=inner_mesh,
          depth=depth,
      )
      sample_locations = np.asarray(
          np.round(sample_locations), dtype=int
      )
      n_vertices, n_points, _ = sample_locations.shape
      masked = _masked_indices(
          np.vstack(sample_locations), img_shape, mask=mask
      )
      sample_locations = np.rollaxis(sample_locations, -1)
      sample_indices = np.ravel_multi_index(
          sample_locations, img_shape, mode="clip"
      ).ravel()
      row_indices, _ = np.mgrid[:n_vertices, :n_points]
      row_indices = row_indices.ravel()
      row_indices = row_indices[~masked]
      sample_indices = sample_indices[~masked]
      weights = np.ones(len(row_indices))
      proj = sparse.csr_matrix(
          (weights, (row_indices, sample_indices.ravel())),
          shape=(n_vertices, np.prod(img_shape)),
      )
      proj = sklearn.preprocessing.normalize(proj, axis=1, norm="l1")
      return proj
  
imports: import gzip
  import os
  import warnings
  from collections import namedtuple
  from collections.abc import Mapping
  import nibabel
  import numpy
  import sklearn.cluster
  import sklearn.preprocessing
  from nibabel import freesurfer, gifti
  from scipy import interpolate, sparse
  from nilearn import _utils, datasets
  from nilearn._utils import stringify_path
  from nilearn._utils.path_finding import resolve_globbing
  from nilearn.image import get_data, load_img, resampling
label_desc: Use image node_id 12 for calling a global function with example usage: _projection_matrix(mesh, affine, img_shape, kind, radius, n_points, mask, inner_mesh, depth) and returns: proj