text: """
  Radar Generalized Image Quality Equation (RGIQE) calculation(s) and tools for
  application to SICD structures and files.
  """
  
  __classification__ = "UNCLASSIFIED"
  __author__ = "Thomas McCullough"
  
  
  import logging
  from typing import Union, Tuple, Dict, Optional
  
  import numpy
  
  from sarpy.io.complex.base import SICDTypeReader, FlatSICDReader
  from sarpy.io.complex.sicd_elements.SICD import SICDType
  from sarpy.processing.sicd.windows import get_hamming_broadening_factor
  from sarpy.processing.sicd.normalize_sicd import sicd_degrade_reweight, is_uniform_weight
  from sarpy.io.complex.converter import open_complex
  
  logger = logging.getLogger(__name__)
  
  RNIIRS_FIT_PARAMETERS = numpy.array([3.4761, 0.4357], dtype='float64')
  """
  The RNIIRS calculation parameters determined by empirical data fit, 
  parameters updated on 2022-02-01
  """
  
  
  #####################
  # methods for extracting necessary information from the sicd structure
  
  def _verify_sicd_with_noise(sicd: SICDType) -> None:
      """
      Verify that the sicd is appropriately populated with noise.
  
      Parameters
      ----------
      sicd : SICDType
      """
  
      if sicd.Radiometric is None:
          raise ValueError(
              'Radiometric is not populated,\n\t'
              'so no noise estimate can be derived.')
      if sicd.Radiometric.SigmaZeroSFPoly is None:
          raise ValueError(
              'Radiometric.SigmaZeroSFPoly is not populated,\n\t'
              'so no sigma0 noise estimate can be derived.')
      if sicd.Radiometric.NoiseLevel is None:
          raise ValueError(
              'Radiometric.NoiseLevel is not populated,\n\t'
              'so no noise estimate can be derived.')
      if sicd.Radiometric.NoiseLevel.NoiseLevelType != 'ABSOLUTE':
          raise ValueError(
              'Radiometric.NoiseLevel.NoiseLevelType is not `ABSOLUTE``,\n\t'
              'so no noise estimate can be derived.')
  
  
  def get_sigma0_noise(sicd: SICDType) -> float:
      """
      Calculate the absolute noise estimate, in sigma0 power units.
  
      Parameters
      ----------
      sicd : SICDType
  
      Returns
      -------
      float
      """
  
      _verify_sicd_with_noise(sicd)
      noise = sicd.Radiometric.NoiseLevel.NoisePoly[0, 0]  # this is in db
      noise = numpy.exp(numpy.log(10)*0.1*noise)  # this is absolute
  
      # convert to SigmaZero value
      noise *= sicd.Radiometric.SigmaZeroSFPoly[0, 0]
      return noise
  
  
  def get_default_signal_estimate(sicd: SICDType) -> float:
      """
      Gets default signal for use in the RNIIRS calculation. This will be
      1.0 for copolar (or unknown) collections, and 0.25 for cross-pole
      collections.
  
      Parameters
      ----------
      sicd : SICDType
  
      Returns
      -------
      float
      """
  
      if sicd.ImageFormation is None or sicd.ImageFormation.TxRcvPolarizationProc is None:
          return 1.0
  
      # use 1.0 for co-polar collection and 0.25 from cross-polar collection
      pol = sicd.ImageFormation.TxRcvPolarizationProc
      if pol is None or ':' not in pol:
          return 1.0
  
      pols = pol.split(':')
  
      return 1.0 if pols[0] == pols[1] else 0.25
  
  
  def get_bandwidth_area(sicd: SICDType) -> float:
      """
      Calculate the bandwidth area.
  
      Parameters
      ----------
      sicd : SICDType
  
      Returns
      -------
      float
      """
  
      return abs(
          sicd.Grid.Row.ImpRespBW *
          sicd.Grid.Col.ImpRespBW *
          numpy.cos(numpy.deg2rad(sicd.SCPCOA.SlopeAng)))
  
  
  #########################
  # methods for calculating information density and rniirs
  
  def get_information_density(
          bandwidth_area: Union[float, numpy.ndarray],
          signal: Union[float, numpy.ndarray],
          noise: Union[float, numpy.ndarray]) -> Union[float, numpy.ndarray]:
      """
      Calculate the information density from bandwidth area and signal/noise estimates.
  
      Parameters
      ----------
      bandwidth_area : float|numpy.ndarray
      signal : float|numpy.ndarray
      noise : float|numpy.ndarray
  
      Returns
      -------
      float|numpy.ndarray
      """
  
      return bandwidth_area * numpy.log2(1 + signal/noise)
  
  
  def get_rniirs(
          information_density: Union[float, numpy.ndarray]) -> Union[float, numpy.ndarray]:
      r"""
      Calculate an RNIIRS estimate from the information density or
      Shannon-Hartley channel capacity.
  
      This mapping has been empirically determined by fitting Shannon-Hartley channel
      capacity to RNIIRS for some sample images.
  
      The basic model is given by
      :math:`rniirs = a_0 + a_1*\log_2(information\_density)`
  
      To maintain positivity of the estimated rniirs, this transitions to a linear
      model :math:`rniirs = slope*information\_density` with slope given by
      :math:`slope = a_1/(iim\_transition*\log(2))` below the transition point at
      :math:`transition = \exp(1 - \log(2)*a_0/a_1)`.
  
      Parameters
      ----------
      information_density : float|numpy.ndarray
  
      Returns
      -------
      float|numpy.ndarray
      """
  
      a = RNIIRS_FIT_PARAMETERS
      iim_transition = numpy.exp(1 - numpy.log(2) * a[0] / a[1])
      slope = a[1] / (iim_transition * numpy.log(2))
  
      if not isinstance(information_density, numpy.ndarray):
          information_density = numpy.array(information_density, dtype='float64')
      orig_ndim = information_density.ndim
      if orig_ndim == 0:
          information_density = numpy.reshape(information_density, (1, ))
  
      out = numpy.empty(information_density.shape, dtype='float64')
      mask = (information_density > iim_transition)
      mask_other = ~mask
      if numpy.any(mask):
          out[mask] = a[0] + a[1]*numpy.log2(information_density[mask])
      if numpy.any(mask_other):
          out[mask_other] = slope*information_density[mask_other]
  
      if orig_ndim == 0:
          return float(out[0])
      return out
  
  
  def get_information_density_for_rniirs(
          rniirs: Union[float, numpy.ndarray]) -> Union[float, numpy.ndarray]:
      """
      The inverse of :func:`get_rniirs`, this determines the information density
      which yields the given RNIIRS.
  
      *New in version 1.2.35.*
  
      Parameters
      ----------
      rniirs : float|numpy.ndarray
  
      Returns
      -------
      float|numpy.ndarray
      """
  
      a = RNIIRS_FIT_PARAMETERS
      iim_transition = numpy.exp(1 - numpy.log(2) * a[0] / a[1])
      slope = a[1] / (iim_transition * numpy.log(2))
      rniirs_transition = slope*iim_transition
  
      if not isinstance(rniirs, numpy.ndarray):
          rniirs = numpy.array(rniirs, dtype='float64')
      orig_ndim = rniirs.ndim
      if orig_ndim == 0:
          rniirs = numpy.reshape(rniirs, (1, ))
  
      out = numpy.empty(rniirs.shape, dtype='float64')
      mask = (rniirs > rniirs_transition)
      mask_other = ~mask
  
      if numpy.any(mask):
          out[mask] = numpy.exp2((rniirs[mask] - a[0])/a[1])
      if numpy.any(mask_other):
          out[mask_other] = rniirs[mask_other]/slope
  
      if orig_ndim == 0:
          return float(out[0])
      return out
  
  
  def snr_to_rniirs(
          bandwidth_area: Union[float, numpy.ndarray],
          signal: Union[float, numpy.ndarray],
          noise: Union[float, numpy.ndarray]) -> Tuple[Union[float, numpy.ndarray], Union[float, numpy.ndarray]]:
      """
      Calculate the information_density and RNIIRS estimate from bandwidth area and
      signal/noise estimates.
  
      It is assumed that geometric effects for signal and noise have been accounted for
      (i.e. use SigmaZeroSFPoly), and signal and noise have each been averaged to a
      single pixel value.
  
      This mapping has been empirically determined by fitting Shannon-Hartley channel
      capacity to RNIIRS for some sample images.
  
      Parameters
      ----------
      bandwidth_area : float
      signal : float
      noise : float
  
      Returns
      -------
      information_density : float
      rniirs : float
      """
  
      information_density = get_information_density(bandwidth_area, signal, noise)
      rniirs = get_rniirs(information_density)
      return information_density, rniirs
  
  
  def rgiqe(sicd: SICDType) -> Tuple[float, float]:
      """
      Calculate the information_density and (default) estimated RNIIRS for the
      given sicd.
  
      Parameters
      ----------
      sicd : SICDType
  
      Returns
      -------
      information_density : float
      rniirs : float
      """
  
      bandwidth_area = get_bandwidth_area(sicd)
      signal = get_default_signal_estimate(sicd)
      noise = get_sigma0_noise(sicd)
      return snr_to_rniirs(bandwidth_area, signal, noise)
  
  
  def populate_rniirs_for_sicd(
          sicd: SICDType,
          signal: Optional[float] = None,
          noise: Optional[float] = None,
          override: bool = False) -> None:
      """
      This populates the value(s) for RNIIRS and information density in the SICD
      structure, according to the RGIQE. **This modifies the sicd structure in place.**
  
      Parameters
      ----------
      sicd : sarpy.io.complex.sicd_elements.SICD.SICDType
      signal : None|float
          The signal value, in sigma zero.
      noise : None|float
          The noise equivalent sigma zero value.
      override : bool
          Override the value, if present.
      """
  
      if sicd.CollectionInfo is None:
          logger.error(
              'CollectionInfo must not be None.\n\t'
              'Nothing to be done for calculating RNIIRS.')
          return
  
      if sicd.CollectionInfo.Parameters is not None and \
              sicd.CollectionInfo.Parameters.get('PREDICTED_RNIIRS', None) is not None:
          if override:
              logger.info('PREDICTED_RNIIRS already populated, and this value will be overridden.')
          else:
              logger.info('PREDICTED_RNIIRS already populated. Nothing to be done.')
              return
  
      if noise is None:
          try:
              noise = get_sigma0_noise(sicd)
          except Exception as e:
              logger.error(
                  'Encountered an error estimating noise for RNIIRS.\n\t{}'.format(e))
              return
  
      if signal is None:
          signal = get_default_signal_estimate(sicd)
  
      try:
          bw_area = get_bandwidth_area(sicd)
      except Exception as e:
          logger.error(
              'Encountered an error estimating bandwidth area for RNIIRS\n\t{}'.format(e))
          return
  
      inf_density, rniirs = snr_to_rniirs(bw_area, signal, noise)
      logger.info(
          'Calculated INFORMATION_DENSITY = {0:0.5G},\n\t'
          'PREDICTED_RNIIRS = {1:0.5G}'.format(inf_density, rniirs))
      if sicd.CollectionInfo.Parameters is None:
          sicd.CollectionInfo.Parameters = {}  # initialize
      sicd.CollectionInfo.Parameters['INFORMATION_DENSITY'] = '{0:0.2G}'.format(inf_density)
      sicd.CollectionInfo.Parameters['PREDICTED_RNIIRS'] = '{0:0.1f}'.format(rniirs)
  
  
  def get_bandwidth_noise_distribution(
          sicd: SICDType,
          alpha: Union[float, numpy.ndarray],
          desired_information_density: Optional[float] = None,
          desired_rniirs: Optional[float] = None
          ) -> Tuple[Union[Tuple[float, float], numpy.ndarray], Union[float, numpy.ndarray]]:
      r"""
      This function determines SICD degradation parameters (nominally symmetric in
      row/column subaperture degradation) to achieve the desired information density/rniirs.
  
      There is natural one parameter distribution of reducing bandwidth and adding noise
      to achieve the desired RNIIRS/information density, based on :math:`\alpha \in [0, 1]`.
  
      The nominal relation is
  
      .. math::
  
          desired\_information\_density = bandwidth\_area*(bw\_mult(\alpha))^2)\cdot\log_2\left(
          1 + signal/(noise*noise\_mult(\alpha))\right).
  
      For :math:`\alpha=0`, we add no additional noise (:math:`bw\_mult(0) = bw\_min,noise\_mult(0)=1`)
      and use purely sub-aperture degradation, achieved at
  
      .. math::
  
          desired\_information\_density = bandwidth\_area*(bw\_min)^2)\cdot\log_2(1 + snr).
  
      On the other end, at :math:`\alpha=1`, we have :math:`bw\_mult(1)=1, noise\_mult(1)=noise\_mult\_max`
      and derive the noise multiplier which fulfills the required information density.
      For intermediate :math:`0 < \alpha < 1`, we find
  
      .. math::
  
          bw_mult(\alpha) = bw\_min\cdot (1-\alpha)
  
      Then, we find :math:`noise\_multiplier(\alpha)` which fulfills the required
      information density.
  
      .. note::
  
          Choosing subaperture windows is fundamentally a discrete operation, and
          this carries over to the realities of choosing bandwidth multipliers. See
          :func:`get_bidirectional_bandwidth_multiplier_possibilities` for all
          feasible values for bandwidth multipliers and associated noise adjustment
          details.
  
      *Refined in version 1.2.35.*
  
      Parameters
      ----------
      sicd : sarpy.io.complex.sicd_elements.SICD.SICDType
      alpha : float|numpy.ndarray
      desired_information_density : None|float
      desired_rniirs : None|float
  
      Returns
      -------
      bandwidth_multiplier : (float, float)|numpy.ndarray
          The `(row, column)` bandwidth multiplier including the discrete nature
          of this aperture, so the two may not be precisely equal.
      noise_multiplier : float|numpy.ndarray
          The noise multiplier, indicating how much noise to add before the
          subaperture processing.
      """
  
      # validate the desired information density/rniirs
      if (desired_information_density is None and desired_rniirs is None) or \
              (desired_information_density is not None and desired_rniirs is not None):
          raise ValueError('Exactly one of desired_information_density and desired_rniirs must be provided')
  
      if not isinstance(alpha, numpy.ndarray):
          alpha = numpy.array(alpha, dtype='float64')
      orig_ndim = alpha.ndim
      if orig_ndim == 0:
          alpha = numpy.reshape(alpha, (1, ))
  
      if not numpy.all((alpha >= 0) & (alpha <= 1)):
          raise ValueError('values for alpha must be in the interval [0, 1]')
  
      # get the current information density
      bandwidth_area = get_bandwidth_area(sicd)
      signal = get_default_signal_estimate(sicd)  # NB: this is just 1 or 0.25, no scaling issues
      current_nesz = get_sigma0_noise(sicd)
      snr = signal/current_nesz
      current_inf_density = get_information_density(bandwidth_area, signal, current_nesz)
  
      if desired_information_density is not None:
          desired_information_density = float(desired_information_density)
      elif desired_rniirs is not None:
          desired_information_density = get_information_density_for_rniirs(float(desired_rniirs))
  
      if desired_information_density > current_inf_density:
          raise ValueError(
              'The desired information density is {},\n\t'
              'but the current deweighted information density is {}'.format(
                  desired_information_density, current_inf_density))
  
      aperture_size, bw_multiplier = get_bidirectional_bandwidth_multiplier_possibilities(sicd)
  
      # construct the whole list of bandwidth areas and resulting noises after
      # subaperture degrading and deweighting
      bw_areas = bandwidth_area*numpy.multiply.reduce(bw_multiplier, 1)
      inf_densities = get_information_density(bw_areas, signal, current_nesz)
  
      if desired_information_density < inf_densities[-1]:
          raise ValueError(
              'The desired information density is {},\n\t'
              'but the minimum possible with pure subaperture degradation is {}'.format(
                  desired_information_density, inf_densities[-1]))
  
      best_index = numpy.argmin((desired_information_density - inf_densities)**2)
  
      indices = numpy.cast['int32'](best_index - alpha*best_index)
      indices = numpy.clip(indices, 0, best_index)
  
      this_bw_areas = bw_areas[indices]
  
      # NB: inf_dens = bw_area*log2(1 + snr/mult))
      #   snr/mult = 2^(inf_dens/bw_area) - 1
      #   mult = snr/(2^(inf_dens/bw_area) - 1))
  
      required_noise_multiplier = snr/(numpy.exp2(desired_information_density/this_bw_areas) - 1)
      required_noise_multiplier[required_noise_multiplier < 1] = 1
  
      bw_mult_out = numpy.empty(required_noise_multiplier.shape + (2, ), dtype='float64')
      bw_mult_out[:, 0] = bw_multiplier[indices, 0]
      bw_mult_out[:, 1] = bw_multiplier[indices, 1]
  
      if orig_ndim == 0:
          return (float(bw_mult_out[0, 0]), float(bw_mult_out[0, 1])), float(required_noise_multiplier[0])
      return bw_mult_out, required_noise_multiplier
  
  
  #########################
  # helpers for quality degradation function
  
  def _get_uniform_weight_dicts(
          sicd: SICDType) -> Tuple[Optional[Dict], Optional[Dict]]:
      """
      Gets the dictionaries denoting uniform weighting.
  
      Parameters
      ----------
      sicd : sarpy.io.complex.sicd_elements.SICD.SICDType
  
      Returns
      -------
      row_weighting : None|dict
      column_weighting : None|dict
      """
  
      row_weighting = None if is_uniform_weight(sicd, 0) else \
          {'WindowName': 'UNIFORM', 'WgtFunct': numpy.ones((32,), dtype='float64')}
      column_weighting = None if is_uniform_weight(sicd, 1) else \
          {'WindowName': 'UNIFORM', 'WgtFunct': numpy.ones((32,), dtype='float64')}
      return row_weighting, column_weighting
  
  
  def _validate_reader(
          reader: Union[str, SICDTypeReader],
          index: int) -> Tuple[SICDTypeReader, int]:
      """
      Validate the method input:
  
      Parameters
      ----------
      reader : str|SICDTypeReader
      index : int
          The reader index.
  
      Returns
      -------
      reader: SICDTypeReader
      index: int
      """
  
      if isinstance(reader, str):
          reader = open_complex(reader)
  
      if not isinstance(reader, SICDTypeReader):
          raise TypeError('reader input must be a path to a complex file, or a sicd type reader instance')
      index = int(index)
      if not (0 <= index < reader.image_count):
          raise ValueError('index must be between 0 and {}, got {}'.format(reader.image_count, index))
      return reader, index
  
  
  def _map_desired_resolution_to_aperture(
          current_imp_resp_bw: float,
          sample_size: float,
          direction: str,
          direction_size: int,
          desired_resolution: Optional[float] = None,
          desired_bandwidth: Optional[float] = None,
          broadening_factor: Optional[float] = None) -> Tuple[Optional[Tuple[int, int]], float]:
      """
      Determine the appropriate symmetric subaperture range to achieve the desired
      bandwidth or resolution, assuming the given broadening factor.
  
      Parameters
      ----------
      current_imp_resp_bw : float
      sample_size : float
      direction : str
      direction_size : int
          The size of the array along the given direction.
      desired_resolution : None|float
          The desired ImpRespWid (Row, Col) tuple, which will be mapped to ImpRespBW
          assuming uniform weighting. Exactly one of `desired_resolution` and
          `desired_bandwidth` must be provided.
      desired_bandwidth : None|float
          The desired ImpRespBW. Exactly one of `desired_resolution`
          and `desired_bandwidth` must be provided.
      broadening_factor : None|float
          The only applies if `desired_resolution` is provided. If not provided,
          then UNIFORM weighting will be assumed.
  
      Returns
      -------
      indices: None|Tuple[int, int]
      bw_factor : float
      """
  
      if desired_resolution is None and desired_bandwidth is None:
          raise ValueError('One of desire_resolution or desired_bandwidth must be supplied.')
  
      if desired_resolution is not None:
          if broadening_factor is None:
              broadening_factor = get_hamming_broadening_factor(1.0)
          else:
              broadening_factor = float(broadening_factor)
          use_resolution = float(desired_resolution)
  
          use_bandwidth = broadening_factor/use_resolution
      else:
          use_bandwidth = float(desired_bandwidth)
  
      if use_bandwidth > current_imp_resp_bw:
          if desired_resolution is not None:
              raise ValueError(
                  'After mapping from Desired {} ImpRespWid considering uniform weighting,\n\t'
                  'the equivalent desired ImpRespBW is {},\n\t'
                  'but the current ImpRespBW is {}'.format(direction, use_bandwidth, current_imp_resp_bw))
          else:
              raise ValueError(
                  'Desired {} ImpRespBW is given as {},\n\t'
                  'but the current ImpRespBW is {}'.format(direction, use_bandwidth, current_imp_resp_bw))
      elif use_bandwidth == current_imp_resp_bw:
          return None, 1.0
      else:
          oversample = max(1., 1./(sample_size*use_bandwidth))
          ap_size = round(direction_size/oversample)
          start_ind = int(numpy.floor(0.5*(direction_size - ap_size)))
          return (start_ind, start_ind+ap_size), use_bandwidth/current_imp_resp_bw
  
  
  def _map_bandwidth_parameters(
          sicd: SICDType,
          desired_resolution: Optional[Tuple[float, float]] = None,
          desired_bandwidth: Optional[Tuple[float, float]] = None
          ) -> Tuple[Tuple[int, int], float, Tuple[int, int], float]:
      """
      Helper function to map desired resolution or bandwidth to the suitable (centered)
      aperture.
  
      Parameters
      ----------
      sicd : SICDType
      desired_resolution : None|Tuple[float, float]
      desired_bandwidth : None|Tuple[float, float]
  
      Returns
      -------
      row_aperture : Tuple[int, int]
      row_bw_factor : float
      column_aperture : Tuple[int, int]
      column_bw_factor : float
      """
  
      if desired_resolution is not None:
          # get the broadening factor for uniform weighting
          broadening_factor = get_hamming_broadening_factor(1.0)
          row_aperture, row_bw_factor = _map_desired_resolution_to_aperture(
              sicd.Grid.Row.ImpRespBW, sicd.Grid.Row.SS, 'Row', sicd.ImageData.NumRows,
              desired_resolution=desired_resolution[0], broadening_factor=broadening_factor)
          column_aperture, column_bw_factor = _map_desired_resolution_to_aperture(
              sicd.Grid.Col.ImpRespBW, sicd.Grid.Col.SS, 'Col', sicd.ImageData.NumCols,
              desired_resolution=desired_resolution[1], broadening_factor=broadening_factor)
      elif desired_bandwidth is not None:
          row_aperture, row_bw_factor = _map_desired_resolution_to_aperture(
              sicd.Grid.Row.ImpRespBW, sicd.Grid.Row.SS, 'Row', sicd.ImageData.NumRows,
              desired_bandwidth=desired_bandwidth[0])
          column_aperture, column_bw_factor = _map_desired_resolution_to_aperture(
              sicd.Grid.Col.ImpRespBW, sicd.Grid.Col.SS, 'Col', sicd.ImageData.NumCols,
              desired_bandwidth=desired_bandwidth[1])
      else:
          row_aperture, row_bw_factor = None, 1
          column_aperture, column_bw_factor = None, 1
  
      return row_aperture, row_bw_factor, column_aperture, column_bw_factor
  
  
  def get_dimension_bandwidth_multiplier_possibilities(
          sicd: SICDType,
          dimension: int) -> Tuple[numpy.ndarray, numpy.ndarray]:
      """
      Gets the bandwidth possibilities for all centered subapertures along the given
      dimension.
  
      *Introduced in 1.2.35*
  
      Parameters
      ----------
      sicd : SICDType
      dimension : int
          One of `{0, 1}`.
  
      Returns
      -------
      aperture_size : numpy.ndarray
          Of shape `(N, )`
      bandwidth_multiplier : numpy.ndarray
          Of shape `(N, )`
      """
  
      if dimension == 0:
          ap_size = round(sicd.ImageData.NumRows / sicd.Grid.Row.get_oversample_rate())
      else:
          ap_size = round(sicd.ImageData.NumCols / sicd.Grid.Col.get_oversample_rate())
  
      aperture_size = numpy.arange(ap_size, 0, -1, dtype='int32')
      bandwidth_multiplier = aperture_size/float(ap_size)
  
      return aperture_size, bandwidth_multiplier
  
  
  def get_bidirectional_bandwidth_multiplier_possibilities(
          sicd: SICDType) -> Tuple[numpy.ndarray, numpy.ndarray]:
      """
      Gets the bandwidth possibilities for all centered subapertures shrinking
      along both dimensions symmetrically.
  
      *Introduced in 1.2.35*
  
      Parameters
      ----------
      sicd : SICDType
  
      Returns
      -------
      aperture_size : numpy.ndarray
          An array of shape `(N, 2)` for row/column separately.
      bandwidth_multiplier : numpy.ndarray
          An array of shape `(N, 2)` for row/column separately.
      """
  
      row_aperture_size, row_bw_multiplier = get_dimension_bandwidth_multiplier_possibilities(sicd, 0)
      col_aperture_size, col_bw_multiplier = get_dimension_bandwidth_multiplier_possibilities(sicd, 1)
  
      the_size = max(row_aperture_size.size, col_aperture_size.size)
      aperture_size = numpy.empty((the_size, 2), dtype='int32')
      bandwidth_multiplier = numpy.empty((the_size, 2), dtype='float64')
  
      row_indexing = numpy.cast['int32'](
          numpy.ceil(float(row_aperture_size.size - 1)*numpy.arange(the_size)/float(the_size - 1)))
      col_indexing = numpy.cast['int32'](
          numpy.ceil(float(col_aperture_size.size - 1)*numpy.arange(the_size)/float(the_size - 1)))
  
      aperture_size[:, 0] = row_aperture_size[row_indexing]
      aperture_size[:, 1] = col_aperture_size[col_indexing]
  
      bandwidth_multiplier[:, 0] = row_bw_multiplier[row_indexing]
      bandwidth_multiplier[:, 1] = col_bw_multiplier[col_indexing]
  
      return aperture_size, bandwidth_multiplier
  
  
  #########################
  # SICD quality degradation functions
  
  def quality_degrade(
          reader: Union[str, SICDTypeReader],
          index: int = 0,
          output_file: Optional[str] = None,
          desired_resolution: Optional[Tuple[float, float]] = None,
          desired_bandwidth: Optional[Tuple[float, float]] = None,
          desired_nesz: Optional[float] = None,
          **kwargs) -> Optional[FlatSICDReader]:
      r"""
      Create a degraded quality SICD based on the desired resolution (impulse response width)
      or bandwidth (impulse response bandwidth), and the desired Noise Equivalent
      Sigma Zero value. The produced SICD will have **uniform weighting**.
  
      No more than one of `desired_resolution` and `desired_bandwidth` can be provided.
      If None of `desired_resolution`, `desired_bandwidth`, or `desired_nesz` are provided,
      then the SICD will be re-weighted with uniform weighting - even this will
      change the noise and RNIIRS values slightly.
  
      .. warning::
  
          Unless `desired_nesz=None`, this will fail for a SICD which is not fully
          Radiometrically calibrated with `'ABSOLUTE'` noise type.
  
  
      Parameters
      ----------
      reader : str|SICDTypeReader
      index : int
          The reader index to be used.
      output_file : None|str
          If `None`, an in-memory SICD reader instance will be returned. Otherwise,
          this is the path for the produced output SICD file.
      desired_resolution : None|tuple
          The desired ImpRespWid (Row, Col) tuple, which will be mapped to ImpRespBW
          assuming uniform weighting. You cannot provide both `desired_resolution`
          and `desired_bandwidth`.
      desired_bandwidth : None|tuple
          The desired ImpRespBW (Row, Col) tuple. You cannot provide both
          `desired_resolution` and `desired_bandwidth`.
      desired_nesz : None|float
          The desired Noise Equivalent Sigma Zero value in power units, this is after
          modifications which change the noise due to sub-aperture degradation and/or
          de-weighting.
      kwargs
          Keyword arguments passed through to :func:`sarpy.processing.sicd.normalize_sicd.sicd_degrade_reweight`
  
      Returns
      -------
      None|FlatSICDReader
          No return if `output_file` is provided, otherwise the returns the in-memory
          reader object.
      """
  
      reader, index = _validate_reader(reader, index)
      if desired_resolution is not None and desired_bandwidth is not None:
          raise ValueError('Both desired_resolution and desired_bandwidth cannot be supplied.')
  
      sicd = reader.get_sicds_as_tuple()[index]
  
      if desired_nesz is None:
          add_noise = None
      else:
          current_nesz = get_sigma0_noise(sicd)
          add_noise_factor = (desired_nesz - current_nesz)/current_nesz
          if abs(add_noise_factor) < 1e-5:
              add_noise = None
          elif add_noise_factor < 0:
              raise ValueError(
                  'The current nesz value is {},\n\t'
                  'the desired nesz value of {} cannot be achieved.'.format(current_nesz, desired_nesz))
          else:
              add_noise = numpy.exp(numpy.log(10)*0.1*sicd.Radiometric.NoiseLevel.NoisePoly[0, 0])*add_noise_factor
  
      row_aperture, row_bw_factor, column_aperture, column_bw_factor = _map_bandwidth_parameters(
          sicd, desired_resolution=desired_resolution, desired_bandwidth=desired_bandwidth)
  
      row_weighting, column_weighting = _get_uniform_weight_dicts(sicd)
      return sicd_degrade_reweight(
          reader, output_file=output_file, index=index,
          row_aperture=row_aperture, row_weighting=row_weighting,
          column_aperture=column_aperture, column_weighting=column_weighting,
          add_noise=add_noise, **kwargs)
  
  
  def quality_degrade_resolution(
          reader: Union[str, SICDTypeReader],
          index: int = 0,
          output_file: Optional[str] = None,
          desired_resolution: Optional[Tuple[float, float]] = None,
          desired_bandwidth: Optional[Tuple[float, float]] = None,
          **kwargs) -> Optional[FlatSICDReader]:
      """
      Create a degraded quality SICD based on INCREASING the impulse response width
      to the desired resolution or DECREASING the impulse response bandwidth to the
      desired bandwidth.
  
      The produced SICD will have uniform weighting.
  
      Parameters
      ----------
      reader : str|SICDTypeReader
      index : int
          The reader index to be used.
      output_file : None|str
          If `None`, an in-memory SICD reader instance will be returned. Otherwise,
          this is the path for the produced output SICD file.
      desired_resolution : None|tuple
          The desired ImpRespWid (Row, Col) tuple, which will be mapped to ImpRespBW
          assuming uniform weighting. Exactly one of `desired_resolution` and
          `desired_bandwidth` must be provided.
      desired_bandwidth : None|tuple
          The desired ImpRespBW (Row, Col) tuple. Exactly one of `desired_resolution`
          and `desired_bandwidth` must be provided.
      kwargs
          Keyword arguments passed through to :func:`sarpy.processing.sicd.normalize_sicd.sicd_degrade_reweight`
  
      Returns
      -------
      None|FlatSICDReader
          No return if `output_file` is provided, otherwise the returns the in-memory
          reader object.
      """
  
      return quality_degrade(
          reader, index=index, output_file=output_file,
          desired_resolution=desired_resolution, desired_bandwidth=desired_bandwidth,
          **kwargs)
  
  
  def quality_degrade_noise(
          reader: Union[str, SICDTypeReader],
          index: int = 0,
          output_file: Optional[str] = None,
          desired_nesz: Optional[float] = None,
          **kwargs) -> Optional[FlatSICDReader]:
      """
      Create a degraded quality SICD based on INCREASING the noise to the desired
      Noise Equivalent Sigma Zero value. The produced SICD will have uniform weighting.
  
      .. warning::
  
          This will fail for a SICD which is not fully Radiometrically calibrated,
          with ABSOLUTE noise type.
  
      Parameters
      ----------
      reader : str|SICDTypeReader
      index : int
          The reader index to be used.
      output_file : None|str
          If `None`, an in-memory SICD reader instance will be returned. Otherwise,
          this is the path for the produced output SICD file.
      desired_nesz : None|float
          The desired noise equivalent sigma zero value.
      kwargs
          Keyword arguments passed through to :func:`sarpy.processing.sicd.normalize_sicd.sicd_degrade_reweight`
  
      Returns
      -------
      None|FlatSICDReader
          No return if `output_file` is provided, otherwise the returns the in-memory
          reader object.
      """
  
      return quality_degrade(reader, index=index, output_file=output_file, desired_nesz=desired_nesz, **kwargs)
  
  
  def quality_degrade_rniirs(
          reader: Union[str, SICDTypeReader],
          index: int = 0,
          output_file: Optional[str] = None,
          desired_rniirs: Optional[float] = None,
          alpha: float = 0,
          **kwargs) -> Optional[FlatSICDReader]:
      r"""
      Create a degraded quality SICD based on the desired estimated RNIIRS value.
      The produced SICD will have uniform weighting.
  
      The sicd degradation will be performed as follows:
  
      - The current information density/current rniirs **with respect to uniform weighting**
        will be found.
  
      - The information density required to produce the desired rniirs will be found.
  
      - This will be used, along with the :math:`\alpha` value, will be used in
        :func:`get_bandwidth_noise_distribution` to determine the best feasible
        multipliers for bandwidth and noise values.
  
      - These desired bandwidth and noise values will then be used in conjunction
        with :func:`sicd_degrade_reweight`.
  
      .. warning::
  
          This will fail for a SICD which is not fully Radiometrically calibrated,
          with `'ABSOLUTE'` noise type.
  
      Parameters
      ----------
      reader : str|SICDTypeReader
      index : int
          The reader index to be used.
      output_file : None|str
          If `None`, an in-memory SICD reader instance will be returned. Otherwise,
          this is the path for the produced output SICD file.
      desired_rniirs : None|float
          The desired rniirs value, according to the RGIQE methodology.
      alpha : float
          This must be a number in the interval [0, 1] defining the (geometric)
          distribution of variability between required influence from increasing
          noise and require influence of decreasing bandwidth.
      kwargs
          Keyword arguments passed through to :func:`sarpy.processing.sicd.normalize_sicd.sicd_degrade_reweight`
  
      Returns
      -------
      None|FlatSICDReader
          No return if `output_file` is provided, otherwise the returns the in-memory
          reader object.
      """
  
      if desired_rniirs is None:
          return quality_degrade(reader, index=index, output_file=output_file, **kwargs)
  
      reader, index = _validate_reader(reader, index)
      sicd = reader.get_sicds_as_tuple()[index]
      current_noise = numpy.exp(numpy.log(10)*0.1*sicd.Radiometric.NoiseLevel.NoisePoly[0, 0])
  
      bandwidth_multiplier, noise_multiplier = get_bandwidth_noise_distribution(
          sicd, alpha, desired_rniirs=desired_rniirs)
  
      desired_bandwidth = (
          sicd.Grid.Row.ImpRespBW*bandwidth_multiplier[0],
          sicd.Grid.Col.ImpRespBW*bandwidth_multiplier[1])
      add_noise = (noise_multiplier - 1)*current_noise
  
      if alpha == 0 or add_noise <= 0:
          add_noise = None
      row_aperture, row_bw_factor, column_aperture, column_bw_factor = _map_bandwidth_parameters(
          sicd, desired_bandwidth=desired_bandwidth)
      row_weighting, column_weighting = _get_uniform_weight_dicts(sicd)
  
      return sicd_degrade_reweight(
          reader, output_file=output_file, index=index,
          row_aperture=row_aperture, row_weighting=row_weighting,
          column_aperture=column_aperture, column_weighting=column_weighting,
          add_noise=add_noise, **kwargs)
  