imap-processing 0.17.0__py3-none-any.whl → 0.18.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/_version.py +2 -2
- imap_processing/ccsds/excel_to_xtce.py +12 -0
- imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -6
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +11 -0
- imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +11 -0
- imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +24 -0
- imap_processing/cdf/config/imap_hit_l1a_variable_attrs.yaml +163 -100
- imap_processing/cdf/config/imap_hit_l2_variable_attrs.yaml +4 -4
- imap_processing/cdf/config/imap_ialirt_l1_variable_attrs.yaml +97 -54
- imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +119 -36
- imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +16 -90
- imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +30 -0
- imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +15 -1
- imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +60 -0
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +91 -11
- imap_processing/cli.py +28 -5
- imap_processing/codice/codice_l1a.py +36 -48
- imap_processing/codice/codice_l1b.py +1 -1
- imap_processing/codice/codice_l2.py +0 -9
- imap_processing/codice/constants.py +481 -498
- imap_processing/hit/l0/decom_hit.py +2 -2
- imap_processing/hit/l1a/hit_l1a.py +64 -24
- imap_processing/hit/l1b/constants.py +5 -0
- imap_processing/hit/l1b/hit_l1b.py +18 -16
- imap_processing/hit/l2/constants.py +1 -1
- imap_processing/hit/l2/hit_l2.py +4 -5
- imap_processing/ialirt/constants.py +21 -0
- imap_processing/ialirt/generate_coverage.py +188 -0
- imap_processing/ialirt/l0/parse_mag.py +62 -5
- imap_processing/ialirt/l0/process_swapi.py +1 -1
- imap_processing/ialirt/l0/process_swe.py +23 -7
- imap_processing/ialirt/utils/constants.py +22 -16
- imap_processing/ialirt/utils/create_xarray.py +42 -19
- imap_processing/idex/idex_constants.py +1 -5
- imap_processing/idex/idex_l2b.py +246 -67
- imap_processing/idex/idex_l2c.py +30 -196
- imap_processing/lo/l0/lo_apid.py +1 -0
- imap_processing/lo/l1a/lo_l1a.py +44 -0
- imap_processing/lo/packet_definitions/lo_xtce.xml +5359 -106
- imap_processing/mag/constants.py +1 -0
- imap_processing/mag/l1d/__init__.py +0 -0
- imap_processing/mag/l1d/mag_l1d.py +133 -0
- imap_processing/mag/l1d/mag_l1d_data.py +588 -0
- imap_processing/mag/l2/__init__.py +0 -0
- imap_processing/mag/l2/mag_l2.py +25 -20
- imap_processing/mag/l2/mag_l2_data.py +191 -130
- imap_processing/quality_flags.py +20 -2
- imap_processing/spice/geometry.py +25 -3
- imap_processing/spice/pointing_frame.py +1 -1
- imap_processing/spice/spin.py +4 -0
- imap_processing/spice/time.py +51 -0
- imap_processing/swapi/l2/swapi_l2.py +52 -8
- imap_processing/swapi/swapi_utils.py +1 -1
- imap_processing/swe/l1b/swe_l1b.py +2 -4
- imap_processing/ultra/constants.py +49 -1
- imap_processing/ultra/l0/decom_tools.py +15 -8
- imap_processing/ultra/l0/decom_ultra.py +35 -11
- imap_processing/ultra/l0/ultra_utils.py +97 -5
- imap_processing/ultra/l1a/ultra_l1a.py +25 -4
- imap_processing/ultra/l1b/cullingmask.py +3 -3
- imap_processing/ultra/l1b/de.py +53 -15
- imap_processing/ultra/l1b/extendedspin.py +26 -2
- imap_processing/ultra/l1b/lookup_utils.py +171 -50
- imap_processing/ultra/l1b/quality_flag_filters.py +14 -0
- imap_processing/ultra/l1b/ultra_l1b_culling.py +198 -5
- imap_processing/ultra/l1b/ultra_l1b_extended.py +304 -66
- imap_processing/ultra/l1c/helio_pset.py +54 -7
- imap_processing/ultra/l1c/spacecraft_pset.py +9 -1
- imap_processing/ultra/l1c/ultra_l1c.py +2 -0
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +106 -109
- imap_processing/ultra/utils/ultra_l1_utils.py +13 -1
- {imap_processing-0.17.0.dist-info → imap_processing-0.18.0.dist-info}/METADATA +2 -2
- {imap_processing-0.17.0.dist-info → imap_processing-0.18.0.dist-info}/RECORD +76 -83
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_LeftSlit.csv +0 -526
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM45_RightSlit.csv +0 -526
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_LeftSlit.csv +0 -526
- imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +0 -524
- imap_processing/ultra/lookup_tables/EgyNorm.mem.csv +0 -32769
- imap_processing/ultra/lookup_tables/FM45_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
- imap_processing/ultra/lookup_tables/FM90_Startup1_ULTRA_IMGPARAMS_20240719.csv +0 -2
- imap_processing/ultra/lookup_tables/dps_grid45_compressed.cdf +0 -0
- imap_processing/ultra/lookup_tables/ultra45_back-pos-luts.csv +0 -4097
- imap_processing/ultra/lookup_tables/ultra45_tdc_norm.csv +0 -2050
- imap_processing/ultra/lookup_tables/ultra90_back-pos-luts.csv +0 -4097
- imap_processing/ultra/lookup_tables/ultra90_tdc_norm.csv +0 -2050
- imap_processing/ultra/lookup_tables/yadjust.csv +0 -257
- {imap_processing-0.17.0.dist-info → imap_processing-0.18.0.dist-info}/LICENSE +0 -0
- {imap_processing-0.17.0.dist-info → imap_processing-0.18.0.dist-info}/WHEEL +0 -0
- {imap_processing-0.17.0.dist-info → imap_processing-0.18.0.dist-info}/entry_points.txt +0 -0
|
@@ -48,6 +48,13 @@ def create_xarray_from_records(records: list[dict]) -> xr.Dataset: # noqa: PLR0
|
|
|
48
48
|
attrs=cdf_manager.get_variable_attributes("component", check_schema=False),
|
|
49
49
|
)
|
|
50
50
|
|
|
51
|
+
rtn_component = xr.DataArray(
|
|
52
|
+
["radial", "tangential", "normal"],
|
|
53
|
+
name="RTN_component",
|
|
54
|
+
dims=["RTN_component"],
|
|
55
|
+
attrs=cdf_manager.get_variable_attributes("RTN_componentt", check_schema=False),
|
|
56
|
+
)
|
|
57
|
+
|
|
51
58
|
esa_step = xr.DataArray(
|
|
52
59
|
data=np.arange(8, dtype=np.uint8),
|
|
53
60
|
name="esa_step",
|
|
@@ -57,32 +64,39 @@ def create_xarray_from_records(records: list[dict]) -> xr.Dataset: # noqa: PLR0
|
|
|
57
64
|
|
|
58
65
|
energy_ranges = xr.DataArray(
|
|
59
66
|
data=np.arange(15, dtype=np.uint8),
|
|
60
|
-
name="
|
|
61
|
-
dims=["
|
|
62
|
-
attrs=cdf_manager.get_variable_attributes(
|
|
67
|
+
name="codice_hi_h_energy_ranges",
|
|
68
|
+
dims=["codice_hi_h_energy_ranges"],
|
|
69
|
+
attrs=cdf_manager.get_variable_attributes(
|
|
70
|
+
"codice_hi_h_energy_ranges", check_schema=False
|
|
71
|
+
),
|
|
63
72
|
)
|
|
64
73
|
|
|
65
|
-
|
|
74
|
+
elevation = xr.DataArray(
|
|
66
75
|
data=np.arange(4, dtype=np.uint8),
|
|
67
|
-
name="
|
|
68
|
-
dims=["
|
|
69
|
-
attrs=cdf_manager.get_variable_attributes(
|
|
76
|
+
name="codice_hi_h_elevation",
|
|
77
|
+
dims=["codice_hi_h_elevation"],
|
|
78
|
+
attrs=cdf_manager.get_variable_attributes(
|
|
79
|
+
"codice_hi_h_elevation", check_schema=False
|
|
80
|
+
),
|
|
70
81
|
)
|
|
71
82
|
|
|
72
|
-
|
|
83
|
+
spin_angle = xr.DataArray(
|
|
73
84
|
data=np.arange(4, dtype=np.uint8),
|
|
74
|
-
name="
|
|
75
|
-
dims=["
|
|
76
|
-
attrs=cdf_manager.get_variable_attributes(
|
|
85
|
+
name="codice_hi_h_spin_angle",
|
|
86
|
+
dims=["codice_hi_h_spin_angle"],
|
|
87
|
+
attrs=cdf_manager.get_variable_attributes(
|
|
88
|
+
"codice_hi_h_spin_anglen", check_schema=False
|
|
89
|
+
),
|
|
77
90
|
)
|
|
78
91
|
|
|
79
92
|
coords = {
|
|
80
93
|
"epoch": epoch,
|
|
81
94
|
"component": component,
|
|
95
|
+
"RTN_component": rtn_component,
|
|
82
96
|
"esa_step": esa_step,
|
|
83
|
-
"
|
|
84
|
-
"
|
|
85
|
-
"
|
|
97
|
+
"codice_hi_h_energy_ranges": energy_ranges,
|
|
98
|
+
"codice_hi_h_elevation": elevation,
|
|
99
|
+
"codice_hi_h_spin_angle": spin_angle,
|
|
86
100
|
}
|
|
87
101
|
dataset = xr.Dataset(
|
|
88
102
|
coords=coords,
|
|
@@ -93,13 +107,22 @@ def create_xarray_from_records(records: list[dict]) -> xr.Dataset: # noqa: PLR0
|
|
|
93
107
|
for key in instrument_keys:
|
|
94
108
|
attrs = cdf_manager.get_variable_attributes(key, check_schema=False)
|
|
95
109
|
fillval = attrs.get("FILLVAL")
|
|
96
|
-
if key
|
|
110
|
+
if key in ["mag_B_GSE", "mag_B_GSM"]:
|
|
97
111
|
data = np.full((n, 3), fillval, dtype=np.float32)
|
|
98
112
|
dims = ["epoch", "component"]
|
|
99
113
|
dataset[key] = xr.DataArray(data, dims=dims, attrs=attrs)
|
|
100
|
-
elif key
|
|
114
|
+
elif key == "mag_B_RTN":
|
|
115
|
+
data = np.full((n, 3), fillval, dtype=np.float32)
|
|
116
|
+
dims = ["epoch", "RTN_component"]
|
|
117
|
+
dataset[key] = xr.DataArray(data, dims=dims, attrs=attrs)
|
|
118
|
+
elif key.startswith("codice_hi"):
|
|
101
119
|
data = np.full((n, 15, 4, 4), fillval, dtype=np.float32)
|
|
102
|
-
dims = [
|
|
120
|
+
dims = [
|
|
121
|
+
"epoch",
|
|
122
|
+
"codice_hi_h_energy_ranges",
|
|
123
|
+
"codice_hi_h_elevation",
|
|
124
|
+
"codice_hi_h_spin_angle",
|
|
125
|
+
]
|
|
103
126
|
dataset[key] = xr.DataArray(data, dims=dims, attrs=attrs)
|
|
104
127
|
elif key == "swe_counterstreaming_electrons":
|
|
105
128
|
data = np.full(n, fillval, dtype=np.uint8)
|
|
@@ -123,11 +146,11 @@ def create_xarray_from_records(records: list[dict]) -> xr.Dataset: # noqa: PLR0
|
|
|
123
146
|
for key, val in record.items():
|
|
124
147
|
if key in ["apid", "met", "met_in_utc", "ttj2000ns"]:
|
|
125
148
|
continue
|
|
126
|
-
elif key
|
|
149
|
+
elif key in ["mag_B_GSE", "mag_B_GSM", "mag_B_RTN"]:
|
|
127
150
|
dataset[key].data[i, :] = val
|
|
128
151
|
elif key.startswith("swe_normalized_counts"):
|
|
129
152
|
dataset[key].data[i, :] = val
|
|
130
|
-
elif key.startswith("
|
|
153
|
+
elif key.startswith("codice_hi"):
|
|
131
154
|
dataset[key].data[i, :, :, :] = val
|
|
132
155
|
else:
|
|
133
156
|
dataset[key].data[i] = val
|
|
@@ -82,13 +82,9 @@ SPICE_ARRAYS = [
|
|
|
82
82
|
"spin_phase",
|
|
83
83
|
]
|
|
84
84
|
|
|
85
|
-
# Default IDEX Healpix parameters
|
|
86
|
-
# Used in IDEX l2c processing
|
|
87
|
-
IDEX_HEALPIX_NSIDE = 8
|
|
88
|
-
IDEX_HEALPIX_NESTED = False
|
|
89
85
|
# Default IDEX Rectangular parameters
|
|
90
86
|
# Used in IDEX l2c processing
|
|
91
|
-
IDEX_SPACING_DEG =
|
|
87
|
+
IDEX_SPACING_DEG = 6
|
|
92
88
|
|
|
93
89
|
# Define the pointing reference frame for IDEX
|
|
94
90
|
IDEX_EVENT_REFERENCE_FRAME = SpiceFrame.ECLIPJ2000
|
imap_processing/idex/idex_l2b.py
CHANGED
|
@@ -29,8 +29,10 @@ from datetime import datetime, timedelta
|
|
|
29
29
|
import numpy as np
|
|
30
30
|
import xarray as xr
|
|
31
31
|
|
|
32
|
+
from imap_processing.ena_maps.utils.spatial_utils import AzElSkyGrid
|
|
32
33
|
from imap_processing.idex.idex_constants import (
|
|
33
34
|
FG_TO_KG,
|
|
35
|
+
IDEX_SPACING_DEG,
|
|
34
36
|
SECONDS_IN_DAY,
|
|
35
37
|
IDEXEvtAcquireCodes,
|
|
36
38
|
)
|
|
@@ -71,6 +73,11 @@ CHARGE_BIN_EDGES = np.array(
|
|
|
71
73
|
)
|
|
72
74
|
SPIN_PHASE_BIN_EDGES = np.array([0, 90, 180, 270, 360])
|
|
73
75
|
|
|
76
|
+
# Get the rectangular map grid with the specified spacing
|
|
77
|
+
SKY_GRID = AzElSkyGrid(IDEX_SPACING_DEG)
|
|
78
|
+
LON_BINS_EDGES = SKY_GRID.az_bin_edges
|
|
79
|
+
LAT_BINS_EDGES = SKY_GRID.el_bin_edges
|
|
80
|
+
|
|
74
81
|
|
|
75
82
|
def idex_l2b(
|
|
76
83
|
l2a_datasets: list[xr.Dataset], evt_datasets: list[xr.Dataset]
|
|
@@ -102,17 +109,32 @@ def idex_l2b(
|
|
|
102
109
|
# Concat all the l2a datasets together
|
|
103
110
|
l2a_dataset = xr.concat(l2a_datasets, dim="epoch")
|
|
104
111
|
epoch_doy_unique = np.unique(epoch_to_doy(l2a_dataset["epoch"].data))
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
112
|
+
(
|
|
113
|
+
counts_by_charge,
|
|
114
|
+
counts_by_mass,
|
|
115
|
+
counts_by_charge_map,
|
|
116
|
+
counts_by_mass_map,
|
|
117
|
+
daily_epoch,
|
|
118
|
+
) = compute_counts_by_charge_and_mass(l2a_dataset, epoch_doy_unique)
|
|
108
119
|
# Get science acquisition percentage for each day
|
|
109
120
|
daily_on_percentage = get_science_acquisition_on_percentage(evt_dataset)
|
|
110
|
-
|
|
111
|
-
|
|
121
|
+
(
|
|
122
|
+
rate_by_charge,
|
|
123
|
+
rate_by_mass,
|
|
124
|
+
rate_by_charge_map,
|
|
125
|
+
rate_by_mass_map,
|
|
126
|
+
rate_quality_flags,
|
|
127
|
+
) = compute_rates_by_charge_and_mass(
|
|
128
|
+
counts_by_charge,
|
|
129
|
+
counts_by_mass,
|
|
130
|
+
counts_by_charge_map,
|
|
131
|
+
counts_by_mass_map,
|
|
132
|
+
epoch_doy_unique,
|
|
133
|
+
daily_on_percentage,
|
|
112
134
|
)
|
|
113
135
|
# Create l2b Dataset
|
|
114
|
-
charge_bins = np.arange(len(CHARGE_BIN_EDGES))
|
|
115
|
-
mass_bins = np.arange(len(CHARGE_BIN_EDGES))
|
|
136
|
+
charge_bins = np.arange(len(CHARGE_BIN_EDGES) - 1)
|
|
137
|
+
mass_bins = np.arange(len(CHARGE_BIN_EDGES) - 1)
|
|
116
138
|
spin_phase_bins = np.arange(len(SPIN_PHASE_BIN_EDGES) - 1)
|
|
117
139
|
epoch = xr.DataArray(
|
|
118
140
|
name="epoch",
|
|
@@ -120,7 +142,7 @@ def idex_l2b(
|
|
|
120
142
|
dims="epoch",
|
|
121
143
|
attrs=idex_attrs.get_variable_attributes("epoch", check_schema=False),
|
|
122
144
|
)
|
|
123
|
-
|
|
145
|
+
data_vars = {
|
|
124
146
|
"impact_day_of_year": xr.DataArray(
|
|
125
147
|
name="impact_day_of_year",
|
|
126
148
|
data=epoch_doy_unique,
|
|
@@ -136,7 +158,7 @@ def idex_l2b(
|
|
|
136
158
|
"charge_labels": xr.DataArray(
|
|
137
159
|
name="impact_charge_labels",
|
|
138
160
|
data=charge_bins.astype(str),
|
|
139
|
-
dims="
|
|
161
|
+
dims="impact_charge",
|
|
140
162
|
attrs=idex_attrs.get_variable_attributes(
|
|
141
163
|
"charge_labels", check_schema=False
|
|
142
164
|
),
|
|
@@ -144,7 +166,7 @@ def idex_l2b(
|
|
|
144
166
|
"spin_phase_labels": xr.DataArray(
|
|
145
167
|
name="spin_phase_labels",
|
|
146
168
|
data=spin_phase_bins.astype(str),
|
|
147
|
-
dims="
|
|
169
|
+
dims="spin_phase",
|
|
148
170
|
attrs=idex_attrs.get_variable_attributes(
|
|
149
171
|
"spin_phase_labels", check_schema=False
|
|
150
172
|
),
|
|
@@ -152,64 +174,135 @@ def idex_l2b(
|
|
|
152
174
|
"mass_labels": xr.DataArray(
|
|
153
175
|
name="mass_labels",
|
|
154
176
|
data=mass_bins.astype(str),
|
|
155
|
-
dims="
|
|
177
|
+
dims="mass",
|
|
156
178
|
attrs=idex_attrs.get_variable_attributes("mass_labels", check_schema=False),
|
|
157
179
|
),
|
|
158
|
-
"
|
|
159
|
-
name="
|
|
180
|
+
"rectangular_lon_pixel_label": xr.DataArray(
|
|
181
|
+
name="rectangular_lon_pixel_label",
|
|
182
|
+
data=SKY_GRID.az_bin_midpoints.astype(str),
|
|
183
|
+
dims="rectangular_lon_pixel",
|
|
184
|
+
attrs=idex_attrs.get_variable_attributes(
|
|
185
|
+
"rectangular_lon_pixel_label", check_schema=False
|
|
186
|
+
),
|
|
187
|
+
),
|
|
188
|
+
"rectangular_lat_pixel_label": xr.DataArray(
|
|
189
|
+
name="rectangular_lat_pixel_label",
|
|
190
|
+
data=SKY_GRID.el_bin_midpoints.astype(str),
|
|
191
|
+
dims="rectangular_lat_pixel",
|
|
192
|
+
attrs=idex_attrs.get_variable_attributes(
|
|
193
|
+
"rectangular_lat_pixel_label", check_schema=False
|
|
194
|
+
),
|
|
195
|
+
),
|
|
196
|
+
"impact_charge": xr.DataArray(
|
|
197
|
+
name="impact_charge",
|
|
160
198
|
data=charge_bins,
|
|
161
|
-
dims="
|
|
199
|
+
dims="impact_charge",
|
|
162
200
|
attrs=idex_attrs.get_variable_attributes(
|
|
163
|
-
"
|
|
201
|
+
"impact_charge", check_schema=False
|
|
164
202
|
),
|
|
165
203
|
),
|
|
166
|
-
"
|
|
167
|
-
name="
|
|
204
|
+
"mass": xr.DataArray(
|
|
205
|
+
name="mass",
|
|
168
206
|
data=mass_bins,
|
|
169
|
-
dims="
|
|
170
|
-
attrs=idex_attrs.get_variable_attributes("
|
|
207
|
+
dims="mass",
|
|
208
|
+
attrs=idex_attrs.get_variable_attributes("mass", check_schema=False),
|
|
171
209
|
),
|
|
172
|
-
"
|
|
173
|
-
name="
|
|
210
|
+
"spin_phase": xr.DataArray(
|
|
211
|
+
name="spin_phase",
|
|
174
212
|
data=spin_phase_bins,
|
|
175
|
-
dims="
|
|
213
|
+
dims="spin_phase",
|
|
214
|
+
attrs=idex_attrs.get_variable_attributes("spin_phase", check_schema=False),
|
|
215
|
+
),
|
|
216
|
+
"rectangular_lon_pixel": xr.DataArray(
|
|
217
|
+
name="rectangular_lon_pixel",
|
|
218
|
+
data=SKY_GRID.az_bin_midpoints,
|
|
219
|
+
dims="rectangular_lon_pixel",
|
|
220
|
+
attrs=idex_attrs.get_variable_attributes(
|
|
221
|
+
"rectangular_lon_pixel", check_schema=False
|
|
222
|
+
),
|
|
223
|
+
),
|
|
224
|
+
"rectangular_lat_pixel": xr.DataArray(
|
|
225
|
+
name="rectangular_lat_pixel",
|
|
226
|
+
data=SKY_GRID.el_bin_midpoints,
|
|
227
|
+
dims="rectangular_lat_pixel",
|
|
176
228
|
attrs=idex_attrs.get_variable_attributes(
|
|
177
|
-
"
|
|
229
|
+
"rectangular_lat_pixel", check_schema=False
|
|
178
230
|
),
|
|
179
231
|
),
|
|
180
232
|
"counts_by_charge": xr.DataArray(
|
|
181
233
|
name="counts_by_charge",
|
|
182
234
|
data=counts_by_charge.astype(np.int64),
|
|
183
|
-
dims=("epoch", "
|
|
235
|
+
dims=("epoch", "impact_charge", "spin_phase"),
|
|
184
236
|
attrs=idex_attrs.get_variable_attributes("counts_by_charge"),
|
|
185
237
|
),
|
|
186
238
|
"counts_by_mass": xr.DataArray(
|
|
187
239
|
name="counts_by_mass",
|
|
188
240
|
data=counts_by_mass.astype(np.int64),
|
|
189
|
-
dims=("epoch", "
|
|
241
|
+
dims=("epoch", "mass", "spin_phase"),
|
|
190
242
|
attrs=idex_attrs.get_variable_attributes("counts_by_mass"),
|
|
191
243
|
),
|
|
192
244
|
"rate_by_charge": xr.DataArray(
|
|
193
245
|
name="rate_by_charge",
|
|
194
246
|
data=rate_by_charge,
|
|
195
|
-
dims=("epoch", "
|
|
247
|
+
dims=("epoch", "impact_charge", "spin_phase"),
|
|
196
248
|
attrs=idex_attrs.get_variable_attributes("rate_by_charge"),
|
|
197
249
|
),
|
|
198
250
|
"rate_by_mass": xr.DataArray(
|
|
199
251
|
name="rate_by_mass",
|
|
200
252
|
data=rate_by_mass,
|
|
201
|
-
dims=("epoch", "
|
|
253
|
+
dims=("epoch", "mass", "spin_phase"),
|
|
202
254
|
attrs=idex_attrs.get_variable_attributes("rate_by_mass"),
|
|
203
255
|
),
|
|
256
|
+
"counts_by_charge_map": xr.DataArray(
|
|
257
|
+
name="counts_by_charge_map",
|
|
258
|
+
data=counts_by_charge_map.astype(np.int64),
|
|
259
|
+
dims=(
|
|
260
|
+
"epoch",
|
|
261
|
+
"impact_charge",
|
|
262
|
+
"rectangular_lon_pixel",
|
|
263
|
+
"rectangular_lat_pixel",
|
|
264
|
+
),
|
|
265
|
+
attrs=idex_attrs.get_variable_attributes("counts_by_charge_map"),
|
|
266
|
+
),
|
|
267
|
+
"counts_by_mass_map": xr.DataArray(
|
|
268
|
+
name="counts_by_mass_map",
|
|
269
|
+
data=counts_by_mass_map.astype(np.int64),
|
|
270
|
+
dims=(
|
|
271
|
+
"epoch",
|
|
272
|
+
"mass",
|
|
273
|
+
"rectangular_lon_pixel",
|
|
274
|
+
"rectangular_lat_pixel",
|
|
275
|
+
),
|
|
276
|
+
attrs=idex_attrs.get_variable_attributes("counts_by_mass_map"),
|
|
277
|
+
),
|
|
278
|
+
"rate_by_charge_map": xr.DataArray(
|
|
279
|
+
name="rate_by_charge_map",
|
|
280
|
+
data=rate_by_charge_map,
|
|
281
|
+
dims=(
|
|
282
|
+
"epoch",
|
|
283
|
+
"impact_charge",
|
|
284
|
+
"rectangular_lon_pixel",
|
|
285
|
+
"rectangular_lat_pixel",
|
|
286
|
+
),
|
|
287
|
+
attrs=idex_attrs.get_variable_attributes("rate_by_charge_map"),
|
|
288
|
+
),
|
|
289
|
+
"rate_by_mass_map": xr.DataArray(
|
|
290
|
+
name="rate_by_mass_map",
|
|
291
|
+
data=rate_by_mass_map,
|
|
292
|
+
dims=(
|
|
293
|
+
"epoch",
|
|
294
|
+
"mass",
|
|
295
|
+
"rectangular_lon_pixel",
|
|
296
|
+
"rectangular_lat_pixel",
|
|
297
|
+
),
|
|
298
|
+
attrs=idex_attrs.get_variable_attributes("rate_by_mass_map"),
|
|
299
|
+
),
|
|
204
300
|
}
|
|
205
301
|
l2b_dataset = xr.Dataset(
|
|
206
302
|
coords={"epoch": epoch},
|
|
207
|
-
data_vars=
|
|
303
|
+
data_vars=data_vars,
|
|
208
304
|
attrs=idex_attrs.get_global_attributes("imap_idex_l2b_sci"),
|
|
209
305
|
)
|
|
210
|
-
# Copy longitude and latitude from the l2a dataset
|
|
211
|
-
l2b_dataset["longitude"] = l2a_dataset["longitude"].copy()
|
|
212
|
-
l2b_dataset["latitude"] = l2a_dataset["latitude"].copy()
|
|
213
306
|
|
|
214
307
|
logger.info("IDEX L2B science data processing completed.")
|
|
215
308
|
|
|
@@ -218,9 +311,9 @@ def idex_l2b(
|
|
|
218
311
|
|
|
219
312
|
def compute_counts_by_charge_and_mass(
|
|
220
313
|
l2a_dataset: xr.Dataset, epoch_doy_unique: np.ndarray
|
|
221
|
-
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
314
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
222
315
|
"""
|
|
223
|
-
Compute the dust
|
|
316
|
+
Compute the dust counts by charge and mass by spin phase or lon and lat per day.
|
|
224
317
|
|
|
225
318
|
Parameters
|
|
226
319
|
----------
|
|
@@ -231,20 +324,43 @@ def compute_counts_by_charge_and_mass(
|
|
|
231
324
|
|
|
232
325
|
Returns
|
|
233
326
|
-------
|
|
234
|
-
tuple[np.ndarray, np.ndarray, np.ndarray]
|
|
327
|
+
tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]
|
|
235
328
|
Two 3D arrays containing counts by charge or mass, and by spin phase for each
|
|
236
|
-
dataset,
|
|
329
|
+
dataset, Two 4D arrays containing counts by charge or mass, and by lon and lat
|
|
330
|
+
for each dataset, and a 1D array of daily epoch values.
|
|
237
331
|
"""
|
|
238
332
|
# Initialize arrays to hold counts.
|
|
239
|
-
# There should be 4 spin phase bins,
|
|
333
|
+
# There should be 4 spin phase bins, 10 charge bins, and 10 mass bins.
|
|
240
334
|
# The first bin for charge and mass is for values below the first bin edge.
|
|
241
335
|
counts_by_charge = np.zeros(
|
|
242
|
-
(
|
|
336
|
+
(
|
|
337
|
+
len(epoch_doy_unique),
|
|
338
|
+
len(CHARGE_BIN_EDGES) - 1,
|
|
339
|
+
len(SPIN_PHASE_BIN_EDGES) - 1,
|
|
340
|
+
),
|
|
243
341
|
)
|
|
244
342
|
counts_by_mass = np.zeros(
|
|
245
|
-
(len(epoch_doy_unique), len(MASS_BIN_EDGES), len(SPIN_PHASE_BIN_EDGES) - 1),
|
|
343
|
+
(len(epoch_doy_unique), len(MASS_BIN_EDGES) - 1, len(SPIN_PHASE_BIN_EDGES) - 1),
|
|
344
|
+
)
|
|
345
|
+
# Initialize arrays to hold count maps. Each map is a 3 or 4D array with shape
|
|
346
|
+
# (epoch, 10 [charge or mass], 60 [longitude bins], 30 [latitude bins]).
|
|
347
|
+
counts_by_charge_map = np.zeros(
|
|
348
|
+
(
|
|
349
|
+
len(epoch_doy_unique),
|
|
350
|
+
len(CHARGE_BIN_EDGES) - 1,
|
|
351
|
+
len(LON_BINS_EDGES) - 1,
|
|
352
|
+
len(LAT_BINS_EDGES) - 1,
|
|
353
|
+
),
|
|
354
|
+
)
|
|
355
|
+
counts_by_mass_map = np.zeros(
|
|
356
|
+
(
|
|
357
|
+
len(epoch_doy_unique),
|
|
358
|
+
len(MASS_BIN_EDGES) - 1,
|
|
359
|
+
len(LON_BINS_EDGES) - 1,
|
|
360
|
+
len(LAT_BINS_EDGES) - 1,
|
|
361
|
+
),
|
|
246
362
|
)
|
|
247
|
-
daily_epoch = np.zeros(len(epoch_doy_unique))
|
|
363
|
+
daily_epoch = np.zeros(len(epoch_doy_unique), dtype=np.float64)
|
|
248
364
|
for i in range(len(epoch_doy_unique)):
|
|
249
365
|
doy = epoch_doy_unique[i]
|
|
250
366
|
# Get the indices for the current day
|
|
@@ -258,39 +374,88 @@ def compute_counts_by_charge_and_mass(
|
|
|
258
374
|
]
|
|
259
375
|
charge_vals = l2a_dataset["target_low_impact_charge"].data[current_day_indices]
|
|
260
376
|
spin_phase_angles = l2a_dataset["spin_phase"].data[current_day_indices]
|
|
377
|
+
# Make sure longitude values are in the range [0, 360)
|
|
378
|
+
longitude = np.mod(l2a_dataset["longitude"].data[current_day_indices], 360)
|
|
379
|
+
latitude = l2a_dataset["latitude"].data[current_day_indices]
|
|
261
380
|
# Convert units
|
|
262
|
-
mass_vals = FG_TO_KG * np.
|
|
381
|
+
mass_vals = FG_TO_KG * np.atleast_1d(mass_vals)
|
|
263
382
|
# Bin masses
|
|
264
|
-
binned_mass = np.
|
|
383
|
+
binned_mass = np.asarray(np.digitize(mass_vals, bins=MASS_BIN_EDGES))
|
|
265
384
|
# Bin charges
|
|
266
|
-
binned_charge = np.
|
|
385
|
+
binned_charge = np.asarray(np.digitize(charge_vals, bins=CHARGE_BIN_EDGES))
|
|
267
386
|
# Bin spin phases
|
|
268
387
|
binned_spin_phase = bin_spin_phases(spin_phase_angles)
|
|
388
|
+
# Bin longitude and latitude into the rectangular grid.
|
|
389
|
+
binned_longitude = np.asarray(np.digitize(longitude, bins=LON_BINS_EDGES))
|
|
390
|
+
# Latitude should be binned with the right edge included. 90 is a valid latitude
|
|
391
|
+
binned_latitude = np.asarray(np.digitize(latitude, bins=LAT_BINS_EDGES))
|
|
392
|
+
# Clip latitude value above the right edge to be in the last bin
|
|
393
|
+
binned_latitude = np.clip(binned_latitude, 1, len(LAT_BINS_EDGES) - 1)
|
|
269
394
|
# If the values in the array are beyond the bounds of bins, 0 or len(bins) it is
|
|
270
395
|
# returned as such. In this case, the desired result is to place the values
|
|
271
|
-
# beyond the
|
|
272
|
-
binned_charge
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
396
|
+
# beyond the first or last bin into the first or last bin, respectively.
|
|
397
|
+
binned_charge = np.clip(binned_charge, 1, len(CHARGE_BIN_EDGES) - 1)
|
|
398
|
+
binned_mass = np.clip(binned_mass, 1, len(MASS_BIN_EDGES) - 1)
|
|
399
|
+
|
|
400
|
+
# Count dust events for each spin phase, mass bin, charge bin, and bin into
|
|
401
|
+
# a rectangular grid
|
|
402
|
+
for mass_bin, charge_bin, spin_phase_bin, lon_bin, lat_bin in zip(
|
|
403
|
+
binned_mass,
|
|
404
|
+
binned_charge,
|
|
405
|
+
binned_spin_phase,
|
|
406
|
+
binned_longitude,
|
|
407
|
+
binned_latitude,
|
|
281
408
|
):
|
|
282
|
-
counts_by_mass[i, mass_bin, spin_phase_bin] += 1
|
|
283
|
-
counts_by_charge[i, charge_bin, spin_phase_bin] += 1
|
|
409
|
+
counts_by_mass[i, mass_bin - 1, spin_phase_bin] += 1
|
|
410
|
+
counts_by_charge[i, charge_bin - 1, spin_phase_bin] += 1
|
|
411
|
+
counts_by_mass_map[i, mass_bin - 1, lon_bin - 1, lat_bin - 1] += 1
|
|
412
|
+
counts_by_charge_map[i, charge_bin - 1, lon_bin - 1, lat_bin - 1] += 1
|
|
413
|
+
|
|
414
|
+
return (
|
|
415
|
+
counts_by_charge,
|
|
416
|
+
counts_by_mass,
|
|
417
|
+
counts_by_charge_map,
|
|
418
|
+
counts_by_mass_map,
|
|
419
|
+
daily_epoch,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def compute_rates(
|
|
424
|
+
counts: np.ndarray, epoch_doy_percent_on: np.ndarray, non_zero_inds: np.ndarray
|
|
425
|
+
) -> np.ndarray:
|
|
426
|
+
"""
|
|
427
|
+
Compute the count rates given the percent uptime of IDEX.
|
|
428
|
+
|
|
429
|
+
Parameters
|
|
430
|
+
----------
|
|
431
|
+
counts : np.ndarray
|
|
432
|
+
Count values for the dust events.
|
|
433
|
+
epoch_doy_percent_on : np.ndarray
|
|
434
|
+
Percentage of time science acquisition was on for each day of the year.
|
|
435
|
+
non_zero_inds : np.ndarray
|
|
436
|
+
Indices of the days with non-zero science acquisition percentage.
|
|
437
|
+
|
|
438
|
+
Returns
|
|
439
|
+
-------
|
|
440
|
+
np.ndarray
|
|
441
|
+
Count rates.
|
|
442
|
+
"""
|
|
443
|
+
while len(epoch_doy_percent_on.shape) < len(counts.shape):
|
|
444
|
+
epoch_doy_percent_on = np.expand_dims(epoch_doy_percent_on, axis=-1)
|
|
284
445
|
|
|
285
|
-
return
|
|
446
|
+
return counts[non_zero_inds] / (
|
|
447
|
+
0.01 * epoch_doy_percent_on[non_zero_inds] * SECONDS_IN_DAY
|
|
448
|
+
)
|
|
286
449
|
|
|
287
450
|
|
|
288
451
|
def compute_rates_by_charge_and_mass(
|
|
289
452
|
counts_by_charge: np.ndarray,
|
|
290
453
|
counts_by_mass: np.ndarray,
|
|
454
|
+
counts_by_charge_map: np.ndarray,
|
|
455
|
+
counts_by_mass_map: np.ndarray,
|
|
291
456
|
epoch_doy: np.ndarray,
|
|
292
457
|
daily_on_percentage: dict,
|
|
293
|
-
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
458
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
|
|
294
459
|
"""
|
|
295
460
|
Compute the dust event counts rates by charge and mass by spin phase for each day.
|
|
296
461
|
|
|
@@ -299,7 +464,11 @@ def compute_rates_by_charge_and_mass(
|
|
|
299
464
|
counts_by_charge : np.ndarray
|
|
300
465
|
3D array containing counts by charge and spin phase for each dataset.
|
|
301
466
|
counts_by_mass : np.ndarray
|
|
302
|
-
3D array containing counts by mass and
|
|
467
|
+
3D array containing counts by mass and lon and lat for each dataset.
|
|
468
|
+
counts_by_charge_map : np.ndarray
|
|
469
|
+
4D array containing counts by charge and lon and lat for each dataset.
|
|
470
|
+
counts_by_mass_map : np.ndarray
|
|
471
|
+
4D array containing counts by mass and spin phase for each dataset.
|
|
303
472
|
epoch_doy : np.ndarray
|
|
304
473
|
Unique days of year corresponding to the epochs in the dataset.
|
|
305
474
|
daily_on_percentage : dict
|
|
@@ -314,6 +483,8 @@ def compute_rates_by_charge_and_mass(
|
|
|
314
483
|
# Initialize arrays to hold rates.
|
|
315
484
|
rate_by_charge = np.full(counts_by_charge.shape, -1.0)
|
|
316
485
|
rate_by_mass = np.full(counts_by_mass.shape, -1.0)
|
|
486
|
+
rate_by_charge_map = np.full(counts_by_charge_map.shape, -1.0)
|
|
487
|
+
rate_by_mass_map = np.full(counts_by_mass_map.shape, -1.0)
|
|
317
488
|
# Initialize an array to hold quality flags for each epoch. A quality flag of 0
|
|
318
489
|
# indicates that there was no science acquisition data for that epoch, and the rate
|
|
319
490
|
# is not valid. A quality flag of 1 indicates that the rate is valid.
|
|
@@ -336,18 +507,26 @@ def compute_rates_by_charge_and_mass(
|
|
|
336
507
|
# acquisition time.
|
|
337
508
|
non_zero_inds = np.where(epoch_doy_percent_on > 0)[0]
|
|
338
509
|
# Compute rates only for days with non-zero science acquisition percentage
|
|
339
|
-
rate_by_charge[non_zero_inds] =
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
510
|
+
rate_by_charge[non_zero_inds] = compute_rates(
|
|
511
|
+
counts_by_charge, epoch_doy_percent_on, non_zero_inds
|
|
512
|
+
)
|
|
513
|
+
rate_by_mass[non_zero_inds] = compute_rates(
|
|
514
|
+
counts_by_mass, epoch_doy_percent_on, non_zero_inds
|
|
343
515
|
)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
516
|
+
rate_by_charge_map[non_zero_inds] = compute_rates(
|
|
517
|
+
counts_by_charge_map, epoch_doy_percent_on, non_zero_inds
|
|
518
|
+
)
|
|
519
|
+
rate_by_mass_map[non_zero_inds] = compute_rates(
|
|
520
|
+
counts_by_mass_map, epoch_doy_percent_on, non_zero_inds
|
|
348
521
|
)
|
|
349
522
|
|
|
350
|
-
return
|
|
523
|
+
return (
|
|
524
|
+
rate_by_charge,
|
|
525
|
+
rate_by_mass,
|
|
526
|
+
rate_by_charge_map,
|
|
527
|
+
rate_by_mass_map,
|
|
528
|
+
rate_quality_flags,
|
|
529
|
+
)
|
|
351
530
|
|
|
352
531
|
|
|
353
532
|
def bin_spin_phases(spin_phases: xr.DataArray) -> np.ndarray:
|
|
@@ -370,7 +549,7 @@ def bin_spin_phases(spin_phases: xr.DataArray) -> np.ndarray:
|
|
|
370
549
|
f"phase angle range, [0, 360)."
|
|
371
550
|
)
|
|
372
551
|
# Shift spin phases by +45° so that the first bin starts at 0°.
|
|
373
|
-
# Use mod to wrap values
|
|
552
|
+
# Use mod to wrap values >= 360 to 0.
|
|
374
553
|
shifted_spin_phases = (spin_phases + 45) % 360
|
|
375
554
|
# Use np.digitize to find the bin index for each spin phase.
|
|
376
555
|
bin_indices = np.digitize(shifted_spin_phases, SPIN_PHASE_BIN_EDGES, right=False)
|