imap-processing 0.7.0__py3-none-any.whl → 0.8.0__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/__init__.py +1 -1
- imap_processing/_version.py +2 -2
- imap_processing/ccsds/excel_to_xtce.py +34 -2
- imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +1 -1
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +145 -30
- imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +36 -36
- imap_processing/cdf/config/imap_hi_variable_attrs.yaml +36 -8
- imap_processing/cdf/config/imap_hit_l1b_variable_attrs.yaml +9 -0
- imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +7 -7
- imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +32 -33
- imap_processing/cdf/config/imap_mag_l1_variable_attrs.yaml +24 -28
- imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +1 -0
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +133 -78
- imap_processing/cdf/config/imap_variable_schema.yaml +13 -0
- imap_processing/cdf/imap_cdf_manager.py +31 -27
- imap_processing/cli.py +12 -10
- imap_processing/codice/codice_l1a.py +151 -61
- imap_processing/codice/constants.py +1 -1
- imap_processing/codice/decompress.py +4 -9
- imap_processing/codice/utils.py +1 -0
- imap_processing/glows/l1b/glows_l1b.py +3 -3
- imap_processing/glows/l1b/glows_l1b_data.py +59 -37
- imap_processing/glows/l2/glows_l2_data.py +123 -0
- imap_processing/hi/l1a/histogram.py +1 -1
- imap_processing/hi/l1a/science_direct_event.py +1 -1
- imap_processing/hi/l1b/hi_l1b.py +85 -11
- imap_processing/hi/l1c/hi_l1c.py +23 -1
- imap_processing/hi/utils.py +1 -1
- imap_processing/hit/hit_utils.py +221 -0
- imap_processing/hit/l0/constants.py +118 -0
- imap_processing/hit/l0/decom_hit.py +186 -153
- imap_processing/hit/l1a/hit_l1a.py +20 -175
- imap_processing/hit/l1b/hit_l1b.py +33 -153
- imap_processing/idex/idex_l1a.py +10 -9
- imap_processing/lo/l0/decompression_tables/decompression_tables.py +1 -1
- imap_processing/lo/l0/lo_science.py +1 -1
- imap_processing/lo/packet_definitions/lo_xtce.xml +1 -3296
- imap_processing/mag/l0/decom_mag.py +4 -3
- imap_processing/mag/l1a/mag_l1a.py +11 -11
- imap_processing/mag/l1b/mag_l1b.py +89 -7
- imap_processing/spice/geometry.py +126 -4
- imap_processing/swapi/l1/swapi_l1.py +1 -1
- imap_processing/swapi/l2/swapi_l2.py +1 -1
- imap_processing/swe/l1b/swe_l1b_science.py +8 -8
- imap_processing/tests/ccsds/test_data/expected_output.xml +1 -0
- imap_processing/tests/ccsds/test_excel_to_xtce.py +4 -4
- imap_processing/tests/cdf/test_imap_cdf_manager.py +0 -10
- imap_processing/tests/codice/conftest.py +1 -17
- imap_processing/tests/codice/data/imap_codice_l0_raw_20241110_v001.pkts +0 -0
- imap_processing/tests/codice/test_codice_l0.py +8 -2
- imap_processing/tests/codice/test_codice_l1a.py +127 -107
- imap_processing/tests/codice/test_codice_l1b.py +1 -0
- imap_processing/tests/codice/test_decompress.py +7 -7
- imap_processing/tests/conftest.py +54 -15
- imap_processing/tests/glows/conftest.py +6 -0
- imap_processing/tests/glows/test_glows_l1b.py +9 -9
- imap_processing/tests/glows/test_glows_l1b_data.py +9 -9
- imap_processing/tests/glows/test_glows_l2_data.py +0 -0
- imap_processing/tests/hi/test_data/l1a/imap_hi_l1a_45sensor-de_20250415_v000.cdf +0 -0
- imap_processing/tests/hi/test_hi_l1b.py +71 -1
- imap_processing/tests/hi/test_hi_l1c.py +10 -2
- imap_processing/tests/hi/test_utils.py +4 -3
- imap_processing/tests/hit/{test_hit_decom.py → test_decom_hit.py} +84 -35
- imap_processing/tests/hit/test_hit_l1a.py +2 -197
- imap_processing/tests/hit/test_hit_l1b.py +156 -25
- imap_processing/tests/hit/test_hit_utils.py +218 -0
- imap_processing/tests/idex/conftest.py +1 -1
- imap_processing/tests/idex/imap_idex_l0_raw_20231214_v001.pkts +0 -0
- imap_processing/tests/idex/impact_14_tof_high_data.txt +4444 -4444
- imap_processing/tests/idex/test_idex_l0.py +3 -3
- imap_processing/tests/idex/test_idex_l1a.py +1 -1
- imap_processing/tests/lo/test_lo_science.py +2 -2
- imap_processing/tests/mag/imap_mag_l1a_norm-magi_20251017_v001.cdf +0 -0
- imap_processing/tests/mag/test_mag_l1b.py +59 -3
- imap_processing/tests/spice/test_data/imap_ena_sim_metakernel.template +3 -1
- imap_processing/tests/spice/test_geometry.py +84 -4
- imap_processing/tests/swe/conftest.py +33 -0
- imap_processing/tests/swe/l1_validation/swe_l0_unpacked-data_20240510_v001_VALIDATION_L1B_v3.dat +4332 -0
- imap_processing/tests/swe/test_swe_l1b.py +29 -8
- imap_processing/tests/test_utils.py +1 -1
- imap_processing/tests/ultra/test_data/l1/dps_exposure_helio_45_E12.cdf +0 -0
- imap_processing/tests/ultra/test_data/l1/dps_exposure_helio_45_E24.cdf +0 -0
- imap_processing/tests/ultra/unit/test_de.py +108 -0
- imap_processing/tests/ultra/unit/test_ultra_l1b.py +27 -3
- imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +31 -10
- imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +21 -11
- imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +9 -44
- imap_processing/ultra/constants.py +8 -3
- imap_processing/ultra/l1b/de.py +174 -30
- imap_processing/ultra/l1b/ultra_l1b_annotated.py +24 -10
- imap_processing/ultra/l1b/ultra_l1b_extended.py +21 -14
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +70 -119
- {imap_processing-0.7.0.dist-info → imap_processing-0.8.0.dist-info}/METADATA +15 -14
- {imap_processing-0.7.0.dist-info → imap_processing-0.8.0.dist-info}/RECORD +98 -113
- imap_processing/cdf/cdf_attribute_manager.py +0 -322
- imap_processing/cdf/config/shared/default_global_cdf_attrs_schema.yaml +0 -246
- imap_processing/cdf/config/shared/default_variable_cdf_attrs_schema.yaml +0 -466
- imap_processing/hit/l0/data_classes/housekeeping.py +0 -240
- imap_processing/hit/l0/data_classes/science_packet.py +0 -259
- imap_processing/hit/l0/utils/hit_base.py +0 -57
- imap_processing/tests/cdf/shared/default_global_cdf_attrs_schema.yaml +0 -246
- imap_processing/tests/cdf/shared/default_variable_cdf_attrs_schema.yaml +0 -466
- imap_processing/tests/cdf/test_cdf_attribute_manager.py +0 -353
- imap_processing/tests/codice/data/imap_codice_l0_hi-counters-aggregated_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hi-counters-singles_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hi-omni_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hi-pha_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hi-sectored_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_hskp_20100101_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-counters-aggregated_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-counters-singles_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-angular_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-priority_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-species_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-pha_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-sw-angular_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-sw-priority_20240429_v001.pkts +0 -0
- imap_processing/tests/codice/data/imap_codice_l0_lo-sw-species_20240429_v001.pkts +0 -0
- imap_processing/tests/idex/imap_idex_l0_raw_20230725_v001.pkts +0 -0
- imap_processing/tests/mag/imap_mag_l1a_burst-magi_20231025_v001.cdf +0 -0
- /imap_processing/tests/hit/test_data/{imap_hit_l0_hk_20100105_v001.pkts → imap_hit_l0_raw_20100105_v001.pkts} +0 -0
- {imap_processing-0.7.0.dist-info → imap_processing-0.8.0.dist-info}/LICENSE +0 -0
- {imap_processing-0.7.0.dist-info → imap_processing-0.8.0.dist-info}/WHEEL +0 -0
- {imap_processing-0.7.0.dist-info → imap_processing-0.8.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,109 +1,112 @@
|
|
|
1
1
|
"""Decommutate HIT CCSDS science data."""
|
|
2
2
|
|
|
3
|
-
from collections import namedtuple
|
|
4
|
-
|
|
5
3
|
import numpy as np
|
|
6
4
|
import xarray as xr
|
|
7
5
|
|
|
6
|
+
from imap_processing.hit.l0.constants import (
|
|
7
|
+
COUNTS_DATA_STRUCTURE,
|
|
8
|
+
EXPONENT_BITS,
|
|
9
|
+
FLAG_PATTERN,
|
|
10
|
+
FRAME_SIZE,
|
|
11
|
+
MANTISSA_BITS,
|
|
12
|
+
MOD_10_MAPPING,
|
|
13
|
+
)
|
|
8
14
|
from imap_processing.utils import convert_to_binary_string
|
|
9
15
|
|
|
10
|
-
# TODO: Consider moving global values into a config file
|
|
11
|
-
|
|
12
|
-
# Structure to hold binary details for a
|
|
13
|
-
# section of science data. Used to unpack
|
|
14
|
-
# binary data.
|
|
15
|
-
HITPacking = namedtuple(
|
|
16
|
-
"HITPacking",
|
|
17
|
-
[
|
|
18
|
-
"bit_length",
|
|
19
|
-
"section_length",
|
|
20
|
-
"shape",
|
|
21
|
-
],
|
|
22
|
-
)
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
17
|
+
def subcom_sectorates(sci_dataset: xr.Dataset) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Subcommutate sectorates data.
|
|
20
|
+
|
|
21
|
+
Sector rates data contains rates for 5 species and 10
|
|
22
|
+
energy ranges. This function subcommutates the sector
|
|
23
|
+
rates data by organizing the rates by species. Which
|
|
24
|
+
species and energy range the data belongs to is determined
|
|
25
|
+
by taking the mod 10 value of the corresponding header
|
|
26
|
+
minute count value in the dataset. A mapping of mod 10
|
|
27
|
+
values to species and energy ranges is provided in constants.py.
|
|
28
|
+
|
|
29
|
+
MOD_10_MAPPING = {
|
|
30
|
+
0: {"species": "H", "energy_min": 1.8, "energy_max": 3.6},
|
|
31
|
+
1: {"species": "H", "energy_min": 4, "energy_max": 6},
|
|
32
|
+
2: {"species": "H", "energy_min": 6, "energy_max": 10},
|
|
33
|
+
3: {"species": "4He", "energy_min": 4, "energy_max": 6},
|
|
34
|
+
...
|
|
35
|
+
9: {"species": "Fe", "energy_min": 4, "energy_max": 12}}
|
|
36
|
+
|
|
37
|
+
The data is added to the dataset as new data fields named
|
|
38
|
+
according to their species. They have 4 dimensions: epoch
|
|
39
|
+
energy index, declination, and azimuth. The energy index
|
|
40
|
+
dimension is used to distinguish between the different energy
|
|
41
|
+
ranges the data belongs to. The energy min and max values for
|
|
42
|
+
each species are also added to the dataset as new data fields.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
sci_dataset : xr.Dataset
|
|
47
|
+
Xarray dataset containing parsed HIT science data.
|
|
48
|
+
"""
|
|
49
|
+
# TODO:
|
|
50
|
+
# - Update to use fill values defined in attribute manager which
|
|
51
|
+
# isn't passed into this module nor defined for L1A sci data yet
|
|
52
|
+
# - Determine naming convention for species data fields in dataset
|
|
53
|
+
# (i.e. h, H, hydrogen, Hydrogen, etc.)
|
|
54
|
+
# - Remove raw "sectorates" data from dataset after processing is complete?
|
|
55
|
+
# - consider moving this function to hit_l1a.py
|
|
56
|
+
|
|
57
|
+
# Calculate mod 10 values
|
|
58
|
+
hdr_min_count_mod_10 = sci_dataset.hdr_minute_cnt.values % 10
|
|
59
|
+
|
|
60
|
+
# Reference mod 10 mapping to initialize data structure for species and
|
|
61
|
+
# energy ranges and add 8x15 arrays with fill values for each science frame.
|
|
62
|
+
num_frames = len(hdr_min_count_mod_10)
|
|
63
|
+
data_by_species_and_energy_range = {
|
|
64
|
+
key: {**value, "rates": np.full((num_frames, 8, 15), fill_value=np.nan)}
|
|
65
|
+
for key, value in MOD_10_MAPPING.items()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Update rates for science frames where data is available
|
|
69
|
+
for i, mod_10 in enumerate(hdr_min_count_mod_10):
|
|
70
|
+
data_by_species_and_energy_range[mod_10]["rates"][i] = sci_dataset[
|
|
71
|
+
"sectorates"
|
|
72
|
+
].values[i]
|
|
73
|
+
|
|
74
|
+
# H has 3 energy ranges, 4He, CNO, NeMgSi have 2, and Fe has 1.
|
|
75
|
+
# Aggregate sector rates and energy min/max values for each species.
|
|
76
|
+
# First, initialize dictionaries to store rates and min/max energy values by species
|
|
77
|
+
data_by_species: dict = {
|
|
78
|
+
value["species"]: {"rates": [], "energy_min": [], "energy_max": []}
|
|
79
|
+
for value in data_by_species_and_energy_range.values()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for value in data_by_species_and_energy_range.values():
|
|
83
|
+
species = value["species"]
|
|
84
|
+
data_by_species[species]["rates"].append(value["rates"])
|
|
85
|
+
data_by_species[species]["energy_min"].append(value["energy_min"])
|
|
86
|
+
data_by_species[species]["energy_max"].append(value["energy_max"])
|
|
87
|
+
|
|
88
|
+
# Add sector rates by species to the dataset
|
|
89
|
+
for species, data in data_by_species.items():
|
|
90
|
+
# Rates data has shape: energy_index, epoch, declination, azimuth
|
|
91
|
+
# Convert rates to numpy array and transpose axes to get
|
|
92
|
+
# shape: epoch, energy_index, declination, azimuth
|
|
93
|
+
rates_data = np.transpose(np.array(data["rates"]), axes=(1, 0, 2, 3))
|
|
94
|
+
|
|
95
|
+
sci_dataset[species] = xr.DataArray(
|
|
96
|
+
data=rates_data,
|
|
97
|
+
dims=["epoch", f"{species}_energy_index", "declination", "azimuth"],
|
|
98
|
+
name=species,
|
|
99
|
+
)
|
|
100
|
+
sci_dataset[f"{species}_energy_min"] = xr.DataArray(
|
|
101
|
+
data=np.array(data["energy_min"]),
|
|
102
|
+
dims=[f"{species}_energy_index"],
|
|
103
|
+
name=f"{species}_energy_min",
|
|
104
|
+
)
|
|
105
|
+
sci_dataset[f"{species}_energy_max"] = xr.DataArray(
|
|
106
|
+
data=np.array(data["energy_max"]),
|
|
107
|
+
dims=[f"{species}_energy_index"],
|
|
108
|
+
name=f"{species}_energy_max",
|
|
109
|
+
)
|
|
107
110
|
|
|
108
111
|
|
|
109
112
|
def parse_data(bin_str: str, bits_per_index: int, start: int, end: int) -> list:
|
|
@@ -138,13 +141,13 @@ def parse_count_rates(sci_dataset: xr.Dataset) -> None:
|
|
|
138
141
|
Parse binary count rates data and update dataset.
|
|
139
142
|
|
|
140
143
|
This function parses the binary count rates data,
|
|
141
|
-
stored as
|
|
144
|
+
stored as count_rates_raw in the dataset,
|
|
142
145
|
according to data structure details provided in
|
|
143
146
|
COUNTS_DATA_STRUCTURE. The parsed data, representing
|
|
144
147
|
integers, is added to the dataset as new data
|
|
145
148
|
fields.
|
|
146
149
|
|
|
147
|
-
Note:
|
|
150
|
+
Note: count_rates_raw is added to the dataset by
|
|
148
151
|
the assemble_science_frames function, which organizes
|
|
149
152
|
the binary science data packets by science frames.
|
|
150
153
|
|
|
@@ -154,7 +157,7 @@ def parse_count_rates(sci_dataset: xr.Dataset) -> None:
|
|
|
154
157
|
Xarray dataset containing HIT science packets
|
|
155
158
|
from a CCSDS file.
|
|
156
159
|
"""
|
|
157
|
-
counts_binary = sci_dataset.
|
|
160
|
+
counts_binary = sci_dataset.count_rates_raw
|
|
158
161
|
# initialize the starting bit for the sections of data
|
|
159
162
|
section_start = 0
|
|
160
163
|
# Decommutate binary data for each counts data field
|
|
@@ -176,17 +179,18 @@ def parse_count_rates(sci_dataset: xr.Dataset) -> None:
|
|
|
176
179
|
low_gain = data[1::2] # Items at odd indices 1, 3, 5, etc.
|
|
177
180
|
parsed_data[i] = [high_gain, low_gain]
|
|
178
181
|
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
# - decompress data
|
|
183
|
-
# - Follow up with HIT team about erates and evrates.
|
|
184
|
-
# (i.e.Should these be arrays containing all the sub fields
|
|
185
|
-
# or should each subfield be it's own data field/array)
|
|
182
|
+
# Decompress data where needed
|
|
183
|
+
if all(x not in field for x in ["hdr", "spare", "pha"]):
|
|
184
|
+
parsed_data = np.vectorize(decompress_rates_16_to_32)(parsed_data)
|
|
186
185
|
|
|
187
186
|
# Get dims for data variables (yaml file not created yet)
|
|
188
187
|
if len(field_meta.shape) > 1:
|
|
189
|
-
|
|
188
|
+
if "sectorates" in field:
|
|
189
|
+
# Reshape data to 8x15 for declination and azimuth look directions
|
|
190
|
+
parsed_data = np.array(parsed_data).reshape((-1, *field_meta.shape))
|
|
191
|
+
dims = ["epoch", "declination", "azimuth"]
|
|
192
|
+
elif "sngrates" in field:
|
|
193
|
+
dims = ["epoch", "gain", f"{field}_index"]
|
|
190
194
|
elif field_meta.shape[0] > 1:
|
|
191
195
|
dims = ["epoch", f"{field}_index"]
|
|
192
196
|
else:
|
|
@@ -214,7 +218,7 @@ def is_sequential(counters: np.ndarray) -> np.bool_:
|
|
|
214
218
|
return np.all(np.diff(counters) == 1)
|
|
215
219
|
|
|
216
220
|
|
|
217
|
-
def
|
|
221
|
+
def get_valid_starting_indices(flags: np.ndarray, counters: np.ndarray) -> np.ndarray:
|
|
218
222
|
"""
|
|
219
223
|
Find valid starting indices for science frames.
|
|
220
224
|
|
|
@@ -241,9 +245,6 @@ def find_valid_starting_indices(flags: np.ndarray, counters: np.ndarray) -> np.n
|
|
|
241
245
|
valid_indices : np.ndarray
|
|
242
246
|
Array of valid indices for science frames.
|
|
243
247
|
"""
|
|
244
|
-
# TODO: consider combining functions to get valid indices to reduce
|
|
245
|
-
# code tracing
|
|
246
|
-
|
|
247
248
|
# Use sliding windows to compare segments of the array (20 packets) with the
|
|
248
249
|
# pattern. This generates an array of overlapping sub-arrays, each of length
|
|
249
250
|
# 20, from the flags array and is used to slide the "window" across the array
|
|
@@ -254,40 +255,13 @@ def find_valid_starting_indices(flags: np.ndarray, counters: np.ndarray) -> np.n
|
|
|
254
255
|
# Get the starting indices of matches
|
|
255
256
|
match_indices = np.where(matches)[0]
|
|
256
257
|
# Filter for only indices from valid science frames with sequential counters
|
|
257
|
-
|
|
258
|
+
sequential_check = [
|
|
259
|
+
is_sequential(counters[idx : idx + FRAME_SIZE]) for idx in match_indices
|
|
260
|
+
]
|
|
261
|
+
valid_indices: np.ndarray = np.array(match_indices[sequential_check], dtype=int)
|
|
258
262
|
return valid_indices
|
|
259
263
|
|
|
260
264
|
|
|
261
|
-
def get_valid_indices(
|
|
262
|
-
indices: np.ndarray, counters: np.ndarray, size: int
|
|
263
|
-
) -> np.ndarray:
|
|
264
|
-
"""
|
|
265
|
-
Get valid indices for science frames.
|
|
266
|
-
|
|
267
|
-
Check if the packet sequence counters for the science frames
|
|
268
|
-
are sequential. If they are, the science frame is valid and
|
|
269
|
-
an updated array of valid indices is returned.
|
|
270
|
-
|
|
271
|
-
Parameters
|
|
272
|
-
----------
|
|
273
|
-
indices : np.ndarray
|
|
274
|
-
Array of indices where the packet grouping flags match the pattern.
|
|
275
|
-
counters : np.ndarray
|
|
276
|
-
Array of packet sequence counters.
|
|
277
|
-
size : int
|
|
278
|
-
Size of science frame. 20 packets per science frame.
|
|
279
|
-
|
|
280
|
-
Returns
|
|
281
|
-
-------
|
|
282
|
-
valid_indices : np.ndarray
|
|
283
|
-
Array of valid indices for science frames.
|
|
284
|
-
"""
|
|
285
|
-
# Check if the packet sequence counters are sequential by getting an array
|
|
286
|
-
# of boolean values where True indicates the counters are sequential.
|
|
287
|
-
sequential_check = [is_sequential(counters[idx : idx + size]) for idx in indices]
|
|
288
|
-
return indices[sequential_check]
|
|
289
|
-
|
|
290
|
-
|
|
291
265
|
def update_ccsds_header_dims(sci_dataset: xr.Dataset) -> xr.Dataset:
|
|
292
266
|
"""
|
|
293
267
|
Update dimensions of CCSDS header fields.
|
|
@@ -334,8 +308,8 @@ def assemble_science_frames(sci_dataset: xr.Dataset) -> xr.Dataset:
|
|
|
334
308
|
The first six packets contain count rates data
|
|
335
309
|
The last 14 packets contain pulse height event data
|
|
336
310
|
|
|
337
|
-
These groups are added to the dataset as
|
|
338
|
-
and
|
|
311
|
+
These groups are added to the dataset as count_rates_raw
|
|
312
|
+
and pha_raw.
|
|
339
313
|
|
|
340
314
|
Parameters
|
|
341
315
|
----------
|
|
@@ -368,7 +342,7 @@ def assemble_science_frames(sci_dataset: xr.Dataset) -> xr.Dataset:
|
|
|
368
342
|
total_packets = len(epoch_data)
|
|
369
343
|
|
|
370
344
|
# Find starting indices for valid science frames
|
|
371
|
-
starting_indices =
|
|
345
|
+
starting_indices = get_valid_starting_indices(seq_flgs, seq_ctrs)
|
|
372
346
|
|
|
373
347
|
# Check for extra packets at start and end of file
|
|
374
348
|
# TODO: Will need to handle these extra packets when processing multiple files
|
|
@@ -400,19 +374,75 @@ def assemble_science_frames(sci_dataset: xr.Dataset) -> xr.Dataset:
|
|
|
400
374
|
pha.append("".join(science_data_frame[6:]))
|
|
401
375
|
# Get first packet's epoch for the science frame
|
|
402
376
|
epoch_per_science_frame = np.append(epoch_per_science_frame, epoch_data[idx])
|
|
403
|
-
# TODO: Filter ccsds header fields to only include packets from the
|
|
404
|
-
# valid science frames. Doesn't need to be grouped by frames though
|
|
405
377
|
|
|
406
378
|
# Add new data variables to the dataset
|
|
407
379
|
sci_dataset = sci_dataset.drop_vars("epoch")
|
|
408
380
|
sci_dataset.coords["epoch"] = epoch_per_science_frame
|
|
409
|
-
sci_dataset["
|
|
410
|
-
count_rates, dims=["epoch"], name="
|
|
381
|
+
sci_dataset["count_rates_raw"] = xr.DataArray(
|
|
382
|
+
count_rates, dims=["epoch"], name="count_rates_raw"
|
|
411
383
|
)
|
|
412
|
-
sci_dataset["
|
|
384
|
+
sci_dataset["pha_raw"] = xr.DataArray(pha, dims=["epoch"], name="pha_raw")
|
|
413
385
|
return sci_dataset
|
|
414
386
|
|
|
415
387
|
|
|
388
|
+
def decompress_rates_16_to_32(packed: int) -> int:
|
|
389
|
+
"""
|
|
390
|
+
Will decompress rates data from 16 bits to 32 bits.
|
|
391
|
+
|
|
392
|
+
This function decompresses the rates data from 16-bit integers
|
|
393
|
+
to 32-bit integers. The compressed integer (packed) combines
|
|
394
|
+
two parts:
|
|
395
|
+
|
|
396
|
+
1. Mantissa: Represents the significant digits of the value.
|
|
397
|
+
2. Exponent: Determines how much to scale the mantissa (using powers of 2).
|
|
398
|
+
|
|
399
|
+
These parts are packed together into a single 16-bit integer.
|
|
400
|
+
|
|
401
|
+
Parameters
|
|
402
|
+
----------
|
|
403
|
+
packed : int
|
|
404
|
+
Compressed 16-bit integer.
|
|
405
|
+
|
|
406
|
+
Returns
|
|
407
|
+
-------
|
|
408
|
+
decompressed_int : int
|
|
409
|
+
Decompressed integer.
|
|
410
|
+
"""
|
|
411
|
+
# In compressed formats, the exponent and mantissa are tightly packed together.
|
|
412
|
+
# The mask ensures you correctly separate the mantissa (useful for reconstructing
|
|
413
|
+
# the value) from the exponent (used for scaling).
|
|
414
|
+
# set to 16 bits
|
|
415
|
+
output_mask = 0xFFFF
|
|
416
|
+
|
|
417
|
+
# Packed is the compressed integer
|
|
418
|
+
# Right bit shift to get the exponent
|
|
419
|
+
power = packed >> MANTISSA_BITS
|
|
420
|
+
|
|
421
|
+
# Decompress the data depending on the value of the exponent
|
|
422
|
+
# If the exponent (power) extracted from the packed 16-bit integer is greater
|
|
423
|
+
# than 1, the compressed value needs to be decompressed by reconstructing the
|
|
424
|
+
# integer using the mantissa and exponent. If the condition is false, the
|
|
425
|
+
# compressed and uncompressed values are considered the same.
|
|
426
|
+
decompressed_int: int
|
|
427
|
+
if power > 1:
|
|
428
|
+
# Retrieve the "mantissa" portion of the packed value by masking out the
|
|
429
|
+
# exponent bits
|
|
430
|
+
mantissa_mask = output_mask >> EXPONENT_BITS
|
|
431
|
+
mantissa = packed & mantissa_mask
|
|
432
|
+
|
|
433
|
+
# Shift the mantissa to the left by 1 to account for the hidden bit
|
|
434
|
+
# (always set to 1)
|
|
435
|
+
mantissa_with_hidden_bit = mantissa | (0x0001 << MANTISSA_BITS)
|
|
436
|
+
|
|
437
|
+
# Scale the mantissa by the exponent by shifting it to the left by (power - 1)
|
|
438
|
+
decompressed_int = mantissa_with_hidden_bit << (power - 1)
|
|
439
|
+
else:
|
|
440
|
+
# The compressed and uncompressed values are the same
|
|
441
|
+
decompressed_int = packed
|
|
442
|
+
|
|
443
|
+
return decompressed_int
|
|
444
|
+
|
|
445
|
+
|
|
416
446
|
def decom_hit(sci_dataset: xr.Dataset) -> xr.Dataset:
|
|
417
447
|
"""
|
|
418
448
|
Group and decode HIT science data packets.
|
|
@@ -465,7 +495,10 @@ def decom_hit(sci_dataset: xr.Dataset) -> xr.Dataset:
|
|
|
465
495
|
# Parse count rates data from binary and add to dataset
|
|
466
496
|
parse_count_rates(sci_dataset)
|
|
467
497
|
|
|
498
|
+
# Further organize sector rates by species type
|
|
499
|
+
subcom_sectorates(sci_dataset)
|
|
500
|
+
|
|
468
501
|
# TODO:
|
|
469
|
-
#
|
|
502
|
+
# -clean up dataset - remove raw binary data, raw sectorates? Any other fields?
|
|
470
503
|
|
|
471
504
|
return sci_dataset
|