diff --git a/python/packages/nisar/workflows/gcov.py b/python/packages/nisar/workflows/gcov.py index c4d6a3328..414357800 100644 --- a/python/packages/nisar/workflows/gcov.py +++ b/python/packages/nisar/workflows/gcov.py @@ -109,7 +109,8 @@ def apply_noise_correction(data_block, noise_product, is_backscatter): def prepare_rslc(in_file, freq, pol, out_file, lines_per_block, flag_rslc_to_backscatter, flag_apply_noise_correction, - pol_2=None, format="ENVI"): + pol_2=None, format="ENVI", + radiometric_calibration_lut=None): ''' Copy RSLC dataset to GDAL format converting RSLC real and imaginary parts from float16 to float32. If the flag @@ -144,6 +145,8 @@ def prepare_rslc(in_file, freq, pol, out_file, lines_per_block, Polarization associated with the second RSLC format: str, optional GDAL-friendly format + radiometric_calibration_lut: isce3.core.LUT2d or None + Radiometric calibration look-up table (LUT) Returns ------- @@ -165,6 +168,8 @@ def prepare_rslc(in_file, freq, pol, out_file, lines_per_block, f' {flag_symmetrize}') info_channel.log(' apply noise correction:' f' {flag_apply_noise_correction}') + info_channel.log(' apply radiometric calibration LUT:' + f' {radiometric_calibration_lut is not None}') pol_ref = f'HDF5:{in_file}:/{pol_dataset_path}' @@ -225,8 +230,9 @@ def prepare_rslc(in_file, freq, pol, out_file, lines_per_block, lines_per_block = min(rslc_length, lines_per_block) num_blocks = int(np.ceil(rslc_length / lines_per_block)) - # get radar_grid for noise correction - if flag_apply_noise_correction: + # get radar_grid for noise correction and radiometric calibration + if (flag_apply_noise_correction or + radiometric_calibration_lut is not None): radar_grid = rslc.getRadarGrid(frequency=freq) for block in range(num_blocks): @@ -284,6 +290,27 @@ def prepare_rslc(in_file, freq, pol, out_file, lines_per_block, # average cross-pol channels data_block = 0.5 * (data_block + data_block_2) + if radiometric_calibration_lut is not None: + for i in range(block_length): + slant_ranges = radar_grid.slant_ranges + radiometric_calibraton_slant_ranges = \ + radiometric_calibration_lut.eval(i + line_start, + slant_ranges) + + # apply radiometric calibration by dividing the data block by + # the radiometric calibration slant-range line + if flag_rslc_to_backscatter: + + # if the data block is backscatter, convert the + # radiometric calibration LUT to power/intensity + # (square) + data_block[i, :] = (data_block[i, :] / + radiometric_calibraton_slant_ranges ** + 2) + else: + data_block[i, :] = (data_block[i, :] / + radiometric_calibraton_slant_ranges) + # write to GDAL raster out_ds.GetRasterBand(1).WriteArray(data_block, yoff=line_start, xoff=0) @@ -331,6 +358,7 @@ def _run(cfg, raster_scratch_dir): ''' info_channel = journal.info("gcov.run") + error_channel = journal.error("gcov.run") info_channel.log("Starting GCOV workflow") # pull parameters from cfg @@ -392,6 +420,10 @@ def _run(cfg, raster_scratch_dir): # unpack geocode run parameters geocode_dict = cfg['processing']['geocode'] + radiometric_calibration_lut_name = \ + geocode_dict['radiometric_calibration_lut'] + flag_apply_rtc = geocode_dict['apply_rtc'] + apply_range_ionospheric_delay_correction = \ geocode_dict['apply_range_ionospheric_delay_correction'] @@ -410,6 +442,10 @@ def _run(cfg, raster_scratch_dir): min_block_size_mb = cfg["processing"]["geocode"]['min_block_size'] max_block_size_mb = cfg["processing"]["geocode"]['max_block_size'] + # unpack RTC run parameters + rtc_dict = cfg['processing']['rtc'] + input_terrain_radiometry = rtc_dict['input_terrain_radiometry'] + # optional keyword arguments , i.e. arguments that may or may not be # included in the call to geocode() optional_geo_kwargs = {} @@ -442,6 +478,36 @@ def _run(cfg, raster_scratch_dir): # init parameters shared between frequencyA and frequencyB sub-bands slc = SLC(hdf5file=input_hdf5) + + radiometric_calibration_lut = None + if (radiometric_calibration_lut_name is not None and + radiometric_calibration_lut_name != 'disabled'): + info_channel.log(' radiometric calibration LUT:' + f' {radiometric_calibration_lut_name}') + + if flag_apply_rtc and radiometric_calibration_lut_name == "gamma0": + err_str = ('ERROR radiometric calibration LUT cannot be "gamma0"' + ' if RTC is enabled (`apply_rtc is True`)') + error_channel.log(err_str) + raise ValueError(err_str) + + # if RTC is enabled, ensure that `radiometric_calibration_lut_name` + # and `input_terrain_radiometry` are the same: + if (flag_apply_rtc and (radiometric_calibration_lut_name != + input_terrain_radiometry)): + err_str = ('The radiometric calibration LUT' + f' ("{radiometric_calibration_lut_name}")' + ' does not match the RTC input terrain radiometry' + f' ("{input_terrain_radiometry}")') + error_channel.log(err_str) + raise ValueError(err_str) + + radiometric_calibration_lut = \ + slc.getRadiometricCalibrationLUT( + lut_name=radiometric_calibration_lut_name) + else: + info_channel.log(' radiometric calibration LUT: "disabled"') + zero_doppler = isce3.core.LUT2d() native_doppler = slc.getDopplerCentroid() @@ -509,7 +575,8 @@ def _run(cfg, raster_scratch_dir): symmetrized_hv_temp.name, 2**11, # 2**11 = 2048 lines flag_rslc_to_backscatter=flag_rslc_to_backscatter, flag_apply_noise_correction=flag_apply_noise_correction, - pol_2='VH', format=output_gcov_terms_raster_files_format) + pol_2='VH', format=output_gcov_terms_raster_files_format, + radiometric_calibration_lut=radiometric_calibration_lut) # Since HV and VH were symmetrized into HV, remove VH from # `pol_list` and `from input_raster_dict`. @@ -542,7 +609,8 @@ def _run(cfg, raster_scratch_dir): temp_pol_file.name, 2**12, # 2**12 = 4096 lines flag_rslc_to_backscatter=flag_rslc_to_backscatter, flag_apply_noise_correction=flag_apply_noise_correction, - format=output_gcov_terms_raster_files_format) + format=output_gcov_terms_raster_files_format, + radiometric_calibration_lut=radiometric_calibration_lut) input_raster_list.append(input_raster) diff --git a/python/packages/nisar/workflows/gcov_runconfig.py b/python/packages/nisar/workflows/gcov_runconfig.py index 6422594af..a7c10cf68 100644 --- a/python/packages/nisar/workflows/gcov_runconfig.py +++ b/python/packages/nisar/workflows/gcov_runconfig.py @@ -61,6 +61,16 @@ def load(self): rtc_dict['algorithm_type_enum'] = \ isce3.geometry.normalize_rtc_algorithm(rtc_dict['algorithm_type']) + if (rtc_dict['input_terrain_radiometry'] is None and + (geocode_dict['radiometric_calibration_lut'] == 'beta0' or + geocode_dict['radiometric_calibration_lut'] == 'sigma0')): + rtc_dict['input_terrain_radiometry'] = \ + geocode_dict['radiometric_calibration_lut'] + + # otherwise, the RTC input terrain radiometry defaults to `beta0` + elif rtc_dict['input_terrain_radiometry'] is None: + rtc_dict['input_terrain_radiometry'] = 'beta0' + if rtc_dict['input_terrain_radiometry'] == "sigma0": rtc_dict['input_terrain_radiometry_enum'] = \ isce3.geometry.RtcInputTerrainRadiometry.SIGMA_NAUGHT_ELLIPSOID diff --git a/share/nisar/defaults/gcov.yaml b/share/nisar/defaults/gcov.yaml index 548331d5b..bb5d0f2f0 100644 --- a/share/nisar/defaults/gcov.yaml +++ b/share/nisar/defaults/gcov.yaml @@ -238,10 +238,24 @@ runconfig: # "area_projection" (default) algorithm_type: area_projection - # Input terrain radiometry convention. Choices: - # "beta0" (default) - # "sigma0" - input_terrain_radiometry: beta0 + # OPTIONAL - Choices: + # 'beta0' (default if `radiometric_calibration_lut` == 'beta0' or + # `radiometric_calibration_lut` == 'disabled')) + # 'sigma0' (default if `radiometric_calibration_lut` == 'sigma0') + # + # The field `input_terrain_radiometry` defines the input terrain + # radiometric convention for RTC. Its value should be consistent + # with the field `radiometric_calibration_lut` in the geocode + # group. If that field is set to 'beta0' or 'sigma0', + # the `input_terrain_radiometry` should be assigned to the same + # value. + # For that reason, if `input_terrain_radiometry` is not set and + # `radiometric_calibration_lut is set to either 'beta0' or + # 'sigma0', `input_terrain_radiometry` will default to + # to that value (i.e., 'beta0' or 'sigma0'). Otherwise, + # if `radiometric_calibration_lut` is disabled (default), + # the `input_terrain_radiometry` will default to 'beta0'. + input_terrain_radiometry: # Minimum RTC area factor (in dB). Specifies the lowest # allowable denominator (in dB) for dividing GCOV samples. @@ -278,6 +292,24 @@ runconfig: # If `False`, the entire `rtc` group is ignored. apply_rtc: True + # Radiometric calibration look-up table (LUT) + # + # Determines the type of radiometric calibration to be applied to input + # RLSC values. + # + # The radiometric calibration is applied to the RSLC before RTC. + # Therefore, if `apply_rtc` is enabled, this field should be consistent + # with the RTC group field `input_terrain_radiometry`. Otherwise, + # the software will raise an error. + # + # The value "gamma0" implies that the radiometric calibration will + # be applied instead of RTC. In that case, the field `apply_rtc` + # should be `False`, otherwise the software will raise an error. + # + # The field `abs_rad_cal` provides an additional constant factor + # that can be optionally applied to RSLC + radiometric_calibration_lut: disabled + # NOT YET IMPLEMENTED (it is expected that the RSLC product already # incorporates static tropospheric delay corrections -- # no additional correction is currently applied) diff --git a/share/nisar/schemas/gcov.yaml b/share/nisar/schemas/gcov.yaml index 72e98c145..7ce63f618 100644 --- a/share/nisar/schemas/gcov.yaml +++ b/share/nisar/schemas/gcov.yaml @@ -317,12 +317,24 @@ pre_process_options: rtc_options: # Output type (defines the terrain radiometry convention for the output + # It defaults to "gamma0". output_type: enum('gamma0', 'sigma0', required=False) # RTC algorithm type algorithm_type: enum('area_projection', 'bilinear_distribution', required=False) - # Input terrain radiometry convention + # The field `input_terrain_radiometry` defines the input terrain + # radiometric convention for RTC. Its value should be consistent + # with the field `radiometric_calibration_lut` in the geocode + # group. If that field is set to 'beta0' or 'sigma0', + # the `input_terrain_radiometry` should be assigned to the same + # value. + # For that reason, if `input_terrain_radiometry` is not set and + # `radiometric_calibration_lut is set to either 'beta0' or + # 'sigma0', `input_terrain_radiometry` will default to + # to that value (i.e., 'beta0' or 'sigma0'). Otherwise, + # if `radiometric_calibration_lut` is disabled (default), + # the `input_terrain_radiometry` will default to 'beta0'. input_terrain_radiometry: enum('beta0', 'sigma0', required=False) # Minimum RTC area factor (in dB). Specifies the lowest @@ -354,6 +366,24 @@ geocode_options: # sinc, bilinear, bicubic, nearest, and biquintic algorithm_type: enum('area_projection', 'sinc', 'bilinear', 'bicubic', 'nearest', 'biquintic', required=False) + # Radiometric calibration look-up table (LUT) + # + # Determines the type of radiometric calibration to be applied to input + # RLSC values. + # + # The radiometric calibration is applied to the RSLC before RTC. + # Therefore, if `apply_rtc` is enabled, this field should be consistent + # with the RTC group field `input_terrain_radiometry`. Otherwise, + # the software will raise an error. + # + # The value "gamma0" implies that the radiometric calibration will + # be applied instead of RTC. In that case, the field `apply_rtc` + # should be `False`, otherwise the software will raise an error. + # + # The field `abs_rad_cal` provides an additional constant factor + # that can be optionally applied to RSLC + radiometric_calibration_lut: enum('disabled', 'beta0', 'sigma0', 'gamma0', required=False) + # Apply radiometric terrain correction # If `False`, the entire `rtc` group is ignored. apply_rtc: bool(required=False)