Skip to content

Commit 1254f55

Browse files
authored
CoDICE: Hi omni refactor (#2361)
1 parent 3530e8a commit 1254f55

8 files changed

Lines changed: 446 additions & 1398 deletions

File tree

imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml

Lines changed: 65 additions & 1369 deletions
Large diffs are not rendered by default.
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
"""CoDICE Hi Omni L1A processing functions."""
2+
3+
import logging
4+
from pathlib import Path
5+
6+
import numpy as np
7+
import xarray as xr
8+
9+
from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
10+
from imap_processing.codice import constants
11+
from imap_processing.codice.decompress import decompress
12+
from imap_processing.codice.utils import (
13+
CODICEAPID,
14+
ViewTabInfo,
15+
apply_replacements_to_attrs,
16+
get_codice_epoch_time,
17+
get_energy_info,
18+
get_view_tab_info,
19+
read_sci_lut,
20+
)
21+
from imap_processing.spice.time import met_to_ttj2000ns
22+
23+
logger = logging.getLogger(__name__)
24+
25+
26+
def l1a_hi_omni(unpacked_dataset: xr.Dataset, lut_file: Path) -> xr.Dataset:
27+
"""
28+
Process CoDICE Hi Omni L1A data.
29+
30+
Parameters
31+
----------
32+
unpacked_dataset : xarray.Dataset
33+
Unpacked dataset from L0 packet file.
34+
lut_file : Path
35+
Path to the LUT file for processing.
36+
37+
Returns
38+
-------
39+
xarray.Dataset
40+
Processed L1A dataset for Hi Omni data.
41+
"""
42+
# Implementation of Hi Omni L1A processing goes here
43+
# Get these values from unpacked data. These are used to
44+
# lookup in LUT table.
45+
table_id = unpacked_dataset["table_id"].values[0]
46+
view_id = unpacked_dataset["view_id"].values[0]
47+
apid = unpacked_dataset["pkt_apid"].values[0]
48+
plan_id = unpacked_dataset["plan_id"].values[0]
49+
plan_step = unpacked_dataset["plan_step"].values[0]
50+
51+
logger.info(
52+
f"Processing species with - APID: {apid}, View ID: {view_id}, "
53+
f"Table ID: {table_id}, Plan ID: {plan_id}, Plan Step: {plan_step}"
54+
)
55+
56+
# ========== Get LUT Data ===========
57+
# Read information from LUT
58+
sci_lut_data = read_sci_lut(lut_file, table_id)
59+
60+
view_tab_info = get_view_tab_info(sci_lut_data, view_id, apid)
61+
view_tab_obj = ViewTabInfo(
62+
apid=apid,
63+
view_id=view_id,
64+
sensor=view_tab_info["sensor"],
65+
three_d_collapsed=view_tab_info["3d_collapse"],
66+
collapse_table=view_tab_info["collapse_table"],
67+
)
68+
69+
if view_tab_obj.sensor != 1:
70+
raise ValueError("Unsupported sensor ID for Hi processing.")
71+
72+
# ========= Decompress and Reshape Data ===========
73+
# Lookup SW or NSW species based on APID
74+
if view_tab_obj.apid == CODICEAPID.COD_HI_OMNI_SPECIES_COUNTS:
75+
species_data = sci_lut_data["data_product_hi_tab"]["0"]["omni"]
76+
species_names = species_data.keys()
77+
logical_source_id = "imap_codice_l1a_hi-omni"
78+
else:
79+
raise ValueError(f"Unknown apid {view_tab_obj.apid} in Hi species processing.")
80+
81+
compression_algorithm = constants.HI_COMPRESSION_ID_LOOKUP[view_tab_obj.view_id]
82+
# Decompress data using byte count information from decommed data
83+
binary_data_list = unpacked_dataset["data"].values
84+
byte_count_list = unpacked_dataset["byte_count"].values
85+
86+
# The decompressed data in the shape of (epoch, n). Then reshape later.
87+
decompressed_data = [
88+
decompress(
89+
packet_data[:byte_count],
90+
compression_algorithm,
91+
)
92+
for (packet_data, byte_count) in zip(
93+
binary_data_list, byte_count_list, strict=False
94+
)
95+
]
96+
97+
# ========= Get Epoch Time Data ===========
98+
# Epoch center time and delta
99+
epoch_center, deltas = get_codice_epoch_time(
100+
unpacked_dataset["acq_start_seconds"].values,
101+
unpacked_dataset["acq_start_subseconds"].values,
102+
unpacked_dataset["spin_period"].values,
103+
view_tab_obj,
104+
)
105+
106+
three_d_collapsed = view_tab_obj.three_d_collapsed
107+
num_packets = len(binary_data_list)
108+
109+
# Repeat deltas n_spins times to match new num_epochs
110+
n_spins = int(16 / three_d_collapsed)
111+
repeated_deltas = np.tile(deltas, n_spins)
112+
# Calculate center of new epoch times using this
113+
# formula:
114+
# epoch_time = epoch_center + (i * delta)
115+
# where i = 0 to n_spins.
116+
# We are repeating each center time 'n_spins' times to
117+
# get new epochs and then multiply by factor. Final and new epoch shape
118+
# is (num_packets * n_spins). It's in seconds.
119+
# TODO: why multiply by 2?
120+
epoch_times = (
121+
np.repeat(epoch_center, n_spins)
122+
+ np.tile(np.arange(n_spins), num_packets)
123+
* np.repeat(deltas, n_spins)
124+
/ 1e9
125+
* 2
126+
)
127+
128+
# ========== Initialize CDF Dataset with Coordinates ===========
129+
cdf_attrs = ImapCdfAttributes()
130+
cdf_attrs.add_instrument_global_attrs("codice")
131+
cdf_attrs.add_instrument_variable_attrs("codice", "l1a")
132+
133+
l1a_dataset = xr.Dataset(
134+
coords={
135+
"epoch": xr.DataArray(
136+
met_to_ttj2000ns(epoch_times),
137+
dims=("epoch",),
138+
attrs=cdf_attrs.get_variable_attributes("epoch", check_schema=False),
139+
),
140+
"epoch_delta_minus": xr.DataArray(
141+
repeated_deltas,
142+
dims=("epoch",),
143+
attrs=cdf_attrs.get_variable_attributes(
144+
"epoch_delta_minus", check_schema=False
145+
),
146+
),
147+
"epoch_delta_plus": xr.DataArray(
148+
repeated_deltas,
149+
dims=("epoch",),
150+
attrs=cdf_attrs.get_variable_attributes(
151+
"epoch_delta_plus", check_schema=False
152+
),
153+
),
154+
},
155+
attrs=cdf_attrs.get_global_attributes(logical_source_id),
156+
)
157+
158+
# Reshape decompressed data to:
159+
# decompressed_data -> (9, 480)
160+
# Then we will parse 480 data into species below for looping.
161+
decompressed_data = np.array(decompressed_data).reshape(num_packets, n_spins * 120)
162+
163+
# Use chunks of (energy_x) to put data in its energy bins as done below.
164+
# Eg. [15, 15, 15, 18, 18, 15, 18, 5, 1]
165+
# where each number is energy dimension for species 'x'.
166+
species_chunk_sizes = [
167+
len(species_data[species]["min_energy"]) for species in species_names
168+
]
169+
start_idx = 0
170+
for index, (species_name, data) in enumerate(species_data.items()):
171+
# Add coordinate for 'energy_{species_name}'
172+
centers, energy_minus, energy_plus = get_energy_info(data)
173+
energy_attrs = cdf_attrs.get_variable_attributes(
174+
"hi-energy-attrs", check_schema=False
175+
)
176+
energy_attrs = apply_replacements_to_attrs(
177+
energy_attrs, {"species": species_name}
178+
)
179+
l1a_dataset = l1a_dataset.assign_coords(
180+
{
181+
f"energy_{species_name}": xr.DataArray(
182+
np.array(centers),
183+
dims=(f"energy_{species_name}",),
184+
attrs=energy_attrs,
185+
)
186+
}
187+
)
188+
# Add energy minus variables
189+
delta_attrs = cdf_attrs.get_variable_attributes("hi-energy-delta-attrs")
190+
delta_attrs = apply_replacements_to_attrs(
191+
delta_attrs, {"species": species_name, "operation": "minus"}
192+
)
193+
l1a_dataset[f"energy_{species_name}_minus"] = xr.DataArray(
194+
energy_minus, dims=(f"energy_{species_name}",), attrs=delta_attrs
195+
)
196+
# Add energy plus variable
197+
delta_attrs = apply_replacements_to_attrs(
198+
delta_attrs, {"species": species_name, "operation": "plus"}
199+
)
200+
l1a_dataset[f"energy_{species_name}_plus"] = xr.DataArray(
201+
energy_plus, dims=(f"energy_{species_name}",), attrs=delta_attrs
202+
)
203+
204+
# Now, we put species data into its energy bins using indices like this:
205+
# Eg. species h's 4 spins data are in these indices:
206+
# All h energy data of first spin = [0,4,8,12,… 56].
207+
# All h energy data of second spin = [1,5,9,…,57].
208+
# All h energy data of third spin = [2,6,10,…,58].
209+
# All h energy data of fourth spin = [3,7,11,…,59].
210+
# In other words, H - [0 - 59] contains 4 spins data of h's 15 energy bins
211+
# and repeated this pattern for other species in order.
212+
# Eg. He3 - [60 - 119] and so on.
213+
214+
chunk_size = species_chunk_sizes[index]
215+
# Now parse the decompressed data into species as mentioned in above comment
216+
# using start and end indices.
217+
# End indices is start + (chunk size * n_spins)
218+
end_idx = start_idx + chunk_size * n_spins
219+
# Get specie's data by (num_epochs, start_idx:end_idx)
220+
# Eg. (9, 60) for H
221+
species_data = decompressed_data[:, start_idx:end_idx]
222+
# Reshape the data to (num_epochs, species_chunk_size, n_spins) to begin
223+
# getting data into it's final state.
224+
# Eg. (9, 15, 4)
225+
species_data = species_data.reshape(-1, chunk_size, n_spins)
226+
# Then transpose into (num_epochs, n_spins, species_chunk_size) and reshape
227+
# into (num_epochs * n_spins, species_chunk_size) to get final state.
228+
# Eg. (36, 15)
229+
species_data = species_data.transpose(0, 2, 1).reshape(-1, chunk_size)
230+
species_attrs = cdf_attrs.get_variable_attributes("hi-species-attrs")
231+
species_attrs = apply_replacements_to_attrs(
232+
species_attrs, {"species": species_name}
233+
)
234+
l1a_dataset[species_name] = xr.DataArray(
235+
species_data,
236+
dims=("epoch", f"energy_{species_name}"),
237+
attrs=species_attrs,
238+
)
239+
species_unc_attrs = cdf_attrs.get_variable_attributes("hi-species-unc-attrs")
240+
species_unc_attrs = apply_replacements_to_attrs(
241+
species_unc_attrs, {"species": species_name}
242+
)
243+
l1a_dataset[f"unc_{species_name}"] = xr.DataArray(
244+
np.sqrt(species_data),
245+
dims=("epoch", f"energy_{species_name}"),
246+
attrs=species_unc_attrs,
247+
)
248+
# Increment start index
249+
start_idx = end_idx
250+
251+
# ========= Add Additional Variables ===========
252+
# Repeat spin_period and data_quality to match new epoch shape (num_epochs)
253+
l1a_dataset["spin_period"] = xr.DataArray(
254+
np.repeat(unpacked_dataset["spin_period"].values, n_spins)
255+
* constants.SPIN_PERIOD_CONVERSION,
256+
dims=("epoch",),
257+
attrs=cdf_attrs.get_variable_attributes("spin_period"),
258+
)
259+
l1a_dataset["data_quality"] = xr.DataArray(
260+
np.repeat(unpacked_dataset["suspect"].values, n_spins),
261+
dims=("epoch",),
262+
attrs=cdf_attrs.get_variable_attributes("data_quality"),
263+
)
264+
265+
return l1a_dataset

imap_processing/codice/codice_l1a_lo_angular.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
index_to_position,
2020
read_sci_lut,
2121
)
22+
from imap_processing.spice.time import met_to_ttj2000ns
2223

