Skip to content

Commit f3de64a

Browse files
authored
1641 hi pset epoch should be pointing start time (#2346)
* Make PSET epoch time be start of pointing Add epoch_delta varaible to PSET * Use l1b met time rather than epoch to avoid extra time conversions * Fix silly test mistake
1 parent b663f8f commit f3de64a

File tree

3 files changed

+67
-14
lines changed

3 files changed

+67
-14
lines changed

imap_processing/cdf/config/imap_hi_variable_attrs.yaml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,8 +432,18 @@ hi_de_nominal_bin:
432432

433433
# Define override values for epoch as defined in imap_constant_attrs.yaml
434434
hi_pset_epoch:
435-
CATDESC: Midpoint time of pointing, number of nanoseconds since J2000 with leap seconds included
436-
BIN_LOCATION: 0.5
435+
CATDESC: Start time of pointing, number of nanoseconds since J2000 with leap seconds included
436+
DELTA_PLUS_VAR: epoch_delta
437+
BIN_LOCATION: 0
438+
439+
epoch_delta:
440+
<<: *default_int64
441+
CATDESC: Number of nanoseconds in spacecraft pointing covered by this pointing set product
442+
FIELDNAM: epoch delta
443+
UNITS: ns
444+
VAR_TYPE: support_data
445+
DISPLAY_TYPE: no_plot
446+
TIME_SCALE: Terrestrial Time
437447

438448
hi_pset_esa_energy_step:
439449
<<: *default_esa_energy_step

imap_processing/hi/hi_l1c.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@
2929
frame_transform,
3030
frame_transform_az_el,
3131
)
32+
from imap_processing.spice.repoint import get_pointing_times
3233
from imap_processing.spice.spin import (
3334
get_instrument_spin_phase,
3435
get_spin_data,
3536
)
36-
from imap_processing.spice.time import ttj2000ns_to_et
37+
from imap_processing.spice.time import met_to_ttj2000ns, ttj2000ns_to_et
3738

