imap-processing 0.17.0__py3-none-any.whl → 0.19.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/ancillary/ancillary_dataset_combiner.py +161 -1
- 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 +312 -274
- imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +39 -28
- imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +1048 -183
- imap_processing/cdf/config/imap_constant_attrs.yaml +4 -2
- imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +12 -0
- imap_processing/cdf/config/imap_hi_global_cdf_attrs.yaml +5 -0
- imap_processing/cdf/config/imap_hit_global_cdf_attrs.yaml +10 -4
- 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_l2a_variable_attrs.yaml +33 -4
- imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +44 -44
- imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +77 -61
- imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +30 -0
- imap_processing/cdf/config/imap_lo_l1a_variable_attrs.yaml +4 -15
- imap_processing/cdf/config/imap_lo_l1c_variable_attrs.yaml +189 -98
- imap_processing/cdf/config/imap_mag_global_cdf_attrs.yaml +99 -2
- imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +24 -1
- imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +60 -0
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +99 -11
- imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +50 -7
- imap_processing/cli.py +121 -44
- imap_processing/codice/codice_l1a.py +165 -77
- imap_processing/codice/codice_l1b.py +1 -1
- imap_processing/codice/codice_l2.py +118 -19
- imap_processing/codice/constants.py +1217 -1089
- imap_processing/decom.py +1 -4
- imap_processing/ena_maps/ena_maps.py +32 -25
- imap_processing/ena_maps/utils/naming.py +8 -2
- imap_processing/glows/ancillary/imap_glows_exclusions-by-instr-team_20250923_v002.dat +10 -0
- imap_processing/glows/ancillary/imap_glows_map-of-excluded-regions_20250923_v002.dat +393 -0
- imap_processing/glows/ancillary/imap_glows_map-of-uv-sources_20250923_v002.dat +593 -0
- imap_processing/glows/ancillary/imap_glows_pipeline_settings_20250923_v002.json +54 -0
- imap_processing/glows/ancillary/imap_glows_suspected-transients_20250923_v002.dat +10 -0
- imap_processing/glows/l1b/glows_l1b.py +99 -9
- imap_processing/glows/l1b/glows_l1b_data.py +350 -38
- imap_processing/glows/l2/glows_l2.py +11 -0
- imap_processing/hi/hi_l1a.py +124 -3
- imap_processing/hi/hi_l1b.py +154 -71
- imap_processing/hi/hi_l2.py +84 -51
- imap_processing/hi/utils.py +153 -8
- imap_processing/hit/l0/constants.py +3 -0
- imap_processing/hit/l0/decom_hit.py +5 -8
- imap_processing/hit/l1a/hit_l1a.py +375 -45
- imap_processing/hit/l1b/constants.py +5 -0
- imap_processing/hit/l1b/hit_l1b.py +61 -131
- imap_processing/hit/l2/constants.py +1 -1
- imap_processing/hit/l2/hit_l2.py +10 -11
- imap_processing/ialirt/calculate_ingest.py +219 -0
- imap_processing/ialirt/constants.py +32 -1
- imap_processing/ialirt/generate_coverage.py +201 -0
- imap_processing/ialirt/l0/ialirt_spice.py +5 -2
- imap_processing/ialirt/l0/parse_mag.py +337 -29
- imap_processing/ialirt/l0/process_hit.py +5 -3
- imap_processing/ialirt/l0/process_swapi.py +41 -25
- imap_processing/ialirt/l0/process_swe.py +23 -7
- imap_processing/ialirt/process_ephemeris.py +70 -14
- 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_l0.py +2 -2
- imap_processing/idex/idex_l1a.py +2 -3
- imap_processing/idex/idex_l1b.py +2 -3
- imap_processing/idex/idex_l2a.py +130 -4
- imap_processing/idex/idex_l2b.py +313 -119
- imap_processing/idex/idex_utils.py +1 -3
- imap_processing/lo/l0/lo_apid.py +1 -0
- imap_processing/lo/l0/lo_science.py +25 -24
- imap_processing/lo/l1a/lo_l1a.py +44 -0
- imap_processing/lo/l1b/lo_l1b.py +3 -3
- imap_processing/lo/l1c/lo_l1c.py +116 -50
- imap_processing/lo/l2/lo_l2.py +29 -29
- imap_processing/lo/lo_ancillary.py +55 -0
- imap_processing/lo/packet_definitions/lo_xtce.xml +5359 -106
- imap_processing/mag/constants.py +1 -0
- imap_processing/mag/l1a/mag_l1a.py +1 -0
- imap_processing/mag/l1a/mag_l1a_data.py +26 -0
- imap_processing/mag/l1b/mag_l1b.py +3 -2
- imap_processing/mag/l1c/interpolation_methods.py +14 -15
- imap_processing/mag/l1c/mag_l1c.py +23 -6
- imap_processing/mag/l1d/__init__.py +0 -0
- imap_processing/mag/l1d/mag_l1d.py +176 -0
- imap_processing/mag/l1d/mag_l1d_data.py +725 -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 +199 -130
- imap_processing/quality_flags.py +28 -2
- imap_processing/spice/geometry.py +101 -36
- imap_processing/spice/pointing_frame.py +1 -7
- imap_processing/spice/repoint.py +29 -2
- imap_processing/spice/spin.py +32 -8
- imap_processing/spice/time.py +60 -19
- imap_processing/swapi/l1/swapi_l1.py +10 -4
- imap_processing/swapi/l2/swapi_l2.py +66 -24
- imap_processing/swapi/swapi_utils.py +1 -1
- imap_processing/swe/l1b/swe_l1b.py +3 -6
- imap_processing/ultra/constants.py +28 -3
- 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 +102 -12
- imap_processing/ultra/l1a/ultra_l1a.py +26 -6
- imap_processing/ultra/l1b/cullingmask.py +6 -3
- imap_processing/ultra/l1b/de.py +122 -26
- imap_processing/ultra/l1b/extendedspin.py +29 -2
- imap_processing/ultra/l1b/lookup_utils.py +424 -50
- imap_processing/ultra/l1b/quality_flag_filters.py +23 -0
- imap_processing/ultra/l1b/ultra_l1b_culling.py +356 -5
- imap_processing/ultra/l1b/ultra_l1b_extended.py +534 -90
- imap_processing/ultra/l1c/helio_pset.py +127 -7
- imap_processing/ultra/l1c/l1c_lookup_utils.py +256 -0
- imap_processing/ultra/l1c/spacecraft_pset.py +90 -15
- imap_processing/ultra/l1c/ultra_l1c.py +6 -0
- imap_processing/ultra/l1c/ultra_l1c_culling.py +85 -0
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +446 -341
- imap_processing/ultra/l2/ultra_l2.py +0 -1
- imap_processing/ultra/utils/ultra_l1_utils.py +40 -3
- imap_processing/utils.py +3 -4
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/METADATA +3 -3
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/RECORD +126 -126
- imap_processing/idex/idex_l2c.py +0 -250
- imap_processing/spice/kernels.py +0 -187
- 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.19.0.dist-info}/LICENSE +0 -0
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/WHEEL +0 -0
- {imap_processing-0.17.0.dist-info → imap_processing-0.19.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,21 +1,45 @@
|
|
|
1
1
|
"""Culls Events for ULTRA L1b."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
from collections import namedtuple
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
6
7
|
import pandas as pd
|
|
7
8
|
import xarray as xr
|
|
8
9
|
from numpy.typing import NDArray
|
|
9
10
|
|
|
10
|
-
from imap_processing.quality_flags import
|
|
11
|
+
from imap_processing.quality_flags import (
|
|
12
|
+
ImapAttitudeUltraFlags,
|
|
13
|
+
ImapDEScatteringUltraFlags,
|
|
14
|
+
ImapHkUltraFlags,
|
|
15
|
+
ImapInstrumentUltraFlags,
|
|
16
|
+
ImapRatesUltraFlags,
|
|
17
|
+
)
|
|
11
18
|
from imap_processing.spice.spin import get_spin_data
|
|
12
19
|
from imap_processing.ultra.constants import UltraConstants
|
|
20
|
+
from imap_processing.ultra.l1b.lookup_utils import (
|
|
21
|
+
get_scattering_coefficients,
|
|
22
|
+
get_scattering_thresholds,
|
|
23
|
+
)
|
|
24
|
+
from imap_processing.ultra.l1b.quality_flag_filters import DE_QUALITY_FLAG_FILTERS
|
|
13
25
|
|
|
14
26
|
logging.basicConfig(level=logging.INFO)
|
|
15
27
|
logger = logging.getLogger(__name__)
|
|
16
28
|
|
|
17
29
|
SPIN_DURATION = 15 # Default spin duration in seconds.
|
|
18
30
|
|
|
31
|
+
RateResult = namedtuple(
|
|
32
|
+
"RateResult",
|
|
33
|
+
[
|
|
34
|
+
"start_per_spin",
|
|
35
|
+
"stop_per_spin",
|
|
36
|
+
"coin_per_spin",
|
|
37
|
+
"start_pulses",
|
|
38
|
+
"stop_pulses",
|
|
39
|
+
"coin_pulses",
|
|
40
|
+
],
|
|
41
|
+
)
|
|
42
|
+
|
|
19
43
|
|
|
20
44
|
def get_energy_histogram(
|
|
21
45
|
spin_number: NDArray, energy: NDArray
|
|
@@ -106,6 +130,10 @@ def flag_attitude(
|
|
|
106
130
|
|
|
107
131
|
spin_period = spin_df.loc[spin_df.spin_number.isin(spins), "spin_period_sec"]
|
|
108
132
|
spin_starttime = spin_df.loc[spin_df.spin_number.isin(spins), "spin_start_met"]
|
|
133
|
+
spin_phase_valid = spin_df.loc[spin_df.spin_number.isin(spins), "spin_phase_valid"]
|
|
134
|
+
spin_period_valid = spin_df.loc[
|
|
135
|
+
spin_df.spin_number.isin(spins), "spin_period_valid"
|
|
136
|
+
]
|
|
109
137
|
spin_rates = 60 / spin_period # 60 seconds in a minute
|
|
110
138
|
bad_spin_rate_indices = (spin_rates < UltraConstants.CULLING_RPM_MIN) | (
|
|
111
139
|
spin_rates > UltraConstants.CULLING_RPM_MAX
|
|
@@ -118,9 +146,59 @@ def flag_attitude(
|
|
|
118
146
|
mismatch_indices = compare_aux_univ_spin_table(aux_dataset, spins, spin_df)
|
|
119
147
|
quality_flags[mismatch_indices] |= ImapAttitudeUltraFlags.AUXMISMATCH.value
|
|
120
148
|
|
|
149
|
+
# Spin phase validity flag
|
|
150
|
+
phase_invalid_indices = spin_phase_valid == 0
|
|
151
|
+
quality_flags[phase_invalid_indices] |= ImapAttitudeUltraFlags.SPINPHASE.value
|
|
152
|
+
|
|
153
|
+
# Spin period validity flag
|
|
154
|
+
period_invalid_indices = ~spin_period_valid
|
|
155
|
+
quality_flags[period_invalid_indices] |= ImapAttitudeUltraFlags.SPINPERIOD.value
|
|
156
|
+
|
|
121
157
|
return quality_flags, spin_rates, spin_period, spin_starttime
|
|
122
158
|
|
|
123
159
|
|
|
160
|
+
def flag_hk(spin_number: NDArray) -> NDArray:
|
|
161
|
+
"""
|
|
162
|
+
Flag data based on hk.
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
spin_number : NDArray
|
|
167
|
+
Spin number at each direct event.
|
|
168
|
+
|
|
169
|
+
Returns
|
|
170
|
+
-------
|
|
171
|
+
quality_flags : NDArray
|
|
172
|
+
Quality flags..
|
|
173
|
+
"""
|
|
174
|
+
spins = np.unique(spin_number) # Get unique spins
|
|
175
|
+
quality_flags = np.full(spins.shape, ImapHkUltraFlags.NONE.value, dtype=np.uint16)
|
|
176
|
+
|
|
177
|
+
return quality_flags
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def flag_imap_instruments(spin_number: NDArray) -> NDArray:
|
|
181
|
+
"""
|
|
182
|
+
Flag data based on other IMAP instruments.
|
|
183
|
+
|
|
184
|
+
Parameters
|
|
185
|
+
----------
|
|
186
|
+
spin_number : NDArray
|
|
187
|
+
Spin number at each direct event.
|
|
188
|
+
|
|
189
|
+
Returns
|
|
190
|
+
-------
|
|
191
|
+
quality_flags : NDArray
|
|
192
|
+
Quality flags..
|
|
193
|
+
"""
|
|
194
|
+
spins = np.unique(spin_number) # Get unique spins
|
|
195
|
+
quality_flags = np.full(
|
|
196
|
+
spins.shape, ImapInstrumentUltraFlags.NONE.value, dtype=np.uint16
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
return quality_flags
|
|
200
|
+
|
|
201
|
+
|
|
124
202
|
def get_n_sigma(count_rates: NDArray, mean_duration: float, sigma: int = 6) -> NDArray:
|
|
125
203
|
"""
|
|
126
204
|
Calculate the threshold for the HIGHRATES flag.
|
|
@@ -140,7 +218,8 @@ def get_n_sigma(count_rates: NDArray, mean_duration: float, sigma: int = 6) -> N
|
|
|
140
218
|
threshold : NDArray
|
|
141
219
|
Threshold for applying HIGHRATES flag.
|
|
142
220
|
"""
|
|
143
|
-
|
|
221
|
+
# Take the Sample Standard Deviation.
|
|
222
|
+
sigma_per_energy = np.std(count_rates, axis=1, ddof=1)
|
|
144
223
|
n_sigma_per_energy = sigma * sigma_per_energy
|
|
145
224
|
mean_per_energy = np.mean(count_rates, axis=1)
|
|
146
225
|
# Must have a HIGHRATES threshold of at least 3 counts per spin.
|
|
@@ -149,7 +228,7 @@ def get_n_sigma(count_rates: NDArray, mean_duration: float, sigma: int = 6) -> N
|
|
|
149
228
|
return threshold
|
|
150
229
|
|
|
151
230
|
|
|
152
|
-
def
|
|
231
|
+
def flag_rates(
|
|
153
232
|
spin_number: NDArray, energy: NDArray, sigma: int = 6
|
|
154
233
|
) -> tuple[NDArray, NDArray, NDArray, NDArray]:
|
|
155
234
|
"""
|
|
@@ -182,8 +261,6 @@ def flag_spin(
|
|
|
182
261
|
count_rates.shape, ImapRatesUltraFlags.NONE.value, dtype=np.uint16
|
|
183
262
|
)
|
|
184
263
|
|
|
185
|
-
# Zero counts/spin/energy level
|
|
186
|
-
quality_flags[counts == 0] |= ImapRatesUltraFlags.ZEROCOUNTS.value
|
|
187
264
|
threshold = get_n_sigma(count_rates, duration, sigma=sigma)
|
|
188
265
|
|
|
189
266
|
bin_edges = np.array(UltraConstants.CULLING_ENERGY_BIN_EDGES)
|
|
@@ -194,6 +271,10 @@ def flag_spin(
|
|
|
194
271
|
indices_n_sigma = count_rates > threshold[:, np.newaxis]
|
|
195
272
|
quality_flags[indices_n_sigma] |= ImapRatesUltraFlags.HIGHRATES.value
|
|
196
273
|
|
|
274
|
+
# Flags the first and last spin
|
|
275
|
+
quality_flags[:, 0] |= ImapRatesUltraFlags.FIRSTSPIN.value
|
|
276
|
+
quality_flags[:, -1] |= ImapRatesUltraFlags.LASTSPIN.value
|
|
277
|
+
|
|
197
278
|
return quality_flags, spin, energy_midpoints, threshold
|
|
198
279
|
|
|
199
280
|
|
|
@@ -256,3 +337,273 @@ def compare_aux_univ_spin_table(
|
|
|
256
337
|
mismatch_indices[missing_spin_mask] = True
|
|
257
338
|
|
|
258
339
|
return mismatch_indices
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
# TODO: Make this a common util since it is being used for the de and rates packets.
|
|
343
|
+
def get_spin_and_duration(met: NDArray, spin: NDArray) -> tuple[NDArray, NDArray]:
|
|
344
|
+
"""
|
|
345
|
+
Get the spin number and duration.
|
|
346
|
+
|
|
347
|
+
Parameters
|
|
348
|
+
----------
|
|
349
|
+
met : NDArray
|
|
350
|
+
Mission elapsed time.
|
|
351
|
+
spin : NDArray
|
|
352
|
+
Spin number 0-255.
|
|
353
|
+
|
|
354
|
+
Returns
|
|
355
|
+
-------
|
|
356
|
+
assigned_spin_number : NDArray
|
|
357
|
+
Spin number for packet data product.
|
|
358
|
+
"""
|
|
359
|
+
# Packet data.
|
|
360
|
+
# Since the spin number in the direct events packet
|
|
361
|
+
# is only 8 bits it goes from 0-255.
|
|
362
|
+
# Within a pointing that means we will always have duplicate spin numbers.
|
|
363
|
+
# In other words, different spins will be represented by the same spin number.
|
|
364
|
+
# Just to make certain that we won't accidentally combine
|
|
365
|
+
# multiple spins we need to sort by time here.
|
|
366
|
+
sort_idx = np.argsort(met)
|
|
367
|
+
packet_met_sorted = met[sort_idx]
|
|
368
|
+
packet_spin_sorted = spin[sort_idx]
|
|
369
|
+
# Here we are finding the start and end indices of each spin in the sorted array.
|
|
370
|
+
is_new_spin = np.concatenate(
|
|
371
|
+
[[True], packet_spin_sorted.values[1:] != packet_spin_sorted.values[:-1]]
|
|
372
|
+
)
|
|
373
|
+
spin_start_indices = np.where(is_new_spin)[0]
|
|
374
|
+
spin_end_indices = np.append(spin_start_indices[1:], len(packet_met_sorted))
|
|
375
|
+
|
|
376
|
+
# Universal Spin Table.
|
|
377
|
+
spin_df = get_spin_data()
|
|
378
|
+
# Retrieve the met values of the start of the spin.
|
|
379
|
+
spin_start_mets = spin_df["spin_start_met"].values
|
|
380
|
+
# Retrieve the corresponding spin numbers.
|
|
381
|
+
spin_numbers = spin_df["spin_number"].values
|
|
382
|
+
spin_period_sec = spin_df["spin_period_sec"].values
|
|
383
|
+
assigned_spin_number_sorted = np.empty(packet_spin_sorted.shape, dtype=np.uint32)
|
|
384
|
+
assigned_spin_duration_sorted = np.empty(packet_spin_sorted.shape, dtype=np.float32)
|
|
385
|
+
# These last 8 bits are the same as the spin number in the DE packet.
|
|
386
|
+
# So this will give us choices of which spins are
|
|
387
|
+
# available to assign to the packet data.
|
|
388
|
+
possible_spins = spin_numbers & 0xFF
|
|
389
|
+
|
|
390
|
+
# Assign each group based on time.
|
|
391
|
+
for start, end in zip(spin_start_indices, spin_end_indices, strict=False):
|
|
392
|
+
# Now that we have the possible spins from the Universal Spin Table,
|
|
393
|
+
# we match the times of those spins to the nearest times in the DE data.
|
|
394
|
+
possible_times = spin_start_mets[
|
|
395
|
+
possible_spins == packet_spin_sorted.values[start]
|
|
396
|
+
]
|
|
397
|
+
# Get nearest time for matching spins.
|
|
398
|
+
nearest_idx = np.abs(possible_times - packet_met_sorted.values[start]).argmin()
|
|
399
|
+
nearest_value = possible_times[nearest_idx]
|
|
400
|
+
assigned_spin_number_sorted[start:end] = spin_numbers[
|
|
401
|
+
spin_start_mets == nearest_value
|
|
402
|
+
]
|
|
403
|
+
assigned_spin_duration_sorted[start:end] = spin_period_sec[
|
|
404
|
+
spin_start_mets == nearest_value
|
|
405
|
+
]
|
|
406
|
+
|
|
407
|
+
# Undo the sort to match original order.
|
|
408
|
+
assigned_spin_number = np.empty_like(assigned_spin_number_sorted)
|
|
409
|
+
assigned_spin_number[sort_idx] = assigned_spin_number_sorted
|
|
410
|
+
assigned_duration = np.empty_like(assigned_spin_duration_sorted)
|
|
411
|
+
assigned_duration[sort_idx] = assigned_spin_duration_sorted
|
|
412
|
+
|
|
413
|
+
return assigned_spin_number, assigned_duration
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def get_pulses_per_spin(rates: xr.Dataset) -> RateResult:
|
|
417
|
+
"""
|
|
418
|
+
Get the total number of pulses per spin.
|
|
419
|
+
|
|
420
|
+
Parameters
|
|
421
|
+
----------
|
|
422
|
+
rates : xr.Dataset
|
|
423
|
+
Rates dataset.
|
|
424
|
+
|
|
425
|
+
Returns
|
|
426
|
+
-------
|
|
427
|
+
start_per_spin : NDArray
|
|
428
|
+
Total start pulses per spin.
|
|
429
|
+
stop_per_spin : NDArray
|
|
430
|
+
Total stop pulses per spin.
|
|
431
|
+
coin_per_spin : NDArray
|
|
432
|
+
Total coincidence pulses per spin.
|
|
433
|
+
start_pulses : NDArray
|
|
434
|
+
Total start pulses.
|
|
435
|
+
stop_pulses : NDArray
|
|
436
|
+
Total stop pulses.
|
|
437
|
+
coin_pulses : NDArray
|
|
438
|
+
Total coincidence pulses.
|
|
439
|
+
"""
|
|
440
|
+
spin_number, duration = get_spin_and_duration(rates["shcoarse"], rates["spin"])
|
|
441
|
+
|
|
442
|
+
# Top coin pulses
|
|
443
|
+
top_coin_pulses = np.stack(
|
|
444
|
+
[v for k, v in rates.items() if k.startswith("coin_t")], axis=1
|
|
445
|
+
)
|
|
446
|
+
max_top_coin_pulse = np.max(top_coin_pulses, axis=1)
|
|
447
|
+
|
|
448
|
+
# Bottom coin pulses
|
|
449
|
+
bottom_coin_pulses = np.stack(
|
|
450
|
+
[v for k, v in rates.items() if k.startswith("coin_b")], axis=1
|
|
451
|
+
)
|
|
452
|
+
max_bottom_coin_pulse = np.max(bottom_coin_pulses, axis=1)
|
|
453
|
+
|
|
454
|
+
# Top stop pulses
|
|
455
|
+
top_stop_pulses = np.stack(
|
|
456
|
+
[v for k, v in rates.items() if k.startswith("stop_t")], axis=1
|
|
457
|
+
)
|
|
458
|
+
max_top_stop_pulse = np.max(top_stop_pulses, axis=1)
|
|
459
|
+
|
|
460
|
+
# Bottom stop pulses
|
|
461
|
+
bottom_stop_pulses = np.stack(
|
|
462
|
+
[v for k, v in rates.items() if k.startswith("stop_b")], axis=1
|
|
463
|
+
)
|
|
464
|
+
max_bottom_stop_pulse = np.max(bottom_stop_pulses, axis=1)
|
|
465
|
+
|
|
466
|
+
stop_pulses = max_top_stop_pulse + max_bottom_stop_pulse
|
|
467
|
+
start_pulses = rates["start_rf"] + rates["start_lf"]
|
|
468
|
+
coin_pulses = max_top_coin_pulse + max_bottom_coin_pulse
|
|
469
|
+
|
|
470
|
+
unique_spins, spin_idx = np.unique(spin_number, return_inverse=True)
|
|
471
|
+
|
|
472
|
+
start_per_spin = np.bincount(spin_idx, weights=start_pulses)
|
|
473
|
+
stop_per_spin = np.bincount(spin_idx, weights=stop_pulses)
|
|
474
|
+
coin_per_spin = np.bincount(spin_idx, weights=coin_pulses)
|
|
475
|
+
|
|
476
|
+
return RateResult(
|
|
477
|
+
start_per_spin=start_per_spin,
|
|
478
|
+
stop_per_spin=stop_per_spin,
|
|
479
|
+
coin_per_spin=coin_per_spin,
|
|
480
|
+
start_pulses=start_pulses,
|
|
481
|
+
stop_pulses=stop_pulses,
|
|
482
|
+
coin_pulses=coin_pulses,
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def flag_scattering(
|
|
487
|
+
tof_energy: NDArray,
|
|
488
|
+
theta: NDArray,
|
|
489
|
+
phi: NDArray,
|
|
490
|
+
ancillary_files: dict,
|
|
491
|
+
sensor: str,
|
|
492
|
+
quality_flags: NDArray,
|
|
493
|
+
) -> None:
|
|
494
|
+
"""
|
|
495
|
+
Flag events where either theta or phi FWHM exceed the threshold or equal nan.
|
|
496
|
+
|
|
497
|
+
Parameters
|
|
498
|
+
----------
|
|
499
|
+
tof_energy : NDArray
|
|
500
|
+
TOF energy for each event in keV.
|
|
501
|
+
theta : NDArray
|
|
502
|
+
Elevation angles in degrees.
|
|
503
|
+
phi : NDArray
|
|
504
|
+
Azimuth angles in degrees.
|
|
505
|
+
ancillary_files : dict[Path]
|
|
506
|
+
Ancillary files.
|
|
507
|
+
sensor : str
|
|
508
|
+
Sensor name: "ultra45" or "ultra90".
|
|
509
|
+
quality_flags : NDArray
|
|
510
|
+
Quality flags.
|
|
511
|
+
"""
|
|
512
|
+
scattering_thresholds = get_scattering_thresholds(ancillary_files)
|
|
513
|
+
|
|
514
|
+
for (e_min, e_max), threshold in scattering_thresholds.items():
|
|
515
|
+
event_mask = (tof_energy >= e_min) & (tof_energy < e_max)
|
|
516
|
+
# Input the theta and phi values for the current energy range.
|
|
517
|
+
# Returns a_theta_val, g_theta_val, a_phi_val, g_phi_val
|
|
518
|
+
theta_coeffs, phi_coeffs = get_scattering_coefficients(
|
|
519
|
+
theta[event_mask],
|
|
520
|
+
phi[event_mask],
|
|
521
|
+
lookup_tables=None,
|
|
522
|
+
ancillary_files=ancillary_files,
|
|
523
|
+
instrument_id=int(sensor[-2:]),
|
|
524
|
+
)
|
|
525
|
+
# FWHM_PHI = A_PHI * E^G_PHI
|
|
526
|
+
# FWHM_THETA = A_THETA * E^G_THETA
|
|
527
|
+
fwhm_theta = theta_coeffs[:, 0] * tof_energy[event_mask] ** theta_coeffs[:, 1]
|
|
528
|
+
fwhm_phi = phi_coeffs[:, 0] * tof_energy[event_mask] ** phi_coeffs[:, 1]
|
|
529
|
+
is_nan = np.isnan(fwhm_theta) | np.isnan(fwhm_phi)
|
|
530
|
+
quality_flags[np.where(event_mask)[0][is_nan]] |= (
|
|
531
|
+
ImapDEScatteringUltraFlags.NAN_PHI_OR_THETA.value
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
theta_exceeds = fwhm_theta > threshold
|
|
535
|
+
phi_exceeds = fwhm_phi > threshold
|
|
536
|
+
either_exceeds = theta_exceeds | phi_exceeds
|
|
537
|
+
|
|
538
|
+
# Set flags for events where either theta or phi FWHM exceed the threshold
|
|
539
|
+
quality_flags[np.where(event_mask)[0][either_exceeds]] |= (
|
|
540
|
+
ImapDEScatteringUltraFlags.ABOVE_THRESHOLD.value
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def get_de_rejection_mask(
|
|
545
|
+
quality_scattering: NDArray, quality_outliers: NDArray
|
|
546
|
+
) -> NDArray:
|
|
547
|
+
"""
|
|
548
|
+
Create boolean mask where event is rejected due to relevant flags.
|
|
549
|
+
|
|
550
|
+
Parameters
|
|
551
|
+
----------
|
|
552
|
+
quality_scattering : NDArray
|
|
553
|
+
Quality scattering flags.
|
|
554
|
+
quality_outliers : NDArray
|
|
555
|
+
Quality outliers flags.
|
|
556
|
+
|
|
557
|
+
Returns
|
|
558
|
+
-------
|
|
559
|
+
rejected : NDArray
|
|
560
|
+
Rejected events where True = rejected.
|
|
561
|
+
"""
|
|
562
|
+
# Bitmasks from the DE_QUALITY_FLAG_FILTERS
|
|
563
|
+
scattering_mask = sum(
|
|
564
|
+
flag.value for flag in DE_QUALITY_FLAG_FILTERS["quality_scattering"]
|
|
565
|
+
)
|
|
566
|
+
outliers_mask = sum(
|
|
567
|
+
flag.value for flag in DE_QUALITY_FLAG_FILTERS["quality_outliers"]
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
# Boolean mask where event is rejected due to relevant flags
|
|
571
|
+
rejected = ((quality_scattering & scattering_mask) != 0) | (
|
|
572
|
+
(quality_outliers & outliers_mask) != 0
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
return rejected
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
def count_rejected_events_per_spin(
|
|
579
|
+
spins: NDArray, quality_scattering: NDArray, quality_outliers: NDArray
|
|
580
|
+
) -> NDArray:
|
|
581
|
+
"""
|
|
582
|
+
Count rejected events per spin based on DE_QUALITY_FLAG_FILTERS.
|
|
583
|
+
|
|
584
|
+
Parameters
|
|
585
|
+
----------
|
|
586
|
+
spins : NDArray
|
|
587
|
+
Spins in which each direct event is within.
|
|
588
|
+
quality_scattering : NDArray
|
|
589
|
+
Quality scattering flags.
|
|
590
|
+
quality_outliers : NDArray
|
|
591
|
+
Quality outliers flags.
|
|
592
|
+
|
|
593
|
+
Returns
|
|
594
|
+
-------
|
|
595
|
+
rejected_counts : NDArray
|
|
596
|
+
Rejected counts per spin.
|
|
597
|
+
"""
|
|
598
|
+
# Boolean mask where event is rejected due to relevant flags
|
|
599
|
+
rejected = get_de_rejection_mask(quality_scattering, quality_outliers)
|
|
600
|
+
|
|
601
|
+
# Unique spin numbers
|
|
602
|
+
unique_spins = np.unique(spins)
|
|
603
|
+
|
|
604
|
+
# Count rejected events per spin
|
|
605
|
+
rejected_counts = np.array(
|
|
606
|
+
[np.count_nonzero(rejected[spins == spin]) for spin in unique_spins], dtype=int
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
return rejected_counts
|