diff --git a/python/extensions/pybind_isce3/core/Constants.cpp b/python/extensions/pybind_isce3/core/Constants.cpp index 35ebd21b8..058c9eb71 100644 --- a/python/extensions/pybind_isce3/core/Constants.cpp +++ b/python/extensions/pybind_isce3/core/Constants.cpp @@ -20,4 +20,12 @@ void add_constants(py::module & core) core.attr("GLOBAL_MAX_HEIGHT") = py::float_(isce3::core::GLOBAL_MAX_HEIGHT); core.attr("WGS84_ELLIPSOID") = isce3::core::Ellipsoid(); core.attr("SINC_HALF") = isce3::core::SINC_HALF; + + // TODO agree on LUT postings. + core.attr("RSLC_CALIBRATION_SECTION_SAMPLING") = 50; + + // Extra margin added to RSLC LUTs to ensure that, after + // geocoding with an interpolation algoritm (e.g., bicubic spline), + // the LUTs fully cover the geocoded imagery extents. + core.attr("RSLC_LUTS_EXTRA_MARGIN_IN_PIXELS") = 11; } diff --git a/python/packages/isce3/geometry/doppler.py b/python/packages/isce3/geometry/doppler.py index 03af78f63..a5ee669fa 100644 --- a/python/packages/isce3/geometry/doppler.py +++ b/python/packages/isce3/geometry/doppler.py @@ -131,6 +131,12 @@ def make_doppler_lut_from_attitude( # computed. dem.compute_min_max_mean_height() + # crop az_time using orbit and attitude extents + min_time = max([orbit.start_time, attitude.start_time]) + max_time = min([orbit.end_time, attitude.end_time]) + az_time = az_time[(az_time > min_time) & (az_time < max_time)] + + # create Doppler Centroid array filled with zeros dop = np.zeros((len(az_time), len(slant_range))) # Using the default EL bounds [-45, 45] deg can cause trouble when looking diff --git a/python/packages/nisar/products/writers/SLC.py b/python/packages/nisar/products/writers/SLC.py index 15c704099..cf39726df 100644 --- a/python/packages/nisar/products/writers/SLC.py +++ b/python/packages/nisar/products/writers/SLC.py @@ -956,11 +956,6 @@ def add_calibration_section(self, frequency, pol, gamma0_lut: LUT2d): assert len(pol) == 2 and pol[0] in "HVLR" and pol[1] in "HV" - # TODO agree on LUT postings. - calibration_section_sampling = 50 - t = az_time_orig_vect[::calibration_section_sampling] - r = slant_range_orig_vect[::calibration_section_sampling] - cal_group = self.root.require_group("metadata/calibrationInformation") # TODO Populate backscatter conversion layers. Plan is for beta0=1, @@ -997,7 +992,9 @@ def add_calibration_section(self, frequency, pol, eap_group = cal_group.require_group( f"frequency{frequency}/elevationAntennaPattern") - t, r = require_lut_axes(eap_group, epoch, t, r, + t, r = require_lut_axes( + eap_group, epoch, az_time_orig_vect, + slant_range_orig_vect, "calibration elevationAntennaPattern records") dummy_array = np.ones((t.size, r.size), dtype=np.complex64) diff --git a/python/packages/nisar/workflows/focus.py b/python/packages/nisar/workflows/focus.py index e5d39337e..d630a453f 100644 --- a/python/packages/nisar/workflows/focus.py +++ b/python/packages/nisar/workflows/focus.py @@ -291,12 +291,12 @@ def get_total_grid_bounds(rawfiles: list[str]): return epoch, tmin, tmax, rmin, rmax -def get_total_grid(rawfiles: list[str], dt, dr): +def get_total_grid(rawfiles: list[str], dt, dr, extra_margin_in_pixels=0): epoch, tmin, tmax, rmin, rmax = get_total_grid_bounds(rawfiles) - nt = int(np.ceil((tmax - tmin) / dt)) + 1 - nr = int(np.ceil((rmax - rmin) / dr)) + 1 - t = isce3.core.Linspace(tmin, dt, nt) - r = isce3.core.Linspace(rmin, dr, nr) + nt = int(np.ceil((tmax - tmin) / dt)) + 1 + 2 * extra_margin_in_pixels + nr = int(np.ceil((rmax - rmin) / dr)) + 1 + 2 * extra_margin_in_pixels + t = isce3.core.Linspace(tmin - extra_margin_in_pixels * dt, dt, nt) + r = isce3.core.Linspace(rmin - extra_margin_in_pixels * dr, dr, nr) return epoch, t, r @@ -397,7 +397,13 @@ def make_doppler_lut(rawfiles: list[str], # Now do the actual calculations. wvl = isce3.core.speed_of_light / fc - epoch_in, t, r = get_total_grid(rawfiles, azimuth_spacing, range_spacing) + + # Get grid for Doppler LUT with an extra margin to ensure that, after + # geocoding with an interpolation algoritm (e.g., bicubic spline), + # the LUT fully covers the geocoded imagery extents. + epoch_in, t, r = get_total_grid( + rawfiles, azimuth_spacing, range_spacing, + extra_margin_in_pixels=isce3.core.RSLC_LUTS_EXTRA_MARGIN_IN_PIXELS) # If timespan is too small, only one time may be provided, causing the LUT # construction to fail. Fall back to t ± Δt/2 to preserve az spacing. @@ -1764,13 +1770,27 @@ def focus(runconfig, runconfig_path=""): cal = get_calibration(cfg, band.width) slc.set_calibration(cal, frequency) - # add calibration section for each polarization + # add calibration section based on a downsampled radar grid, + # including an extra margin to ensure that, after geocoding + # with an interpolation algoritm (e.g., bicubic spline), + # the LUTs fully cover the geocoded imagery extents. + calibration_section_sampling = \ + isce3.core.RSLC_LUTS_EXTRA_MARGIN_IN_PIXELS + multilooked_radar_grid = og.multilook( + calibration_section_sampling, calibration_section_sampling) + + luts_extra_margin_in_pixels = \ + isce3.core.RSLC_LUTS_EXTRA_MARGIN_IN_PIXELS + extended_radar_grid = multilooked_radar_grid.add_margin( + luts_extra_margin_in_pixels, luts_extra_margin_in_pixels) + for pol in pols: - slc.add_calibration_section(frequency, pol, og.sensing_times, - orbit.reference_epoch, og.slant_ranges, + slc.add_calibration_section(frequency, pol, + extended_radar_grid.sensing_times, + orbit.reference_epoch, + extended_radar_grid.slant_ranges, beta0_lut, sigma0_lut, gamma0_lut) - freq = next(iter(get_bands(common_mode))) slc.set_geolocation_grid(orbit, ogrid[freq], dop[freq], epsg=cfg.processing.metadata_cube_epsg, dem=dem,