From da95af1971b8ea0069b12182f026a51e92d87be9 Mon Sep 17 00:00:00 2001 From: Xiaodong Huang Date: Tue, 19 Sep 2023 20:40:36 +0000 Subject: [PATCH 1/7] sm r3.4 --- tools/imagesets/oracle8conda/distrib_nisar/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/imagesets/oracle8conda/distrib_nisar/Dockerfile b/tools/imagesets/oracle8conda/distrib_nisar/Dockerfile index c34cb42af..45ecd19c0 100644 --- a/tools/imagesets/oracle8conda/distrib_nisar/Dockerfile +++ b/tools/imagesets/oracle8conda/distrib_nisar/Dockerfile @@ -12,7 +12,7 @@ RUN cd /opt \ && git clone https://$GIT_OAUTH_TOKEN@github-fn.jpl.nasa.gov/NISAR-ADT/SoilMoisture \ && git clone https://$GIT_OAUTH_TOKEN@github-fn.jpl.nasa.gov/NISAR-ADT/QualityAssurance \ && cd /opt/QualityAssurance && git checkout v4.0.0 && rm -rf .git \ - && cd /opt/SoilMoisture && git checkout f62fe7b47001aea2195f3c8e88d5f7d3a30e71a7 && rm -rf .git + && cd /opt/SoilMoisture && git checkout 93a364c05de2819fce0df704126320cfb5face68 && rm -rf .git FROM $distrib_img From b6cfbbb18c2c162e4f6c0e08996d3f9531d1295f Mon Sep 17 00:00:00 2001 From: Xiaodong Huang Date: Thu, 23 May 2024 21:23:36 +0000 Subject: [PATCH 2/7] change the SM commit id for R4.0.2 --- tools/imagesets/oracle8conda/distrib_nisar/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/imagesets/oracle8conda/distrib_nisar/Dockerfile b/tools/imagesets/oracle8conda/distrib_nisar/Dockerfile index 27c27c5c2..d118c6252 100644 --- a/tools/imagesets/oracle8conda/distrib_nisar/Dockerfile +++ b/tools/imagesets/oracle8conda/distrib_nisar/Dockerfile @@ -12,7 +12,7 @@ RUN cd /opt \ && git clone https://$GIT_OAUTH_TOKEN@github-fn.jpl.nasa.gov/NISAR-ADT/SoilMoisture \ && git clone https://$GIT_OAUTH_TOKEN@github-fn.jpl.nasa.gov/NISAR-ADT/QualityAssurance \ && cd /opt/QualityAssurance && git checkout v9.0.0 && rm -rf .git \ - && cd /opt/SoilMoisture && git checkout d4391e350dd8781a79b2f736ec84bc05967b38d9 && rm -rf .git + && cd /opt/SoilMoisture && git checkout 9b437761673d97004b46e22a74ee67cc1b26e280 && rm -rf .git FROM $distrib_img From d0b4e949d155c99f291111d726fa5d113ea53941 Mon Sep 17 00:00:00 2001 From: Xiaodong Huang Date: Thu, 10 Sep 2026 03:17:37 +0000 Subject: [PATCH 3/7] update the insar mask --- .../packages/nisar/workflows/geocode_insar.py | 108 +++++++++++++++--- .../workflows/geocode_insar_runconfig.py | 6 +- 2 files changed, 93 insertions(+), 21 deletions(-) diff --git a/python/packages/nisar/workflows/geocode_insar.py b/python/packages/nisar/workflows/geocode_insar.py index 6db6bc408..6e3be021e 100644 --- a/python/packages/nisar/workflows/geocode_insar.py +++ b/python/packages/nisar/workflows/geocode_insar.py @@ -119,6 +119,65 @@ def get_mask_ds_input_output(src_freq_path, dst_freq_path, input_hdf5, return input_rasters, dataset_paths +def get_valid_mask_input_output(src_freq_path, dst_freq_path, pol, input_hdf5, + input_product_type=InputProduct.RUNW): + """Create input raster objects and output dataset paths for valid masks + + Collects the validMask datasets associated with the given frequency and + polarization, and pairs each one with the HDF5 path it should be written + to in the geocoded output product. Which groups are visited depends on the + input product type: RUNW yields both the pixel offsets and interferogram + masks, while RIFG and ROFF each yield a single mask. + + Parameters + ---------- + src_freq_path : str + HDF5 path to frequency group of input dataset + dst_freq_path : str + HDF5 path to frequency group of output dataset + pol : str + Polarization of input dataset + input_hdf5 : str + Path to input RUNW, RIFG, or ROFF HDF5 + input_product_type : InputProduct + Input product type, one of RUNW, RIFG, ROFF + + Returns + ------- + input_rasters : list of isce3.io.Raster + Valid mask input raster objects + dataset_paths : list of str + HDF5 paths to the geocoded valid mask datasets, in the same order as + `input_rasters` + """ + + src_group_paths = [] + dst_group_paths = [] + + input_rasters = [] + dataset_paths = [] + + if input_product_type is InputProduct.RUNW: + src_group_paths.append(f'{src_freq_path}/pixelOffsets/{pol}') + dst_group_paths.append(f'{dst_freq_path}/pixelOffsets/{pol}') + src_group_paths.append(f'{src_freq_path}/interferogram/{pol}') + dst_group_paths.append(f'{dst_freq_path}/unwrappedInterferogram/{pol}') + elif input_product_type is InputProduct.RIFG: + src_group_paths.append(f'{src_freq_path}/interferogram/{pol}') + dst_group_paths.append(f'{dst_freq_path}/wrappedInterferogram/{pol}') + elif input_product_type is InputProduct.ROFF: + src_group_paths.append(f'{src_freq_path}/pixelOffsets/{pol}') + dst_group_paths.append(f'{dst_freq_path}/pixelOffsets/{pol}') + + # prepare input valid mask raster + for src_group_path, dst_group_path in zip(src_group_paths,dst_group_paths): + input_raster_str = f"HDF5:{input_hdf5}:/{src_group_path}/validMask" + input_raster = isce3.io.Raster(input_raster_str) + input_rasters.append(input_raster) + dataset_paths.append(f"{dst_group_path}/validMask") + + return input_rasters, dataset_paths + def get_ds_input_output(src_freq_path, dst_freq_path, pol, input_hdf5, dataset_name, off_layer=None, input_product_type=InputProduct.RUNW): @@ -391,12 +450,22 @@ def get_raster_lists(all_geocoded_dataset_flags, if not all_geocoded_dataset_flags[ds_name]: continue - if ds_name == 'mask': - input_rasters, mask_out_ds_paths = \ - get_mask_ds_input_output(src_freq_path, - dst_freq_path, - input_hdf5,input_product_type, - is_runw_offset_product) + if ds_name in ['mask', 'valid_mask']: + if ds_name == 'mask': + input_rasters, mask_out_ds_paths = \ + get_mask_ds_input_output(src_freq_path, + dst_freq_path, + input_hdf5,input_product_type, + is_runw_offset_product) + if ds_name == 'valid_mask': + mask_out_ds_paths = [] + for pol in pol_list: + _input_rasters, _mask_out_ds_paths = \ + get_valid_mask_input_output(src_freq_path, dst_freq_path, pol, + input_hdf5,input_product_type) + input_rasters += _input_rasters + mask_out_ds_paths += _mask_out_ds_paths + # Prepare output raster access the HDF5 dataset for datasets to be # geocoded for path in mask_out_ds_paths: @@ -713,7 +782,7 @@ def cpu_run(cfg, input_hdf5, output_hdf5, input_product_type=InputProduct.RUNW): dem_raster, block_size, az_correction=az_correction, srg_correction=srg_correction) - desired = ["mask"] + desired = ["mask", "valid_mask"] geocode_obj.data_interpolator = 'NEAREST' cpu_geocode_rasters(geocode_obj, geo_datasets, desired, freq, pol_list, input_hdf5, dst_h5, @@ -778,7 +847,7 @@ def cpu_run(cfg, input_hdf5, output_hdf5, input_product_type=InputProduct.RUNW): az_correction=az_correction, srg_correction=srg_correction) - desired = ["mask"] + desired = ["mask","valid_mask"] geocode_obj.data_interpolator = 'NEAREST' cpu_geocode_rasters(geocode_obj, geo_datasets, desired, freq, pol_list, input_hdf5, dst_h5, radar_grid, @@ -813,7 +882,7 @@ def cpu_run(cfg, input_hdf5, output_hdf5, input_product_type=InputProduct.RUNW): az_correction=az_correction, srg_correction=srg_correction) - desired = ["mask"] + desired = ["mask", "valid_mask"] geocode_obj.data_interpolator = 'NEAREST' cpu_geocode_rasters(geocode_obj, geo_datasets, desired, freq, pol_list, input_hdf5, dst_h5, radar_grid, @@ -1087,9 +1156,10 @@ def gpu_run(cfg, input_hdf5, output_hdf5, input_product_type=InputProduct.RUNW): srg_correction=srg_correction) # Geocode subswath mask - desired_geo_dataset_names = ["mask"] - interpolation_methods = [isce3.core.DataInterpMethod.NEAREST] - invalid_values = [255] + desired_geo_dataset_names = ["mask", "valid_mask"] + interpolation_methods = [isce3.core.DataInterpMethod.NEAREST] * \ + len(desired_geo_dataset_names) + invalid_values = [255] * len(desired_geo_dataset_names) rdr_geometry = isce3.container.RadarGeometry(radar_grid, orbit, @@ -1269,9 +1339,10 @@ def gpu_run(cfg, input_hdf5, output_hdf5, input_product_type=InputProduct.RUNW): srg_correction=srg_correction) # Geocode subswath mask - desired_geo_dataset_names = ["mask"] - interpolation_methods = [isce3.core.DataInterpMethod.NEAREST] - invalid_values = [255] + desired_geo_dataset_names = ["mask", "valid_mask"] + interpolation_methods = [isce3.core.DataInterpMethod.NEAREST] * \ + len(desired_geo_dataset_names) + invalid_values = [255] * len(desired_geo_dataset_names) gpu_geocode_rasters(geocoded_dataset_flags, desired_geo_dataset_names, @@ -1316,9 +1387,10 @@ def gpu_run(cfg, input_hdf5, output_hdf5, input_product_type=InputProduct.RUNW): srg_correction=srg_correction) # Geocode subswath mask - desired_geo_dataset_names = ["mask"] - interpolation_methods = [isce3.core.DataInterpMethod.NEAREST] - invalid_values = [255] + desired_geo_dataset_names = ["mask", "valid_mask"] + interpolation_methods = [isce3.core.DataInterpMethod.NEAREST] * \ + len(desired_geo_dataset_names) + invalid_values = [255] * len(desired_geo_dataset_names) gpu_geocode_rasters(geocoded_dataset_flags, desired_geo_dataset_names, diff --git a/python/packages/nisar/workflows/geocode_insar_runconfig.py b/python/packages/nisar/workflows/geocode_insar_runconfig.py index 9de4079a8..77c00c301 100644 --- a/python/packages/nisar/workflows/geocode_insar_runconfig.py +++ b/python/packages/nisar/workflows/geocode_insar_runconfig.py @@ -32,14 +32,14 @@ def geocode_insar_cfg_check(cfg): 'ionosphere_phase_screen_uncertainty', 'unwrapped_phase', 'along_track_offset', 'slant_range_offset', 'correlation_surface_peak', - 'mask'] + 'mask','valid_mask'] goff_datasets = ['along_track_offset', 'snr', 'along_track_offset_variance', 'correlation_surface_peak', 'cross_offset_variance', 'slant_range_offset', 'slant_range_offset_variance', - 'mask'] + 'mask', 'valid_mask'] wrapped_datasets = ['coherence_magnitude', 'wrapped_interferogram', - 'mask'] + 'mask', 'valid_mask'] # insert both geocode datasets in dict keyed on datasets name geocode_datasets = {'gunw_datasets': gunw_datasets, From 12dc20b7108df240a619337f1c411358525bff9b Mon Sep 17 00:00:00 2001 From: Xiaodong Huang Date: Thu, 10 Sep 2026 03:26:47 +0000 Subject: [PATCH 4/7] update the subswath mask --- .../nisar/products/insar/GOFF_writer.py | 29 + .../nisar/products/insar/GUNW_writer.py | 49 +- .../nisar/products/insar/InSAR_L1_writer.py | 68 ++- .../nisar/products/insar/ROFF_writer.py | 13 + python/packages/nisar/products/insar/utils.py | 507 +++++++++++++++--- 5 files changed, 559 insertions(+), 107 deletions(-) diff --git a/python/packages/nisar/products/insar/GOFF_writer.py b/python/packages/nisar/products/insar/GOFF_writer.py index 9c6b033a1..d66e0101e 100644 --- a/python/packages/nisar/products/insar/GOFF_writer.py +++ b/python/packages/nisar/products/insar/GOFF_writer.py @@ -149,6 +149,35 @@ def add_grids_to_hdf5(self): self.add_list_of_layers(grids_freq_group) for pol in pol_list: + + # Create the valid mask for each polarization + pixeloffsets_pol_name = \ + f"{pixeloffsets_group_name}/{pol}" + pixeloffsets_pol_group = \ + self.require_group(pixeloffsets_pol_name) + + yds, xds = set_get_geo_info( + self, + pixeloffsets_pol_name, + goff_geogrids, + ) + self._create_2d_dataset( + pixeloffsets_pol_group, + "validMask", + goff_shape, + np.uint8, + (f"Valid mask for the {pol} layers: " + "bit 1 = reference (1=valid, 0=invalid), bit 0 = secondary (1=valid, 0=invalid)"), + Units.unitless, + grids_val, + long_name="Valid data mask", + xds=xds, + yds=yds, + fill_value=np.uint8(255), + ) + pixeloffsets_pol_group['validMask'].attrs['valid_min'] = np.uint8(0) + + # Create the offsets layers for layer in layers: pixeloffsets_pol_layer_name = \ f"{pixeloffsets_group_name}/{pol}/{layer}" diff --git a/python/packages/nisar/products/insar/GUNW_writer.py b/python/packages/nisar/products/insar/GUNW_writer.py index cbe3c837b..7938425df 100644 --- a/python/packages/nisar/products/insar/GUNW_writer.py +++ b/python/packages/nisar/products/insar/GUNW_writer.py @@ -349,23 +349,27 @@ def add_grids_to_hdf5(self): unwrapped_ds_params = [ ("coherenceMagnitude", np.float32, f"Coherence magnitude between {pol} layers", - Units.unitless), + Units.unitless, None, None), ("connectedComponents", np.uint16, f"Connected components for {pol} layer", - Units.unitless), + Units.unitless,None, None), ("ionospherePhaseScreen", np.float32, "Ionosphere phase screen", - Units.radian), + Units.radian,None, None), ("ionospherePhaseScreenUncertainty", np.float32, "Uncertainty of the ionosphere phase screen", - "radians"), + "radians",None, None), ("unwrappedPhase", np.float32, f"Unwrapped interferogram between {pol} layers", - Units.radian), + Units.radian,None, None), + ("validMask", np.uint8, + (f"Valid mask for the {pol} layers: " + "bit 1 = reference (1=valid, 0=invalid), bit 0 = secondary (1=valid, 0=invalid)"), + Units.unitless,np.uint8(255), "Valid data mask"), ] for ds_param in unwrapped_ds_params: - ds_name, ds_datatype, ds_description, ds_unit\ + ds_name, ds_datatype, ds_description, ds_unit, fill_value, long_name\ = ds_param self._create_2d_dataset( unwrapped_pol_group, @@ -377,7 +381,10 @@ def add_grids_to_hdf5(self): grids_val, xds=xds, yds=yds, + long_name=long_name, + fill_value=fill_value ) + unwrapped_pol_group['validMask'].attrs['valid_min'] = np.uint8(0) wrapped_pol_name = f"{wrapped_group_name}/{pol}" wrapped_pol_group = self.require_group(wrapped_pol_name) @@ -393,14 +400,18 @@ def add_grids_to_hdf5(self): wrapped_ds_params = [ ("coherenceMagnitude", np.float32, f"Coherence magnitude between {pol} layers", - Units.unitless), + Units.unitless, None, None), ("wrappedInterferogram", np.complex64, f"Complex wrapped interferogram between {pol} layers", - Units.unitless), + Units.unitless, None, None), + ("validMask", np.uint8, + (f"Valid mask for the {pol} layers: " + "bit 1 = reference (1=valid, 0=invalid), bit 0 = secondary (1=valid, 0=invalid)"), + Units.unitless,np.uint8(255), "Valid data mask"), ] for ds_param in wrapped_ds_params: - ds_name, ds_datatype, ds_description, ds_unit\ + ds_name, ds_datatype, ds_description, ds_unit, fill_value, long_name\ = ds_param self._create_2d_dataset( wrapped_pol_group, @@ -412,7 +423,10 @@ def add_grids_to_hdf5(self): grids_val, xds=xds, yds=yds, + long_name=long_name, + fill_value=fill_value ) + wrapped_pol_group['validMask'].attrs['valid_min'] = np.uint8(0) pixeloffsets_pol_name = f"{pixeloffsets_group_name}/{pol}" pixeloffsets_pol_group = self.require_group( @@ -430,17 +444,21 @@ def add_grids_to_hdf5(self): pixel_offsets_ds_params = [ ("alongTrackOffset", np.float32, "Along-track offset", - Units.meter), + Units.meter, None, None), ("correlationSurfacePeak", np.float32, "Normalized cross-correlation surface peak", - Units.unitless), + Units.unitless, None, None), ("slantRangeOffset", np.float32, "Slant range offset", - Units.meter), + Units.meter, None, None), + ("validMask", np.uint8, + (f"Valid mask for the {pol} layers: " + "bit 1 = reference (1=valid, 0=invalid), bit 0 = secondary (1=valid, 0=invalid)"), + Units.unitless,np.uint8(255), "Valid data mask"), ] for ds_param in pixel_offsets_ds_params: - ds_name, ds_datatype, ds_description, ds_unit\ + ds_name, ds_datatype, ds_description, ds_unit, fill_value, long_name\ = ds_param self._create_2d_dataset( pixeloffsets_pol_group, @@ -452,4 +470,7 @@ def add_grids_to_hdf5(self): grids_val, xds=xds, yds=yds, - ) \ No newline at end of file + long_name=long_name, + fill_value=fill_value + ) + pixeloffsets_pol_group['validMask'].attrs['valid_min'] = np.uint8(0) diff --git a/python/packages/nisar/products/insar/InSAR_L1_writer.py b/python/packages/nisar/products/insar/InSAR_L1_writer.py index 1d122aae2..697ad7753 100644 --- a/python/packages/nisar/products/insar/InSAR_L1_writer.py +++ b/python/packages/nisar/products/insar/InSAR_L1_writer.py @@ -17,8 +17,8 @@ from .InSAR_base_writer import InSARBaseWriter from .product_paths import L1GroupsPaths from .units import Units -from .utils import (extract_datetime_from_string, generate_dem_rdr, - generate_insar_mask, +from .utils import (extract_datetime_from_string, extract_pol_valid_mask, + generate_dem_rdr, generate_insar_mask, get_geolocation_grid_cube_obj, save_to_hdf5_ds) @@ -312,30 +312,52 @@ def _add_datasets_to_pixel_offset_group(self): pixel_offsets_ds_params = [ ( "alongTrackOffset", + np.float32, "Along-track offset", Units.meter, + None, ), ( "correlationSurfacePeak", + np.float32, "Normalized correlation surface peak", Units.unitless, + None, ), ( "slantRangeOffset", + np.float32, "Slant range offset", Units.meter, + None, + ), + ( + "slantRangeOffset", + np.float32, + "Slant range offset", + Units.meter, + None, + ), + ( + "validMask", + np.uint8, + f"Valid mask for the {pol} layers: bit 1 = reference (1=valid, 0=invalid), bit 0 = secondary (1=valid, 0=invalid)", + Units.unitless, + np.uint8(255), ), ] for pixel_offsets_ds_param in pixel_offsets_ds_params: - ds_name, ds_description, ds_unit = pixel_offsets_ds_param + ds_name, ds_type, ds_description, ds_unit, fill_value\ + = pixel_offsets_ds_param self._create_2d_dataset( offset_pol_group, ds_name, off_shape, - np.float32, + ds_type, ds_description, units=ds_unit, + fill_value=fill_value, ) def add_pixel_offsets_to_swaths_group(self): @@ -531,7 +553,7 @@ def add_pixel_offsets_to_swaths_group(self): az_idx = np.round([rslc_radar_grid.azimuth_index(az) for az in offset_zero_doppler_time]) - offset_group['mask'][...] = \ + offset_group['mask'][...], pol_valid_mask = \ generate_insar_mask(self.ref_rslc, self.sec_rslc, self.ref_h5py_file_obj, @@ -545,6 +567,21 @@ def add_pixel_offsets_to_swaths_group(self): # add the datasets to pixel offsets group self._add_datasets_to_pixel_offset_group() + # Update the validMask in the pixelOffsets groups for each polarization + for pol in pol_list: + + offset_pol_group_name = ( + f"{offset_group_name}/{pol}" + ) + offset_pol_group = self.require_group(offset_pol_group_name) + + # Extract polarization-dependent valid mask + valid_mask = extract_pol_valid_mask(pol_valid_mask, pol) + + offset_pol_group['validMask'][...] = valid_mask + offset_pol_group['validMask'].attrs['valid_min'] = np.uint8(0) + offset_pol_group['validMask'].attrs['long_name'] = to_bytes("Valid data mask") + def add_interferogram_to_swaths_group(self, is_unwrapped=False): """ Add the interferogram group to the swaths group @@ -762,7 +799,7 @@ def add_interferogram_to_swaths_group(self, is_unwrapped=False): az_idx = np.round([rslc_radar_grid.azimuth_index(az) for az in igram_zero_doppler_time]) - igram_group['mask'][...] = \ + igram_group['mask'][...], pol_valid_mask = \ generate_insar_mask(self.ref_rslc, self.sec_rslc, self.ref_h5py_file_obj, @@ -787,11 +824,20 @@ def add_interferogram_to_swaths_group(self, is_unwrapped=False): np.float32, f"Coherence magnitude between {pol} layers", Units.unitless, + None, + ), + ( + "validMask", + np.uint8, + f"Valid mask for the {pol} layers: bit 1 = reference (1=valid, 0=invalid), bit 0 = secondary (1=valid, 0=invalid)", + Units.unitless, + np.uint8(255), ), ] for igram_ds_param in igram_ds_params: - ds_name, ds_dtype, ds_description, ds_unit = igram_ds_param + ds_name, ds_dtype, ds_description, ds_unit, fill_value\ + = igram_ds_param self._create_2d_dataset( igram_pol_group, ds_name, @@ -799,7 +845,15 @@ def add_interferogram_to_swaths_group(self, is_unwrapped=False): ds_dtype, ds_description, units=ds_unit, + fill_value=fill_value ) + if ds_name == 'validMask': + # Extract polarization-dependent valid mask + valid_mask = extract_pol_valid_mask(pol_valid_mask, pol) + + igram_pol_group['validMask'][...] = valid_mask + igram_pol_group['validMask'].attrs['valid_min'] = np.uint8(0) + igram_pol_group['validMask'].attrs['long_name'] = to_bytes("Valid data mask") def add_swaths_to_hdf5(self): """ diff --git a/python/packages/nisar/products/insar/ROFF_writer.py b/python/packages/nisar/products/insar/ROFF_writer.py index da6345d83..f5cfdd9f0 100644 --- a/python/packages/nisar/products/insar/ROFF_writer.py +++ b/python/packages/nisar/products/insar/ROFF_writer.py @@ -341,6 +341,19 @@ def _add_datasets_to_pixel_offset_group(self): f"{swaths_freq_group_name}/pixelOffsets/{pol}" pixeloffsets_pol_group = \ self.require_group(offset_pol_group_name) + + self._create_2d_dataset( + pixeloffsets_pol_group, + "validMask", + off_shape, + np.uint8, + (f"Valid mask for the {pol} layers: " + "bit 1 = reference (1=valid, 0=invalid), bit 0 = secondary (1=valid, 0=invalid)"), + units=Units.unitless, + long_name="Valid data mask", + fill_value=np.uint8(255), + ) + for layer in proc_cfg["offsets_product"]: if layer.startswith("layer"): layer_group_name = f"{offset_pol_group_name}/{layer}" diff --git a/python/packages/nisar/products/insar/utils.py b/python/packages/nisar/products/insar/utils.py index 0643bde0c..90becba4a 100644 --- a/python/packages/nisar/products/insar/utils.py +++ b/python/packages/nisar/products/insar/utils.py @@ -494,6 +494,265 @@ def generate_dem_rdr(radar_grid_obj, dem_src = None +def _subswath_numbers(subswaths, + intervals, + azi_idx_arr, + rg_idx_arr): + """ + Vectorized equivalent of SubSwaths.get_sample_sub_swath over index + arrays. + + Returns 0 for out-of-swath samples, otherwise the 1-based number of + the first sub-swath whose per-line valid-sample interval + [start, end) contains the sample. An empty interval array claims + every in-bounds sample (matching the scalar API's short-circuit), + and a dataset without sub-swath information assigns 1 everywhere in + bounds. + + Parameters + ---------- + subswaths : isce3.product.SubSwaths + The subswath object of the RSLC + intervals : list of numpy.ndarray + Per-sub-swath [start, end) valid-sample interval arrays, i.e. + [subswaths.get_valid_samples_array(s) for s = 1..num_sub_swaths] + azi_idx_arr : numpy.ndarray + Integer azimuth indices + rg_idx_arr : numpy.ndarray + Integer slant range indices + + Returns + ---------- + numpy.ndarray + int64 sub-swath numbers, same shape as the index arrays + """ + in_bounds = ((azi_idx_arr >= 0) & (azi_idx_arr < subswaths.length) & + (rg_idx_arr >= 0) & (rg_idx_arr < subswaths.width)) + numbers = np.zeros(azi_idx_arr.shape, dtype=np.int64) + if not intervals: + return np.where(in_bounds, np.int64(1), numbers) + + # Clipped so the per-line gather stays legal; out-of-bounds samples + # are excluded through in_bounds + azi_gather = np.clip(azi_idx_arr, 0, subswaths.length - 1) + for number, interval in enumerate(intervals, start=1): + if interval.size == 0: + claimed = in_bounds + else: + claimed = (in_bounds & + (rg_idx_arr >= interval[azi_gather, 0]) & + (rg_idx_arr < interval[azi_gather, 1])) + unassigned = numbers == 0 + numbers[unassigned & claimed] = number + if not unassigned.any(): + break + + return numbers + + +class _RSLCInputDataExceptionMask: + """ + Sliding-window reader for an RSLC inputDataExceptionMask dataset. + + Keeps one contiguous block of lines resident, in the dataset's + native dtype, and reads a new block only when a request falls + outside it. New blocks extend from the request in the direction + the requests are moving and are snapped to the dataset's chunk + rows, so a monotonic sweep through the radar grid reads each chunk + once. + + Parameters + ---------- + dataset : h5py.Dataset or None + The inputDataExceptionMask dataset, of shape (lines, samples). + None if the RSLC has no such dataset, in which case every + request returns uint8 zeros without any I/O. + lines : int + Number of lines of the swath + samples : int + Number of samples of the swath + block_lines : int, optional + Nominal number of lines per block read. Raised to the chunk + height of the dataset if that is larger. + + Raises + ------ + ValueError + If the dataset shape differs from (lines, samples) + + Notes + ----- + Line indices are bounds-checked on every request; sample indices + are not, so negative sample indices would wrap around as in NumPy. + """ + + def __init__(self, dataset, lines, samples, block_lines=256): + self._dset = dataset + self._lines = lines + self._start = 0 + if dataset is None: + # Zero-stride view of a single zero: spans the whole grid + # without allocating it, so no request ever triggers a read + self._block = np.broadcast_to(np.uint8(0), (lines, samples)) + return + if dataset.shape != (lines, samples): + raise ValueError( + f"inputDataExceptionMask shape {dataset.shape} differs " + f"from the swath shape {(lines, samples)}") + self._chunk_lines = dataset.chunks[0] if dataset.chunks else 1 + self._block_lines = max(block_lines, self._chunk_lines) + self._block = np.empty((0, samples), dtype=dataset.dtype) + + def _ensure(self, lo, hi): + """ + Make lines lo..hi (inclusive) resident. + + Parameters + ---------- + lo : int + First line requested + hi : int + Last line requested, hi >= lo + + Raises + ------ + IndexError + If the line range is not within [0, lines) + """ + if lo < 0 or hi >= self._lines: + raise IndexError( + f"lines {lo}..{hi} outside the radar grid " + f"[0, {self._lines})") + end = self._start + len(self._block) + if self._start <= lo and hi < end: + return + if lo >= end: + # Moving forward: read ahead of the request + start = lo + elif hi < self._start: + # Moving backward: read behind the request + start = min(lo, hi + 1 - self._block_lines) + else: + # Request straddles the block: center on it + start = lo - max(0, self._block_lines - (hi - lo + 1)) // 2 + chunk = self._chunk_lines + start = max(0, start) // chunk * chunk + stop = max(hi + 1, start + self._block_lines) + stop = min(self._lines, -(-stop // chunk) * chunk) + self._block = self._dset[start:stop] + self._start = start + + def row(self, i, rg_idx): + """ + Values of one line at the given samples. + + Parameters + ---------- + i : int + Line index + rg_idx : numpy.ndarray + 1-D integer sample indices, all in bounds + + Returns + ------- + numpy.ndarray + Values in the dataset's native dtype, same shape as rg_idx + + Raises + ------ + IndexError + If i is not within [0, lines) + """ + self._ensure(i, i) + return self._block[i - self._start, rg_idx] + + def gather(self, az_idx, rg_idx, valid): + """ + Values at scattered (line, sample) positions. + + Parameters + ---------- + az_idx : numpy.ndarray + 1-D integer line indices + rg_idx : numpy.ndarray + 1-D integer sample indices, same shape as az_idx + valid : numpy.ndarray + 1-D boolean array, same shape as az_idx. Indices only need + to be in bounds where it is True. + + Returns + ------- + numpy.ndarray + Values in the dataset's native dtype where valid is True + and 0 elsewhere, same shape as az_idx + + Raises + ------ + IndexError + If any valid line index is not within [0, lines) + """ + out = np.zeros(az_idx.shape, dtype=self._block.dtype) + sel = np.flatnonzero(valid) + if sel.size: + az = az_idx[sel] + self._ensure(int(az.min()), int(az.max())) + out[sel] = self._block[az - self._start, rg_idx[sel]] + return out + + +def _subswath_numbers(subswaths, intervals, azi_idx, rg_idx): + """ + Vectorized equivalent of SubSwaths.get_sample_sub_swath. + + Returns 0 for out-of-swath samples, otherwise the 1-based number of + the first sub-swath whose per-line valid-sample interval + [start, end) contains the sample. An empty interval array claims + every in-bounds sample (matching the scalar API's short-circuit), + and a dataset without sub-swath information assigns 1 everywhere in + bounds. + + Parameters + ---------- + subswaths : isce3.product.SubSwaths + The sub-swaths object of the RSLC + intervals : list of numpy.ndarray + Per-sub-swath [start, end) valid-sample interval arrays, i.e. + [subswaths.get_valid_samples_array(s) for s = 1..num_sub_swaths] + azi_idx : int or numpy.ndarray + Integer azimuth indices, broadcastable against rg_idx + rg_idx : int or numpy.ndarray + Integer slant range indices, broadcastable against azi_idx + + Returns + ------- + numpy.ndarray + np.byte sub-swath numbers, of the broadcast shape of the indices + """ + in_bounds = ((azi_idx >= 0) & (azi_idx < subswaths.length) & + (rg_idx >= 0) & (rg_idx < subswaths.width)) + numbers = np.zeros_like(in_bounds, dtype=np.uint8) + if not intervals: + return np.where(in_bounds, np.uint8(1), numbers) + + # Clipped so the per-line gather stays legal; out-of-bounds samples + # are excluded through in_bounds + azi_gather = np.clip(azi_idx, 0, subswaths.length - 1) + remaining = in_bounds + for number, interval in enumerate(intervals, start=1): + if interval.size == 0: + claimed = remaining + else: + claimed = (remaining & + (rg_idx >= interval[azi_gather, 0]) & + (rg_idx < interval[azi_gather, 1])) + numbers[claimed] = number + remaining = remaining & ~claimed + if not remaining.any(): + break + + return numbers + + def generate_insar_mask(ref_rslc_obj, sec_rslc_obj, ref_rslc_h5_obj, @@ -503,114 +762,190 @@ def generate_insar_mask(ref_rslc_obj, freq, azi_idx_arr, rg_idx_arr): - """ - Generate the InSAR 2d array mask + Generate the InSAR mask on a grid of reference radar-grid indices. + + Each mask value is a uint32 packing: + + - bits 0-7: 10 * reference sub-swath number + secondary sub-swath + number, where 0 means the sample is outside that RSLC's swath + - bits 8-15: low 8 bits of the secondary inputDataExceptionMask + - bits 16-23: low 8 bits of the reference inputDataExceptionMask + + The geometric coregistration offsets are read at the truncated + reference indices of each output pixel and the secondary position + is rounded to the nearest secondary sample. Output pixels outside + the reference radar grid are 0. Parameters - --------- + ---------- ref_rslc_obj : SLC The SLC object for the reference RSLC sec_rslc_obj : SLC The SLC object for the secondary RSLC + ref_rslc_h5_obj : h5py.File + The opened HDF5 file of the reference RSLC + sec_rslc_h5_obj : h5py.File + The opened HDF5 file of the secondary RSLC range_offset_path : str - The path of the range offset product from geo2rdr + The path of the range offset raster from geo2rdr, on the + reference radar grid azimuth_offset_path : str - The path of the azimuth offset product from the geo2r + The path of the azimuth offset raster from geo2rdr, on the + reference radar grid freq : str The swath frequency ('A' or 'B') - azi_idx_arr : np.ndarray - The index array along the azimuth direction - rg_idx_arr : np.ndarray - The index array along the range direction + azi_idx_arr : numpy.ndarray + 1-D azimuth indices of the output rows in the reference radar + grid; may be fractional or outside the grid + rg_idx_arr : numpy.ndarray + 1-D slant range indices of the output columns in the reference + radar grid; may be fractional or outside the grid Returns - ---------- + ------- numpy.ndarray - mask at a given frequency + uint32 mask of shape (len(azi_idx_arr), len(rg_idx_arr)) """ - # Reference and Secondary RSLC files + # Reference and secondary RSLC swaths ref_swath = ref_rslc_obj.getSwathMetadata(freq) sec_swath = sec_rslc_obj.getSwathMetadata(freq) - ref_subswaths = ref_rslc_obj.getSwathMetadata(freq).sub_swaths() - sec_subswaths = sec_rslc_obj.getSwathMetadata(freq).sub_swaths() - - # Read the range and azimuth offsets products + ref_subswaths = ref_swath.sub_swaths() + sec_subswaths = sec_swath.sub_swaths() + + # Fetch each sub-swath's per-line valid-sample interval array once + # (1-based API); the per-sample sub-swath tests then run as numpy + # array operations instead of scalar SubSwaths.get_sample_sub_swath + # calls per output pixel + ref_intervals = [ref_subswaths.get_valid_samples_array(s) + for s in range(1, ref_subswaths.num_sub_swaths + 1)] + sec_intervals = [sec_subswaths.get_valid_samples_array(s) + for s in range(1, sec_subswaths.num_sub_swaths + 1)] + + # Range and azimuth offset rasters, read one line at a time in the + # loop below (the datasets are kept alive while the bands are used) src_range_offset = gdal.Open(range_offset_path) src_azimuth_offset = gdal.Open(azimuth_offset_path) - range_offset_band = src_range_offset.GetRasterBand(1) azimuth_offset_band = src_azimuth_offset.GetRasterBand(1) - # Load the input data exception mask - input_exception_mask_path = \ - lambda swath: f"{swath}/frequency{freq}/inputDataExceptionMask" - def _load_exception_mask(h5_obj, rslc_obj, swath): - path = input_exception_mask_path(rslc_obj.SwathPath) - return h5_obj[path][()].astype(np.uint8) if path in h5_obj \ - else np.zeros((swath.lines, swath.samples), dtype=np.uint8) - - ref_input_exception_mask = _load_exception_mask(ref_rslc_h5_obj, - ref_rslc_obj, - ref_swath) - sec_input_exception_mask = _load_exception_mask(sec_rslc_h5_obj, - sec_rslc_obj, - sec_swath) - - mask = [] - for i in azi_idx_arr: - # Check if the azimuth index is within the radar grid - if i >= 0 and i < ref_swath.lines: - range_off = \ - range_offset_band.ReadAsArray(0, - int(i), - ref_swath.samples, - 1) - azimuth_off = \ - azimuth_offset_band.ReadAsArray(0, - int(i), - ref_swath.samples, - 1) - for j in rg_idx_arr: - - # Initialize the all mask ids to be 0 - mask_id = 0 - subswath_mask_id = 0 - ref_input_exception_mask_id = 0 - sec_input_exception_mask_id = 0 - - # Check if the range index is within the swath - if j >= 0 and j < ref_swath.samples: - subswath_mask_id = _compute_subswath_mask_id(int(i),int(j), - azimuth_off[0,int(j)], - range_off[0,int(j)], - ref_subswaths, - sec_subswaths) - - # reference RSLC input exception mask id - ref_input_exception_mask_id = ref_input_exception_mask[int(i),int(j)] << 16 - - # secondary RSLC input exception mask id - sec_i = round(i + azimuth_off[0,int(j)]) - sec_j = round(j + range_off[0,int(j)]) - if ((sec_i >=0 and sec_i < sec_swath.lines) and - (sec_j >=0 and sec_j < sec_swath.samples)): - sec_input_exception_mask_id = sec_input_exception_mask[sec_i,sec_j] << 8 - - # mask id - mask_id = subswath_mask_id | ref_input_exception_mask_id | sec_input_exception_mask_id - - # append the mask id - mask.append(mask_id) - - # The azimuth index is not in the radar grid meaning no subswath mask - else: - mask += [0] * len(rg_idx_arr) + # Input data exception masks, opened but not loaded; lines are read + # in blocks as the loop below sweeps through the radar grid + def _open_exception_mask(h5_obj, rslc_obj, swath): + path = f"{rslc_obj.SwathPath}/frequency{freq}/inputDataExceptionMask" + return _RSLCInputDataExceptionMask( + h5_obj.get(path), swath.lines, swath.samples) + + ref_exception_mask = _open_exception_mask(ref_rslc_h5_obj, + ref_rslc_obj, + ref_swath) + sec_exception_mask = _open_exception_mask(sec_rslc_h5_obj, + sec_rslc_obj, + sec_swath) + + # Integer reference indices of the output columns: int() truncates + # toward zero, as does astype + rg_idx_int = rg_idx_arr.astype(np.int64) + col_out_of_swath = (rg_idx_arr < 0) | (rg_idx_arr >= ref_swath.samples) + # Clipped copy so the per-line gathers stay legal; out-of-swath + # columns are zeroed at the end of each row + rg_gather = np.clip(rg_idx_int, 0, ref_swath.samples - 1) + + mask = np.zeros((len(azi_idx_arr), len(rg_idx_arr)), dtype=np.uint32) + # Polarization dependent valid mask + pol_valid_mask = np.zeros((len(azi_idx_arr), len(rg_idx_arr)), dtype=np.uint16) + + for row, i in enumerate(azi_idx_arr): + # Rows outside the reference radar grid stay 0 + if not (0 <= i < ref_swath.lines): + continue + + # Geometric coregistration offsets at the truncated reference + # indices of the output pixels + i_int = int(i) + rg_off = range_offset_band.ReadAsArray( + 0, i_int, ref_swath.samples, 1)[0][rg_gather] + az_off = azimuth_offset_band.ReadAsArray( + 0, i_int, ref_swath.samples, 1)[0][rg_gather] + + # Sub-swath numbers of the reference RSLC and, at the nearest + # secondary sample (int(x + 0.5) of the scalar code, i.e. + # truncation toward zero), of the secondary RSLC + ref_num = _subswath_numbers(ref_subswaths, ref_intervals, + i_int, rg_idx_int) + sec_num = _subswath_numbers( + sec_subswaths, sec_intervals, + np.trunc(i_int + az_off + 0.5).astype(np.int64), + np.trunc(rg_idx_int + rg_off + 0.5).astype(np.int64)) + mask_row = (10 * ref_num + sec_num).astype(np.uint32) + + # Reference RSLC input exception mask bits: keep the low 8 bits, + # then widen to uint32 before the shift so the packing is safe + # under NEP 50 scalar promotion as well + ref_exception_mask_row = ref_exception_mask.row(i_int, rg_gather) + mask_row |= (ref_exception_mask_row + .astype(np.uint8).astype(np.uint32) << 16) + + # polarization dependent mask for the reference RSLC + pol_mask_row = ref_exception_mask_row & np.uint16(0xFF00) + + # Secondary RSLC input exception mask bits at the nearest + # secondary sample (round() of the scalar code, i.e. half to + # even, as np.rint); out-of-swath samples are zeroed by gather() + sec_i = np.rint(i + az_off).astype(np.int64) + sec_j = np.rint(rg_idx_arr + rg_off).astype(np.int64) + sec_in_swath = ((sec_i >= 0) & (sec_i < sec_swath.lines) & + (sec_j >= 0) & (sec_j < sec_swath.samples)) + + sec_exception_mask_row = sec_exception_mask.gather(sec_i, sec_j, sec_in_swath) + mask_row |= (sec_exception_mask_row + .astype(np.uint8).astype(np.uint32) << 8) + + # polarization dependent mask combing with the secondary RSLC + pol_mask_row |= (sec_exception_mask_row & np.uint16(0xFF00)) >> 8 + + mask_row[col_out_of_swath] = 0 + pol_mask_row[col_out_of_swath] = 0 + + mask[row] = mask_row + pol_valid_mask[row] = pol_mask_row + + return mask, pol_valid_mask + + +def extract_pol_valid_mask(pol_valid_mask, pol): + """ + Extract polarization-dependent valid mask from the combined mask. + + Creates a binary mask where bit 1 indicates reference polarization validity + and bit 0 indicates secondary polarization validity. + + Parameters + ---------- + pol_valid_mask : numpy.ndarray + uint16 mask array where bits 8-15 are for reference polarization + and bits 0-7 are for secondary polarization. Each bit corresponds + to a polarization: HH(0), HV(1), VH(2), VV(3), LH(4), LV(5), RH(6), RV(7) + pol : str + Polarization identifier (e.g., 'HH', 'HV', 'VH', 'VV', 'LH', 'LV', 'RH', 'RV') + + Returns + ------- + numpy.ndarray + uint8 array where bit 1 = reference valid (1=valid, 0=invalid) + and bit 0 = secondary valid (1=valid, 0=invalid) + """ + # Map polarization to bit position (0-7) based on the standard order + pol_to_bit = {'HH': 0, 'HV': 1, 'VH': 2, 'VV': 3, + 'LH': 4, 'LV': 5, 'RH': 6, 'RV': 7} + bit_pos = pol_to_bit.get(pol, 0) + + # Extract reference (high byte) and secondary (low byte) bits + ref_valid = (pol_valid_mask >> (bit_pos + 8)) & 1 + sec_valid = (pol_valid_mask >> bit_pos) & 1 - del ref_input_exception_mask - del sec_input_exception_mask + # Create binary mask: bit 1 = reference, bit 0 = secondary + valid_mask = ((ref_valid << 1) | sec_valid).astype(np.uint8) - return np.array(mask).reshape( - (len(azi_idx_arr), - len(rg_idx_arr))).astype(np.uint32) \ No newline at end of file + return valid_mask \ No newline at end of file From 24aedb8ba77f3b6f15ab4c4eab2d107b8c5f22e3 Mon Sep 17 00:00:00 2001 From: Xiaodong Huang Date: Thu, 10 Sep 2026 17:29:41 +0000 Subject: [PATCH 5/7] accommodate the old RSLC --- python/packages/nisar/products/insar/utils.py | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/python/packages/nisar/products/insar/utils.py b/python/packages/nisar/products/insar/utils.py index 90becba4a..e94ed4c2d 100644 --- a/python/packages/nisar/products/insar/utils.py +++ b/python/packages/nisar/products/insar/utils.py @@ -887,8 +887,14 @@ def _open_exception_mask(h5_obj, rslc_obj, swath): mask_row |= (ref_exception_mask_row .astype(np.uint8).astype(np.uint32) << 16) - # polarization dependent mask for the reference RSLC - pol_mask_row = ref_exception_mask_row & np.uint16(0xFF00) + # To accommodate the old RSLC with uint8 inputDataExceptionMask, + # and the valid polarization dependent mask will use the + # subswath mask + if ref_exception_mask._dset.dtype == np.dtype('uint8'): + pol_mask_row = (ref_num > 0).astype(np.uint16) << 8 + else: + # polarization dependent mask for the reference RSLC + pol_mask_row = ref_exception_mask_row & np.uint16(0xFF00) # Secondary RSLC input exception mask bits at the nearest # secondary sample (round() of the scalar code, i.e. half to @@ -902,8 +908,14 @@ def _open_exception_mask(h5_obj, rslc_obj, swath): mask_row |= (sec_exception_mask_row .astype(np.uint8).astype(np.uint32) << 8) - # polarization dependent mask combing with the secondary RSLC - pol_mask_row |= (sec_exception_mask_row & np.uint16(0xFF00)) >> 8 + # To accommodate the old RSLC with uint8 inputDataExceptionMask, + # and the valid polarization dependent mask will use the + # subswath mask + if sec_exception_mask._dset.dtype == np.dtype('uint8'): + pol_mask_row |= (sec_num > 0).astype(np.uint16) + else: + # polarization dependent mask combing with the secondary RSLC + pol_mask_row |= (sec_exception_mask_row & np.uint16(0xFF00)) >> 8 mask_row[col_out_of_swath] = 0 pol_mask_row[col_out_of_swath] = 0 From 61005be8035ecfd1c50769bde58bc5ae20d8ef80 Mon Sep 17 00:00:00 2001 From: Xiaodong Huang Date: Thu, 10 Sep 2026 18:53:18 +0000 Subject: [PATCH 6/7] fix the insar unit test failures --- python/packages/nisar/products/insar/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/packages/nisar/products/insar/utils.py b/python/packages/nisar/products/insar/utils.py index e94ed4c2d..6e05499b4 100644 --- a/python/packages/nisar/products/insar/utils.py +++ b/python/packages/nisar/products/insar/utils.py @@ -889,8 +889,8 @@ def _open_exception_mask(h5_obj, rslc_obj, swath): # To accommodate the old RSLC with uint8 inputDataExceptionMask, # and the valid polarization dependent mask will use the - # subswath mask - if ref_exception_mask._dset.dtype == np.dtype('uint8'): + # subswath mask. + if ref_exception_mask._block.dtype == np.dtype('uint8'): pol_mask_row = (ref_num > 0).astype(np.uint16) << 8 else: # polarization dependent mask for the reference RSLC @@ -911,7 +911,7 @@ def _open_exception_mask(h5_obj, rslc_obj, swath): # To accommodate the old RSLC with uint8 inputDataExceptionMask, # and the valid polarization dependent mask will use the # subswath mask - if sec_exception_mask._dset.dtype == np.dtype('uint8'): + if sec_exception_mask._block.dtype == np.dtype('uint8'): pol_mask_row |= (sec_num > 0).astype(np.uint16) else: # polarization dependent mask combing with the secondary RSLC From 0c2ff0af5e03e7560bc5aaffda72c1de35a7a2f6 Mon Sep 17 00:00:00 2001 From: Xiaodong Huang Date: Fri, 11 Sep 2026 17:35:38 +0000 Subject: [PATCH 7/7] fix the geocode_insar for the pixelOffsets in the RUNW product --- .../packages/nisar/workflows/geocode_insar.py | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/python/packages/nisar/workflows/geocode_insar.py b/python/packages/nisar/workflows/geocode_insar.py index 6e3be021e..4fb6e8513 100644 --- a/python/packages/nisar/workflows/geocode_insar.py +++ b/python/packages/nisar/workflows/geocode_insar.py @@ -82,7 +82,7 @@ def get_mask_ds_input_output(src_freq_path, dst_freq_path, input_hdf5, input_product_type: enum Input product type, which is one of RUNW, ROFF, RIFG is_runw_offset_product : bool - Is THE pixel offset products of the RUNW product + Is the pixel offset products of the RUNW product Returns ------- input_raster : isce3.io.Raster @@ -120,7 +120,8 @@ def get_mask_ds_input_output(src_freq_path, dst_freq_path, input_hdf5, return input_rasters, dataset_paths def get_valid_mask_input_output(src_freq_path, dst_freq_path, pol, input_hdf5, - input_product_type=InputProduct.RUNW): + input_product_type=InputProduct.RUNW, + is_runw_offset_product = False): """Create input raster objects and output dataset paths for valid masks Collects the validMask datasets associated with the given frequency and @@ -141,7 +142,8 @@ def get_valid_mask_input_output(src_freq_path, dst_freq_path, pol, input_hdf5, Path to input RUNW, RIFG, or ROFF HDF5 input_product_type : InputProduct Input product type, one of RUNW, RIFG, ROFF - + is_runw_offset_product : bool + Is the pixel offset products of the RUNW product Returns ------- input_rasters : list of isce3.io.Raster @@ -158,10 +160,12 @@ def get_valid_mask_input_output(src_freq_path, dst_freq_path, pol, input_hdf5, dataset_paths = [] if input_product_type is InputProduct.RUNW: - src_group_paths.append(f'{src_freq_path}/pixelOffsets/{pol}') - dst_group_paths.append(f'{dst_freq_path}/pixelOffsets/{pol}') - src_group_paths.append(f'{src_freq_path}/interferogram/{pol}') - dst_group_paths.append(f'{dst_freq_path}/unwrappedInterferogram/{pol}') + if is_runw_offset_product: + src_group_paths.append(f'{src_freq_path}/pixelOffsets/{pol}') + dst_group_paths.append(f'{dst_freq_path}/pixelOffsets/{pol}') + else: + src_group_paths.append(f'{src_freq_path}/interferogram/{pol}') + dst_group_paths.append(f'{dst_freq_path}/unwrappedInterferogram/{pol}') elif input_product_type is InputProduct.RIFG: src_group_paths.append(f'{src_freq_path}/interferogram/{pol}') dst_group_paths.append(f'{dst_freq_path}/wrappedInterferogram/{pol}') @@ -462,7 +466,8 @@ def get_raster_lists(all_geocoded_dataset_flags, for pol in pol_list: _input_rasters, _mask_out_ds_paths = \ get_valid_mask_input_output(src_freq_path, dst_freq_path, pol, - input_hdf5,input_product_type) + input_hdf5,input_product_type, + is_runw_offset_product) input_rasters += _input_rasters mask_out_ds_paths += _mask_out_ds_paths @@ -804,7 +809,7 @@ def cpu_run(cfg, input_hdf5, output_hdf5, input_product_type=InputProduct.RUNW): block_size, az_correction=az_correction, srg_correction=srg_correction) - desired = ["mask"] + desired = ["mask", "valid_mask"] geocode_obj.data_interpolator = 'NEAREST' cpu_geocode_rasters(geocode_obj, geo_datasets, desired, freq, pol_list, input_hdf5, dst_h5, @@ -1271,9 +1276,10 @@ def gpu_run(cfg, input_hdf5, output_hdf5, input_product_type=InputProduct.RUNW): srg_correction=srg_correction) # Geocode subswath mask - desired_geo_dataset_names = ["mask"] - interpolation_methods = [isce3.core.DataInterpMethod.NEAREST] - invalid_values = [255] + desired_geo_dataset_names = ["mask", "valid_mask"] + interpolation_methods = [isce3.core.DataInterpMethod.NEAREST] * \ + len(desired_geo_dataset_names) + invalid_values = [255] * len(desired_geo_dataset_names) rdr_geometry = isce3.container.RadarGeometry(radar_grid, orbit,