Skip to content

Commit 7e80f6a

Browse files
authored
Implement CoDICE L1a hi-ialirt (IMAP-Science-Operations-Center#1786)
1 parent 0bf9278 commit 7e80f6a

4 files changed

Lines changed: 187 additions & 153 deletions

File tree

imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,17 @@ hi-ialirt-h:
401401
LABL_PTR_2: inst_az
402402
VALIDMAX: 16777216
403403

404+
hi-ialirt-energy_h:
405+
<<: *hi_energies_default
406+
CATDESC: Energy Table for h
407+
DELTA_MINUS_VAR: energy_h_delta
408+
DELTA_PLUS_VAR: energy_h_delta
409+
410+
hi-ialirt-energy_h_delta:
411+
<<: *hi_energies_default
412+
CATDESC: Delta of energies for h
413+
FIELDNAM: Delta Energy
414+
404415
# hi-omni
405416
hi-omni-h:
406417
<<: *counters

imap_processing/codice/codice_l1a.py

Lines changed: 139 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ class CoDICEL1aPipeline:
7474
Retrieve the ESA sweep values.
7575
get_hi_energy_table_data(species)
7676
Retrieve energy table data for CoDICE-Hi products
77+
reshape_binned_data(dataset)
78+
Reshape data arrays for binned datasets.
7779
reshape_data()
7880
Reshape the data arrays based on the data product being made.
7981
set_data_product_config()
@@ -92,8 +94,7 @@ def calculate_epoch_values(self) -> NDArray[int]:
9294
Calculate and return the values to be used for `epoch`.
9395
9496
On CoDICE, the epoch values are derived from the `acq_start_seconds` and
95-
`acq_start_subseconds` fields in the packet. The exception to this is
96-
the I-ALiRT packets, which use "acquisition_time".
97+
`acq_start_subseconds` fields in the packet.
9798
9899
Note that the `acq_start_subseconds` field needs to be converted from
99100
microseconds to seconds.
@@ -134,7 +135,7 @@ def decompress_data(self, science_values: list[NDArray[str]] | list[str]) -> Non
134135

135136
# I-ALiRT data already has byte count cut-off applied, so treat
136137
# it slightly differently
137-
if self.config["dataset_name"] == "imap_codice_l1a_lo-ialirt":
138+
if "ialirt" in self.config["dataset_name"]:
138139
for packet_data in science_values:
139140
# Convert from bit string to byte object
140141
values = int(packet_data, 2).to_bytes(
@@ -506,6 +507,79 @@ def get_hi_energy_table_data(
506507

507508
return centers, deltas
508509

510+
def reshape_binned_data(self, dataset: xr.Dataset) -> dict[str, list]:
511+
"""
512+
Reshape data arrays for binned datasets.
513+
514+
Binned datasets get reshaped based on the number of species and their
515+
corresponding number of energy bins. Additionally, the number of spins
516+
during data acquisition are collapsed/summed which also needs to be taken
517+
into account when reshaping into the correct dimensions.
518+
519+
Parameters
520+
----------
521+
dataset : xarray.Dataset
522+
``xarray`` dataset for the data product.
523+
524+
Returns
525+
-------
526+
data : dict[str, list]
527+
Data arrays for each species.
528+
"""
529+
# This will hold all of the data per-species and support variables,
530+
# ready to be put in a CDF file
531+
data: dict[str, list] = {}
532+
for species in self.config["energy_table"]:
533+
data[species] = []
534+
data["epoch"] = []
535+
data["spin_period"] = []
536+
data["data_quality"] = []
537+
538+
# Get the number of spins per species
539+
num_spins = self.config["num_spins"]
540+
541+
# Iterate through each epoch's data and pull out the data for each
542+
# species
543+
stacked_data = np.array(self.raw_data, dtype=np.uint32)
544+
for i, epoch in enumerate(stacked_data):
545+
current_epoch = dataset.epoch.data[i]
546+
position = 0
547+
for species in self.config["energy_table"]:
548+
# Subtracting one here since the table includes endpoints
549+
num_bins = len(self.config["energy_table"][species]) - 1
550+
species_data = (
551+
epoch[position : position + num_bins * self.config["num_spins"]]
552+
.reshape(num_bins, num_spins)
553+
.T
554+
)
555+
556+
# Now pull out the data for each spin within the species data
557+
for spin_data in species_data:
558+
data[species].append(spin_data)
559+
560+
# We only need one set of support variables in the CDF,
561+
# so just iterate using one species for these
562+
if species == "h":
563+
# For each spin, we add <spin_period>*<num_spins> to the
564+
# epoch value
565+
spin_period = (
566+
dataset.spin_period.data[i]
567+
* constants.SPIN_PERIOD_CONVERSION
568+
)
569+
epoch_value = current_epoch + np.int64(
570+
(spin_period * num_spins) * 1e9 # Convert from s to ns
571+
)
572+
data["epoch"].append(epoch_value)
573+
current_epoch = epoch_value
574+
575+
# Other support variables
576+
data["spin_period"].append(spin_period)
577+
data["data_quality"].append(dataset.suspect.data[i])
578+
579+
position += num_bins * num_spins
580+
581+
return data
582+
509583
def reshape_data(self) -> None:
510584
"""
511585
Reshape the data arrays based on the data product being made.
@@ -625,7 +699,9 @@ def group_ialirt_data(packets: xr.Dataset, data_field_range: range) -> list[byte
625699
return grouped_data
626700

627701

628-
def create_binned_dataset(apid: int, dataset: xr.Dataset) -> xr.Dataset:
702+
def create_binned_dataset(
703+
apid: int, dataset: xr.Dataset, science_values: list[str]
704+
) -> xr.Dataset:
629705
"""
630706
Create dataset for data that is binned by energy.
631707
@@ -640,6 +716,8 @@ def create_binned_dataset(apid: int, dataset: xr.Dataset) -> xr.Dataset:
640716
The APID of the packet.
641717
dataset : xarray.Dataset
642718
The packets to process.
719+
science_values : list[str]
720+
The values of the "data" field of the dataset.
643721
644722
Returns
645723
-------
@@ -649,9 +727,6 @@ def create_binned_dataset(apid: int, dataset: xr.Dataset) -> xr.Dataset:
649727
# TODO: hi-sectored data product should be processed similar to hi-omni,
650728
# so I should be able to use this method.
651729

652-
# Extract the data
653-
science_values = [packet.data for packet in dataset.data]
654-
655730
# Get the four "main" parameters for processing
656731
table_id, plan_id, plan_step, view_id = get_params(dataset)
657732

@@ -661,61 +736,7 @@ def create_binned_dataset(apid: int, dataset: xr.Dataset) -> xr.Dataset:
661736
pipeline.set_data_product_config(apid, dataset)
662737
pipeline.decompress_data(science_values)
663738

664-
# hi-omni data gets reshaped a bit differently than other products,
665-
# so we need to stray away from the nominal pipeline
666-
stacked_data = np.stack(
667-
[np.array(item, dtype=np.uint32) for item in pipeline.raw_data]
668-
)
669-
670-
# This will hold all of the data per-species and support variables,
671-
# ready to be put in a CDF file
672-
data: dict[str, list] = {}
673-
for species in pipeline.config["energy_table"]:
674-
data[species] = []
675-
data["epoch"] = []
676-
data["spin_period"] = []
677-
data["data_quality"] = []
678-
679-
# Get the number of spins per species
680-
num_spins = pipeline.config["num_spins"]
681-
682-
# Iterate through each epoch's data and pull out the data for each
683-
# species
684-
for i, epoch in enumerate(stacked_data):
685-
current_epoch = dataset.epoch.data[i]
686-
position = 0
687-
for species in pipeline.config["energy_table"]:
688-
num_bins = (
689-
len(pipeline.config["energy_table"][species]) - 1
690-
) # Subtracting one here since the table includes endpoints
691-
species_data = (
692-
epoch[position : position + num_bins * pipeline.config["num_spins"]]
693-
.reshape(num_bins, num_spins)
694-
.T
695-
)
696-
697-
# Now pull out the data for each spin within the species data
698-
for spin_data in species_data:
699-
data[species].append(spin_data)
700-
701-
# We only need one set of support variables in the CDF,
702-
# so just iterate using one species for these
703-
if species == "h":
704-
# For each spin, we add <spin_period>*<num_spins> to the epoch value
705-
spin_period = (
706-
dataset.spin_period.data[i] * constants.SPIN_PERIOD_CONVERSION
707-
)
708-
epoch_value = current_epoch + np.int64(
709-
(spin_period * num_spins) * 1e9 # Convert from s to ns
710-
)
711-
data["epoch"].append(epoch_value)
712-
current_epoch = epoch_value
713-
714-
# Other support variables
715-
data["spin_period"].append(spin_period)
716-
data["data_quality"].append(dataset.suspect.data[i])
717-
718-
position += num_bins * num_spins
739+
data = pipeline.reshape_binned_data(dataset)
719740

720741
# Create the main dataset to hold all the variables
721742
coord = xr.DataArray(
@@ -957,42 +978,64 @@ def create_ialirt_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
957978
# Group together packets of I-ALiRT data to form complete data sets
958979
grouped_data = group_ialirt_data(packets, data_field_range)
959980

981+
# Process each group to get the science data and corresponding metadata
960982
science_values, metadata_values = process_ialirt_data_streams(grouped_data)
961983

962-
# Run the pipeline to create a dataset for the product
963-
pipeline = CoDICEL1aPipeline(
964-
metadata_values["TABLE_ID"][0],
965-
metadata_values["PLAN_ID"][0],
966-
metadata_values["PLAN_STEP"][0],
967-
metadata_values["VIEW_ID"][0],
968-
)
969-
pipeline.set_data_product_config(apid, packets)
970-
pipeline.decompress_data(science_values)
971-
pipeline.reshape_data()
972-
973-
# The calculate_epoch_values method needs acq_start_seconds and
974-
# acq_start_subseconds attributes on the dataset
975-
pipeline.dataset["acq_start_seconds"] = ("_", metadata_values["ACQ_START_SECONDS"])
976-
pipeline.dataset["acq_start_subseconds"] = (
977-
"_",
978-
metadata_values["ACQ_START_SUBSECONDS"],
979-
)
984+
# How data are processed is different for lo-iarlirt and hi-ialirt
985+
if apid == CODICEAPID.COD_HI_IAL:
986+
# Set some necessary values and process as a binned dataset similar to
987+
# a hi-omni data product
988+
metadata_for_processing = [
989+
"table_id",
990+
"plan_id",
991+
"plan_step",
992+
"view_id",
993+
"spin_period",
994+
"suspect",
995+
]
996+
for var in metadata_for_processing:
997+
packets[var] = metadata_values[var.upper()]
998+
dataset = create_binned_dataset(apid, packets, science_values)
999+
1000+
elif apid == CODICEAPID.COD_LO_IAL:
1001+
# Create a nominal instance of the pipeline and process similar to a
1002+
# lo-sw-species data product
1003+
pipeline = CoDICEL1aPipeline(
1004+
metadata_values["TABLE_ID"][0],
1005+
metadata_values["PLAN_ID"][0],
1006+
metadata_values["PLAN_STEP"][0],
1007+
metadata_values["VIEW_ID"][0],
1008+
)
1009+
pipeline.set_data_product_config(apid, packets)
1010+
pipeline.decompress_data(science_values)
1011+
pipeline.reshape_data()
1012+
1013+
# The calculate_epoch_values method needs acq_start_seconds and
1014+
# acq_start_subseconds attributes on the dataset
1015+
pipeline.dataset["acq_start_seconds"] = (
1016+
"_",
1017+
metadata_values["ACQ_START_SECONDS"],
1018+
)
1019+
pipeline.dataset["acq_start_subseconds"] = (
1020+
"_",
1021+
metadata_values["ACQ_START_SUBSECONDS"],
1022+
)
9801023

981-
pipeline.define_coordinates()
1024+
pipeline.define_coordinates()
9821025

983-
# The dataset also needs the metadata that will be carried through
984-
# to the final data product
985-
for field in [
986-
"spin_period",
987-
"suspect",
988-
"st_bias_gain_mode",
989-
"sw_bias_gain_mode",
990-
"rgfo_half_spin",
991-
"nso_half_spin",
992-
]:
993-
pipeline.dataset[field] = ("_", metadata_values[field.upper()])
1026+
# The dataset also needs the metadata that will be carried through
1027+
# to the final data product
1028+
for field in [
1029+
"spin_period",
1030+
"suspect",
1031+
"st_bias_gain_mode",
1032+
"sw_bias_gain_mode",
1033+
"rgfo_half_spin",
1034+
"nso_half_spin",
1035+
]:
1036+
pipeline.dataset[field] = ("_", metadata_values[field.upper()])
9941037

995-
dataset = pipeline.define_data_variables()
1038+
dataset = pipeline.define_data_variables()
9961039

9971040
return dataset
9981041

@@ -1379,13 +1422,14 @@ def process_codice_l1a(file_path: Path) -> list[xr.Dataset]:
13791422
logger.info(f"\nFinal data product:\n{processed_dataset}\n")
13801423

13811424
# I-ALiRT data
1382-
elif apid in [CODICEAPID.COD_LO_IAL]:
1425+
elif apid in [CODICEAPID.COD_LO_IAL, CODICEAPID.COD_HI_IAL]:
13831426
processed_dataset = create_ialirt_dataset(apid, dataset)
13841427
logger.info(f"\nFinal data product:\n{processed_dataset}\n")
13851428

13861429
# hi-omni data
13871430
elif apid == CODICEAPID.COD_HI_OMNI_SPECIES_COUNTS:
1388-
processed_dataset = create_binned_dataset(apid, dataset)
1431+
science_values = [packet.data for packet in dataset.data]
1432+
processed_dataset = create_binned_dataset(apid, dataset, science_values)
13891433
logger.info(f"\nFinal data product:\n{processed_dataset}\n")
13901434

13911435
# Everything else
@@ -1406,11 +1450,6 @@ def process_codice_l1a(file_path: Path) -> list[xr.Dataset]:
14061450

14071451
logger.info(f"\nFinal data product:\n{processed_dataset}\n")
14081452

1409-
# TODO: Still need to implement hi-ialirt
1410-
elif apid == CODICEAPID.COD_HI_IAL:
1411-
logger.info("\tStill need to properly implement")
1412-
processed_dataset = None
1413-
14141453
# For APIDs that don't require processing
14151454
else:
14161455
logger.info(f"\t{apid} does not require processing")

imap_processing/codice/constants.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,27 @@
226226
# and deltas of the bins, which then get stored in the CDF files for future use.
227227
# These are defined in the "Data Products - Hi" tab of the "*-SCI-LUT-*.xml"
228228
# spreadsheet that largely defines CoDICE processing.
229+
IALIRT_ENERGY_TABLE = {
230+
"h": [
231+
0.05,
232+
0.070710678,
233+
0.1,
234+
0.141421356,
235+
0.2,
236+
0.282842712,
237+
0.4,
238+
0.565685425,
239+
0.8,
240+
1.13137085,
241+
1.6,
242+
2.2627417,
243+
3.2,
244+
4.5254834,
245+
6.4,
246+
9.050966799,
247+
],
248+
}
249+
229250
OMNI_ENERGY_TABLE = {
230251
"h": [
231252
0.05,
@@ -381,7 +402,7 @@
381402
DATA_PRODUCT_CONFIGURATIONS: dict[CODICEAPID | int, dict] = {
382403
CODICEAPID.COD_HI_IAL: {
383404
"dataset_name": "imap_codice_l1a_hi-ialirt",
384-
"energy_table": OMNI_ENERGY_TABLE,
405+
"energy_table": IALIRT_ENERGY_TABLE,
385406
"input_dims": {"esa_step": 15, "inst_az": 4},
386407
"instrument": "hi",
387408
"num_counters": 1,

0 commit comments

Comments
 (0)