//  [python]
def _refine_using_noise(self, noise_file_name: str, sicds: Union[SICDType, List[SICDType]]) -> None:
        """

        Parameters
        ----------
        noise_file_name : str
        sicds : SICDType|List[SICDType]

        Returns
        -------
        None
        """

        # do not use before Sentinel baseline processing calibration update on 25 Nov 2015.
        if self._base_sicd.ImageCreation.DateTime < numpy.datetime64('2015-11-25'):
            return
        root_node = _parse_xml(noise_file_name, without_ns=True)
        if isinstance(sicds, SICDType):
            sicds = [sicds, ]

        mode_id = sicds[0].CollectionInfo.RadarMode.ModeID
        lines_per_burst = sicds[0].ImageData.NumCols
        range_size_pixels = sicds[0].ImageData.NumRows

        def extract_vector(stem):
            # type: (str) -> Tuple[List[numpy.ndarray], List[Union[None, numpy.ndarray]], List[numpy.ndarray]]
            lines = []
            pixels = []
            noises = []
            noise_vector_list = root_node.findall('./{0:s}VectorList/{0:s}Vector'.format(stem))
            for i, noise_vector in enumerate(noise_vector_list):
                line = numpy.fromstring(noise_vector.find('./line').text, dtype=numpy.int64, sep=' ')
                # some datasets have noise vectors for negative lines - ignore these
                if numpy.all(line < 0):
                    continue
                pixel_node = noise_vector.find('./pixel')  # does not exist for azimuth noise
                if pixel_node is not None:
                    pixel = numpy.fromstring(pixel_node.text, dtype=numpy.int64, sep=' ')
                else:
                    pixel = None
                noise = numpy.fromstring(noise_vector.find('./{}Lut'.format(stem)).text, dtype=numpy.float64, sep=' ')
                # some datasets do not have any noise data (all 0's) - skipping these will throw things into disarray
                if not numpy.all(noise == 0):
                    # convert noise to dB - what about -inf values?
                    noise = 10*numpy.log10(noise)
                assert isinstance(noise, numpy.ndarray)

                # do some validity checks
                if (mode_id == 'IW') and numpy.any((line % lines_per_burst) != 0) and (i != len(noise_vector_list)-1):
                    # NB: the final burst has different timing
                    logger.error(
                        'Noise file should have one lut per burst, but more are present.\n\t'
                        'This may lead to confusion.')
                if (pixel is not None) and (pixel[-1] > range_size_pixels):
                    logger.error(
                        'Noise file has more pixels in LUT than range size.\n\t'
                        'This may lead to confusion.')

                lines.append(line)
                pixels.append(pixel)
                noises.append(noise)
            return lines, pixels, noises

        def populate_noise(sicd, index):  # type: (SICDType, int) -> None
            # NB: the default order was 7 before refactoring...that seems excessive.
            rg_poly_order = min(5, range_pixel[0].size-1)
            if sicd.CollectionInfo.RadarMode.ModeID[0] == 'S':
                # STRIPMAP - all LUTs apply
                # Treat range and azimuth polynomial components as fully independent
                az_poly_order = min(4, len(range_line) - 1)

                # NB: the previous rammed together two one-dimensional polys, but
                # we should do a 2-d fit.
                coords_rg = (range_pixel[0] + sicd.ImageData.FirstRow -
                             sicd.ImageData.SCPPixel.Row)*sicd.Grid.Row.SS
                coords_az = (range_line + sicd.ImageData.FirstCol - sicd.ImageData.SCPPixel.Col)*sicd.Grid.Col.SS

                coords_az_2d, coords_rg_2d = numpy.meshgrid(coords_az, coords_rg)
                noise_poly, res, rank, sing_vals = two_dim_poly_fit(
                    coords_rg_2d, coords_az_2d, numpy.array(range_noise),
                    x_order=rg_poly_order, y_order=az_poly_order, x_scale=1e-3, y_scale=1e-3, rcond=1e-40)
                logger.info(
                    'NoisePoly fit details:\n\t'
                    'root mean square residuals = {}\n\t'
                    'rank = {}\n\t'
                    'singular values = {}'.format(res, rank, sing_vals))
            else:
                # TOPSAR has single LUT per burst
                # Treat range and azimuth polynomial components as weakly independent
                if index >= len(range_pixel):
                    logger.error(
                        'We have run out of noise information.\n\t'
                        'Current index = {}, length of noise array = {}.\n\t'
                        'The previous noise information will be used to populate the NoisePoly.'.format(
                            index, len(range_pixel)))
                rp_array = range_pixel[min(index, len(range_pixel)-1)]
                rn_array = range_noise[min(index, len(range_pixel)-1)]
                coords_rg = (rp_array + sicd.ImageData.FirstRow -
                             sicd.ImageData.SCPPixel.Row)*sicd.Grid.Row.SS
                rg_poly = numpy.array(
                    polynomial.polyfit(coords_rg, rn_array, rg_poly_order))
                az_poly = None
                if azimuth_noise is not None:
                    line0 = lines_per_burst*index
                    coords_az = (azimuth_line[0] - line0 -
                                 sicd.ImageData.SCPPixel.Col)*sicd.Grid.Col.SS
                    valid_lines = (azimuth_line[0] >= line0) & (azimuth_line[0] < line0 + lines_per_burst)
                    valid_count = numpy.sum(valid_lines)
                    if valid_count > 1:
                        az_poly_order = min(2, valid_lines.size-1)
                        az_poly = numpy.array(
                            polynomial.polyfit(coords_az[valid_lines], azimuth_noise[valid_lines], az_poly_order))
                if az_poly is not None:
                    noise_poly = numpy.zeros((rg_poly.size, az_poly.size), dtype=numpy.float64)
                    noise_poly[:, 0] += rg_poly
                    noise_poly[0, :] += az_poly
                else:
                    noise_poly = numpy.reshape(rg_poly, (-1, 1))
            if sicd.Radiometric is None:
                sicd.Radiometric = RadiometricType()
            sicd.Radiometric.NoiseLevel = NoiseLevelType_(NoiseLevelType='ABSOLUTE',
                                                          NoisePoly=Poly2DType(Coefs=noise_poly))

        # extract our noise vectors (used in populate_noise through implicit reference)
        if root_node.find('./noiseVectorList') is not None:
            # probably prior to March 2018
            range_line, range_pixel, range_noise = extract_vector('noise')
            azimuth_line, azimuth_pixel, azimuth_noise = None, None, None
        else:
            # noiseRange and noiseAzimuth fields began in March 2018
            range_line, range_pixel, range_noise = extract_vector('noiseRange')
            azimuth_line, azimuth_pixel, azimuth_noise = extract_vector('noiseAzimuth')
            azimuth_line = numpy.concatenate(azimuth_line, axis=0)

        # NB: range_line is actually a list of 1 element arrays - probably should have been parsed better
        range_line = numpy.concatenate(range_line, axis=0)
        for ind, sic in enumerate(sicds):
            populate_noise(sic, ind)