code: def add_noise(vol, snr=1.0, S0=None, noise_type="rician", rng=None):
      """Add noise of specified distribution to a 4D array.
  
      Parameters
      ----------
      vol : array, shape (X,Y,Z,W)
          Diffusion measurements in `W` directions at each ``(X, Y, Z)`` voxel
          position.
      snr : float, optional
          The desired signal-to-noise ratio.  (See notes below.)
      S0 : float, optional
          Reference signal for specifying `snr` (defaults to 1).
      noise_type : string, optional
          The distribution of noise added. Can be either 'gaussian' for Gaussian
          distributed noise, 'rician' for Rice-distributed noise (default) or
          'rayleigh' for a Rayleigh distribution.
      rng : numpy.random.Generator class, optional
          Numpy's random generator for setting seed values when needed.
          Default is None.
  
      Returns
      -------
      vol : array, same shape as vol
          Volume with added noise.
  
      Notes
      -----
      SNR is defined here, following [1]_, as ``S0 / sigma``, where ``sigma`` is
      the standard deviation of the two Gaussian distributions forming the real
      and imaginary components of the Rician noise distribution (see [2]_).
  
      References
      ----------
      .. [1] Descoteaux, Angelino, Fitzgibbons and Deriche (2007) Regularized,
             fast and robust q-ball imaging. MRM, 58: 497-510
      .. [2] Gudbjartson and Patz (2008). The Rician distribution of noisy MRI
             data. MRM 34: 910-914.
  
      Examples
      --------
      >>> signal = np.arange(800).reshape(2, 2, 2, 100)
      >>> signal_w_noise = add_noise(signal, snr=10, noise_type='rician')
  
      """
      orig_shape = vol.shape
      vol_flat = np.reshape(vol.copy(), (-1, vol.shape[-1]))
  
      if S0 is None:
          S0 = np.max(vol)
  
      for vox_idx, signal in enumerate(vol_flat):
          vol_flat[vox_idx] = vox.add_noise(
              signal, snr=snr, S0=S0, noise_type=noise_type, rng=rng
          )
  
      return np.reshape(vol_flat, orig_shape)
  
file_path: dipy/dipy/sims/phantom.py
imports: import numpy
  from dipy.sims.voxel import single_tensor, diffusion_evals
  import dipy.sims.voxel
  from dipy.core.geometry import vec2vec_rotmat
  from dipy.data import get_fnames
  from dipy.core.gradients import gradient_table
label_desc: Use image node_id 1 for calling a global function with example usage: add_noise(vol, snr, S0, noise_type, rng) and returns: np