imap-processing 0.18.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/cdf/config/imap_codice_l1a_variable_attrs.yaml +301 -274
- imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +28 -28
- imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +1044 -203
- 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_idex_l2a_variable_attrs.yaml +33 -4
- imap_processing/cdf/config/imap_idex_l2b_variable_attrs.yaml +8 -91
- imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +106 -16
- 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 +85 -2
- imap_processing/cdf/config/imap_mag_l1c_variable_attrs.yaml +24 -1
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +12 -4
- imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +50 -7
- imap_processing/cli.py +95 -41
- imap_processing/codice/codice_l1a.py +131 -31
- imap_processing/codice/codice_l2.py +118 -10
- imap_processing/codice/constants.py +740 -595
- 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 +3 -6
- imap_processing/hit/l1a/hit_l1a.py +311 -21
- imap_processing/hit/l1b/hit_l1b.py +54 -126
- imap_processing/hit/l2/hit_l2.py +6 -6
- imap_processing/ialirt/calculate_ingest.py +219 -0
- imap_processing/ialirt/constants.py +12 -2
- imap_processing/ialirt/generate_coverage.py +15 -2
- imap_processing/ialirt/l0/ialirt_spice.py +5 -2
- imap_processing/ialirt/l0/parse_mag.py +293 -42
- imap_processing/ialirt/l0/process_hit.py +5 -3
- imap_processing/ialirt/l0/process_swapi.py +41 -25
- imap_processing/ialirt/process_ephemeris.py +70 -14
- 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 +158 -143
- imap_processing/idex/idex_utils.py +1 -3
- imap_processing/lo/l0/lo_science.py +25 -24
- 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/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/mag_l1d.py +57 -14
- imap_processing/mag/l1d/mag_l1d_data.py +167 -30
- imap_processing/mag/l2/mag_l2_data.py +10 -2
- imap_processing/quality_flags.py +9 -1
- imap_processing/spice/geometry.py +76 -33
- imap_processing/spice/pointing_frame.py +0 -6
- imap_processing/spice/repoint.py +29 -2
- imap_processing/spice/spin.py +28 -8
- imap_processing/spice/time.py +12 -22
- imap_processing/swapi/l1/swapi_l1.py +10 -4
- imap_processing/swapi/l2/swapi_l2.py +15 -17
- imap_processing/swe/l1b/swe_l1b.py +1 -2
- imap_processing/ultra/constants.py +1 -24
- imap_processing/ultra/l0/ultra_utils.py +9 -11
- imap_processing/ultra/l1a/ultra_l1a.py +1 -2
- imap_processing/ultra/l1b/cullingmask.py +6 -3
- imap_processing/ultra/l1b/de.py +81 -23
- imap_processing/ultra/l1b/extendedspin.py +13 -10
- imap_processing/ultra/l1b/lookup_utils.py +281 -28
- imap_processing/ultra/l1b/quality_flag_filters.py +10 -1
- imap_processing/ultra/l1b/ultra_l1b_culling.py +161 -3
- imap_processing/ultra/l1b/ultra_l1b_extended.py +253 -47
- imap_processing/ultra/l1c/helio_pset.py +97 -24
- imap_processing/ultra/l1c/l1c_lookup_utils.py +256 -0
- imap_processing/ultra/l1c/spacecraft_pset.py +83 -16
- imap_processing/ultra/l1c/ultra_l1c.py +6 -2
- imap_processing/ultra/l1c/ultra_l1c_culling.py +85 -0
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +385 -277
- imap_processing/ultra/l2/ultra_l2.py +0 -1
- imap_processing/ultra/utils/ultra_l1_utils.py +28 -3
- imap_processing/utils.py +3 -4
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/METADATA +2 -2
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/RECORD +102 -95
- imap_processing/idex/idex_l2c.py +0 -84
- imap_processing/spice/kernels.py +0 -187
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/LICENSE +0 -0
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/WHEEL +0 -0
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.0.dist-info}/entry_points.txt +0 -0
|
@@ -2,13 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from imap_processing.quality_flags import (
|
|
4
4
|
FlagNameMixin,
|
|
5
|
+
ImapDEOutliersUltraFlags,
|
|
6
|
+
ImapDEScatteringUltraFlags,
|
|
5
7
|
ImapRatesUltraFlags,
|
|
6
8
|
)
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
SPIN_QUALITY_FLAG_FILTERS: dict[str, list[FlagNameMixin]] = {
|
|
9
11
|
"quality_attitude": [],
|
|
10
12
|
"quality_ena_rates": [
|
|
11
13
|
ImapRatesUltraFlags.FIRSTSPIN,
|
|
12
14
|
ImapRatesUltraFlags.LASTSPIN,
|
|
13
15
|
],
|
|
14
16
|
}
|
|
17
|
+
|
|
18
|
+
DE_QUALITY_FLAG_FILTERS: dict[str, list[FlagNameMixin]] = {
|
|
19
|
+
"quality_outliers": [ImapDEOutliersUltraFlags.FOV],
|
|
20
|
+
"quality_scattering": [
|
|
21
|
+
ImapDEScatteringUltraFlags.ABOVE_THRESHOLD,
|
|
22
|
+
],
|
|
23
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
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
|
|
@@ -9,18 +10,36 @@ from numpy.typing import NDArray
|
|
|
9
10
|
|
|
10
11
|
from imap_processing.quality_flags import (
|
|
11
12
|
ImapAttitudeUltraFlags,
|
|
13
|
+
ImapDEScatteringUltraFlags,
|
|
12
14
|
ImapHkUltraFlags,
|
|
13
15
|
ImapInstrumentUltraFlags,
|
|
14
16
|
ImapRatesUltraFlags,
|
|
15
17
|
)
|
|
16
18
|
from imap_processing.spice.spin import get_spin_data
|
|
17
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
|
|
18
25
|
|
|
19
26
|
logging.basicConfig(level=logging.INFO)
|
|
20
27
|
logger = logging.getLogger(__name__)
|
|
21
28
|
|
|
22
29
|
SPIN_DURATION = 15 # Default spin duration in seconds.
|
|
23
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
|
+
|
|
24
43
|
|
|
25
44
|
def get_energy_histogram(
|
|
26
45
|
spin_number: NDArray, energy: NDArray
|
|
@@ -369,7 +388,7 @@ def get_spin_and_duration(met: NDArray, spin: NDArray) -> tuple[NDArray, NDArray
|
|
|
369
388
|
possible_spins = spin_numbers & 0xFF
|
|
370
389
|
|
|
371
390
|
# Assign each group based on time.
|
|
372
|
-
for start, end in zip(spin_start_indices, spin_end_indices):
|
|
391
|
+
for start, end in zip(spin_start_indices, spin_end_indices, strict=False):
|
|
373
392
|
# Now that we have the possible spins from the Universal Spin Table,
|
|
374
393
|
# we match the times of those spins to the nearest times in the DE data.
|
|
375
394
|
possible_times = spin_start_mets[
|
|
@@ -394,7 +413,7 @@ def get_spin_and_duration(met: NDArray, spin: NDArray) -> tuple[NDArray, NDArray
|
|
|
394
413
|
return assigned_spin_number, assigned_duration
|
|
395
414
|
|
|
396
415
|
|
|
397
|
-
def get_pulses_per_spin(rates: xr.Dataset) ->
|
|
416
|
+
def get_pulses_per_spin(rates: xr.Dataset) -> RateResult:
|
|
398
417
|
"""
|
|
399
418
|
Get the total number of pulses per spin.
|
|
400
419
|
|
|
@@ -411,6 +430,12 @@ def get_pulses_per_spin(rates: xr.Dataset) -> tuple[NDArray, NDArray, NDArray]:
|
|
|
411
430
|
Total stop pulses per spin.
|
|
412
431
|
coin_per_spin : NDArray
|
|
413
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.
|
|
414
439
|
"""
|
|
415
440
|
spin_number, duration = get_spin_and_duration(rates["shcoarse"], rates["spin"])
|
|
416
441
|
|
|
@@ -448,4 +473,137 @@ def get_pulses_per_spin(rates: xr.Dataset) -> tuple[NDArray, NDArray, NDArray]:
|
|
|
448
473
|
stop_per_spin = np.bincount(spin_idx, weights=stop_pulses)
|
|
449
474
|
coin_per_spin = np.bincount(spin_idx, weights=coin_pulses)
|
|
450
475
|
|
|
451
|
-
return
|
|
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
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# TODO: Come back and add in FSW logic.
|
|
4
4
|
import logging
|
|
5
|
+
from collections import namedtuple
|
|
5
6
|
from enum import Enum
|
|
6
7
|
|
|
7
8
|
import numpy as np
|
|
@@ -16,6 +17,7 @@ from imap_processing.ultra.constants import UltraConstants
|
|
|
16
17
|
from imap_processing.ultra.l1b.lookup_utils import (
|
|
17
18
|
get_angular_profiles,
|
|
18
19
|
get_back_position,
|
|
20
|
+
get_ebins,
|
|
19
21
|
get_energy_efficiencies,
|
|
20
22
|
get_energy_norm,
|
|
21
23
|
get_image_params,
|
|
@@ -26,6 +28,10 @@ from imap_processing.ultra.l1b.lookup_utils import (
|
|
|
26
28
|
|
|
27
29
|
logger = logging.getLogger(__name__)
|
|
28
30
|
|
|
31
|
+
FILLVAL_UINT8 = 255
|
|
32
|
+
FILLVAL_FLOAT32 = -1.0e31
|
|
33
|
+
FILLVAL_FLOAT64 = -1.0e31
|
|
34
|
+
|
|
29
35
|
|
|
30
36
|
class StartType(Enum):
|
|
31
37
|
"""Start Type: 1=Left, 2=Right."""
|
|
@@ -50,6 +56,9 @@ class CoinType(Enum):
|
|
|
50
56
|
Bottom = 2
|
|
51
57
|
|
|
52
58
|
|
|
59
|
+
PHTOFResult = namedtuple("PHTOFResult", ["tof", "t2", "xb", "yb", "tofx", "tofy"])
|
|
60
|
+
|
|
61
|
+
|
|
53
62
|
def get_front_x_position(
|
|
54
63
|
start_type: ndarray, start_position_tdc: ndarray, sensor: str, ancillary_files: dict
|
|
55
64
|
) -> ndarray:
|
|
@@ -161,7 +170,7 @@ def get_front_y_position(
|
|
|
161
170
|
|
|
162
171
|
def get_ph_tof_and_back_positions(
|
|
163
172
|
de_dataset: xarray.Dataset, xf: np.ndarray, sensor: str, ancillary_files: dict
|
|
164
|
-
) ->
|
|
173
|
+
) -> PHTOFResult:
|
|
165
174
|
"""
|
|
166
175
|
Calculate back xb, yb position and tof.
|
|
167
176
|
|
|
@@ -196,6 +205,10 @@ def get_ph_tof_and_back_positions(
|
|
|
196
205
|
Back positions in x direction (hundredths of a millimeter).
|
|
197
206
|
yb : np.array
|
|
198
207
|
Back positions in y direction (hundredths of a millimeter).
|
|
208
|
+
tofx : np.array
|
|
209
|
+
X front position tof offset (tenths of a nanosecond).
|
|
210
|
+
tofy : np.array
|
|
211
|
+
Y front position tof offset (tenths of a nanosecond).
|
|
199
212
|
"""
|
|
200
213
|
indices = np.nonzero(
|
|
201
214
|
np.isin(de_dataset["stop_type"], [StopType.Top.value, StopType.Bottom.value])
|
|
@@ -278,7 +291,7 @@ def get_ph_tof_and_back_positions(
|
|
|
278
291
|
stop_type_bottom
|
|
279
292
|
] / 10 * get_image_params("XFTTOF", sensor, ancillary_files)
|
|
280
293
|
|
|
281
|
-
return tof, t2, xb, yb
|
|
294
|
+
return PHTOFResult(tof=tof, t2=t2, xb=xb, yb=yb, tofx=tofx, tofy=tofy)
|
|
282
295
|
|
|
283
296
|
|
|
284
297
|
def get_path_length(
|
|
@@ -530,9 +543,9 @@ def get_de_velocity(
|
|
|
530
543
|
v_y = delta_v[:, 1] / tof * 1e3
|
|
531
544
|
v_z = delta_v[:, 2] / tof * 1e3
|
|
532
545
|
|
|
533
|
-
v_x[tof < 0] =
|
|
534
|
-
v_y[tof < 0] =
|
|
535
|
-
v_z[tof < 0] =
|
|
546
|
+
v_x[tof < 0] = FILLVAL_FLOAT32 # used as fillvals
|
|
547
|
+
v_y[tof < 0] = FILLVAL_FLOAT32
|
|
548
|
+
v_z[tof < 0] = FILLVAL_FLOAT32
|
|
536
549
|
|
|
537
550
|
velocities = np.vstack((v_x, v_y, v_z)).T
|
|
538
551
|
|
|
@@ -621,13 +634,17 @@ def get_de_energy_kev(v: np.ndarray, species: np.ndarray) -> NDArray:
|
|
|
621
634
|
# Compute the sum of squares.
|
|
622
635
|
v2 = np.sum(vv**2, axis=1)
|
|
623
636
|
|
|
624
|
-
|
|
625
|
-
|
|
637
|
+
# Only compute where species == 1 and v is valid
|
|
638
|
+
index_hydrogen = species == 1
|
|
639
|
+
valid_velocity = np.isfinite(v2)
|
|
640
|
+
valid_mask = index_hydrogen & valid_velocity
|
|
641
|
+
|
|
642
|
+
energy = np.full_like(v2, FILLVAL_FLOAT32)
|
|
626
643
|
|
|
627
644
|
# TODO: we will calculate the energies of the different species here.
|
|
628
645
|
# 1/2 mv^2 in Joules, convert to keV
|
|
629
|
-
energy[
|
|
630
|
-
0.5 * UltraConstants.MASS_H * v2[
|
|
646
|
+
energy[valid_mask] = (
|
|
647
|
+
0.5 * UltraConstants.MASS_H * v2[valid_mask] * UltraConstants.J_KEV
|
|
631
648
|
)
|
|
632
649
|
|
|
633
650
|
return energy
|
|
@@ -933,7 +950,7 @@ def get_spin_number(de_met: NDArray, de_spin: NDArray) -> NDArray:
|
|
|
933
950
|
possible_spins = spin_numbers & 0xFF
|
|
934
951
|
|
|
935
952
|
# Assign each group based on time.
|
|
936
|
-
for start, end in zip(spin_start_indices, spin_end_indices):
|
|
953
|
+
for start, end in zip(spin_start_indices, spin_end_indices, strict=False):
|
|
937
954
|
# Now that we have the possible spins from the Universal Spin Table,
|
|
938
955
|
# we match the times of those spins to the nearest times in the DE data.
|
|
939
956
|
possible_times = spin_start_mets[possible_spins == de_spin_sorted[start]]
|
|
@@ -1030,8 +1047,11 @@ def interpolate_fwhm(
|
|
|
1030
1047
|
)
|
|
1031
1048
|
|
|
1032
1049
|
# Note: will return nan for those out-of-bounds inputs.
|
|
1033
|
-
|
|
1034
|
-
|
|
1050
|
+
phi_vals = interp_phi((energy, phi_inst))
|
|
1051
|
+
theta_vals = interp_theta((energy, theta_inst))
|
|
1052
|
+
|
|
1053
|
+
phi_interp = np.where(np.isnan(phi_vals), FILLVAL_FLOAT32, phi_vals)
|
|
1054
|
+
theta_interp = np.where(np.isnan(theta_vals), FILLVAL_FLOAT32, theta_vals)
|
|
1035
1055
|
|
|
1036
1056
|
return phi_interp, theta_interp
|
|
1037
1057
|
|
|
@@ -1069,8 +1089,8 @@ def get_fwhm(
|
|
|
1069
1089
|
theta_interp : NDArray
|
|
1070
1090
|
Interpolated theta FWHM values.
|
|
1071
1091
|
"""
|
|
1072
|
-
phi_interp = np.full_like(phi_inst,
|
|
1073
|
-
theta_interp = np.full_like(theta_inst,
|
|
1092
|
+
phi_interp = np.full_like(phi_inst, FILLVAL_FLOAT64, dtype=np.float64)
|
|
1093
|
+
theta_interp = np.full_like(theta_inst, FILLVAL_FLOAT64, dtype=np.float64)
|
|
1074
1094
|
lt_table = get_angular_profiles("left", sensor, ancillary_files)
|
|
1075
1095
|
rt_table = get_angular_profiles("right", sensor, ancillary_files)
|
|
1076
1096
|
|
|
@@ -1089,20 +1109,12 @@ def get_fwhm(
|
|
|
1089
1109
|
return phi_interp, theta_interp
|
|
1090
1110
|
|
|
1091
1111
|
|
|
1092
|
-
def
|
|
1093
|
-
energy: NDArray, phi_inst: NDArray, theta_inst: NDArray, ancillary_files: dict
|
|
1094
|
-
) -> NDArray:
|
|
1112
|
+
def get_efficiency_interpolator(ancillary_files: dict) -> RegularGridInterpolator:
|
|
1095
1113
|
"""
|
|
1096
|
-
|
|
1114
|
+
Return a callable function that interpolates efficiency values for each event.
|
|
1097
1115
|
|
|
1098
1116
|
Parameters
|
|
1099
1117
|
----------
|
|
1100
|
-
energy : NDArray
|
|
1101
|
-
Energy values for each event.
|
|
1102
|
-
phi_inst : NDArray
|
|
1103
|
-
Instrument-frame azimuth angle for each event.
|
|
1104
|
-
theta_inst : NDArray
|
|
1105
|
-
Instrument-frame elevation angle for each event.
|
|
1106
1118
|
ancillary_files : dict
|
|
1107
1119
|
Ancillary files.
|
|
1108
1120
|
|
|
@@ -1127,14 +1139,54 @@ def get_efficiency(
|
|
|
1127
1139
|
(theta_vals, phi_vals, energy_vals),
|
|
1128
1140
|
efficiency_grid,
|
|
1129
1141
|
bounds_error=False,
|
|
1130
|
-
fill_value=
|
|
1142
|
+
fill_value=FILLVAL_FLOAT32,
|
|
1131
1143
|
)
|
|
1132
1144
|
|
|
1145
|
+
return interpolator
|
|
1146
|
+
|
|
1147
|
+
|
|
1148
|
+
def get_efficiency(
|
|
1149
|
+
energy: NDArray,
|
|
1150
|
+
phi_inst: NDArray,
|
|
1151
|
+
theta_inst: NDArray,
|
|
1152
|
+
ancillary_files: dict,
|
|
1153
|
+
interpolator: RegularGridInterpolator = None,
|
|
1154
|
+
) -> np.ndarray:
|
|
1155
|
+
"""
|
|
1156
|
+
Return interpolated efficiency values for each event.
|
|
1157
|
+
|
|
1158
|
+
Parameters
|
|
1159
|
+
----------
|
|
1160
|
+
energy : NDArray
|
|
1161
|
+
Energy values for each event.
|
|
1162
|
+
phi_inst : NDArray
|
|
1163
|
+
Instrument-frame azimuth angle for each event.
|
|
1164
|
+
theta_inst : NDArray
|
|
1165
|
+
Instrument-frame elevation angle for each event.
|
|
1166
|
+
ancillary_files : dict
|
|
1167
|
+
Ancillary files.
|
|
1168
|
+
interpolator : RegularGridInterpolator, optional
|
|
1169
|
+
Precomputed interpolator to use for efficiency lookup.
|
|
1170
|
+
If None, a new interpolator will be created from the ancillary files.
|
|
1171
|
+
|
|
1172
|
+
Returns
|
|
1173
|
+
-------
|
|
1174
|
+
efficiency : NDArray
|
|
1175
|
+
Interpolated efficiency values.
|
|
1176
|
+
"""
|
|
1177
|
+
if not interpolator:
|
|
1178
|
+
interpolator = get_efficiency_interpolator(ancillary_files)
|
|
1179
|
+
|
|
1133
1180
|
return interpolator((theta_inst, phi_inst, energy))
|
|
1134
1181
|
|
|
1135
1182
|
|
|
1136
1183
|
def determine_ebin_pulse_height(
|
|
1137
|
-
energy:
|
|
1184
|
+
energy: NDArray,
|
|
1185
|
+
tof: NDArray,
|
|
1186
|
+
path_length: NDArray,
|
|
1187
|
+
backtofvalid: NDArray,
|
|
1188
|
+
coinphvalid: NDArray,
|
|
1189
|
+
ancillary_files: dict,
|
|
1138
1190
|
) -> NDArray:
|
|
1139
1191
|
"""
|
|
1140
1192
|
Determine the species for pulse-height events.
|
|
@@ -1152,12 +1204,18 @@ def determine_ebin_pulse_height(
|
|
|
1152
1204
|
|
|
1153
1205
|
Parameters
|
|
1154
1206
|
----------
|
|
1155
|
-
energy :
|
|
1207
|
+
energy : NDArray
|
|
1156
1208
|
Energy from the PH event (keV).
|
|
1157
|
-
tof :
|
|
1209
|
+
tof : NDArray
|
|
1158
1210
|
Time of flight of the PH event (tenths of a nanosecond).
|
|
1159
|
-
path_length :
|
|
1211
|
+
path_length : NDArray
|
|
1160
1212
|
Path length (r) (hundredths of a millimeter).
|
|
1213
|
+
backtofvalid : NDArray
|
|
1214
|
+
Boolean array indicating if the back TOF is valid.
|
|
1215
|
+
coinphvalid : NDArray
|
|
1216
|
+
Boolean array indicating if the Coincidence PH is valid.
|
|
1217
|
+
ancillary_files : dict
|
|
1218
|
+
Ancillary files containing the lookup tables.
|
|
1161
1219
|
|
|
1162
1220
|
Returns
|
|
1163
1221
|
-------
|
|
@@ -1166,15 +1224,22 @@ def determine_ebin_pulse_height(
|
|
|
1166
1224
|
"""
|
|
1167
1225
|
# PH event TOF normalization to Z axis
|
|
1168
1226
|
ctof, _ = get_ctof(tof, path_length, type="PH")
|
|
1169
|
-
# TODO: need lookup tables
|
|
1170
|
-
# placeholder
|
|
1171
|
-
ebin = np.full(len(ctof), 255, dtype=np.uint8)
|
|
1172
1227
|
|
|
1173
|
-
|
|
1228
|
+
ebins = np.full(path_length.shape, FILLVAL_UINT8, dtype=np.uint8)
|
|
1229
|
+
valid = backtofvalid & coinphvalid
|
|
1230
|
+
ebins[valid] = get_ebins(
|
|
1231
|
+
"l1b-tofxph", energy[valid], ctof[valid], ebins[valid], ancillary_files
|
|
1232
|
+
)
|
|
1233
|
+
|
|
1234
|
+
return ebins
|
|
1174
1235
|
|
|
1175
1236
|
|
|
1176
1237
|
def determine_ebin_ssd(
|
|
1177
|
-
energy:
|
|
1238
|
+
energy: NDArray,
|
|
1239
|
+
tof: NDArray,
|
|
1240
|
+
path_length: NDArray,
|
|
1241
|
+
sensor: str,
|
|
1242
|
+
ancillary_files: dict,
|
|
1178
1243
|
) -> NDArray:
|
|
1179
1244
|
"""
|
|
1180
1245
|
Determine the species for SSD events.
|
|
@@ -1194,29 +1259,170 @@ def determine_ebin_ssd(
|
|
|
1194
1259
|
|
|
1195
1260
|
Parameters
|
|
1196
1261
|
----------
|
|
1197
|
-
energy :
|
|
1262
|
+
energy : NDArray
|
|
1198
1263
|
Energy from the SSD event (keV).
|
|
1199
|
-
tof :
|
|
1264
|
+
tof : NDArray
|
|
1200
1265
|
Time of flight of the SSD event (tenths of a nanosecond).
|
|
1201
|
-
path_length :
|
|
1266
|
+
path_length : NDArray
|
|
1202
1267
|
Path length (r) (hundredths of a millimeter).
|
|
1268
|
+
sensor : str
|
|
1269
|
+
Sensor name: "ultra45" or "ultra90".
|
|
1270
|
+
ancillary_files : dict
|
|
1271
|
+
Ancillary files containing the lookup tables.
|
|
1203
1272
|
|
|
1204
1273
|
Returns
|
|
1205
1274
|
-------
|
|
1206
|
-
bin :
|
|
1275
|
+
bin : NDArray
|
|
1207
1276
|
Species bin.
|
|
1208
1277
|
"""
|
|
1209
1278
|
# SSD event TOF normalization to Z axis
|
|
1210
1279
|
ctof, _ = get_ctof(tof, path_length, type="SSD")
|
|
1211
1280
|
|
|
1212
|
-
|
|
1281
|
+
ebins = np.full(path_length.shape, FILLVAL_UINT8, dtype=np.uint8)
|
|
1282
|
+
steep_path_length = get_image_params("PathSteepThresh", sensor, ancillary_files)
|
|
1283
|
+
medium_path_length = get_image_params("PathMediumThresh", sensor, ancillary_files)
|
|
1284
|
+
|
|
1285
|
+
steep_mask = path_length < steep_path_length
|
|
1286
|
+
medium_mask = (path_length >= steep_path_length) & (
|
|
1287
|
+
path_length < medium_path_length
|
|
1288
|
+
)
|
|
1289
|
+
flat_mask = path_length >= medium_path_length
|
|
1290
|
+
|
|
1291
|
+
ebins[steep_mask] = get_ebins(
|
|
1292
|
+
f"l1b-{sensor[5::]}sensor-tofxesteep",
|
|
1293
|
+
energy[steep_mask],
|
|
1294
|
+
ctof[steep_mask],
|
|
1295
|
+
ebins[steep_mask],
|
|
1296
|
+
ancillary_files,
|
|
1297
|
+
)
|
|
1298
|
+
ebins[medium_mask] = get_ebins(
|
|
1299
|
+
f"l1b-{sensor[5::]}sensor-tofxemedium",
|
|
1300
|
+
energy[medium_mask],
|
|
1301
|
+
ctof[medium_mask],
|
|
1302
|
+
ebins[medium_mask],
|
|
1303
|
+
ancillary_files,
|
|
1304
|
+
)
|
|
1305
|
+
ebins[flat_mask] = get_ebins(
|
|
1306
|
+
f"l1b-{sensor[5::]}sensor-tofxeflat",
|
|
1307
|
+
energy[flat_mask],
|
|
1308
|
+
ctof[flat_mask],
|
|
1309
|
+
ebins[flat_mask],
|
|
1310
|
+
ancillary_files,
|
|
1311
|
+
)
|
|
1312
|
+
|
|
1313
|
+
return ebins
|
|
1314
|
+
|
|
1315
|
+
|
|
1316
|
+
def is_back_tof_valid(
|
|
1317
|
+
de_dataset: xarray.Dataset,
|
|
1318
|
+
xf: NDArray,
|
|
1319
|
+
sensor: str,
|
|
1320
|
+
ancillary_files: dict,
|
|
1321
|
+
) -> NDArray:
|
|
1322
|
+
"""
|
|
1323
|
+
Determine whether back TOF is valid based on stop type.
|
|
1324
|
+
|
|
1325
|
+
Parameters
|
|
1326
|
+
----------
|
|
1327
|
+
de_dataset : xarray.Dataset
|
|
1328
|
+
Data in xarray format.
|
|
1329
|
+
xf : NDArray
|
|
1330
|
+
X front position in (hundredths of a millimeter).
|
|
1331
|
+
Has same length as de_dataset.
|
|
1332
|
+
sensor : str
|
|
1333
|
+
Sensor name: "ultra45" or "ultra90".
|
|
1334
|
+
ancillary_files : dict
|
|
1335
|
+
Ancillary files for lookup.
|
|
1336
|
+
|
|
1337
|
+
Returns
|
|
1338
|
+
-------
|
|
1339
|
+
valid_mask : NDArray
|
|
1340
|
+
Boolean array indicating whether back TOF is valid.
|
|
1341
|
+
|
|
1342
|
+
Notes
|
|
1343
|
+
-----
|
|
1344
|
+
From page 33 of the IMAP-Ultra Flight Software Specification document.
|
|
1345
|
+
"""
|
|
1346
|
+
_, _, _, _, tofx, tofy = get_ph_tof_and_back_positions(
|
|
1347
|
+
de_dataset, xf, "ultra45", ancillary_files
|
|
1348
|
+
)
|
|
1349
|
+
diff = tofy - tofx
|
|
1350
|
+
|
|
1351
|
+
indices = np.nonzero(
|
|
1352
|
+
np.isin(de_dataset["stop_type"], [StopType.Top.value, StopType.Bottom.value])
|
|
1353
|
+
)[0]
|
|
1354
|
+
de_ph = de_dataset.isel(epoch=indices)
|
|
1355
|
+
|
|
1356
|
+
top_mask = de_ph["stop_type"] == StopType.Top.value
|
|
1357
|
+
bottom_mask = de_ph["stop_type"] == StopType.Bottom.value
|
|
1358
|
+
|
|
1359
|
+
valid = np.zeros_like(diff, dtype=bool)
|
|
1360
|
+
|
|
1361
|
+
diff_tp_min = get_image_params("TOFDiffTpMin", sensor, ancillary_files)
|
|
1362
|
+
diff_tp_max = get_image_params("TOFDiffTpMax", sensor, ancillary_files)
|
|
1363
|
+
diff_bt_min = get_image_params("TOFDiffBtMin", sensor, ancillary_files)
|
|
1364
|
+
diff_bt_max = get_image_params("TOFDiffBtMax", sensor, ancillary_files)
|
|
1365
|
+
|
|
1366
|
+
valid[top_mask] = (diff[top_mask] >= diff_tp_min) & (diff[top_mask] <= diff_tp_max)
|
|
1367
|
+
valid[bottom_mask] = (diff[bottom_mask] >= diff_bt_min) & (
|
|
1368
|
+
diff[bottom_mask] <= diff_bt_max
|
|
1369
|
+
)
|
|
1370
|
+
|
|
1371
|
+
return valid
|
|
1372
|
+
|
|
1373
|
+
|
|
1374
|
+
def is_coin_ph_valid(
|
|
1375
|
+
etof: NDArray,
|
|
1376
|
+
xc: NDArray,
|
|
1377
|
+
xb: NDArray,
|
|
1378
|
+
sensor: str,
|
|
1379
|
+
ancillary_files: dict,
|
|
1380
|
+
) -> NDArray:
|
|
1381
|
+
"""
|
|
1382
|
+
Determine whether Coincidence-PH data are valid.
|
|
1383
|
+
|
|
1384
|
+
This is based on thresholds defined in the IMAP-Ultra Flight Software Specification
|
|
1385
|
+
(see page 36).
|
|
1386
|
+
|
|
1387
|
+
Parameters
|
|
1388
|
+
----------
|
|
1389
|
+
etof : NDArray
|
|
1390
|
+
Electron TOF (tenths of a nanosecond).
|
|
1391
|
+
xc : NDArray
|
|
1392
|
+
Coincidence X position (hundredths of a mm).
|
|
1393
|
+
xb : NDArray
|
|
1394
|
+
Back X position (hundredths of a mm).
|
|
1395
|
+
sensor : str
|
|
1396
|
+
Sensor name: "ultra45" or "ultra90".
|
|
1397
|
+
ancillary_files : dict
|
|
1398
|
+
Ancillary files for lookup.
|
|
1399
|
+
|
|
1400
|
+
Returns
|
|
1401
|
+
-------
|
|
1402
|
+
valid_mask : NDArray
|
|
1403
|
+
Boolean array indicating Coin-PH validity.
|
|
1404
|
+
|
|
1405
|
+
Notes
|
|
1406
|
+
-----
|
|
1407
|
+
Logic derived from page 36 of the IMAP-Ultra Flight Software Specification document.
|
|
1408
|
+
"""
|
|
1409
|
+
etof_min = get_image_params("eTOFMin", sensor, ancillary_files)
|
|
1410
|
+
etof_max = get_image_params("eTOFMax", sensor, ancillary_files)
|
|
1411
|
+
|
|
1412
|
+
etof_valid = (etof >= etof_min) & (etof <= etof_max)
|
|
1413
|
+
|
|
1414
|
+
diff_x = xc - xb
|
|
1415
|
+
etof_offset1 = get_image_params("eTOFOff1", sensor, ancillary_files)
|
|
1416
|
+
etof_offset2 = get_image_params("eTOFOff2", sensor, ancillary_files)
|
|
1417
|
+
etof_slope1 = get_image_params("eTOFSlope1", sensor, ancillary_files)
|
|
1418
|
+
etof_slope2 = get_image_params("eTOFSlope2", sensor, ancillary_files)
|
|
1419
|
+
|
|
1420
|
+
t1 = (etof - etof_offset1) * etof_slope1 / 1024
|
|
1421
|
+
t2 = (etof - etof_offset2) * etof_slope2 / 1024
|
|
1422
|
+
|
|
1423
|
+
condition_1 = (diff_x >= t1) & (diff_x <= t2)
|
|
1424
|
+
condition_2 = (diff_x >= -t2) & (diff_x <= -t1)
|
|
1213
1425
|
|
|
1214
|
-
|
|
1215
|
-
# if r < get_image_params("PathSteepThresh"):
|
|
1216
|
-
# # bin = ExTOFSpeciesSteep[energy, ctof]
|
|
1217
|
-
# elif r < get_image_params("PathMediumThresh"):
|
|
1218
|
-
# # bin = ExTOFSpeciesMedium[energy, ctof]
|
|
1219
|
-
# else:
|
|
1220
|
-
# # bin = ExTOFSpeciesFlat[energy, ctof]
|
|
1426
|
+
spatial_valid = condition_1 | condition_2
|
|
1221
1427
|
|
|
1222
|
-
return
|
|
1428
|
+
return etof_valid & spatial_valid
|