2324
logger = logging.getLogger(__name__)
2425

@@ -76,12 +77,12 @@ def _despin_species_data(
7677
despun_data[:, :, orientation_a, :12, pos_idx] = species_data[
7778
:, :, orientation_a, :, pos_idx
7879
]
79-
# Case 2: position 12-24, orientation B, append to second half
80+
# Case 2: position 13-24, orientation B, append to second half
8081
despun_data[:, :, orientation_b, 12:, pos_idx] = species_data[
8182
:, :, orientation_b, :, pos_idx
8283
]
8384
else:
84-
# Case 3: position 12-24, orientation A, append to second half
85+
# Case 3: position 13-24, orientation A, append to second half
8586
despun_data[:, :, orientation_a, 12:, pos_idx] = species_data[
8687
:, :, orientation_a, :, pos_idx
8788
]
@@ -214,7 +215,7 @@ def l1a_lo_angular(unpacked_dataset: xr.Dataset, lut_file: Path) -> xr.Dataset:
214215
l1a_dataset = xr.Dataset(
215216
coords={
216217
"epoch": xr.DataArray(
217-
epoch_center,
218+
met_to_ttj2000ns(epoch_center),
218219
dims=("epoch",),
219220
attrs=cdf_attrs.get_variable_attributes("epoch", check_schema=False),
220221
),

imap_processing/codice/codice_l1a_lo_species.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
get_view_tab_info,
1919
read_sci_lut,
2020
)
21+
from imap_processing.spice.time import met_to_ttj2000ns
2122

2223
logger = logging.getLogger(__name__)
2324

@@ -145,7 +146,7 @@ def l1a_lo_species(unpacked_dataset: xr.Dataset, lut_file: Path) -> xr.Dataset:
145146
l1a_dataset = xr.Dataset(
146147
coords={
147148
"epoch": xr.DataArray(
148-
epoch_center,
149+
met_to_ttj2000ns(epoch_center),
149150
dims=("epoch",),
150151
attrs=cdf_attrs.get_variable_attributes("epoch", check_schema=False),
151152
),

imap_processing/codice/codice_new_l1a.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from imap_data_access import ProcessingInputCollection
77

88
from imap_processing import imap_module_directory
9+
from imap_processing.codice.codice_l1a_hi_omni import l1a_hi_omni
910
from imap_processing.codice.codice_l1a_lo_angular import l1a_lo_angular
1011
from imap_processing.codice.codice_l1a_lo_species import l1a_lo_species
1112
from imap_processing.codice.utils import (
@@ -60,5 +61,7 @@ def process_l1a(dependency: ProcessingInputCollection) -> list[xr.Dataset]:
6061
elif apid == CODICEAPID.COD_LO_NSW_ANGULAR_COUNTS:
6162
logger.info("Processing Lo NSW Angular Counts")
6263
datasets.append(l1a_lo_angular(datasets_by_apid[apid], lut_file))
64+
elif apid == CODICEAPID.COD_HI_OMNI_SPECIES_COUNTS:
65+
datasets.append(l1a_hi_omni(datasets_by_apid[apid], lut_file))
6366

6467
return datasets

0 commit comments

Comments
 (0)