3839
N_SPIN_BINS = 3600
3940
SPIN_PHASE_BIN_EDGES = np.linspace(0, 1, N_SPIN_BINS + 1)
@@ -101,14 +102,14 @@ def generate_pset_dataset(
101102
config_df = CalibrationProductConfig.from_csv(calibration_prod_config_path)
102103

103104
pset_dataset = empty_pset_dataset(
104-
de_dataset.epoch.data[0],
105+
de_dataset.ccsds_met.data.mean(),
105106
de_dataset.esa_energy_step,
106107
config_df.cal_prod_config.number_of_products,
107108
logical_source_parts["sensor"],
108109
)
109-
pset_et = ttj2000ns_to_et(pset_dataset.epoch.data[0])
110110
# Calculate and add despun_z, hae_latitude, and hae_longitude variables to
111111
# the pset_dataset
112+
pset_et = ttj2000ns_to_et(pset_dataset.epoch.data[0])
112113
pset_dataset.update(pset_geometry(pset_et, logical_source_parts["sensor"]))
113114
# Bin the counts into the spin-bins
114115
pset_dataset.update(pset_counts(pset_dataset.coords, config_df, de_dataset))
@@ -121,15 +122,16 @@ def generate_pset_dataset(
121122

122123

123124
def empty_pset_dataset(
124-
epoch_val: int, l1b_energy_steps: xr.DataArray, n_cal_prods: int, sensor_str: str
125+
l1b_met: float, l1b_energy_steps: xr.DataArray, n_cal_prods: int, sensor_str: str
125126
) -> xr.Dataset:
126127
"""
127128
Allocate an empty xarray.Dataset with appropriate pset coordinates.
128129
129130
Parameters
130131
----------
131-
epoch_val : int
132-
The starting epoch in J2000 TT nanoseconds for data in the PSET.
132+
l1b_met : float
133+
Any met from the input L1B DE dataset. This is used to query the
134+
repoint-table data to get the start and end times of the pointing.
133135
l1b_energy_steps : xarray.DataArray
134136
The array of esa_energy_step data from the L1B DE product.
135137
n_cal_prods : int
@@ -148,13 +150,18 @@ def empty_pset_dataset(
148150

149151
# preallocate coordinates xr.DataArrays
150152
coords = dict()
153+
154+
# Get the Pointing start and end times
155+
pointing_mets = get_pointing_times(l1b_met)
156+
epochs = met_to_ttj2000ns(np.asarray(pointing_mets))
157+
151158
# epoch coordinate has only 1 entry for pointing set
152159
epoch_attrs = attr_mgr.get_variable_attributes("epoch", check_schema=False)
153160
epoch_attrs.update(
154161
attr_mgr.get_variable_attributes("hi_pset_epoch", check_schema=False)
155162
)
156163
coords["epoch"] = xr.DataArray(
157-
np.array([epoch_val], dtype=np.int64), # TODO: get dtype from cdf attrs?
164+
np.array([epochs[0]], dtype=np.int64),
158165
name="epoch",
159166
dims=["epoch"],
160167
attrs=epoch_attrs,
@@ -201,6 +208,15 @@ def empty_pset_dataset(
201208

202209
# Allocate the coordinate label variables
203210
data_vars = dict()
211+
# Generate the epoch_delta variable
212+
data_vars["epoch_delta"] = xr.DataArray(
213+
np.diff(epochs),
214+
name="epoch_delta",
215+
dims=["epoch"],
216+
attrs=attr_mgr.get_variable_attributes(
217+
"hi_pset_epoch_delta", check_schema=False
218+
),
219+
)
204220
# Generate label variables
205221
data_vars["esa_energy_step_label"] = xr.DataArray(
206222
coords["esa_energy_step"].values.astype(str),
@@ -257,7 +273,7 @@ def pset_geometry(pset_et: float, sensor_str: str) -> dict[str, xr.DataArray]:
257273
Returns
258274
-------
259275
geometry_vars : dict[str, xarray.DataArray]
260-
Keys are variable names and values are data arrays.
276+
Keys are variable names, and values are data arrays.
261277
"""
262278
geometry_vars = create_dataset_variables(
263279
["despun_z"], (1, 3), att_manager_lookup_str="hi_pset_{0}"

imap_processing/tests/hi/test_hi_l1c.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,26 @@ def test_generate_pset_dataset(
3232
hi_l1_test_data_path,
3333
hi_test_cal_prod_config_path,
3434
use_fake_spin_data_for_time,
35+
use_fake_repoint_data_for_time,
3536
imap_ena_sim_metakernel,
3637
):
3738
"""Test coverage for generate_pset_dataset function"""
3839
use_fake_spin_data_for_time(482372987.999)
3940
l1b_de_path = hi_l1_test_data_path / "imap_hi_l1b_45sensor-de_20250415_v999.cdf"
4041
l1b_dataset = load_cdf(l1b_de_path)
42+
l1b_met = l1b_dataset["ccsds_met"].values[0]
43+
# Set repoint start and end times.
44+
seconds_per_day = 24 * 60 * 60
45+
use_fake_repoint_data_for_time(
46+
np.asarray([l1b_met - 15 * 60, l1b_met + seconds_per_day]),
47+
np.asarray([l1b_met, l1b_met + seconds_per_day + 1]),
48+
)
4149
l1c_dataset = hi_l1c.generate_pset_dataset(
4250
l1b_dataset, hi_test_cal_prod_config_path
4351
)
4452

4553
assert l1c_dataset.epoch.data[0] == l1b_dataset.epoch.data[0].astype(np.int64)
54+
assert l1c_dataset.epoch_delta.data[0] == seconds_per_day * 1e9
4655

4756
np.testing.assert_array_equal(l1c_dataset.despun_z.data.shape, (1, 3))
4857
np.testing.assert_array_equal(l1c_dataset.hae_latitude.data.shape, (1, 3600))
@@ -59,7 +68,7 @@ def test_generate_pset_dataset(
5968
write_cdf(l1c_dataset)
6069

6170

62-
def test_empty_pset_dataset():
71+
def test_empty_pset_dataset(use_fake_repoint_data_for_time):
6372
"""Test coverage for empty_pset_dataset function"""
6473
n_energy_steps = 8
6574
l1b_esa_energy_steps = xr.DataArray(
@@ -68,11 +77,17 @@ def test_empty_pset_dataset():
6877
)
6978
n_calibration_prods = 5
7079
sensor_str = HIAPID.H90_SCI_DE.sensor
80+
l1b_met = 482373065
81+
use_fake_repoint_data_for_time(
82+
np.asarray([l1b_met - 15 * 60, l1b_met + 24 * 60 * 60])
83+
)
84+
7185
dataset = hi_l1c.empty_pset_dataset(
72-
100, l1b_esa_energy_steps, n_calibration_prods, sensor_str
86+
l1b_met, l1b_esa_energy_steps, n_calibration_prods, sensor_str
7387
)
7488

7589
assert dataset.epoch.size == 1
90+
assert dataset.epoch_delta.size == 1
7691
assert dataset.spin_angle_bin.size == 3600
7792
assert dataset.esa_energy_step.size == n_energy_steps
7893
np.testing.assert_array_equal(
@@ -127,7 +142,12 @@ def test_pset_geometry(mock_frame_transform, mock_geom_frame_transform, sensor_s
127142

128143

129144
@pytest.mark.external_test_data
130-
def test_pset_counts(hi_l1_test_data_path, hi_test_cal_prod_config_path):
145+
@mock.patch("imap_processing.hi.hi_l1c.get_pointing_times", return_value=(100, 200))
146+
def test_pset_counts(
147+
mock_pointing_times,
148+
hi_l1_test_data_path,
149+
hi_test_cal_prod_config_path,
150+
):
131151
"""Test coverage for pset_counts function."""
132152
l1b_de_path = hi_l1_test_data_path / "imap_hi_l1b_45sensor-de_20250415_v999.cdf"
133153
l1b_dataset = load_cdf(l1b_de_path)
@@ -145,7 +165,12 @@ def test_pset_counts(hi_l1_test_data_path, hi_test_cal_prod_config_path):
145165

146166

147167
@pytest.mark.external_test_data
148-
def test_pset_counts_empty_l1b(hi_l1_test_data_path, hi_test_cal_prod_config_path):
168+
@mock.patch("imap_processing.hi.hi_l1c.get_pointing_times", return_value=(100, 200))
169+
def test_pset_counts_empty_l1b(
170+
mock_pointing_times,
171+
hi_l1_test_data_path,
172+
hi_test_cal_prod_config_path,
173+
):
149174
"""Test coverage for pset_counts function when the input L1b contains no counts."""
150175
l1b_de_path = hi_l1_test_data_path / "imap_hi_l1b_45sensor-de_20250415_v999.cdf"
151176
l1b_dataset = load_cdf(l1b_de_path)
@@ -254,6 +279,7 @@ def test_pset_backgrounds():
254279
)
255280

256281

282+
@mock.patch("imap_processing.hi.hi_l1c.get_pointing_times", return_value=(100, 200))
257283
@mock.patch("imap_processing.hi.hi_l1c.get_spin_data", return_value=None)
258284
@mock.patch("imap_processing.hi.hi_l1c.get_instrument_spin_phase")
259285
@mock.patch("imap_processing.hi.hi_l1c.get_de_clock_ticks_for_esa_step")
@@ -263,6 +289,7 @@ def test_pset_exposure(
263289
mock_de_clock_ticks,
264290
mock_spin_phase,
265291
mock_spin_data,
292+
mock_pointing_times,
266293
):
267294
"""Test coverage for pset_exposure function"""
268295
l1b_energy_steps = xr.DataArray(

0 commit comments

Comments
 (0)