From d1c4d7f93fe3f9ec11bf11e32259ea02b21a0dcd Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Thu, 3 Jul 2025 11:30:42 -0700 Subject: [PATCH 01/14] disable polarimetric symmetrization by default --- share/nisar/defaults/gcov.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/nisar/defaults/gcov.yaml b/share/nisar/defaults/gcov.yaml index db0c71352..3b9da8a89 100644 --- a/share/nisar/defaults/gcov.yaml +++ b/share/nisar/defaults/gcov.yaml @@ -177,7 +177,7 @@ runconfig: # HV and VH), otherwise, the flag is ignored. # If enabled, the output product's "HV" dataset will contain symmetrized # HV/VH data and the "VH" dataset will be omitted from the output. - symmetrize_cross_pol_channels: True + symmetrize_cross_pol_channels: False # TODO OPTIONAL - Only checked when internet access is available dem_download: From 2ac26942f19a6ff2e3145a96ca52044d09f867ea Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Tue, 22 Jul 2025 13:58:03 -0700 Subject: [PATCH 02/14] revert changes to `symmetrize_cross_pol_channels` --- share/nisar/defaults/gcov.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/nisar/defaults/gcov.yaml b/share/nisar/defaults/gcov.yaml index 3b9da8a89..db0c71352 100644 --- a/share/nisar/defaults/gcov.yaml +++ b/share/nisar/defaults/gcov.yaml @@ -177,7 +177,7 @@ runconfig: # HV and VH), otherwise, the flag is ignored. # If enabled, the output product's "HV" dataset will contain symmetrized # HV/VH data and the "VH" dataset will be omitted from the output. - symmetrize_cross_pol_channels: False + symmetrize_cross_pol_channels: True # TODO OPTIONAL - Only checked when internet access is available dem_download: From 05d7fda0ff21704541179bfc5b0fb5415ebae1b6 Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Tue, 22 Jul 2025 16:01:58 -0700 Subject: [PATCH 03/14] Update GCOV and GSLC specification XMLs --- .../nisar/products/XML/L2/nisar_L2_GCOV.xml | 291 ++++++++++------ .../nisar/products/XML/L2/nisar_L2_GSLC.xml | 321 +++++++++++------- 2 files changed, 371 insertions(+), 241 deletions(-) diff --git a/python/packages/nisar/products/XML/L2/nisar_L2_GCOV.xml b/python/packages/nisar/products/XML/L2/nisar_L2_GCOV.xml index 021b48b6a..a9e931bdb 100644 --- a/python/packages/nisar/products/XML/L2/nisar_L2_GCOV.xml +++ b/python/packages/nisar/products/XML/L2/nisar_L2_GCOV.xml @@ -105,7 +105,7 @@ + shape="numberOfObservations"> List of planned datatakes included in the product @@ -127,13 +127,19 @@ List of frequency layers available in the product - + List of each input raw dataset's observation mode + + - Indicates if the radar operation mode is a diagnostic mode (1-2) or DBFed science (0): 0, 1, or 2 - + Indicates if the radar operation mode is a diagnostic mode (1-2) or DBFed science (0): 0, 1, or 2 + @@ -182,7 +188,7 @@ name="/science/LSAR/identification/processingType" shape="scalar"> Nominal (or) Urgent (or) Custom (or) Undefined + lang="en">Processing pipeline used to generate this granule. "Nominal": standard production system; "Urgent": time-sensitive processing in response to urgent response events; "Custom": user-initiated processing outside the nominal production system X coordinates in specified projection Y coordinates in specified projection @@ -329,7 +337,7 @@ valid_min="0" _FillValue="nan" grid_mapping="projection" - units="1">Radiometric terrain correction factor to normalize GCOV terms from gamma0 to sigma0 + units="1">Radiometric terrain correction (RTC) scaling factor to normalize backscatter coefficients from gamma0 to sigma0, accounting for local terrain Mask indicating the subswath number associated with valid GCOV samples. A GCOV sample is only considered valid if it is generated from fully-focused radar samples. If at least one radar sample in the averaging set is partially focused or invalid, the corresponding mask pixel will contain the value `0`. GCOV pixels outside of the radar acquisition extent are filled with the value `255` + grid_mapping="projection">Mask indicating the subswath number associated with valid GCOV samples. A GCOV sample is only considered valid if it is generated from fully-focused radar samples. If at least one radar sample in the averaging set is partially focused or invalid, the corresponding mask pixel will contain the value 0. GCOV pixels outside of the radar acquisition extent are filled with the value 255 X coordinates in specified projection Y coordinates in specified projection @@ -706,7 +716,7 @@ valid_min="0" _FillValue="nan" grid_mapping="projection" - units="1">Radiometric terrain correction factor to normalize GCOV terms from gamma0 to sigma0 + units="1">Radiometric terrain correction (RTC) scaling factor to normalize backscatter coefficients from gamma0 to sigma0, accounting for local terrain Mask indicating the subswath number associated with valid GCOV samples. A GCOV sample is only considered valid if it is generated from fully-focused radar samples. If at least one radar sample in the averaging set is partially focused or invalid, the corresponding mask pixel will contain the value `0`. GCOV pixels outside of the radar acquisition extent are filled with the value `255` + grid_mapping="projection">Mask indicating the subswath number associated with valid GCOV samples. A GCOV sample is only considered valid if it is generated from fully-focused radar samples. If at least one radar sample in the averaging set is partially focused or invalid, the corresponding mask pixel will contain the value 0. GCOV pixels outside of the radar acquisition extent are filled with the value 255 East component of unit vector of LOS from target to sensor + units="1">East component of the line-of-sight (LOS) unit vector, defined from the target to the sensor, expressed in the east-north-up (ENU) coordinate system with its origin at the target location North component of unit vector of LOS from target to sensor + units="1">North component of the line-of-sight (LOS) unit vector, defined from the target to the sensor, expressed in the east-north-up (ENU) coordinate system with its origin at the target location East component of unit vector along ground track + units="1">East component of the along-track unit vector at the target location, expressed in the east-north-up (ENU) coordinate system and projected onto the horizontal plane (i.e., excluding the up component) North component of unit vector along ground track + units="1">North component of the along-track unit vector at the target location, expressed in the east-north-up (ENU) coordinate system and projected onto the horizontal plane (i.e., excluding the up component) X coordinates in specified projection X coordinates in specified projection - - + + Product map grid projection: EPSG code, with additional projection information as HDF5 Attributes + + + Y coordinates in specified projection + + + X coordinates in specified projection + Crosstalk in H-transmit channel expressed as ratio txV / txH Crosstalk in V-transmit channel expressed as ratio txH / txV Crosstalk in H-receive channel expressed as ratio rxV / rxH Crosstalk in V-receive channel expressed as ratio rxH / rxV + + @@ -2437,14 +2519,14 @@ Reference Terrain Height as a function of map coordinates + units="meters">Reference terrain height as a function of map coordinates scalar values - - - number of datatakes in product - - - number of observations in product + lang="en">Number of observations in product @@ -3372,28 +3445,22 @@ Shape of calibration LUTs + lang="en">Shape of real-valued calibration LUTs - Shape of antenna pattern datasets + lang="en">Shape of complex-valued calibration LUTs - - Shape of crosstalk datasets - - + shape="numberOfObservations"> List of planned datatakes included in the product @@ -127,13 +127,19 @@ List of frequency layers available in the product - + List of each input raw dataset's observation mode + + - Indicates if the radar operation mode is a diagnostic mode (1-2) or DBFed science (0): 0, 1, or 2 - + Indicates if the radar operation mode is a diagnostic mode (1-2) or DBFed science (0): 0, 1, or 2 + @@ -182,7 +188,7 @@ name="/science/LSAR/identification/processingType" shape="scalar"> Nominal (or) Urgent (or) Custom (or) Undefined + lang="en">Processing pipeline used to generate this granule. "Nominal": standard production system; "Urgent": time-sensitive processing in response to urgent response events; "Custom": user-initiated processing outside the nominal production system Mask indicating the subswath number representing valid GSLC samples. Each GSLC pixel is assumed valid if all the pixels in the interpolation window are fully focused in the input RSLC. A value of `0` indicates that at least one RSLC pixel in the interpolation window is partially focused or invalid. Pixels outside of the radar acquisition extent are filled with the value `255`. + _FillValue="255">Mask indicating the subswath number representing valid GSLC samples. Each GSLC pixel is assumed valid if all the pixels in the interpolation window are fully focused in the input RSLC. A value of 0 indicates that at least one RSLC pixel in the interpolation window is partially focused or invalid. Pixels outside of the radar acquisition extent are filled with the value 255. Focused SLC image (HH) @@ -373,7 +379,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image HV" - grid_mapping='projection' + grid_mapping="projection" DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (HV) @@ -393,7 +399,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image VH" - grid_mapping='projection' + grid_mapping="projection" DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (VH) @@ -413,7 +419,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image VV" - grid_mapping='projection' + grid_mapping="projection" DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (VV) @@ -433,7 +439,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image RH" - grid_mapping='projection' + grid_mapping="projection" DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (RH) @@ -453,7 +459,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image RV" - grid_mapping='projection' + grid_mapping="projection" DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (RV) @@ -538,14 +544,14 @@ Mask indicating the subswath number representing valid GSLC samples. Each GSLC pixel is assumed valid if all the pixels in the interpolation window are fully focused in the input RSLC. A value of `0` indicates that at least one RSLC pixel in the interpolation window is partially focused or invalid. Pixels outside of the radar acquisition extent are filled with the value `255`. + _FillValue="255">Mask indicating the subswath number representing valid GSLC samples. Each GSLC pixel is assumed valid if all the pixels in the interpolation window are fully focused in the input RSLC. A value of 0 indicates that at least one RSLC pixel in the interpolation window is partially focused or invalid. Pixels outside of the radar acquisition extent are filled with the value 255. Focused SLC image (HH) @@ -626,7 +632,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image HV" - grid_mapping='projection' + grid_mapping="projection" DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (HV) @@ -646,7 +652,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image VH" - grid_mapping='projection' + grid_mapping="projection" DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (VH) @@ -666,7 +672,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image VV" - grid_mapping='projection' + grid_mapping="projection" DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (VV) @@ -686,7 +692,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image RH" - grid_mapping='projection' + grid_mapping="projection" DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (RH) @@ -706,7 +712,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image RVH" - grid_mapping='projection' + grid_mapping="projection" DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (RV) @@ -832,7 +838,7 @@ _FillValue="nan" grid_mapping="projection" long_name="LOS unit vector X" - units="1">East component of unit vector of LOS from target to sensor + units="1">East component of the line-of-sight (LOS) unit vector, defined from the target to the sensor, expressed in the east-north-up (ENU) coordinate system with its origin at the target location North component of unit vector of LOS from target to sensor + units="1">North component of the line-of-sight (LOS) unit vector, defined from the target to the sensor, expressed in the east-north-up (ENU) coordinate system with its origin at the target location East component of unit vector along ground track + units="1">East component of the along-track unit vector at the target location, expressed in the east-north-up (ENU) coordinate system and projected onto the horizontal plane (i.e., excluding the up component) North component of unit vector along ground track + units="1">North component of the along-track unit vector at the target location, expressed in the east-north-up (ENU) coordinate system and projected onto the horizontal plane (i.e., excluding the up component) X coordinates in specified projection X coordinates in specified projection - - + + Product map grid projection: EPSG code, with additional projection information as HDF5 Attributes + + + Y coordinates in specified projection + + + X coordinates in specified projection + Crosstalk in H-transmit channel expressed as ratio txV / txH Crosstalk in V-transmit channel expressed as ratio txH / txV Crosstalk in H-receive channel expressed as ratio rxV / rxH Crosstalk in V-receive channel expressed as ratio rxH / rxV + + @@ -2096,7 +2174,7 @@ lang="en" _FillValue="nan" grid_mapping="projection" - units="meters">Reference Terrain Height as a function of geographical location + units="meters">Reference terrain height as a function of map coordinates scalar values - - - number of datatakes in product - - - number of observations in product + lang="en">Number of observations in product @@ -3116,28 +3185,22 @@ Shape of calibration LUTs + lang="en">Shape of real-valued calibration LUTs - Shape of antenna pattern datasets + lang="en">Shape of complex-valued calibration LUTs - - Shape of crosstalk datasets - - Number of input L1 SLC granules + name="numberOfInputL1Files"/> From 749058db5115479f727ca055d828370ecd1ec07e Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Wed, 23 Jul 2025 11:45:11 -0700 Subject: [PATCH 04/14] Revert changes to the GCOV and GSLC specification XMLs --- .../nisar/products/XML/L2/nisar_L2_GCOV.xml | 291 ++++++---------- .../nisar/products/XML/L2/nisar_L2_GSLC.xml | 321 +++++++----------- 2 files changed, 241 insertions(+), 371 deletions(-) diff --git a/python/packages/nisar/products/XML/L2/nisar_L2_GCOV.xml b/python/packages/nisar/products/XML/L2/nisar_L2_GCOV.xml index a9e931bdb..021b48b6a 100644 --- a/python/packages/nisar/products/XML/L2/nisar_L2_GCOV.xml +++ b/python/packages/nisar/products/XML/L2/nisar_L2_GCOV.xml @@ -105,7 +105,7 @@ + shape="numberOfDatatakes"> List of planned datatakes included in the product @@ -127,19 +127,13 @@ List of frequency layers available in the product - - List of each input raw dataset's observation mode - - - Indicates if the radar operation mode is a diagnostic mode (1-2) or DBFed science (0): 0, 1, or 2 - + Indicates if the radar operation mode is a diagnostic mode (1-2) or DBFed science (0): 0, 1, or 2 + @@ -188,7 +182,7 @@ name="/science/LSAR/identification/processingType" shape="scalar"> Processing pipeline used to generate this granule. "Nominal": standard production system; "Urgent": time-sensitive processing in response to urgent response events; "Custom": user-initiated processing outside the nominal production system + lang="en">Nominal (or) Urgent (or) Custom (or) Undefined X coordinates in specified projection Y coordinates in specified projection @@ -337,7 +329,7 @@ valid_min="0" _FillValue="nan" grid_mapping="projection" - units="1">Radiometric terrain correction (RTC) scaling factor to normalize backscatter coefficients from gamma0 to sigma0, accounting for local terrain + units="1">Radiometric terrain correction factor to normalize GCOV terms from gamma0 to sigma0 Mask indicating the subswath number associated with valid GCOV samples. A GCOV sample is only considered valid if it is generated from fully-focused radar samples. If at least one radar sample in the averaging set is partially focused or invalid, the corresponding mask pixel will contain the value 0. GCOV pixels outside of the radar acquisition extent are filled with the value 255 + grid_mapping="projection">Mask indicating the subswath number associated with valid GCOV samples. A GCOV sample is only considered valid if it is generated from fully-focused radar samples. If at least one radar sample in the averaging set is partially focused or invalid, the corresponding mask pixel will contain the value `0`. GCOV pixels outside of the radar acquisition extent are filled with the value `255` X coordinates in specified projection Y coordinates in specified projection @@ -716,7 +706,7 @@ valid_min="0" _FillValue="nan" grid_mapping="projection" - units="1">Radiometric terrain correction (RTC) scaling factor to normalize backscatter coefficients from gamma0 to sigma0, accounting for local terrain + units="1">Radiometric terrain correction factor to normalize GCOV terms from gamma0 to sigma0 Mask indicating the subswath number associated with valid GCOV samples. A GCOV sample is only considered valid if it is generated from fully-focused radar samples. If at least one radar sample in the averaging set is partially focused or invalid, the corresponding mask pixel will contain the value 0. GCOV pixels outside of the radar acquisition extent are filled with the value 255 + grid_mapping="projection">Mask indicating the subswath number associated with valid GCOV samples. A GCOV sample is only considered valid if it is generated from fully-focused radar samples. If at least one radar sample in the averaging set is partially focused or invalid, the corresponding mask pixel will contain the value `0`. GCOV pixels outside of the radar acquisition extent are filled with the value `255` East component of the line-of-sight (LOS) unit vector, defined from the target to the sensor, expressed in the east-north-up (ENU) coordinate system with its origin at the target location + units="1">East component of unit vector of LOS from target to sensor North component of the line-of-sight (LOS) unit vector, defined from the target to the sensor, expressed in the east-north-up (ENU) coordinate system with its origin at the target location + units="1">North component of unit vector of LOS from target to sensor East component of the along-track unit vector at the target location, expressed in the east-north-up (ENU) coordinate system and projected onto the horizontal plane (i.e., excluding the up component) + units="1">East component of unit vector along ground track North component of the along-track unit vector at the target location, expressed in the east-north-up (ENU) coordinate system and projected onto the horizontal plane (i.e., excluding the up component) + units="1">North component of unit vector along ground track X coordinates in specified projection X coordinates in specified projection + + - - Product map grid projection: EPSG code, with additional projection information as HDF5 Attributes - - - Y coordinates in specified projection - - - X coordinates in specified projection - Crosstalk in H-transmit channel expressed as ratio txV / txH Crosstalk in V-transmit channel expressed as ratio txH / txV Crosstalk in H-receive channel expressed as ratio rxV / rxH Crosstalk in V-receive channel expressed as ratio rxH / rxV - - @@ -2519,14 +2437,14 @@ Reference terrain height as a function of map coordinates + units="meters">Reference Terrain Height as a function of map coordinates scalar values + + + number of datatakes in product + + + Number of observations in product + lang="en">number of observations in product @@ -3445,22 +3372,28 @@ Shape of real-valued calibration LUTs + lang="en">Shape of calibration LUTs - Shape of complex-valued calibration LUTs + lang="en">Shape of antenna pattern datasets + + Shape of crosstalk datasets + + + shape="numberOfDatatakes"> List of planned datatakes included in the product @@ -127,19 +127,13 @@ List of frequency layers available in the product - - List of each input raw dataset's observation mode - - - Indicates if the radar operation mode is a diagnostic mode (1-2) or DBFed science (0): 0, 1, or 2 - + Indicates if the radar operation mode is a diagnostic mode (1-2) or DBFed science (0): 0, 1, or 2 + @@ -188,7 +182,7 @@ name="/science/LSAR/identification/processingType" shape="scalar"> Processing pipeline used to generate this granule. "Nominal": standard production system; "Urgent": time-sensitive processing in response to urgent response events; "Custom": user-initiated processing outside the nominal production system + lang="en">Nominal (or) Urgent (or) Custom (or) Undefined Mask indicating the subswath number representing valid GSLC samples. Each GSLC pixel is assumed valid if all the pixels in the interpolation window are fully focused in the input RSLC. A value of 0 indicates that at least one RSLC pixel in the interpolation window is partially focused or invalid. Pixels outside of the radar acquisition extent are filled with the value 255. + _FillValue="255">Mask indicating the subswath number representing valid GSLC samples. Each GSLC pixel is assumed valid if all the pixels in the interpolation window are fully focused in the input RSLC. A value of `0` indicates that at least one RSLC pixel in the interpolation window is partially focused or invalid. Pixels outside of the radar acquisition extent are filled with the value `255`. Focused SLC image (HH) @@ -379,7 +373,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image HV" - grid_mapping="projection" + grid_mapping='projection' DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (HV) @@ -399,7 +393,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image VH" - grid_mapping="projection" + grid_mapping='projection' DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (VH) @@ -419,7 +413,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image VV" - grid_mapping="projection" + grid_mapping='projection' DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (VV) @@ -439,7 +433,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image RH" - grid_mapping="projection" + grid_mapping='projection' DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (RH) @@ -459,7 +453,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image RV" - grid_mapping="projection" + grid_mapping='projection' DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (RV) @@ -544,14 +538,14 @@ Mask indicating the subswath number representing valid GSLC samples. Each GSLC pixel is assumed valid if all the pixels in the interpolation window are fully focused in the input RSLC. A value of 0 indicates that at least one RSLC pixel in the interpolation window is partially focused or invalid. Pixels outside of the radar acquisition extent are filled with the value 255. + _FillValue="255">Mask indicating the subswath number representing valid GSLC samples. Each GSLC pixel is assumed valid if all the pixels in the interpolation window are fully focused in the input RSLC. A value of `0` indicates that at least one RSLC pixel in the interpolation window is partially focused or invalid. Pixels outside of the radar acquisition extent are filled with the value `255`. Focused SLC image (HH) @@ -632,7 +626,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image HV" - grid_mapping="projection" + grid_mapping='projection' DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (HV) @@ -652,7 +646,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image VH" - grid_mapping="projection" + grid_mapping='projection' DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (VH) @@ -672,7 +666,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image VV" - grid_mapping="projection" + grid_mapping='projection' DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (VV) @@ -692,7 +686,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image RH" - grid_mapping="projection" + grid_mapping='projection' DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (RH) @@ -712,7 +706,7 @@ mean_real_value="Arithmetic average of the real part of the numeric data points" sample_stddev_real="Standard deviation of the real part of the numeric data points" long_name="Geocoded single-look complex image RVH" - grid_mapping="projection" + grid_mapping='projection' DIMENSION_LIST="HDF5 internal attribute" _FillValue="(nan+nan*j)" units="1">Focused SLC image (RV) @@ -838,7 +832,7 @@ _FillValue="nan" grid_mapping="projection" long_name="LOS unit vector X" - units="1">East component of the line-of-sight (LOS) unit vector, defined from the target to the sensor, expressed in the east-north-up (ENU) coordinate system with its origin at the target location + units="1">East component of unit vector of LOS from target to sensor North component of the line-of-sight (LOS) unit vector, defined from the target to the sensor, expressed in the east-north-up (ENU) coordinate system with its origin at the target location + units="1">North component of unit vector of LOS from target to sensor East component of the along-track unit vector at the target location, expressed in the east-north-up (ENU) coordinate system and projected onto the horizontal plane (i.e., excluding the up component) + units="1">East component of unit vector along ground track North component of the along-track unit vector at the target location, expressed in the east-north-up (ENU) coordinate system and projected onto the horizontal plane (i.e., excluding the up component) + units="1">North component of unit vector along ground track X coordinates in specified projection X coordinates in specified projection + + - - Product map grid projection: EPSG code, with additional projection information as HDF5 Attributes - - - Y coordinates in specified projection - - - X coordinates in specified projection - Crosstalk in H-transmit channel expressed as ratio txV / txH Crosstalk in V-transmit channel expressed as ratio txH / txV Crosstalk in H-receive channel expressed as ratio rxV / rxH Crosstalk in V-receive channel expressed as ratio rxH / rxV - - @@ -2174,7 +2096,7 @@ lang="en" _FillValue="nan" grid_mapping="projection" - units="meters">Reference terrain height as a function of map coordinates + units="meters">Reference Terrain Height as a function of geographical location scalar values + + + number of datatakes in product + + + Number of observations in product + lang="en">number of observations in product @@ -3185,22 +3116,28 @@ Shape of real-valued calibration LUTs + lang="en">Shape of calibration LUTs - Shape of complex-valued calibration LUTs + lang="en">Shape of antenna pattern datasets + + Shape of crosstalk datasets + + Number of input L1 SLC granules + name="numberOfInputL0BFiles"/> From fa38bc003b79929a469fe53d612a60ac513f821c Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Mon, 6 Oct 2025 15:29:03 -0700 Subject: [PATCH 05/14] Make the radar grid azimuth and range spacing parameters configurable in the static layers runconfig --- python/packages/nisar/workflows/static.py | 210 +++++++++++++--------- share/nisar/defaults/static.yaml | 10 ++ share/nisar/schemas/static.yaml | 2 + 3 files changed, 141 insertions(+), 81 deletions(-) diff --git a/python/packages/nisar/workflows/static.py b/python/packages/nisar/workflows/static.py index e7165519b..736659497 100644 --- a/python/packages/nisar/workflows/static.py +++ b/python/packages/nisar/workflows/static.py @@ -15,7 +15,8 @@ from nisar.static.geo_grid import get_output_geo_grid from nisar.static.geometry_layers import compute_geometry_layers from nisar.static.granule_id import form_granule_id -from nisar.static.layover_shadow_mask import compute_geocoded_layover_shadow_mask +from nisar.static.layover_shadow_mask import \ + compute_geocoded_layover_shadow_mask from nisar.static.logging import get_logger, log_elapsed_time from nisar.static.product import ( build_hdf5_dataset_creation_kwds_dict, @@ -25,7 +26,8 @@ ) from nisar.static.rtc_anf_layers import compute_rtc_anf_layers from nisar.static.runconfig import get_runconfig_params -from nisar.static.util import get_raster_dataset_metadata_item, scratch_directory +from nisar.static.util import get_raster_dataset_metadata_item, \ + scratch_directory from nisar.static.water_mask import binarize_and_reproject_water_mask import isce3 @@ -34,7 +36,8 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: """ - Run the NISAR Static Layers workflow with the specified run configuration file. + Run the NISAR Static Layers workflow with the specified run configuration + file. Will generate a single STATIC HDF5 granule, as specified in the runconfig. @@ -65,51 +68,75 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: # Construct a DEM interpolator. dem_interp_method = processing_params["dem"]["interp_method"] - dem = isce3.geometry.DEMInterpolator(dem_raster) - dem.interp_method = dem_interp_method # Construct the output geocoded coordinate grid. geo_grid_params = processing_params["geo_grid"] geo_grid = get_output_geo_grid(dem_raster=dem_raster, **geo_grid_params) logger.info(f"Output geo grid: {geo_grid}") - # Parse the orbit and attitude data from the input XML files. Crop the data to the - # time interval of interest to avoid possible geo2rdr convergence errors due to - # ambiguity between orbit periods. + # Parse the orbit and attitude data from the input XML files. Crop the + # data to the time interval of interest to avoid possible geo2rdr + # convergence errors due to ambiguity between orbit periods. orbit, attitude = get_cropped_orbit_and_attitude( orbit_xml_file=dynamic_ancillary_files["orbit_xml_file"], pointing_xml_file=dynamic_ancillary_files["pointing_xml_file"], **processing_params["ephemeris"], ) - # Get the Doppler centroid associated with the radar grid. NISAR image grids are - # always zero-Doppler. + # Get the Doppler centroid associated with the radar grid. NISAR image + # grids are always zero-Doppler. img_grid_doppler = isce3.core.LUT2d() - # Estimate the required radar grid spacing necessary to avoid undersampling the - # output geocoded grid. + # Estimate the required radar grid spacing necessary to avoid + # undersampling the output geocoded grid. # XXX: We deliberately don't pass geo2rdr parameters to either - # `infer_radar_grid_spacing_from_geo_grid()` or `get_bounding_radar_grid()` because - # these functions use `geo2rdr_bracket`, which takes different parameters than the - # legacy `geo2rdr` routine that's used by most of the workflow. Exposing both sets - # of parameters would introduce a lot of additional bookkeeping for seemingly little - # benefit. + # `infer_radar_grid_spacing_from_geo_grid()` or `get_bounding_radar_grid()` + # because these functions use `geo2rdr_bracket`, which takes different + # parameters than the legacy `geo2rdr` routine that's used by most of the + # workflow. Exposing both sets of parameters would introduce a lot of + # additional bookkeeping for seemingly little benefit. logger.info("Estimate maximum required radar grid spacing") radar_grid_params = processing_params["radar_grid"] look_side = radar_grid_params["look_side"] wavelength = radar_grid_params["wavelength"] - az_spacing, rg_spacing = isce3.geometry.infer_radar_grid_spacing_from_geo_grid( - geo_grid=geo_grid, - dem=dem, - orbit=orbit, - doppler=img_grid_doppler, - look_side=look_side, - wavelength=wavelength, - **radar_grid_params["spacing"], - ) - # Compute a radar grid whose footprint on the ground encloses the geocoded grid on - # which each output layer is defined. + radar_grid_spacing_params = radar_grid_params["spacing"] + az_spacing = radar_grid_spacing_params["az_spacing"] + rg_spacing = radar_grid_spacing_params["rg_spacing"] + + logger.info(f'az_spacing from runconfig: {az_spacing}') + logger.info(f'rg_spacing from runconfig: {rg_spacing}') + az_spacing_inferred, rg_spacing_inferred = \ + isce3.geometry.infer_radar_grid_spacing_from_geo_grid( + geo_grid=geo_grid, + dem=dem, + orbit=orbit, + doppler=img_grid_doppler, + look_side=look_side, + wavelength=wavelength, + **radar_grid_params["spacing"], + ) + logger.info(f'az_spacing from inferred: {az_spacing_inferred}') + logger.info(f'rg_spacing from inferred: {rg_spacing_inferred}') + + if rg_spacing is None or az_spacing is None: + az_spacing_inferred, rg_spacing_inferred = \ + isce3.geometry.infer_radar_grid_spacing_from_geo_grid( + geo_grid=geo_grid, + dem=dem, + orbit=orbit, + doppler=img_grid_doppler, + look_side=look_side, + wavelength=wavelength, + **radar_grid_params["spacing"], + ) + if rg_spacing is None: + rg_spacing = rg_spacing_inferred + if az_spacing is None: + az_spacing = az_spacing_inferred + + # Compute a radar grid whose footprint on the ground encloses the geocoded + # grid on which each output layer is defined. logger.info("Compute a radar grid spanning the region of interest") radar_grid = isce3.geometry.get_bounding_radar_grid( geo_grid=geo_grid, @@ -133,14 +160,15 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: **processing_params["doppler"], ) - # Create a (possibly temporary) scratch directory to store intermediate files. + # Create a (possibly temporary) scratch directory to store intermediate + # files. logger.info("Create scratch directory") with scratch_directory( product_paths["scratch_dir"], delete=product_paths["delete_scratch_dir"] ) as scratch_dir: - # Compute static geometry layers (height above ellipsoid, line-of-sight X and Y, - # local incidence angle). Results are stored as GeoTIFF files in the scratch - # directory. + # Compute static geometry layers (height above ellipsoid, + # line-of-sight X and Y, local incidence angle). Results are stored as + # GeoTIFF files in the scratch directory. logger.info("Compute static geometry layers") geo2rdr_params = processing_params["geo2rdr"] with log_elapsed_time(logger.info, "Computing static geometry layers"): @@ -155,13 +183,16 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: dem_interp_method=dem_interp_method, geo2rdr_params=geo2rdr_params, ) - reprojected_dem, los_east, los_north, local_inc_angle = geometry_layers + reprojected_dem, los_east, los_north, local_inc_angle = \ + geometry_layers - # Compute static mask layers (geocoded layover/shadow mask and water mask). + # Compute static mask layers (geocoded layover/shadow mask and water + # mask). # Results are stored as GeoTIFF files in the scratch directory. logger.info("Compute geocoded layover/shadow mask layer") geocode_params = processing_params["geocode"] - with log_elapsed_time(logger.info, "Computing geocoded layover/shadow mask"): + with log_elapsed_time(logger.info, + "Computing geocoded layover/shadow mask"): layover_shadow_mask = compute_geocoded_layover_shadow_mask( radar_grid=radar_grid, orbit=orbit, @@ -179,7 +210,8 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: ) logger.info("Compute re-projected binary water mask layer") - with log_elapsed_time(logger.info, "Computing re-projected binary water mask"): + with log_elapsed_time(logger.info, + "Computing re-projected binary water mask"): binary_water_mask = binarize_and_reproject_water_mask( water_distance_raster_file=water_mask_raster_file, geo_grid=geo_grid, @@ -187,43 +219,48 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: **processing_params["water_mask"], ) - # Compute radiometric terrain correction (RTC) area normalization factor (ANF) - # layers. Results are stored as GeoTIFF files in the scratch directory. + # Compute radiometric terrain correction (RTC) area normalization + # factor (ANF) layers. Results are stored as GeoTIFF files in the + # scratch directory. logger.info("Compute RTC area normalization factor layers") rtc_params = processing_params["rtc"] - with log_elapsed_time(logger.info, "Computing RTC area normalization layers"): - gamma0_to_beta0_factor, gamma0_to_sigma0_factor = compute_rtc_anf_layers( - radar_grid=radar_grid, - orbit=orbit, - native_doppler=native_doppler, - img_grid_doppler=img_grid_doppler, - geo_grid=geo_grid, - dem_raster=dem_raster, - scratch_dir=scratch_dir, - dem_interp_method=dem_interp_method, - geo2rdr_params=geo2rdr_params, - **geocode_params, - **rtc_params, - ) + with log_elapsed_time(logger.info, + "Computing RTC area normalization layers"): + gamma0_to_beta0_factor, gamma0_to_sigma0_factor = \ + compute_rtc_anf_layers( + radar_grid=radar_grid, + orbit=orbit, + native_doppler=native_doppler, + img_grid_doppler=img_grid_doppler, + geo_grid=geo_grid, + dem_raster=dem_raster, + scratch_dir=scratch_dir, + dem_interp_method=dem_interp_method, + geo2rdr_params=geo2rdr_params, + **geocode_params, + **rtc_params, + ) # Infer the orbit pass direction from the orbit velocity vectors. orbit_pass_direction = isce3.core.get_orbit_pass_direction(orbit) - # Pop 'product_counter' from the dict. This parameter is used to form the - # granule ID but doesn't correspond to any dataset in the 'identification' group - # of the product. The other dict contents will be passed as keyword arguments to - # `populate_identification_group()` below. + # Pop 'product_counter' from the dict. This parameter is used to form + # the granule ID but doesn't correspond to any dataset in the + # 'identification' group of the product. The other dict contents will + # be passed as keyword arguments to `populate_identification_group()` + # below. product_counter = primary_executable_params.pop("product_counter") # Get `validity_start_datetime` from the input parameters as a - # `datetime.datetime` object. If it was passed as a non-quoted string in ISO - # 8601 format, `ruamel.yaml` will have already converted it. Otherwise, manually - # convert it here. + # `datetime.datetime` object. If it was passed as a non-quoted string + # in ISO 8601 format, `ruamel.yaml` will have already converted it. + # Otherwise, manually convert it here. validity_start_datetime = primary_executable_params.pop( "validity_start_datetime" ) if not isinstance(validity_start_datetime, datetime): - validity_start_datetime = datetime.fromisoformat(validity_start_datetime) + validity_start_datetime = \ + datetime.fromisoformat(validity_start_datetime) # Get the unique ID of the granule based on the input parameters. radar_band = primary_executable_params["radar_band"] @@ -237,7 +274,8 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: x_posting=abs(geo_grid.spacing_x), y_posting=abs(geo_grid.spacing_y), validity_start_datetime=validity_start_datetime, - composite_release_id=primary_executable_params["composite_release_id"], + composite_release_id=primary_executable_params[ + "composite_release_id"], processing_center=primary_executable_params["processing_center"], product_counter=product_counter, **geometry_params, @@ -262,28 +300,34 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: # Populate global attributes in the root group of the file. logger.info("Populate global HDF5 attributes") product_spec = nisar.products.get_product_spec("STATIC") - nisar.products.populate_global_attrs_from_spec(hdf5_file, product_spec) + nisar.products.populate_global_attrs_from_spec(hdf5_file, + product_spec) # Get the current processing datetime (truncated to integer seconds # precision). - processing_datetime = datetime.now(timezone.utc).replace(microsecond=0) + processing_datetime = \ + datetime.now(timezone.utc).replace(microsecond=0) - # XXX: It's not really obvious what should go in the `zeroDopplerStartTime` - # and `zeroDopplerEndTime` datasets in the 'identification' group. For now, - # we'll use the start & stop time of the radar grid, which is roughly + # XXX: It's not really obvious what should go in the + # `zeroDopplerStartTime` and `zeroDopplerEndTime` datasets in the + # 'identification' group. For now, we'll use the start & stop time + # of the radar grid, which is roughly # analogous what they represent in other NISAR L2 products. - img_grid_start_datetime = radar_grid.ref_epoch + isce3.core.TimeDelta( - radar_grid.sensing_start - ) - img_grid_end_datetime = radar_grid.ref_epoch + isce3.core.TimeDelta( - radar_grid.sensing_stop - ) + img_grid_start_datetime = (radar_grid.ref_epoch + + isce3.core.TimeDelta( + radar_grid.sensing_start)) + img_grid_end_datetime = (radar_grid.ref_epoch + + isce3.core.TimeDelta( + radar_grid.sensing_stop)) # Populate the 'identification' group. logger.info("Populate identification metadata in output HDF5 file") - instrument_group = hdf5_file.create_group(f"/science/{radar_band}SAR") - identification_group = instrument_group.create_group("identification") - bounding_polygon = make_geo_grid_bounding_polygon(geo_grid, dem=dem) + instrument_group = \ + hdf5_file.create_group(f"/science/{radar_band}SAR") + identification_group = \ + instrument_group.create_group("identification") + bounding_polygon = make_geo_grid_bounding_polygon(geo_grid, + dem=dem) populate_identification_group( identification_group=identification_group, product_spec=product_spec, @@ -311,13 +355,15 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: ) # Populate the 'grids' group. - logger.info("Populate raster layers and grid coordinates in output HDF5") + logger.info("Populate raster layers and grid coordinates in output" + " HDF5") grids_group = instrument_group.create_group("STATIC/grids") dataset_creation_kwds = build_hdf5_dataset_creation_kwds_dict( dataset_shape=(geo_grid.length, geo_grid.width), **output_params["dataset"] ) - with log_elapsed_time(logger.info, "Writing raster layers to output HDF5"): + with log_elapsed_time(logger.info, "Writing raster layers to" + " output HDF5"): populate_grids_group( grids_group=grids_group, product_spec=product_spec, @@ -360,18 +406,20 @@ def main(args: Sequence[str] | None = None) -> None: Parameters ---------- args : sequence of str or None, optional - The list of arguments. If None, the argument list is taken from `sys.argv`. - Defaults to None. + The list of arguments. If None, the argument list is taken from + `sys.argv`. Defaults to None. """ # Setup the argument parser. - parser = argparse.ArgumentParser(description="Run the NISAR Static Layers workflow") + parser = argparse.ArgumentParser( + description="Run the NISAR Static Layers workflow") parser.add_argument( "config_file", type=Path, help="Run configuration YAML file for the STATIC workflow", ) - # Parse the arguments and convert the result to a dict of keyword arguments. + # Parse the arguments and convert the result to a dict of keyword + # arguments. kwargs = vars(parser.parse_args(args)) # Run the workflow with the unpacked keyword arguments. diff --git a/share/nisar/defaults/static.yaml b/share/nisar/defaults/static.yaml index 83077376f..791536cf2 100644 --- a/share/nisar/defaults/static.yaml +++ b/share/nisar/defaults/static.yaml @@ -225,6 +225,16 @@ runconfig: # Defaults to 0.24. wavelength: 0.24 spacing: + # [OPTIONAL] Azimuth interval, in seconds. Equivalent to the Pulse + # Repetition Interval (PRI). + # If not provided, it will be inferred from the specified geographic + # grid. If provided, must be a positive value. + az_spacing: + # [OPTIONAL] Slant-range spacing, in meters, of the radar grid. + # Must be a positive value. + # If not provided, it will be inferred from the specified geographic + # grid. If provided, must be a positive value. + rg_spacing: # [OPTIONAL] Side length of the NxN grid of samples used to estimate the # required radar grid pixel spacing necessary to avoid undersampling the # output geocoded grid. diff --git a/share/nisar/schemas/static.yaml b/share/nisar/schemas/static.yaml index 7d20a5670..d0110ac4f 100644 --- a/share/nisar/schemas/static.yaml +++ b/share/nisar/schemas/static.yaml @@ -94,6 +94,8 @@ radar_grid_options: bounding_box: include('radar_grid_bounding_box_options', required=False) radar_grid_spacing_options: + az_spacing: num(min=0.0, required=False) + rg_spacing: num(min=0.0, required=False) pts_per_side: int(min=2, required=False) radar_grid_bounding_box_options: From 7b8821741bf73f56a12095138b62080ed71f72d7 Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Tue, 7 Oct 2025 08:57:04 -0700 Subject: [PATCH 06/14] Make the radar grid azimuth and range spacing parameters configurable in the static layers runconfig (2) --- python/packages/nisar/workflows/static.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/packages/nisar/workflows/static.py b/python/packages/nisar/workflows/static.py index 736659497..7cce7de60 100644 --- a/python/packages/nisar/workflows/static.py +++ b/python/packages/nisar/workflows/static.py @@ -68,6 +68,8 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: # Construct a DEM interpolator. dem_interp_method = processing_params["dem"]["interp_method"] + dem = isce3.geometry.DEMInterpolator(dem_raster) + dem.interp_method = dem_interp_method # Construct the output geocoded coordinate grid. geo_grid_params = processing_params["geo_grid"] @@ -103,6 +105,7 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: radar_grid_spacing_params = radar_grid_params["spacing"] az_spacing = radar_grid_spacing_params["az_spacing"] rg_spacing = radar_grid_spacing_params["rg_spacing"] + pts_per_side = radar_grid_spacing_params["pts_per_side"] logger.info(f'az_spacing from runconfig: {az_spacing}') logger.info(f'rg_spacing from runconfig: {rg_spacing}') @@ -115,6 +118,7 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: look_side=look_side, wavelength=wavelength, **radar_grid_params["spacing"], + pts_per_side=pts_per_side, ) logger.info(f'az_spacing from inferred: {az_spacing_inferred}') logger.info(f'rg_spacing from inferred: {rg_spacing_inferred}') From fd7453ba6203e08b8399ff8a7cc37e166add3146 Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Mon, 10 Nov 2025 15:47:38 -0800 Subject: [PATCH 07/14] Make the radar grid azimuth and range spacing parameters configurable in the static layers runconfig --- python/packages/nisar/workflows/static.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/python/packages/nisar/workflows/static.py b/python/packages/nisar/workflows/static.py index 7cce7de60..ebf58e9eb 100644 --- a/python/packages/nisar/workflows/static.py +++ b/python/packages/nisar/workflows/static.py @@ -107,22 +107,6 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: rg_spacing = radar_grid_spacing_params["rg_spacing"] pts_per_side = radar_grid_spacing_params["pts_per_side"] - logger.info(f'az_spacing from runconfig: {az_spacing}') - logger.info(f'rg_spacing from runconfig: {rg_spacing}') - az_spacing_inferred, rg_spacing_inferred = \ - isce3.geometry.infer_radar_grid_spacing_from_geo_grid( - geo_grid=geo_grid, - dem=dem, - orbit=orbit, - doppler=img_grid_doppler, - look_side=look_side, - wavelength=wavelength, - **radar_grid_params["spacing"], - pts_per_side=pts_per_side, - ) - logger.info(f'az_spacing from inferred: {az_spacing_inferred}') - logger.info(f'rg_spacing from inferred: {rg_spacing_inferred}') - if rg_spacing is None or az_spacing is None: az_spacing_inferred, rg_spacing_inferred = \ isce3.geometry.infer_radar_grid_spacing_from_geo_grid( @@ -132,7 +116,7 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: doppler=img_grid_doppler, look_side=look_side, wavelength=wavelength, - **radar_grid_params["spacing"], + pts_per_side=pts_per_side ) if rg_spacing is None: rg_spacing = rg_spacing_inferred From 4ee53ebedeb37a692847194bf67d1459f0a56a45 Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Tue, 20 Jan 2026 16:46:49 -0800 Subject: [PATCH 08/14] Expose sensing start/end times and starting/ending ranges in the STATIC workflow --- .../isce3/geometry/bounding_radar_grid.py | 5 ++ python/packages/nisar/workflows/static.py | 66 +++++++++++++++---- share/nisar/defaults/static.yaml | 24 +++++++ share/nisar/schemas/static.yaml | 4 ++ 4 files changed, 86 insertions(+), 13 deletions(-) diff --git a/python/packages/isce3/geometry/bounding_radar_grid.py b/python/packages/isce3/geometry/bounding_radar_grid.py index d583da79f..2c06b6247 100644 --- a/python/packages/isce3/geometry/bounding_radar_grid.py +++ b/python/packages/isce3/geometry/bounding_radar_grid.py @@ -303,6 +303,11 @@ def get_bounding_radar_grid( Azimuth time spacing of the output grid, in seconds. Must be > 0. rg_spacing : float Slant range spacing of the output grid, in meters. Must be > 0. + sensing_start : float + The sensing start time of the input `geo_grid`, in seconds since the epoch of + `orbit`. + starting_range : float + The starting slant range of the input `geo_grid`, in meters. orbit : isce3.core.Orbit The trajectory of the radar antenna phase center over a time interval that includes the observation times of each point in `geo_grid` at each height diff --git a/python/packages/nisar/workflows/static.py b/python/packages/nisar/workflows/static.py index ebf58e9eb..eb74fd19d 100644 --- a/python/packages/nisar/workflows/static.py +++ b/python/packages/nisar/workflows/static.py @@ -32,6 +32,8 @@ import isce3 from isce3.geometry import make_geo_grid_bounding_polygon +from isce3.core import normalize_look_side +import numpy as np def run_static_layers_workflow(config_file: os.PathLike | str) -> None: @@ -105,8 +107,15 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: radar_grid_spacing_params = radar_grid_params["spacing"] az_spacing = radar_grid_spacing_params["az_spacing"] rg_spacing = radar_grid_spacing_params["rg_spacing"] + pts_per_side = radar_grid_spacing_params["pts_per_side"] + bounding_box_params = radar_grid_params["bounding_box"] + sensing_start = bounding_box_params["sensing_start"] + sensing_end = bounding_box_params["sensing_end"] + starting_range = bounding_box_params["starting_range"] + ending_range = bounding_box_params["ending_range"] + if rg_spacing is None or az_spacing is None: az_spacing_inferred, rg_spacing_inferred = \ isce3.geometry.infer_radar_grid_spacing_from_geo_grid( @@ -123,19 +132,50 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: if az_spacing is None: az_spacing = az_spacing_inferred - # Compute a radar grid whose footprint on the ground encloses the geocoded - # grid on which each output layer is defined. - logger.info("Compute a radar grid spanning the region of interest") - radar_grid = isce3.geometry.get_bounding_radar_grid( - geo_grid=geo_grid, - az_spacing=az_spacing, - rg_spacing=rg_spacing, - orbit=orbit, - look_side=look_side, - wavelength=wavelength, - doppler=img_grid_doppler, - **radar_grid_params["bounding_box"], - ) + if (sensing_start is not None and starting_range is not None and + sensing_end is not None and ending_range is not None): + + # Use the provided bounding box parameters. + # Construct the radar grid. + # Divide `num` by `den`, rounded up to the next smallest integer. + def ceil_divide(num: float, den: float) -> int: + return int(np.ceil(num / den)) + num_az = ceil_divide(sensing_end - sensing_start, az_spacing) + 1 + num_rg = ceil_divide(ending_range - starting_range, rg_spacing) + 1 + + radar_grid = isce3.product.RadarGridParameters( + sensing_start=sensing_start, + wavelength=wavelength, + prf=1.0 / az_spacing, + starting_range=starting_range, + range_pixel_spacing=rg_spacing, + lookside=normalize_look_side(look_side), + length=num_az, + width=num_rg, + ref_epoch=orbit.reference_epoch, + ) + elif (sensing_start is not None or starting_range is not None or + sensing_end is not None or ending_range is not None): + raise ValueError( + "If specifying radar grid bounding box parameters, must provide" + " all of 'sensing_start', 'starting_range', 'sensing_end', and" + " 'ending_range'" + ) + else: + + # Compute a radar grid whose footprint on the ground encloses the geocoded + # grid on which each output layer is defined. + logger.info("Compute a radar grid spanning the region of interest") + radar_grid = isce3.geometry.get_bounding_radar_grid( + geo_grid=geo_grid, + az_spacing=az_spacing, + rg_spacing=rg_spacing, + orbit=orbit, + look_side=look_side, + wavelength=wavelength, + doppler=img_grid_doppler, + **radar_grid_params["bounding_box"], + ) logger.info(f"Using radar grid: {radar_grid}") # Get the native Doppler LUT. diff --git a/share/nisar/defaults/static.yaml b/share/nisar/defaults/static.yaml index 791536cf2..028e1e073 100644 --- a/share/nisar/defaults/static.yaml +++ b/share/nisar/defaults/static.yaml @@ -242,6 +242,30 @@ runconfig: # Defaults to 5. pts_per_side: 5 bounding_box: + # [OPTIONAL] Azimuth start time, in seconds since the epoch, of the first row of + # the radar grid. + # If not provided, it will be inferred from the specified geographic grid. + # If provided, must be a valid azimuth time within the observation time + # interval. + sensing_start: + + # [OPTIONAL] Azimuth ending time, in seconds since the epoch, of the last row of + # the radar grid. + # If not provided, it will be inferred from the specified geographic grid. + # If provided, must be a valid azimuth time within the observation time + # interval. + sensing_end: + + # [OPTIONAL] Starting range, in meters, of the first column of the radar grid. + # If not provided, it will be inferred from the specified geographic grid. + # If provided, must be a positive value. + starting_range: + + # [OPTIONAL] Ending range, in meters, of the last column of the radar grid. + # If not provided, it will be inferred from the specified geographic grid. + # If provided, must be a positive value. + ending_range: + # [OPTIONAL] Lower bound on the height of targets within the region of interest, # in meters above the reference ellipsoid of the output product. # Used to estimate the bounds of a radar grid that spans the region of interest. diff --git a/share/nisar/schemas/static.yaml b/share/nisar/schemas/static.yaml index d0110ac4f..08401be1d 100644 --- a/share/nisar/schemas/static.yaml +++ b/share/nisar/schemas/static.yaml @@ -99,6 +99,10 @@ radar_grid_spacing_options: pts_per_side: int(min=2, required=False) radar_grid_bounding_box_options: + sensing_start: num(required=False) + sensing_end: num(required=False) + starting_range: num(required=False) + ending_range: num(required=False) min_height: num(required=False) max_height: num(required=False) pts_per_edge: int(min=2, required=False) From 31a98f0e626a2f95253199623a3cbcb0ce7984ef Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Wed, 21 Jan 2026 11:42:43 -0800 Subject: [PATCH 09/14] Expose sensing start/end times and starting/ending ranges in the STATIC workflow --- python/packages/nisar/workflows/static.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/python/packages/nisar/workflows/static.py b/python/packages/nisar/workflows/static.py index eb74fd19d..3c081dafb 100644 --- a/python/packages/nisar/workflows/static.py +++ b/python/packages/nisar/workflows/static.py @@ -115,6 +115,11 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: sensing_end = bounding_box_params["sensing_end"] starting_range = bounding_box_params["starting_range"] ending_range = bounding_box_params["ending_range"] + min_height = bounding_box_params["min_height"] + max_height = bounding_box_params["max_height"] + pts_per_edge = bounding_box_params["pts_per_edge"] + az_margin = bounding_box_params["az_margin"] + rg_margin = bounding_box_params["rg_margin"] if rg_spacing is None or az_spacing is None: az_spacing_inferred, rg_spacing_inferred = \ @@ -174,7 +179,11 @@ def ceil_divide(num: float, den: float) -> int: look_side=look_side, wavelength=wavelength, doppler=img_grid_doppler, - **radar_grid_params["bounding_box"], + min_height=min_height, + max_height=max_height, + pts_per_edge=pts_per_edge, + az_margin=az_margin, + rg_margin=rg_margin ) logger.info(f"Using radar grid: {radar_grid}") From 78a018dbd38d374fb7c8a75c5ad257cda045712a Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Mon, 26 Jan 2026 10:10:10 -0800 Subject: [PATCH 10/14] rename some radar grid parameters to match focus --- python/packages/nisar/workflows/static.py | 52 ++++++++++++++--------- share/nisar/defaults/static.yaml | 8 ++-- share/nisar/schemas/static.yaml | 8 ++-- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/python/packages/nisar/workflows/static.py b/python/packages/nisar/workflows/static.py index 3c081dafb..77c59d722 100644 --- a/python/packages/nisar/workflows/static.py +++ b/python/packages/nisar/workflows/static.py @@ -7,6 +7,7 @@ from collections.abc import Sequence from datetime import datetime, timezone from pathlib import Path +from xmlrpc.client import DateTime import h5py import nisar @@ -111,10 +112,10 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: pts_per_side = radar_grid_spacing_params["pts_per_side"] bounding_box_params = radar_grid_params["bounding_box"] - sensing_start = bounding_box_params["sensing_start"] - sensing_end = bounding_box_params["sensing_end"] - starting_range = bounding_box_params["starting_range"] - ending_range = bounding_box_params["ending_range"] + start_time = bounding_box_params["start_time"] + end_time = bounding_box_params["end_time"] + start_range = bounding_box_params["start_range"] + end_range = bounding_box_params["end_range"] min_height = bounding_box_params["min_height"] max_height = bounding_box_params["max_height"] pts_per_edge = bounding_box_params["pts_per_edge"] @@ -137,34 +138,45 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: if az_spacing is None: az_spacing = az_spacing_inferred - if (sensing_start is not None and starting_range is not None and - sensing_end is not None and ending_range is not None): + if (start_time is not None and start_range is not None and + end_time is not None and end_range is not None): - # Use the provided bounding box parameters. - # Construct the radar grid. - # Divide `num` by `den`, rounded up to the next smallest integer. - def ceil_divide(num: float, den: float) -> int: - return int(np.ceil(num / den)) - num_az = ceil_divide(sensing_end - sensing_start, az_spacing) + 1 - num_rg = ceil_divide(ending_range - starting_range, rg_spacing) + 1 + epoch = orbit.reference_epoch + + t0 = (isce3.core.DateTime(start_time) - epoch).total_seconds() + tf = (isce3.core.DateTime(end_time) - epoch).total_seconds() + + num_az = round((tf - t0) / az_spacing) + num_rg = round((end_range - start_range) / rg_spacing) + + logger.info("Using user-specified radar grid bounding box parameters") + logger.info(f' start time: {start_time}') + logger.info(f' end time: {end_time}') + logger.info(f' start range: {start_range}') + logger.info(f' end range: {end_range}') + logger.info(f' az spacing: {az_spacing}') + logger.info(f' rg spacing: {rg_spacing}') + logger.info(f' number of lines: {num_az}') + logger.info(f' number of range samples: {num_rg}') radar_grid = isce3.product.RadarGridParameters( - sensing_start=sensing_start, + sensing_start=t0, wavelength=wavelength, prf=1.0 / az_spacing, - starting_range=starting_range, + starting_range=start_range, range_pixel_spacing=rg_spacing, lookside=normalize_look_side(look_side), length=num_az, width=num_rg, - ref_epoch=orbit.reference_epoch, + ref_epoch=epoch, ) - elif (sensing_start is not None or starting_range is not None or - sensing_end is not None or ending_range is not None): + + elif (start_time is not None or start_range is not None or + end_time is not None or end_range is not None): raise ValueError( "If specifying radar grid bounding box parameters, must provide" - " all of 'sensing_start', 'starting_range', 'sensing_end', and" - " 'ending_range'" + " all of 'start_time', 'start_range', 'end_time', and" + " 'end_range'" ) else: diff --git a/share/nisar/defaults/static.yaml b/share/nisar/defaults/static.yaml index 028e1e073..b59066874 100644 --- a/share/nisar/defaults/static.yaml +++ b/share/nisar/defaults/static.yaml @@ -247,24 +247,24 @@ runconfig: # If not provided, it will be inferred from the specified geographic grid. # If provided, must be a valid azimuth time within the observation time # interval. - sensing_start: + start_time: # [OPTIONAL] Azimuth ending time, in seconds since the epoch, of the last row of # the radar grid. # If not provided, it will be inferred from the specified geographic grid. # If provided, must be a valid azimuth time within the observation time # interval. - sensing_end: + end_time: # [OPTIONAL] Starting range, in meters, of the first column of the radar grid. # If not provided, it will be inferred from the specified geographic grid. # If provided, must be a positive value. - starting_range: + start_range: # [OPTIONAL] Ending range, in meters, of the last column of the radar grid. # If not provided, it will be inferred from the specified geographic grid. # If provided, must be a positive value. - ending_range: + end_range: # [OPTIONAL] Lower bound on the height of targets within the region of interest, # in meters above the reference ellipsoid of the output product. diff --git a/share/nisar/schemas/static.yaml b/share/nisar/schemas/static.yaml index 08401be1d..30baebdc9 100644 --- a/share/nisar/schemas/static.yaml +++ b/share/nisar/schemas/static.yaml @@ -99,10 +99,10 @@ radar_grid_spacing_options: pts_per_side: int(min=2, required=False) radar_grid_bounding_box_options: - sensing_start: num(required=False) - sensing_end: num(required=False) - starting_range: num(required=False) - ending_range: num(required=False) + start_time: timestamp() + end_time: timestamp() + start_range: num(required=False) + end_range: num(required=False) min_height: num(required=False) max_height: num(required=False) pts_per_edge: int(min=2, required=False) From 9db1f5a740af278ad4049cdb7151f988b227b5ac Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Mon, 26 Jan 2026 11:11:01 -0800 Subject: [PATCH 11/14] rename some radar grid parameters to match focus (2) --- share/nisar/schemas/static.yaml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/share/nisar/schemas/static.yaml b/share/nisar/schemas/static.yaml index 30baebdc9..d6d8853f9 100644 --- a/share/nisar/schemas/static.yaml +++ b/share/nisar/schemas/static.yaml @@ -99,8 +99,16 @@ radar_grid_spacing_options: pts_per_side: int(min=2, required=False) radar_grid_bounding_box_options: - start_time: timestamp() - end_time: timestamp() + start_time: any( + timestamp(), + regex(r'^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}:\d{2})?$'), + required=False + ) + end_time: any( + timestamp(), + regex(r'^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}:\d{2})?$'), + required=False + ) start_range: num(required=False) end_range: num(required=False) min_height: num(required=False) From c168e7fb0e4683a0869a48e6f942aa37f4c9c0ca Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Thu, 29 Jan 2026 14:01:24 -0800 Subject: [PATCH 12/14] add margin to user-specified radar-grid extents, if provided --- python/packages/nisar/workflows/static.py | 44 ++++++++++++++++++----- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/python/packages/nisar/workflows/static.py b/python/packages/nisar/workflows/static.py index 77c59d722..9265eb7a3 100644 --- a/python/packages/nisar/workflows/static.py +++ b/python/packages/nisar/workflows/static.py @@ -100,7 +100,6 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: # parameters than the legacy `geo2rdr` routine that's used by most of the # workflow. Exposing both sets of parameters would introduce a lot of # additional bookkeeping for seemingly little benefit. - logger.info("Estimate maximum required radar grid spacing") radar_grid_params = processing_params["radar_grid"] look_side = radar_grid_params["look_side"] wavelength = radar_grid_params["wavelength"] @@ -122,6 +121,40 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: az_margin = bounding_box_params["az_margin"] rg_margin = bounding_box_params["rg_margin"] + # Print user radar grid bounding box parameters, if provided. + if (start_time is not None or start_range is not None or + end_time is not None or end_range is not None): + + logger.info("Using user-specified radar grid bounding box parameters") + if start_time is not None: + logger.info(f' start time: {start_time}') + if az_margin != 0.0: + start_time -= isce3.core.TimeDelta(az_margin) + logger.info(f' adjusted for az margin {az_margin}:' + f' {start_time}') + if end_time is not None: + logger.info(f' end time: {end_time}') + if az_margin != 0.0: + end_time += isce3.core.TimeDelta(az_margin) + logger.info(f' adjusted for az margin {az_margin}:' + f' {end_time}') + if start_range is not None: + logger.info(f' start range: {start_range}') + if rg_margin != 0.0: + start_range -= rg_margin + logger.info(f' adjusted for rg margin {rg_margin}:' + f' {start_range}') + if end_range is not None: + logger.info(f' end range: {end_range}') + if rg_margin != 0.0: + end_range += rg_margin + logger.info(f' adjusted for rg margin {rg_margin}:' + f' {end_range}') + if rg_spacing is not None: + logger.info(f' range spacing: {rg_spacing}') + if az_spacing is not None: + logger.info(f' azimuth time interval: {az_spacing}') + if rg_spacing is None or az_spacing is None: az_spacing_inferred, rg_spacing_inferred = \ isce3.geometry.infer_radar_grid_spacing_from_geo_grid( @@ -135,8 +168,10 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: ) if rg_spacing is None: rg_spacing = rg_spacing_inferred + logger.info(f' inferred range spacing: {rg_spacing}') if az_spacing is None: az_spacing = az_spacing_inferred + logger.info(f' inferred azimuth time interval: {az_spacing}') if (start_time is not None and start_range is not None and end_time is not None and end_range is not None): @@ -149,13 +184,6 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: num_az = round((tf - t0) / az_spacing) num_rg = round((end_range - start_range) / rg_spacing) - logger.info("Using user-specified radar grid bounding box parameters") - logger.info(f' start time: {start_time}') - logger.info(f' end time: {end_time}') - logger.info(f' start range: {start_range}') - logger.info(f' end range: {end_range}') - logger.info(f' az spacing: {az_spacing}') - logger.info(f' rg spacing: {rg_spacing}') logger.info(f' number of lines: {num_az}') logger.info(f' number of range samples: {num_rg}') From fdd79bca42b3f5f844d46c554f01a2c97d0c8744 Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Sun, 8 Feb 2026 09:20:40 -0800 Subject: [PATCH 13/14] enable fractional precision for radargrid start and end times --- share/nisar/defaults/static.yaml | 9 +++++---- share/nisar/schemas/static.yaml | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/share/nisar/defaults/static.yaml b/share/nisar/defaults/static.yaml index b59066874..39916c291 100644 --- a/share/nisar/defaults/static.yaml +++ b/share/nisar/defaults/static.yaml @@ -242,15 +242,16 @@ runconfig: # Defaults to 5. pts_per_side: 5 bounding_box: - # [OPTIONAL] Azimuth start time, in seconds since the epoch, of the first row of - # the radar grid. + # [OPTIONAL] Azimuth start UTC date and time of the start of the radar + # observation, as a string in ISO 8601 format with up to nanosecond precision. # If not provided, it will be inferred from the specified geographic grid. # If provided, must be a valid azimuth time within the observation time # interval. start_time: - # [OPTIONAL] Azimuth ending time, in seconds since the epoch, of the last row of - # the radar grid. + # [OPTIONAL] Azimuth ending UTC date and time of the end of the radar + # observation, as a string in ISO 8601 + # format with up to nanosecond precision. Must be >= `start_time`. # If not provided, it will be inferred from the specified geographic grid. # If provided, must be a valid azimuth time within the observation time # interval. diff --git a/share/nisar/schemas/static.yaml b/share/nisar/schemas/static.yaml index d6d8853f9..610589d57 100644 --- a/share/nisar/schemas/static.yaml +++ b/share/nisar/schemas/static.yaml @@ -101,12 +101,12 @@ radar_grid_spacing_options: radar_grid_bounding_box_options: start_time: any( timestamp(), - regex(r'^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}:\d{2})?$'), + regex(r'^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d{1,9})?$'), required=False ) end_time: any( timestamp(), - regex(r'^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}:\d{2})?$'), + regex(r'^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d{1,9})?$'), required=False ) start_range: num(required=False) From a72949cb198f645ed67cea3c212a43b146b43532 Mon Sep 17 00:00:00 2001 From: "Gustavo H. X. Shiroma" Date: Sun, 8 Feb 2026 11:03:59 -0800 Subject: [PATCH 14/14] convert start & end date-time string to isce3.core.DateTime before adding TimeDelta --- python/packages/nisar/workflows/static.py | 58 +++++++++++++---------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/python/packages/nisar/workflows/static.py b/python/packages/nisar/workflows/static.py index 9265eb7a3..925474e8c 100644 --- a/python/packages/nisar/workflows/static.py +++ b/python/packages/nisar/workflows/static.py @@ -111,8 +111,8 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: pts_per_side = radar_grid_spacing_params["pts_per_side"] bounding_box_params = radar_grid_params["bounding_box"] - start_time = bounding_box_params["start_time"] - end_time = bounding_box_params["end_time"] + start_datetime_str = bounding_box_params["start_time"] + end_datetime_str = bounding_box_params["end_time"] start_range = bounding_box_params["start_range"] end_range = bounding_box_params["end_range"] min_height = bounding_box_params["min_height"] @@ -121,40 +121,50 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: az_margin = bounding_box_params["az_margin"] rg_margin = bounding_box_params["rg_margin"] + start_time = None + end_time = None + # Print user radar grid bounding box parameters, if provided. - if (start_time is not None or start_range is not None or - end_time is not None or end_range is not None): + if (start_datetime_str is not None or start_range is not None or + end_datetime_str is not None or end_range is not None): logger.info("Using user-specified radar grid bounding box parameters") - if start_time is not None: - logger.info(f' start time: {start_time}') - if az_margin != 0.0: - start_time -= isce3.core.TimeDelta(az_margin) - logger.info(f' adjusted for az margin {az_margin}:' - f' {start_time}') - if end_time is not None: - logger.info(f' end time: {end_time}') - if az_margin != 0.0: - end_time += isce3.core.TimeDelta(az_margin) - logger.info(f' adjusted for az margin {az_margin}:' - f' {end_time}') + if start_datetime_str is not None: + logger.info(f' start time: {start_datetime_str}') + start_time = isce3.core.DateTime(start_datetime_str) + + if end_datetime_str is not None: + logger.info(f' end time: {end_datetime_str}') + end_time = isce3.core.DateTime(end_datetime_str) + if start_range is not None: logger.info(f' start range: {start_range}') - if rg_margin != 0.0: - start_range -= rg_margin - logger.info(f' adjusted for rg margin {rg_margin}:' - f' {start_range}') + if end_range is not None: logger.info(f' end range: {end_range}') - if rg_margin != 0.0: - end_range += rg_margin - logger.info(f' adjusted for rg margin {rg_margin}:' - f' {end_range}') + if rg_spacing is not None: logger.info(f' range spacing: {rg_spacing}') if az_spacing is not None: logger.info(f' azimuth time interval: {az_spacing}') + if start_time is not None and az_margin != 0.0: + start_time -= isce3.core.TimeDelta(az_margin) + logger.info(f' start time (adjusted for az. margin) {az_margin}:' + f' {start_time}') + if end_time is not None and az_margin != 0.0: + end_time += isce3.core.TimeDelta(az_margin) + logger.info(f' end time (adjusted for az. margin) {az_margin}:' + f' {end_time}') + if start_range is not None and rg_margin != 0.0: + start_range -= rg_margin + logger.info(f' start range (adjusted for rg margin) {rg_margin}:' + f' {start_range}') + if end_range is not None and rg_margin != 0.0: + end_range += rg_margin + logger.info(f' end range (adjusted for rg margin) {rg_margin}:' + f' {end_range}') + if rg_spacing is None or az_spacing is None: az_spacing_inferred, rg_spacing_inferred = \ isce3.geometry.infer_radar_grid_spacing_from_geo_grid(