Skip to content

Commit ee41ffa

Browse files
authored
Glows obs day updates (IMAP-Science-Operations-Center#1301)
* Adding flags and obs_day updates * Updating flags, adding tests * Fixing tests, and epoch time type * fixing mypy * Fixing mypy bug * fixing merge
1 parent 17aa909 commit ee41ffa

File tree

7 files changed

+139
-40
lines changed

7 files changed

+139
-40
lines changed

imap_processing/glows/l1a/glows_l1a.py

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@
88

99
from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
1010
from imap_processing.glows.l0.decom_glows import decom_packets
11-
from imap_processing.glows.l0.glows_l0_data import DirectEventL0
11+
from imap_processing.glows.l0.glows_l0_data import DirectEventL0, HistogramL0
1212
from imap_processing.glows.l1a.glows_l1a_data import DirectEventL1A, HistogramL1A
13-
from imap_processing.spice.time import TTJ2000_EPOCH, met_to_ttj2000ns
13+
from imap_processing.glows.l1b.glows_l1b_data import HistogramL1B
14+
from imap_processing.spice.time import (
15+
met_to_datetime64,
16+
met_to_ttj2000ns,
17+
)
1418

1519

1620
def create_glows_attr_obj(data_version: str) -> ImapCdfAttributes:
@@ -66,13 +70,19 @@ def glows_l1a(packet_filepath: Path, data_version: str) -> list[xr.Dataset]:
6670
# Create dictionaries to group data by day
6771
de_by_day = process_de_l0(de_l0)
6872
hists_by_day = defaultdict(list)
73+
# Assume the observational day starts with the first packet, then find any new
74+
# observation days.
75+
# TODO: replace determine_observational_day with spin table API
76+
obs_days = [hist_l0[0].SEC]
77+
obs_days += determine_observational_day(hist_l0)
6978

70-
# TODO: Make this its own function?
7179
for hist in hist_l0:
7280
hist_l1a = HistogramL1A(hist)
73-
# Split by IMAP start time
74-
# TODO: Should this be MET?
75-
hist_day = (TTJ2000_EPOCH + met_to_ttj2000ns(hist.SEC)).astype("datetime64[D]")
81+
# Determine the day the histogram belongs to. This finds the observation
82+
# day in obs_day that is nearest the histogram timestamp without going over.
83+
hist_day = next(
84+
(day for day in reversed(obs_days) if day <= hist.SEC), obs_days[-1]
85+
)
7686
hists_by_day[hist_day].append(hist_l1a)
7787

7888
# Generate CDF files for each day
@@ -88,6 +98,36 @@ def glows_l1a(packet_filepath: Path, data_version: str) -> list[xr.Dataset]:
8898
return output_datasets
8999

90100

101+
def determine_observational_day(hist_l0: list[HistogramL0]) -> list:
102+
"""
103+
Find the timestamps for each observational day.
104+
105+
This function temporarily uses the is_night flag to determine the start of a new
106+
observational day, but should eventually use the spin table APIs.
107+
108+
Parameters
109+
----------
110+
hist_l0 : list[HistogramL0]
111+
List of HistogramL0 objects.
112+
113+
Returns
114+
-------
115+
list
116+
List of start times for each observational day.
117+
"""
118+
prev_is_night = -1
119+
obs_day_change = []
120+
for hist in hist_l0:
121+
flags = HistogramL1B.deserialize_flags(hist.FLAGS)
122+
is_night: int = int(flags[6])
123+
if prev_is_night and not is_night:
124+
obs_day_change.append(hist.SEC)
125+
126+
prev_is_night = is_night
127+
128+
return obs_day_change
129+
130+
91131
def process_de_l0(
92132
de_l0: list[DirectEventL0],
93133
) -> dict[np.datetime64, list[DirectEventL1A]]:
@@ -111,7 +151,7 @@ def process_de_l0(
111151
de_by_day = dict()
112152

113153
for de in de_l0:
114-
de_day = (TTJ2000_EPOCH + met_to_ttj2000ns(de.MET)).astype("datetime64[D]")
154+
de_day = (met_to_datetime64(de.MET)).astype("datetime64[D]")
115155
if de_day not in de_by_day:
116156
de_by_day[de_day] = [DirectEventL1A(de)]
117157
# Putting not first data int o last direct event list.
@@ -315,7 +355,7 @@ def generate_histogram_dataset(
315355
Dataset containing the GLOWS L1A histogram CDF output.
316356
"""
317357
# Store timestamps for each HistogramL1A object.
318-
time_data = np.zeros(len(hist_l1a_list), dtype="datetime64[ns]")
358+
time_data = np.zeros(len(hist_l1a_list), dtype="int64")
319359
# TODO Add daily average of histogram counts
320360
# TODO compute average temperature etc
321361
# Data in lists, for each of the 25 time varying datapoints in HistogramL1A

imap_processing/glows/l1b/glows_l1b_data.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,3 +611,26 @@ def output_data(self) -> tuple:
611611
A tuple containing each attribute value in the class.
612612
"""
613613
return tuple(getattr(self, out.name) for out in dataclasses.fields(self))
614+
615+
@staticmethod
616+
def deserialize_flags(raw: int) -> np.ndarray[int]:
617+
"""
618+
Deserialize the flags into a list.
619+
620+
Parameters
621+
----------
622+
raw : int
623+
16 bit integer containing the on-board flags to deserialize.
624+
625+
Returns
626+
-------
627+
flags : np.ndarray
628+
Array of flags as a boolean.
629+
"""
630+
# there are only 10 flags in the on-board flag array, additional flags are added
631+
# later.
632+
flags: np.ndarray[bool] = np.array(
633+
[bool((raw >> i) & 1) for i in range(10)], dtype=bool
634+
)
635+
636+
return flags

imap_processing/tests/glows/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def l1a_dataset(packet_path):
4646

4747
@pytest.fixture()
4848
def l1b_hist_dataset(l1a_dataset):
49-
return glows_l1b(l1a_dataset[0], "v001")
49+
return glows_l1b(l1a_dataset[1], "v001")
5050

5151

5252
@pytest.fixture()

imap_processing/tests/glows/test_glows_l1a_cdf.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import dataclasses
22
from functools import reduce
3-
from pathlib import Path
43

54
import numpy as np
65
import pytest
@@ -16,13 +15,10 @@
1615
from imap_processing.glows.utils.constants import TimeTuple
1716

1817

19-
@pytest.fixture(scope="module")
20-
def l1a_data():
18+
@pytest.fixture()
19+
def l1a_data(packet_path):
2120
"""Read test data from file"""
22-
current_directory = Path(__file__).parent
23-
packet_path = (
24-
current_directory / "validation_data" / "glows_test_packet_20110921_v01.pkts"
25-
)
21+
2622
histogram_l0, de_l0 = decom_glows.decom_packets(packet_path)
2723

2824
histogram_l1a = [HistogramL1A(hist) for hist in histogram_l0]

imap_processing/tests/glows/test_glows_l1a_data.py

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
import pytest
99

1010
from imap_processing.glows import __version__
11+
from imap_processing.glows.l1a.glows_l1a import glows_l1a
1112
from imap_processing.glows.l1a.glows_l1a_data import (
1213
DirectEventL1A,
1314
HistogramL1A,
1415
StatusData,
1516
)
1617
from imap_processing.glows.utils.constants import DirectEvent, GlowsConstants, TimeTuple
18+
from imap_processing.spice.time import met_to_ttj2000ns
1719

1820

1921
@pytest.fixture()
@@ -38,6 +40,22 @@ def test_histogram_list(histogram_test_data, decom_test_data):
3840
assert sum(histogram_test_data.histogram) == histl0.EVENTS
3941

4042

43+
def test_histogram_obs_day(packet_path):
44+
l1a = glows_l1a(packet_path, "v001")
45+
46+
assert len(l1a) == 3
47+
48+
assert "hist" in l1a[0].attrs["Logical_source"]
49+
assert "hist" in l1a[1].attrs["Logical_source"]
50+
51+
# Numbers pulled from the validation data.
52+
# this test assumes that the "is_night" flag switching from true to false is the
53+
# start of the observation day.
54+
55+
assert np.array_equal(l1a[0]["imap_start_time"].data[0], 54232215.0)
56+
assert np.array_equal(l1a[1]["imap_start_time"].data[0], 54232455.0)
57+
58+
4159
def test_histogram_attributes(histogram_test_data):
4260
"""Test other data in histogram packet"""
4361

@@ -488,7 +506,8 @@ def test_expected_de_results(l1a_test_data):
488506

489507

490508
def test_expected_hist_results(l1a_dataset):
491-
hist = l1a_dataset[0]
509+
end_time = l1a_dataset[0]["epoch"].data[-1]
510+
492511
validation_data = (
493512
Path(__file__).parent / "validation_data" / "glows_l1a_hist_validation.json"
494513
)
@@ -521,15 +540,24 @@ def test_expected_hist_results(l1a_dataset):
521540
"pulse_length_variance",
522541
]
523542

524-
for index, data in enumerate(out["output"]):
543+
for data in out["output"]:
544+
epoch_val = met_to_ttj2000ns(
545+
TimeTuple(
546+
data["imap_start_time"]["seconds"],
547+
data["imap_start_time"]["subseconds"],
548+
).to_seconds()
549+
)
550+
551+
# Validation data spans the two obs days, so this selects the correct output
552+
dataset_index = 1 if epoch_val > end_time else 0
553+
datapoint = l1a_dataset[dataset_index].sel(epoch=epoch_val)
554+
525555
for field in time_fields.keys():
526556
expected_time = (
527557
data[field]["seconds"]
528558
+ data[field]["subseconds"] / GlowsConstants.SUBSECOND_LIMIT
529559
)
530-
assert np.array_equal(
531-
expected_time, hist.isel(epoch=index)[time_fields[field]].data
532-
)
560+
assert np.array_equal(expected_time, datapoint[time_fields[field]].data)
533561

534562
for field in compare_fields:
535-
assert np.array_equal(data[field], hist.isel(epoch=index)[field].data)
563+
assert np.array_equal(data[field], datapoint[field].data)

imap_processing/tests/glows/test_glows_l1b_data.py

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
import pytest
66

77
from imap_processing.glows.l1b.glows_l1b import glows_l1b
8-
from imap_processing.glows.l1b.glows_l1b_data import AncillaryParameters, DirectEventL1B
8+
from imap_processing.glows.l1b.glows_l1b_data import (
9+
AncillaryParameters,
10+
DirectEventL1B,
11+
HistogramL1B,
12+
)
13+
from imap_processing.spice.time import met_to_ttj2000ns
914

1015

1116
def test_glows_l1b_ancillary_file():
@@ -75,11 +80,8 @@ def test_glows_l1b_de():
7580

7681

7782
def test_validation_data_histogram(l1a_dataset):
78-
hist_day_one = l1a_dataset[0]
79-
hist_day_two = l1a_dataset[1]
80-
81-
l1b_day_one = glows_l1b(hist_day_one, "v001")
82-
l1b_day_two = glows_l1b(hist_day_two, "v001")
83+
l1b = [glows_l1b(l1a_dataset[0], "v001"), glows_l1b(l1a_dataset[1], "v001")]
84+
end_time = l1b[0]["epoch"].data[-1]
8385

8486
validation_data = (
8587
Path(__file__).parent
@@ -122,26 +124,23 @@ def test_validation_data_histogram(l1a_dataset):
122124
# "spacecraft_velocity_std_dev": "spacecraft_velocity_std_dev",
123125
}
124126

125-
for index, validation_output in enumerate(out["output"]):
126-
if validation_output["imap_start_time"] < 54259215:
127-
# day of 2011-09-20
128-
l1b = l1b_day_one
129-
l1b_index = index
130-
else:
131-
l1b_index = index - l1b_day_one.epoch.size
132-
l1b = l1b_day_two
127+
for validation_output in out["output"]:
128+
epoch_val = met_to_ttj2000ns(validation_output["imap_start_time"])
129+
130+
# Validation data spans the two obs days, so this selects the correct output
131+
dataset_index = 1 if epoch_val > end_time else 0
132+
datapoint = l1b[dataset_index].sel(epoch=epoch_val)
133133

134134
assert np.equal(
135135
validation_output["imap_start_time"],
136-
l1b.isel(epoch=l1b_index).imap_start_time.data,
136+
datapoint.imap_start_time.data,
137137
)
138138

139139
for key in validation_output:
140140
if key not in expected_matching_columns.keys():
141141
continue
142-
143142
np.testing.assert_array_almost_equal(
144-
l1b[expected_matching_columns[key]].isel(epoch=l1b_index).data,
143+
datapoint[expected_matching_columns[key]].data,
145144
validation_output[key],
146145
decimal=1,
147146
)
@@ -185,3 +184,16 @@ def test_validation_data_de(l1a_dataset):
185184
np.testing.assert_array_almost_equal(
186185
l1b[key].isel(epoch=index).data, validation_output[key], decimal=1
187186
)
187+
188+
189+
@pytest.mark.parametrize(
190+
"flags, expected",
191+
[
192+
(0, np.zeros(10)),
193+
(64, np.array([0, 0, 0, 0, 0, 0, 1, 0, 0, 0])),
194+
(65, np.array([1, 0, 0, 0, 0, 0, 1, 0, 0, 0])),
195+
],
196+
)
197+
def test_deserialize_flags(flags, expected):
198+
output = HistogramL1B.deserialize_flags(flags)
199+
assert np.array_equal(output, expected)

imap_processing/tests/glows/test_glows_l2.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def test_generate_l2(l1b_hist_dataset):
6060

6161
expected_values = {
6262
"filter_temperature_average": [57.59],
63-
"filter_temperature_std_dev": [0.23],
63+
"filter_temperature_std_dev": [0.21],
6464
"hv_voltage_average": [1715.4],
6565
"hv_voltage_std_dev": [0.0],
6666
}

0 commit comments

Comments
 (0)