imap-processing 0.19.0__py3-none-any.whl → 0.19.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of imap-processing might be problematic. Click here for more details.
- imap_processing/_version.py +2 -2
- imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -0
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +31 -894
- imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +279 -255
- imap_processing/cdf/config/imap_enamaps_l2-common_variable_attrs.yaml +55 -0
- imap_processing/cdf/config/imap_enamaps_l2-healpix_variable_attrs.yaml +29 -0
- imap_processing/cdf/config/imap_enamaps_l2-rectangular_variable_attrs.yaml +32 -0
- imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +3 -1
- imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +5 -4
- imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +28 -16
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +33 -31
- imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +61 -1
- imap_processing/cli.py +62 -71
- imap_processing/codice/codice_l0.py +2 -1
- imap_processing/codice/codice_l1a.py +47 -49
- imap_processing/codice/codice_l1b.py +42 -32
- imap_processing/codice/codice_l2.py +105 -7
- imap_processing/codice/constants.py +50 -8
- imap_processing/codice/data/lo_stepping_values.csv +1 -1
- imap_processing/ena_maps/ena_maps.py +39 -18
- imap_processing/ena_maps/utils/corrections.py +291 -0
- imap_processing/ena_maps/utils/map_utils.py +20 -4
- imap_processing/glows/l1b/glows_l1b.py +38 -23
- imap_processing/glows/l1b/glows_l1b_data.py +10 -11
- imap_processing/hi/hi_l1c.py +4 -109
- imap_processing/hi/hi_l2.py +34 -23
- imap_processing/hi/utils.py +109 -0
- imap_processing/ialirt/l0/ialirt_spice.py +1 -1
- imap_processing/ialirt/l0/parse_mag.py +18 -4
- imap_processing/ialirt/l0/process_hit.py +9 -4
- imap_processing/ialirt/l0/process_swapi.py +9 -4
- imap_processing/ialirt/l0/process_swe.py +9 -4
- imap_processing/ialirt/utils/create_xarray.py +1 -1
- imap_processing/lo/ancillary_data/imap_lo_hydrogen-geometric-factor_v001.csv +75 -0
- imap_processing/lo/ancillary_data/imap_lo_oxygen-geometric-factor_v001.csv +75 -0
- imap_processing/lo/l1b/lo_l1b.py +90 -16
- imap_processing/lo/l1c/lo_l1c.py +164 -50
- imap_processing/lo/l2/lo_l2.py +941 -127
- imap_processing/mag/l1d/mag_l1d_data.py +36 -3
- imap_processing/mag/l2/mag_l2.py +2 -0
- imap_processing/mag/l2/mag_l2_data.py +4 -3
- imap_processing/quality_flags.py +14 -0
- imap_processing/spice/geometry.py +13 -8
- imap_processing/spice/pointing_frame.py +4 -2
- imap_processing/spice/repoint.py +49 -0
- imap_processing/ultra/constants.py +29 -0
- imap_processing/ultra/l0/decom_tools.py +58 -46
- imap_processing/ultra/l0/decom_ultra.py +21 -9
- imap_processing/ultra/l0/ultra_utils.py +4 -4
- imap_processing/ultra/l1b/badtimes.py +35 -11
- imap_processing/ultra/l1b/de.py +15 -9
- imap_processing/ultra/l1b/extendedspin.py +24 -12
- imap_processing/ultra/l1b/goodtimes.py +112 -0
- imap_processing/ultra/l1b/lookup_utils.py +1 -1
- imap_processing/ultra/l1b/ultra_l1b.py +7 -7
- imap_processing/ultra/l1b/ultra_l1b_culling.py +8 -4
- imap_processing/ultra/l1b/ultra_l1b_extended.py +79 -43
- imap_processing/ultra/l1c/helio_pset.py +68 -39
- imap_processing/ultra/l1c/l1c_lookup_utils.py +45 -12
- imap_processing/ultra/l1c/spacecraft_pset.py +81 -37
- imap_processing/ultra/l1c/ultra_l1c.py +27 -22
- imap_processing/ultra/l1c/ultra_l1c_culling.py +7 -0
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +41 -41
- imap_processing/ultra/l2/ultra_l2.py +75 -18
- imap_processing/ultra/utils/ultra_l1_utils.py +10 -5
- {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/METADATA +2 -2
- {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/RECORD +71 -69
- imap_processing/ultra/l1b/cullingmask.py +0 -90
- imap_processing/ultra/l1c/histogram.py +0 -36
- /imap_processing/glows/ancillary/{imap_glows_pipeline_settings_20250923_v002.json → imap_glows_pipeline-settings_20250923_v002.json} +0 -0
- {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/LICENSE +0 -0
- {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/WHEEL +0 -0
- {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/entry_points.txt +0 -0
imap_processing/cli.py
CHANGED
|
@@ -24,8 +24,7 @@ import imap_data_access
|
|
|
24
24
|
import numpy as np
|
|
25
25
|
import spiceypy
|
|
26
26
|
import xarray as xr
|
|
27
|
-
from imap_data_access import
|
|
28
|
-
from imap_data_access.io import download
|
|
27
|
+
from imap_data_access.io import IMAPDataAccessError, download
|
|
29
28
|
from imap_data_access.processing_input import (
|
|
30
29
|
ProcessingInputCollection,
|
|
31
30
|
ProcessingInputType,
|
|
@@ -407,32 +406,23 @@ class ProcessInstrument(ABC):
|
|
|
407
406
|
A list of file paths to upload to the SDC.
|
|
408
407
|
"""
|
|
409
408
|
if self.upload_to_sdc:
|
|
410
|
-
|
|
411
|
-
for filename in products:
|
|
412
|
-
file_path = ScienceFilePath(filename)
|
|
413
|
-
existing_file = imap_data_access.query(
|
|
414
|
-
instrument=file_path.instrument,
|
|
415
|
-
data_level=file_path.data_level,
|
|
416
|
-
descriptor=file_path.descriptor,
|
|
417
|
-
start_date=file_path.start_date,
|
|
418
|
-
end_date=file_path.start_date,
|
|
419
|
-
repointing=file_path.repointing,
|
|
420
|
-
version=file_path.version,
|
|
421
|
-
extension="cdf",
|
|
422
|
-
table="science",
|
|
423
|
-
)
|
|
424
|
-
if existing_file:
|
|
425
|
-
raise ProcessInstrument.ImapFileExistsError(
|
|
426
|
-
f"File {filename} already exists in the IMAP SDC. "
|
|
427
|
-
"No files were uploaded."
|
|
428
|
-
f"Generated files: {products}."
|
|
429
|
-
)
|
|
430
|
-
|
|
431
|
-
if len(products) == 0:
|
|
409
|
+
if not products:
|
|
432
410
|
logger.info("No files to upload.")
|
|
411
|
+
return
|
|
412
|
+
|
|
433
413
|
for filename in products:
|
|
434
|
-
|
|
435
|
-
|
|
414
|
+
try:
|
|
415
|
+
logger.info(f"Uploading file: {filename}")
|
|
416
|
+
imap_data_access.upload(filename)
|
|
417
|
+
except IMAPDataAccessError as e:
|
|
418
|
+
msg = str(e)
|
|
419
|
+
if "FileAlreadyExists" in msg and "409" in msg:
|
|
420
|
+
logger.warning("Skipping upload of existing file, %s", filename)
|
|
421
|
+
continue
|
|
422
|
+
else:
|
|
423
|
+
logger.error(f"Upload failed with error: {msg}")
|
|
424
|
+
except Exception as e:
|
|
425
|
+
logger.error(f"Upload failed unknown error: {e}")
|
|
436
426
|
|
|
437
427
|
@final
|
|
438
428
|
def process(self) -> None:
|
|
@@ -676,7 +666,7 @@ class Glows(ProcessInstrument):
|
|
|
676
666
|
datasets: list[xr.Dataset] = []
|
|
677
667
|
|
|
678
668
|
if self.data_level == "l1a":
|
|
679
|
-
science_files = dependencies.get_file_paths(source="glows")
|
|
669
|
+
science_files = dependencies.get_file_paths(source="glows", data_type="l0")
|
|
680
670
|
if len(science_files) != 1:
|
|
681
671
|
raise ValueError(
|
|
682
672
|
f"GLOWS L1A requires exactly one input science file, received: "
|
|
@@ -685,33 +675,30 @@ class Glows(ProcessInstrument):
|
|
|
685
675
|
datasets = glows_l1a(science_files[0])
|
|
686
676
|
|
|
687
677
|
if self.data_level == "l1b":
|
|
688
|
-
science_files = dependencies.get_file_paths(source="glows")
|
|
678
|
+
science_files = dependencies.get_file_paths(source="glows", data_type="l1a")
|
|
689
679
|
if len(science_files) != 1:
|
|
690
680
|
raise ValueError(
|
|
691
681
|
f"GLOWS L1B requires exactly one input science file, received: "
|
|
692
682
|
f"{science_files}."
|
|
693
683
|
)
|
|
694
684
|
input_dataset = load_cdf(science_files[0])
|
|
695
|
-
# TODO: Replace this by reading from AWS/ProcessingInputs
|
|
696
|
-
|
|
697
|
-
glows_ancillary_dir = Path(__file__).parent / "glows" / "ancillary"
|
|
698
685
|
|
|
699
686
|
# Create file lists for each ancillary type
|
|
700
|
-
excluded_regions_files =
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
]
|
|
687
|
+
excluded_regions_files = dependencies.get_processing_inputs(
|
|
688
|
+
descriptor="map-of-excluded-regions"
|
|
689
|
+
)[0]
|
|
690
|
+
uv_sources_files = dependencies.get_processing_inputs(
|
|
691
|
+
descriptor="map-of-uv-sources"
|
|
692
|
+
)[0]
|
|
693
|
+
suspected_transients_files = dependencies.get_processing_inputs(
|
|
694
|
+
descriptor="suspected-transients"
|
|
695
|
+
)[0]
|
|
696
|
+
exclusions_by_instr_team_files = dependencies.get_processing_inputs(
|
|
697
|
+
descriptor="exclusions-by-instr-team"
|
|
698
|
+
)[0]
|
|
699
|
+
pipeline_settings = dependencies.get_processing_inputs(
|
|
700
|
+
descriptor="pipeline-settings"
|
|
701
|
+
)[0]
|
|
715
702
|
|
|
716
703
|
# Use end date buffer for ancillary data
|
|
717
704
|
current_day = np.datetime64(
|
|
@@ -730,6 +717,9 @@ class Glows(ProcessInstrument):
|
|
|
730
717
|
exclusions_by_instr_team_combiner = GlowsAncillaryCombiner(
|
|
731
718
|
exclusions_by_instr_team_files, day_buffer
|
|
732
719
|
)
|
|
720
|
+
pipeline_settings_combiner = GlowsAncillaryCombiner(
|
|
721
|
+
pipeline_settings, day_buffer
|
|
722
|
+
)
|
|
733
723
|
|
|
734
724
|
datasets = [
|
|
735
725
|
glows_l1b(
|
|
@@ -738,6 +728,7 @@ class Glows(ProcessInstrument):
|
|
|
738
728
|
uv_sources_combiner.combined_dataset,
|
|
739
729
|
suspected_transients_combiner.combined_dataset,
|
|
740
730
|
exclusions_by_instr_team_combiner.combined_dataset,
|
|
731
|
+
pipeline_settings_combiner.combined_dataset,
|
|
741
732
|
)
|
|
742
733
|
]
|
|
743
734
|
|
|
@@ -1020,16 +1011,19 @@ class Lo(ProcessInstrument):
|
|
|
1020
1011
|
elif self.data_level == "l1b":
|
|
1021
1012
|
data_dict = {}
|
|
1022
1013
|
science_files = dependencies.get_file_paths(source="lo", data_type="l1a")
|
|
1014
|
+
ancillary_files = dependencies.get_file_paths(
|
|
1015
|
+
source="lo", data_type="ancillary"
|
|
1016
|
+
)
|
|
1023
1017
|
logger.info(f"Science files for L1B: {science_files}")
|
|
1024
1018
|
for file in science_files:
|
|
1025
1019
|
dataset = load_cdf(file)
|
|
1026
1020
|
data_dict[dataset.attrs["Logical_source"]] = dataset
|
|
1027
|
-
datasets = lo_l1b.lo_l1b(data_dict)
|
|
1021
|
+
datasets = lo_l1b.lo_l1b(data_dict, ancillary_files)
|
|
1028
1022
|
|
|
1029
1023
|
elif self.data_level == "l1c":
|
|
1030
1024
|
data_dict = {}
|
|
1031
1025
|
anc_dependencies: list = dependencies.get_file_paths(
|
|
1032
|
-
source="lo",
|
|
1026
|
+
source="lo", data_type="ancillary"
|
|
1033
1027
|
)
|
|
1034
1028
|
science_files = dependencies.get_file_paths(source="lo", descriptor="de")
|
|
1035
1029
|
for file in science_files:
|
|
@@ -1039,13 +1033,11 @@ class Lo(ProcessInstrument):
|
|
|
1039
1033
|
|
|
1040
1034
|
elif self.data_level == "l2":
|
|
1041
1035
|
data_dict = {}
|
|
1042
|
-
# TODO: Add ancillary descriptors when maps using them are
|
|
1043
|
-
# implemented.
|
|
1044
|
-
anc_dependencies = []
|
|
1045
1036
|
science_files = dependencies.get_file_paths(source="lo", descriptor="pset")
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1037
|
+
anc_dependencies = dependencies.get_file_paths(data_type="ancillary")
|
|
1038
|
+
|
|
1039
|
+
# Load all pset files into datasets
|
|
1040
|
+
psets = [load_cdf(file) for file in science_files]
|
|
1049
1041
|
data_dict[psets[0].attrs["Logical_source"]] = psets
|
|
1050
1042
|
datasets = lo_l2.lo_l2(data_dict, anc_dependencies, self.descriptor)
|
|
1051
1043
|
return datasets
|
|
@@ -1228,8 +1220,8 @@ class Spacecraft(ProcessInstrument):
|
|
|
1228
1220
|
The list of processed products.
|
|
1229
1221
|
"""
|
|
1230
1222
|
print(f"Processing Spacecraft {self.data_level}")
|
|
1231
|
-
|
|
1232
|
-
if self.
|
|
1223
|
+
processed_dataset = []
|
|
1224
|
+
if self.descriptor == "quaternions":
|
|
1233
1225
|
# File path is expected output file path
|
|
1234
1226
|
input_files = dependencies.get_file_paths(source="spacecraft")
|
|
1235
1227
|
if len(input_files) > 1:
|
|
@@ -1238,26 +1230,21 @@ class Spacecraft(ProcessInstrument):
|
|
|
1238
1230
|
f"{input_files}. Expected only one dependency."
|
|
1239
1231
|
)
|
|
1240
1232
|
datasets = list(quaternions.process_quaternions(input_files[0]))
|
|
1241
|
-
|
|
1242
|
-
elif self.
|
|
1233
|
+
processed_dataset.extend(datasets)
|
|
1234
|
+
elif self.descriptor == "pointing-attitude":
|
|
1243
1235
|
spice_inputs = dependencies.get_file_paths(
|
|
1244
1236
|
data_type=SPICESource.SPICE.value
|
|
1245
1237
|
)
|
|
1246
1238
|
ah_paths = [path for path in spice_inputs if ".ah" in path.suffixes]
|
|
1247
|
-
if len(ah_paths) != 1:
|
|
1248
|
-
raise ValueError(
|
|
1249
|
-
f"Unexpected spice dependencies found for Spacecraft "
|
|
1250
|
-
f"pointing_kernel: {ah_paths}. Expected exactly one "
|
|
1251
|
-
f"attitude history file."
|
|
1252
|
-
)
|
|
1253
1239
|
pointing_kernel_paths = pointing_frame.generate_pointing_attitude_kernel(
|
|
1254
|
-
ah_paths[
|
|
1240
|
+
ah_paths[-1]
|
|
1255
1241
|
)
|
|
1256
|
-
|
|
1242
|
+
processed_dataset.extend(pointing_kernel_paths)
|
|
1257
1243
|
else:
|
|
1258
1244
|
raise NotImplementedError(
|
|
1259
1245
|
f"Spacecraft processing not implemented for level {self.data_level}"
|
|
1260
1246
|
)
|
|
1247
|
+
return processed_dataset
|
|
1261
1248
|
|
|
1262
1249
|
|
|
1263
1250
|
class Swapi(ProcessInstrument):
|
|
@@ -1461,7 +1448,10 @@ class Ultra(ProcessInstrument):
|
|
|
1461
1448
|
}
|
|
1462
1449
|
science_files = dependencies.get_file_paths(source="ultra", data_type="l1b")
|
|
1463
1450
|
l1b_dict = {
|
|
1464
|
-
|
|
1451
|
+
# TODO remove
|
|
1452
|
+
dataset.attrs["Logical_source"].replace(
|
|
1453
|
+
"cullingmask", "goodtimes"
|
|
1454
|
+
): dataset
|
|
1465
1455
|
for dataset in [load_cdf(sci_file) for sci_file in science_files]
|
|
1466
1456
|
}
|
|
1467
1457
|
combined = {**l1a_dict, **l1b_dict}
|
|
@@ -1470,11 +1460,12 @@ class Ultra(ProcessInstrument):
|
|
|
1470
1460
|
for path in anc_paths:
|
|
1471
1461
|
ancillary_files[path.stem.split("_")[2]] = path
|
|
1472
1462
|
spice_paths = dependencies.get_file_paths(data_type="spice")
|
|
1473
|
-
|
|
1474
|
-
|
|
1463
|
+
# Only the helio pset needs IMAP frames
|
|
1464
|
+
if any("imap_frames" in path.as_posix() for path in spice_paths):
|
|
1465
|
+
imap_frames = True
|
|
1475
1466
|
else:
|
|
1476
|
-
|
|
1477
|
-
datasets = ultra_l1c.ultra_l1c(combined, ancillary_files,
|
|
1467
|
+
imap_frames = False
|
|
1468
|
+
datasets = ultra_l1c.ultra_l1c(combined, ancillary_files, imap_frames)
|
|
1478
1469
|
elif self.data_level == "l2":
|
|
1479
1470
|
all_pset_filepaths = dependencies.get_file_paths(
|
|
1480
1471
|
source="ultra", descriptor="pset"
|
|
@@ -39,7 +39,8 @@ def decom_packets(packet_file: Path) -> dict[int, xr.Dataset]:
|
|
|
39
39
|
# TODO: Currently need to use the 'old' packet definition for housekeeping
|
|
40
40
|
# because the simulated housekeeping data being used has various
|
|
41
41
|
# mis-matches from the telemetry definition. This may be updated
|
|
42
|
-
# once new simulated housekeeping data are acquired.
|
|
42
|
+
# once new simulated housekeeping data are acquired. See GitHub issue
|
|
43
|
+
# #2135.
|
|
43
44
|
if "hskp" in str(packet_file):
|
|
44
45
|
xtce_filename = "P_COD_NHK.xml"
|
|
45
46
|
else:
|
|
@@ -125,7 +125,7 @@ class CoDICEL1aPipeline:
|
|
|
125
125
|
# orientation and the azimuth determine which spin sector the data
|
|
126
126
|
# gets stored in.
|
|
127
127
|
# TODO: All these nested for-loops are bad. Try to find a better
|
|
128
|
-
# solution.
|
|
128
|
+
# solution. See GitHub issue #2136.
|
|
129
129
|
for i, epoch_data in enumerate(self.data):
|
|
130
130
|
for energy_index in range(num_energies):
|
|
131
131
|
pixel_orientation = constants.PIXEL_ORIENTATIONS[energy_index]
|
|
@@ -345,7 +345,7 @@ class CoDICEL1aPipeline:
|
|
|
345
345
|
# energy dimension
|
|
346
346
|
# TODO: This bit of code may no longer be needed once I can figure
|
|
347
347
|
# out how to run hi-sectored product through the
|
|
348
|
-
# create_binned_dataset function
|
|
348
|
+
# create_binned_dataset function. See GitHub issue #2137.
|
|
349
349
|
if self.config["dataset_name"] == "imap_codice_l1a_hi-sectored":
|
|
350
350
|
dims = [
|
|
351
351
|
f"energy_{variable_name}" if item == "esa_step" else item
|
|
@@ -367,7 +367,7 @@ class CoDICEL1aPipeline:
|
|
|
367
367
|
# longer need the "esa_step" coordinate
|
|
368
368
|
# TODO: This bit of code may no longer be needed once I can figure
|
|
369
369
|
# out how to run hi-sectored product through the
|
|
370
|
-
# create_binned_dataset function
|
|
370
|
+
# create_binned_dataset function. See GitHub issue #2137.
|
|
371
371
|
if self.config["dataset_name"] == "imap_codice_l1a_hi-sectored":
|
|
372
372
|
for species in self.config["energy_table"]:
|
|
373
373
|
dataset = self.define_energy_bins(dataset, species)
|
|
@@ -822,9 +822,6 @@ def group_ialirt_data(
|
|
|
822
822
|
|
|
823
823
|
# Workaround to get this function working for both I-ALiRT spacecraft
|
|
824
824
|
# data and CoDICE-specific I-ALiRT test data from Joey
|
|
825
|
-
# TODO: Once CoDICE I-ALiRT processing is more established, we can probably
|
|
826
|
-
# do away with processing the test data from Joey and just use the
|
|
827
|
-
# I-ALiRT data that is constructed closer to what we expect in-flight.
|
|
828
825
|
if hasattr(packets, "acquisition_time"):
|
|
829
826
|
time_key = "acquisition_time"
|
|
830
827
|
counter_key = "counter"
|
|
@@ -880,7 +877,7 @@ def create_binned_dataset(
|
|
|
880
877
|
Xarray dataset containing the final processed dataset.
|
|
881
878
|
"""
|
|
882
879
|
# TODO: hi-sectored data product should be processed similar to hi-omni,
|
|
883
|
-
# so I should be able to use this method.
|
|
880
|
+
# so I should be able to use this method. See GitHub issue #2137.
|
|
884
881
|
|
|
885
882
|
# Get the four "main" parameters for processing
|
|
886
883
|
table_id, plan_id, plan_step, view_id = get_params(dataset)
|
|
@@ -901,7 +898,7 @@ def create_binned_dataset(
|
|
|
901
898
|
attrs=pipeline.cdf_attrs.get_variable_attributes("epoch", check_schema=False),
|
|
902
899
|
)
|
|
903
900
|
# TODO: Figure out how to calculate epoch centers and deltas and store them
|
|
904
|
-
# in variables here
|
|
901
|
+
# in variables here. See GitHub issue #1501.
|
|
905
902
|
dataset = xr.Dataset(
|
|
906
903
|
coords={"epoch": coord},
|
|
907
904
|
attrs=pipeline.cdf_attrs.get_global_attributes(pipeline.config["dataset_name"]),
|
|
@@ -941,7 +938,7 @@ def create_binned_dataset(
|
|
|
941
938
|
return dataset
|
|
942
939
|
|
|
943
940
|
|
|
944
|
-
def create_direct_event_dataset(apid: int,
|
|
941
|
+
def create_direct_event_dataset(apid: int, unpacked_dataset: xr.Dataset) -> xr.Dataset:
|
|
945
942
|
"""
|
|
946
943
|
Create dataset for direct event data.
|
|
947
944
|
|
|
@@ -955,7 +952,7 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
|
|
|
955
952
|
dictionary. Padding is added to any fields that have less than 10000 events.
|
|
956
953
|
|
|
957
954
|
In order to process these data, we must take the decommed raw data, group
|
|
958
|
-
the
|
|
955
|
+
the unpacked_dataset appropriately based on their `seq_flgs`, decompress the data,
|
|
959
956
|
then arrange the data into CDF data variables for each priority and bit
|
|
960
957
|
field. For example, P2_SpinAngle represents the spin angles for the 2nd
|
|
961
958
|
priority data.
|
|
@@ -964,8 +961,8 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
|
|
|
964
961
|
----------
|
|
965
962
|
apid : int
|
|
966
963
|
The APID of the packet.
|
|
967
|
-
|
|
968
|
-
The
|
|
964
|
+
unpacked_dataset : xarray.Dataset
|
|
965
|
+
The unpacked dataset to process.
|
|
969
966
|
|
|
970
967
|
Returns
|
|
971
968
|
-------
|
|
@@ -973,13 +970,13 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
|
|
|
973
970
|
Xarray dataset containing the direct event data.
|
|
974
971
|
"""
|
|
975
972
|
# Group and decompress the data
|
|
976
|
-
grouped_data = group_data(
|
|
973
|
+
grouped_data = group_data(unpacked_dataset)
|
|
977
974
|
decompressed_data = [
|
|
978
975
|
decompress(group, CoDICECompression.LOSSLESS) for group in grouped_data
|
|
979
976
|
]
|
|
980
977
|
|
|
981
978
|
# Reshape the packet data into CDF-ready variables
|
|
982
|
-
|
|
979
|
+
reshaped_de_data = reshape_de_data(unpacked_dataset, decompressed_data, apid)
|
|
983
980
|
|
|
984
981
|
# Gather the CDF attributes
|
|
985
982
|
cdf_attrs = ImapCdfAttributes()
|
|
@@ -989,11 +986,11 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
|
|
|
989
986
|
# Determine the epochs to use in the dataset, which are the epochs whenever
|
|
990
987
|
# there is a start of a segment and the priority is 0
|
|
991
988
|
epoch_indices = np.where(
|
|
992
|
-
((
|
|
993
|
-
& (
|
|
989
|
+
((unpacked_dataset.seq_flgs.data == 3) | (unpacked_dataset.seq_flgs.data == 1))
|
|
990
|
+
& (unpacked_dataset.priority.data == 0)
|
|
994
991
|
)[0]
|
|
995
|
-
acq_start_seconds =
|
|
996
|
-
acq_start_subseconds =
|
|
992
|
+
acq_start_seconds = unpacked_dataset.acq_start_seconds[epoch_indices]
|
|
993
|
+
acq_start_subseconds = unpacked_dataset.acq_start_subseconds[epoch_indices]
|
|
997
994
|
|
|
998
995
|
# Calculate epoch variables
|
|
999
996
|
epochs, epochs_delta_minus, epochs_delta_plus = calculate_epoch_values(
|
|
@@ -1051,20 +1048,19 @@ def create_direct_event_dataset(apid: int, packets: xr.Dataset) -> xr.Dataset:
|
|
|
1051
1048
|
)
|
|
1052
1049
|
|
|
1053
1050
|
# Create the CDF data variables for each Priority and Field
|
|
1054
|
-
for
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
)
|
|
1051
|
+
for field in constants.DE_DATA_PRODUCT_CONFIGURATIONS[apid]["cdf_fields"]:
|
|
1052
|
+
if field in ["num_events", "data_quality"]:
|
|
1053
|
+
attrs = cdf_attrs.get_variable_attributes("de_2d_attrs")
|
|
1054
|
+
dims = ["epoch", "priority"]
|
|
1055
|
+
else:
|
|
1056
|
+
attrs = cdf_attrs.get_variable_attributes("de_3d_attrs")
|
|
1057
|
+
dims = ["epoch", "priority", "event_num"]
|
|
1058
|
+
dataset[field] = xr.DataArray(
|
|
1059
|
+
np.array(reshaped_de_data[field]),
|
|
1060
|
+
name=field,
|
|
1061
|
+
dims=dims,
|
|
1062
|
+
attrs=attrs,
|
|
1063
|
+
)
|
|
1068
1064
|
|
|
1069
1065
|
return dataset
|
|
1070
1066
|
|
|
@@ -1490,7 +1486,7 @@ def reshape_de_data(
|
|
|
1490
1486
|
CDF variable names, and the values represent the data.
|
|
1491
1487
|
"""
|
|
1492
1488
|
# Dictionary to hold all the (soon to be restructured) direct event data
|
|
1493
|
-
|
|
1489
|
+
de_data: dict[str, np.ndarray] = {}
|
|
1494
1490
|
|
|
1495
1491
|
# Extract some useful variables
|
|
1496
1492
|
num_priorities = constants.DE_DATA_PRODUCT_CONFIGURATIONS[apid]["num_priorities"]
|
|
@@ -1510,18 +1506,20 @@ def reshape_de_data(
|
|
|
1510
1506
|
|
|
1511
1507
|
# Initialize data arrays for each priority and field to store the data
|
|
1512
1508
|
# We also need arrays to hold number of events and data quality
|
|
1513
|
-
for
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
)
|
|
1524
|
-
|
|
1509
|
+
for field in bit_structure:
|
|
1510
|
+
# if these two, no need to store
|
|
1511
|
+
if field not in ["Priority", "Spare"]:
|
|
1512
|
+
de_data[f"{field}"] = np.full(
|
|
1513
|
+
(num_epochs, num_priorities, 10000),
|
|
1514
|
+
bit_structure[field]["fillval"],
|
|
1515
|
+
dtype=bit_structure[field]["dtype"],
|
|
1516
|
+
)
|
|
1517
|
+
# Add other additional fields of l1a
|
|
1518
|
+
de_data["num_events"] = np.full(
|
|
1519
|
+
(num_epochs, num_priorities), 65535, dtype=np.uint16
|
|
1520
|
+
)
|
|
1521
|
+
|
|
1522
|
+
de_data["data_quality"] = np.full((num_epochs, num_priorities), 255, dtype=np.uint8)
|
|
1525
1523
|
|
|
1526
1524
|
# decompressed_data is one large list of values of length
|
|
1527
1525
|
# (<number of epochs> * <number of priorities>)
|
|
@@ -1545,8 +1543,8 @@ def reshape_de_data(
|
|
|
1545
1543
|
|
|
1546
1544
|
# Number of events and data quality can be determined at this stage
|
|
1547
1545
|
num_events = num_events_arr[epoch_start:epoch_end][i]
|
|
1548
|
-
|
|
1549
|
-
|
|
1546
|
+
de_data["num_events"][epoch_index, priority_num] = num_events
|
|
1547
|
+
de_data["data_quality"][epoch_index, priority_num] = data_quality[i]
|
|
1550
1548
|
|
|
1551
1549
|
# Iterate over each event
|
|
1552
1550
|
for event_index in range(num_events):
|
|
@@ -1577,12 +1575,12 @@ def reshape_de_data(
|
|
|
1577
1575
|
)
|
|
1578
1576
|
|
|
1579
1577
|
# Set the value into the data array
|
|
1580
|
-
|
|
1578
|
+
de_data[f"{field_name}"][epoch_index, priority_num, event_index] = (
|
|
1581
1579
|
value
|
|
1582
1580
|
)
|
|
1583
1581
|
bit_position += field_components["bit_length"]
|
|
1584
1582
|
|
|
1585
|
-
return
|
|
1583
|
+
return de_data
|
|
1586
1584
|
|
|
1587
1585
|
|
|
1588
1586
|
def process_codice_l1a(file_path: Path) -> list[xr.Dataset]:
|
|
@@ -9,18 +9,18 @@ from imap_processing.codice.codice_l1b import process_codice_l1b
|
|
|
9
9
|
dataset = process_codice_l1b(l1a_filenanme)
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
-
# TODO: Figure out how to convert hi-priority data product. Need an updated
|
|
13
|
-
# algorithm document that describes this.
|
|
14
|
-
|
|
15
12
|
import logging
|
|
16
13
|
from pathlib import Path
|
|
17
14
|
|
|
18
15
|
import numpy as np
|
|
19
16
|
import xarray as xr
|
|
20
17
|
|
|
18
|
+
from imap_processing import imap_module_directory
|
|
21
19
|
from imap_processing.cdf.imap_cdf_manager import ImapCdfAttributes
|
|
22
20
|
from imap_processing.cdf.utils import load_cdf
|
|
23
21
|
from imap_processing.codice import constants
|
|
22
|
+
from imap_processing.codice.utils import CODICEAPID
|
|
23
|
+
from imap_processing.utils import packet_file_to_datasets
|
|
24
24
|
|
|
25
25
|
logger = logging.getLogger(__name__)
|
|
26
26
|
logger.setLevel(logging.INFO)
|
|
@@ -49,9 +49,6 @@ def convert_to_rates(
|
|
|
49
49
|
rates_data : np.ndarray
|
|
50
50
|
The converted data array.
|
|
51
51
|
"""
|
|
52
|
-
# TODO: Temporary workaround to create CDFs for SIT-4. Revisit after SIT-4.
|
|
53
|
-
acq_times = 1
|
|
54
|
-
|
|
55
52
|
if descriptor in [
|
|
56
53
|
"lo-counters-aggregated",
|
|
57
54
|
"lo-counters-singles",
|
|
@@ -65,6 +62,13 @@ def convert_to_rates(
|
|
|
65
62
|
]:
|
|
66
63
|
# Applying rate calculation described in section 10.2 of the algorithm
|
|
67
64
|
# document
|
|
65
|
+
# In order to divide by acquisition times, we must reshape the acq
|
|
66
|
+
# time data array to match the data variable shape
|
|
67
|
+
dims = [1] * dataset[variable_name].data.ndim
|
|
68
|
+
dims[1] = 128
|
|
69
|
+
acq_times = dataset.acquisition_time_per_step.data.reshape(dims)
|
|
70
|
+
|
|
71
|
+
# Now perform the calculation
|
|
68
72
|
rates_data = dataset[variable_name].data / (
|
|
69
73
|
acq_times
|
|
70
74
|
* 1e-6 # Converting from microseconds to seconds
|
|
@@ -83,10 +87,8 @@ def convert_to_rates(
|
|
|
83
87
|
rates_data = dataset[variable_name].data / (
|
|
84
88
|
constants.L1B_DATA_PRODUCT_CONFIGURATIONS[descriptor]["num_spin_sectors"]
|
|
85
89
|
* constants.L1B_DATA_PRODUCT_CONFIGURATIONS[descriptor]["num_spins"]
|
|
86
|
-
*
|
|
90
|
+
* constants.HI_ACQUISITION_TIME
|
|
87
91
|
)
|
|
88
|
-
elif descriptor == "hskp":
|
|
89
|
-
rates_data = dataset[variable_name].data / acq_times
|
|
90
92
|
|
|
91
93
|
return rates_data
|
|
92
94
|
|
|
@@ -131,35 +133,43 @@ def process_codice_l1b(file_path: Path) -> xr.Dataset:
|
|
|
131
133
|
# Update the global attributes
|
|
132
134
|
l1b_dataset.attrs = cdf_attrs.get_global_attributes(dataset_name)
|
|
133
135
|
|
|
134
|
-
#
|
|
135
|
-
# TODO: Figure out exactly which hskp variables need to be converted
|
|
136
|
-
# Housekeeping and binned datasets are treated a bit differently since
|
|
137
|
-
# not all variables need to be converted
|
|
136
|
+
# TODO: This was thrown together quickly and should be double-checked
|
|
138
137
|
if descriptor == "hskp":
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
138
|
+
xtce_filename = "codice_packet_definition.xml"
|
|
139
|
+
xtce_packet_definition = Path(
|
|
140
|
+
f"{imap_module_directory}/codice/packet_definitions/{xtce_filename}"
|
|
141
|
+
)
|
|
142
|
+
packet_file = (
|
|
143
|
+
imap_module_directory
|
|
144
|
+
/ "tests"
|
|
145
|
+
/ "codice"
|
|
146
|
+
/ "data"
|
|
147
|
+
/ "imap_codice_l0_raw_20241110_v001.pkts"
|
|
148
|
+
)
|
|
149
|
+
datasets: dict[int, xr.Dataset] = packet_file_to_datasets(
|
|
150
|
+
packet_file, xtce_packet_definition, use_derived_value=True
|
|
151
|
+
)
|
|
152
|
+
l1b_dataset = datasets[CODICEAPID.COD_NHK]
|
|
153
|
+
|
|
154
|
+
# TODO: Drop the same variables as we do in L1a? (see line 1103 in
|
|
155
|
+
# codice_l1a.py
|
|
156
|
+
|
|
147
157
|
else:
|
|
148
158
|
variables_to_convert = getattr(
|
|
149
159
|
constants, f"{descriptor.upper().replace('-', '_')}_VARIABLE_NAMES"
|
|
150
160
|
)
|
|
151
161
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
162
|
+
# Apply the conversion to rates
|
|
163
|
+
for variable_name in variables_to_convert:
|
|
164
|
+
l1b_dataset[variable_name].data = convert_to_rates(
|
|
165
|
+
l1b_dataset, descriptor, variable_name
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Set the variable attributes
|
|
169
|
+
cdf_attrs_key = f"{descriptor}-{variable_name}"
|
|
170
|
+
l1b_dataset[variable_name].attrs = cdf_attrs.get_variable_attributes(
|
|
171
|
+
cdf_attrs_key, check_schema=False
|
|
172
|
+
)
|
|
163
173
|
|
|
164
174
|
logger.info(f"\nFinal data product:\n{l1b_dataset}\n")
|
|
165
175
|
|