Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions python/packages/nisar/antenna/pattern.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from warnings import warn
from collections import defaultdict
from isce3.core import Orbit, Attitude, Linspace
from isce3.geometry import DEMInterpolator
Expand All @@ -11,7 +10,8 @@
from nisar.antenna import TxTrmInfo, RxTrmInfo, TxBMF, RxDBF
from nisar.antenna.beamformer import get_pulse_index
from nisar.antenna.rx_channel_imbalance_helpers import (
compute_all_rx_channel_imbalances_from_l0b
compute_all_rx_channel_imbalances_from_l0b,
_is_product_from_second_band
)
import numpy as np

Expand Down Expand Up @@ -196,6 +196,16 @@ def __init__(self, raw: Raw, dem: DEMInterpolator,
self.freq_band = freq_band
# get all polarization for a frequency band
self.txrx_pols = raw.polarizations[self.freq_band]
# check if it is second band in only split spectrum scenario
is_second_band = True
for p in self.txrx_pols:
is_second_band &= _is_product_from_second_band(
raw, freq_band=freq_band, txrx_pol=p)
self._is_second_band = is_second_band
log.info(
f'Whether frequency band "{freq_band}" is the second band of '
f'SSP -> {self._is_second_band}'
)
# comput all RX channel imbalances over all
# txrx pols of a desired frequency band.
# This RX imbalanced is basically LNA/CALTONE ratio.
Expand Down Expand Up @@ -276,7 +286,7 @@ def __init__(self, raw: Raw, dem: DEMInterpolator,
# fetch RX channel adjustment complex factors from
# instrument file per RX pol.
self.channel_adj_fact_rx[rx_p] = ins.channel_adjustment_factors_rx(
rx_p)
rx_p, is_second_band=self._is_second_band)

# get rx el-cut patterns
self.el_pat_rx[rx_p] = ant.el_cut_all(rx_p)
Expand Down Expand Up @@ -341,7 +351,8 @@ def __init__(self, raw: Raw, dem: DEMInterpolator,
# fetch TX channel adjustment complex factors from
# instrument file per TX linear pol.
self.channel_adj_fact_tx[tx_lp] = (
ins.channel_adjustment_factors_tx(tx_lp)
ins.channel_adjustment_factors_tx(
tx_lp, is_second_band=self._is_second_band)
)

# get tx el-cut patterns
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def get_filenames_dbf(self):

return fn_ta, fn_ac

def channel_adjustment_factors_tx(self, pol):
def channel_adjustment_factors_tx(self, pol, is_second_band=False):
"""
Get channel adjustment complex factors for all TX channels
of a polarization.
Expand All @@ -286,6 +286,12 @@ def channel_adjustment_factors_tx(self, pol):
pol : {'H', 'V'}
Tx polarization. Must be a valid polarization in the instrument
file.
is_second_band : bool, default=False
If True, it will get adjustment factors for the second
frequency band ("B").
If the respective dataset does not exist, it will use
the original dataset for both bands (backward compatibility)
while raising a warning "MissingInstrumentFieldWarning".

Returns
-------
Expand All @@ -305,11 +311,14 @@ def channel_adjustment_factors_tx(self, pol):
None if the field "channelAdjustment" does not exist in the
instrument file. That is, no need for the TX channels adjustment
in TX antenna pattern formation.
A new dataset "amplitudeB" is introduced to cover the second band
(typically called "B" for split spectrum case). This major change is
introduced in v3.0

"""
return self._channel_adjustment_factors('tx', pol)
return self._channel_adjustment_factors('tx', pol, is_second_band)

def channel_adjustment_factors_rx(self, pol):
def channel_adjustment_factors_rx(self, pol, is_second_band=False):
"""
Get channel adjustment complex factors for all RX channels
of a polarization.
Expand All @@ -323,6 +332,12 @@ def channel_adjustment_factors_rx(self, pol):
pol : {'H', 'V'}
Rx polarization. Must be a valid polarization in the instrument
file.
is_second_band : bool, default=False
If True, it will get adjustment factors for the second
frequency band ("B").
If the respective dataset does not exist, it will use
the original dataset for both bands (backward compatibility)
while raising a warning "MissingInstrumentFieldWarning".

Returns
-------
Expand All @@ -342,11 +357,14 @@ def channel_adjustment_factors_rx(self, pol):
None if the field "channelAdjustment" does not exist in the
instrument file. That is, no need for the RX channels adjustment
in RX antenna pattern formation.
A new dataset "amplitudeB" is introduced to cover the second band
(typically called "B" for split spectrum case). This major change is
introduced in v3.0.

"""
return self._channel_adjustment_factors('rx', pol)
return self._channel_adjustment_factors('rx', pol, is_second_band)

def _channel_adjustment_factors(self, side, pol):
def _channel_adjustment_factors(self, side, pol, is_second_band):
"""
Helper function for obtaining TX or RX channel adjustment factors.

Expand All @@ -356,6 +374,12 @@ def _channel_adjustment_factors(self, side, pol):
pol : {'H', 'V'}
Tx or Rx polarization depending on `side`. Must be a valid
polarization in the instrument file.
is_second_band : bool, default=False
If True, it will get adjustment factors for the second
frequency band ("B").
If the respective dataset does not exist, it will use
the original dataset for both bands (backward compatibility)
while raising a warning "MissingInstrumentFieldWarning".

Returns
-------
Expand All @@ -375,6 +399,9 @@ def _channel_adjustment_factors(self, side, pol):
None if the field "channelAdjustment" does not exist in the
instrument file. That is, no need for the TX/RX channels adjustment
in TX/RX antenna pattern formation.
A new dataset "amplitudeB" is introduced to cover the second band
(typically called "B" for split spectrum case). This major change is
introduced in v3.0.

"""
if pol not in self.pols:
Expand All @@ -391,7 +418,19 @@ def _channel_adjustment_factors(self, side, pol):
stacklevel=2)
return None

else:
else: # the channel adjustment group exists
if is_second_band:
try:
ds = grp[f'{side}/amplitudeB']
except KeyError:
warnings.warn(
f'{grp_name}{side}/amplitudeB',
category=MissingInstrumentFieldWarning,
stacklevel=2)
# revert to the original dataset
# used for all bands!
ds = grp[f'{side}/amplitude']
return ds[()]
return grp[f'{side}/amplitude'][()]

def get_crosstalk(self):
Expand Down
9 changes: 9 additions & 0 deletions tests/data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,15 @@ Note that a new instrument HDF5 sample file "REE_INSTRUMENT_TABLE_V2P0.h5" of ve
The total number of range lines is *70*. The total number of range bins is *28927*.
The Tx range lines types are of HPA, LNA, and BYPASS. BYPASS range line interval is *20*.

- **REE_INSTRUMENT_TABLE_V3P0.h5**

The augmented version, *v3.0*, of the instrument HDF5 product "REE_INSTRUMENT_TABLE_V2P0.h5"
whose new spec are introduced on *06/21/2026*.
This file contains extra dataset "amplitudeB" for all polarizations covering both TX and RX
side. E.g, */HPOL/channelAdjustment/tx/amplitudeB*.
This new field supports instrument-related frequency-dependent adjustemnt factors for
the second frequency band (aka, "B") if available in split spectrum case.


## Geoid EGM96

Expand Down
Binary file added tests/data/bf/REE_INSTRUMENT_TABLE_V3P0.h5
Binary file not shown.
79 changes: 79 additions & 0 deletions tests/python/packages/nisar/products/readers/instrument_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,82 @@ def test_instrument_parser_v2p0():
npt.assert_allclose(rx_adj_fact, adj_factors,
err_msg='Wrong RX channel adjustment factors'
f' for "{pol}"')
# check for band=B which does not exist to make sure it
# is handled properly for backward compatibility
tx_adj_fact = ins.channel_adjustment_factors_tx(
pol, is_second_band=True)
npt.assert_allclose(tx_adj_fact, adj_factors,
err_msg=(
'Wrong TX channel adjustment factors '
f'of second band for "{pol}"'
))
rx_adj_fact = ins.channel_adjustment_factors_rx(
pol, is_second_band=True)
npt.assert_allclose(rx_adj_fact, adj_factors,
err_msg=(
'Wrong RX channel adjustment factors '
f'of second band for "{pol}"'
))


def test_instrument_parser_v3p0():
"""
Parsing v3.0 of NISAR instrument table where new dataset
"amplitudeB" is added to v2.0 to support the TX/RX channel
adjustment factors on the second frequency band used in only
split spectrum case!

"""
# sub directory for test files under "isce3/tests/data"
sub_dir = 'bf'

# HDF5 filename for instrument under "sub_dir"
instrument_file = 'REE_INSTRUMENT_TABLE_V3P0.h5'

# channel adjustment factors stored in the file
def _powdb_phasedeg_to_complex(pow_db: float, phs_deg: float) -> float:
"""Convert (power, phase) (dB, deg) to a complex amplitude"""
return 10 ** (pow_db / 20) * np.exp(1j * np.deg2rad(phs_deg))

adj_fact_tx = {}
adj_fact_tx['H'] = np.full(
shape=(12,),
fill_value=_powdb_phasedeg_to_complex(0.1, 10),
dtype='c16'
)
adj_fact_tx['V'] = np.full(
shape=(12,),
fill_value=_powdb_phasedeg_to_complex(0.2, 20),
dtype='c16'
)

adj_fact_rx = {}
adj_fact_rx['H'] = np.full(
shape=(12,),
fill_value=_powdb_phasedeg_to_complex(-0.1, -10),
dtype='c16'
)
adj_fact_rx['V'] = np.full(
shape=(12,),
fill_value=_powdb_phasedeg_to_complex(-0.2, -20),
dtype='c16'
)

# construct the parser
with InstrumentParser(os.path.join(iscetest.data, sub_dir,
instrument_file)) as ins:
for pol in ('H', 'V'):
tx_adj_fact = ins.channel_adjustment_factors_tx(
pol, is_second_band=True)
npt.assert_allclose(tx_adj_fact, adj_fact_tx[pol],
err_msg=(
'Wrong TX channel adjustment factors '
f'of second band for "{pol}"'
))
rx_adj_fact = ins.channel_adjustment_factors_rx(
pol, is_second_band=True)
npt.assert_allclose(rx_adj_fact, adj_fact_rx[pol],
err_msg=(
'Wrong RX channel adjustment factors '
f'of second band for "{pol}"'
))
Loading