diff --git a/python/packages/nisar/antenna/rx_channel_imbalance_helpers.py b/python/packages/nisar/antenna/rx_channel_imbalance_helpers.py index 2a243734a..0d232ec87 100644 --- a/python/packages/nisar/antenna/rx_channel_imbalance_helpers.py +++ b/python/packages/nisar/antenna/rx_channel_imbalance_helpers.py @@ -168,8 +168,20 @@ def compute_rx_channel_imbalance( txrx_pol, caltone_freq=caltone_freq ) - # peak normalized - max_ratio = np.nanmax(abs(lna_caltone_ratio)) + # compute complex caltone offset from the mean caltone + # over all active RX channels. This is used to remove + # phase/amplitude jumps due to WG reset over time, mode + # changes where pulsewidth varies, and finally due to + # change in caltone location onboard! + idx_rxs_active = raw.getListOfRxTRMs(freq_band, txrx_pol) - 1 + caltone_ofs = np.nanmean(caltone_mean[idx_rxs_active]) + # Use constant scalar, e.g., 1945, to scale the Caltone offset. + # This is to preserve the current abscal. (optional) + # This offset is obtained from the median over several + # observations/modes that have the same caltone frequency! + caltone_ofs /= 1945.0 + # peak normalized and apply complex offset + max_ratio = caltone_ofs * np.nanmax(abs(lna_caltone_ratio)) if not np.isclose(max_ratio, 0): lna_caltone_ratio /= max_ratio return lna_caltone_ratio, n_tap_dominant, time_delays, max_ratio diff --git a/python/packages/nisar/workflows/focus.py b/python/packages/nisar/workflows/focus.py index 4ec69e181..5504ad0a2 100644 --- a/python/packages/nisar/workflows/focus.py +++ b/python/packages/nisar/workflows/focus.py @@ -21,7 +21,8 @@ open_rrsd, chirpcorrelator_caltype_from_raw, is_raw_quad_pol, - first_tx_pol_for_quad + first_tx_pol_for_quad, + caltone_frequency_from_raw ) from nisar.products.readers.rslc_cal import (RslcCalibration, parse_rslc_calibration, get_scale_and_delay, check_cal_validity_dates) @@ -1705,7 +1706,43 @@ def get_focused_sub_swaths(rawlist, out_chan, grid, orbit, doppler, dem, azres, return swaths -def get_caltone_algorithm(cfg, fc, fs, n, is_dithered): +def get_caltone_frequency(cfg, raw, pol): + """ + Get Caltone frequency either from RSLC runconfig or from + parsing it from raw L0B low-rate telemerty (DRT). + + Parameters + ---------- + cfg : Struct + RSLC runconfig data. + raw : nisar.products.readers.raw.Raw + NISAR L0B product pareser object + pol : str + Tx-Rx polarization such as "HH", "HV", etc + + Returns + ------- + float + Caltone frequency in Hz. + + """ + caltone_freq = cfg.processing.caltone.frequency + caltone_freq_raw = caltone_frequency_from_raw(raw=raw, txrx_pol=pol) + if caltone_freq is None: + caltone_freq = caltone_freq_raw + name = os.path.basename(raw.filename) + log.info(f'Caltone frequency parsed from raw L0B "{name}" ' + f'is {caltone_freq * 1e-6} (MHz)') + elif not np.isclose(caltone_freq, caltone_freq_raw, rtol=1e-6): + log.warning( + 'Noticeable mismtach in Caltone frequency between user-provided ' + f'one {caltone_freq} (Hz) and L0B-parsed one {caltone_freq_raw} ' + '(Hz)!' + ) + return caltone_freq + + +def get_caltone_algorithm(cfg, fc, fs, n, is_dithered, caltone_freq): """Helper for configuring caltone removal. Parameters @@ -1720,6 +1757,8 @@ def get_caltone_algorithm(cfg, fc, fs, n, is_dithered): Number of samples in raw data. is_dithered : bool Whether we're analyzing a mode with dithered PRI. + caltone_freq : float + Caltone frequency in Hz. Returns ------- @@ -1742,7 +1781,7 @@ def get_caltone_algorithm(cfg, fc, fs, n, is_dithered): elif algorithm == "wavelet": log.info("Will remove wavelet caltone estimate from each pulse.") wavelets = isce3.focus.ToneRemover( - (cfg.processing.caltone.frequency - fc) / fs, + (caltone_freq - fc) / fs, n, cfg.processing.caltone.wavelet_size) else: algorithm = "disabled" @@ -2059,9 +2098,11 @@ def temp(suffix): log.info("Will fill gaps between sub-swaths with zeros.") fs = raw.getChirpParameters(channel_in.freq_id, pol[0])[1] + caltone_freq = get_caltone_frequency(cfg=cfg, raw=raw, pol=pol) + log.info(f'Caltone frequency is {caltone_freq * 1e-6} (MHz)') caltone_algorithm, wavelets = get_caltone_algorithm(cfg, channel_in.band.center, fs, raw_grid.shape[1], - raw.isDithered(channel_in.freq_id)) + raw.isDithered(channel_in.freq_id), caltone_freq) for i in range(0, raw_grid.shape[0], na): pulse = i + pulse_begin @@ -2121,12 +2162,6 @@ def temp(suffix): # Precompute antenna patterns at downsampled spacing if cfg.processing.is_enabled.eap: - # XXX Due to a bug in respective DRT of some L0B products - # (CRID=05007), caltone.frequency is extrated from runconfig - # otherwise, it shall be set to None to be determined from DRT! - # The latter requires RSLC runconfig update to allow caltone - # frequency to be parsed directly from L0B product for more - # flexible configuration over wide range of L0B products. # XXX the intrument-related delay offset used in DBF process # shall be eventually obtained from instrument INT CAL HDF5 # once the respective product spec is updated (delay_ofs_dbf)! @@ -2135,7 +2170,7 @@ def temp(suffix): instparser, orbit, attitude, el_lut=el_lut, freq_band=channel_in.freq_id, - caltone_freq=cfg.processing.caltone.frequency, + caltone_freq=caltone_freq, delay_ofs_dbf=-2.1474e-6) log.info("Precomputing antenna patterns") diff --git a/share/nisar/defaults/focus.yaml b/share/nisar/defaults/focus.yaml index 8bbac52dc..58081b989 100644 --- a/share/nisar/defaults/focus.yaml +++ b/share/nisar/defaults/focus.yaml @@ -526,7 +526,13 @@ runconfig: # table v50 and v51, which went into operations on # 2025-10-17T00:00:00Z. The v50 frequency is 1216.904297e6 Hz # while the v51 frequency is 1214.882813e6 Hz. - frequency: 1214.882813e6 + # For RCID=164 (77 MHz) over Antartica, it is set to + # 1209.492036e6 Hz since 2026-06-08T01:24:07Z. + # Default (Null or missing in user runconfig) implies that + # the Caltone frequency will be extracted from L0B DRT if + # available in L0B product otherwise it will be set to + # 1214.882813e6 Hz internally. + frequency: null # Length of constant-overlap add (COLA) window. wavelet_size: 64