imap-processing 0.18.0__py3-none-any.whl → 0.19.2__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_global_cdf_attrs.yaml +6 -0
- imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +221 -1057
- imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +307 -283
- 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_enamaps_l2-common_variable_attrs.yaml +11 -0
- imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +15 -1
- 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_global_cdf_attrs.yaml +5 -4
- 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_global_cdf_attrs.yaml +20 -8
- imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +45 -35
- imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +110 -7
- imap_processing/cli.py +138 -93
- imap_processing/codice/codice_l0.py +2 -1
- imap_processing/codice/codice_l1a.py +167 -69
- imap_processing/codice/codice_l1b.py +42 -32
- imap_processing/codice/codice_l2.py +215 -9
- imap_processing/codice/constants.py +790 -603
- imap_processing/codice/data/lo_stepping_values.csv +1 -1
- imap_processing/decom.py +1 -4
- imap_processing/ena_maps/ena_maps.py +71 -43
- imap_processing/ena_maps/utils/corrections.py +291 -0
- imap_processing/ena_maps/utils/map_utils.py +20 -4
- 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 +123 -18
- imap_processing/glows/l1b/glows_l1b_data.py +358 -47
- 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_l1c.py +4 -109
- imap_processing/hi/hi_l2.py +104 -60
- imap_processing/hi/utils.py +262 -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 +6 -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/ialirt/utils/create_xarray.py +1 -1
- 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/ancillary_data/imap_lo_hydrogen-geometric-factor_v001.csv +75 -0
- imap_processing/lo/ancillary_data/imap_lo_oxygen-geometric-factor_v001.csv +75 -0
- imap_processing/lo/l0/lo_science.py +25 -24
- imap_processing/lo/l1b/lo_l1b.py +93 -19
- imap_processing/lo/l1c/lo_l1c.py +273 -93
- imap_processing/lo/l2/lo_l2.py +949 -135
- 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 +202 -32
- imap_processing/mag/l2/mag_l2.py +2 -0
- imap_processing/mag/l2/mag_l2_data.py +14 -5
- imap_processing/quality_flags.py +23 -1
- imap_processing/spice/geometry.py +89 -39
- imap_processing/spice/pointing_frame.py +4 -8
- imap_processing/spice/repoint.py +78 -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 +30 -24
- imap_processing/ultra/l0/ultra_utils.py +9 -11
- imap_processing/ultra/l1a/ultra_l1a.py +1 -2
- imap_processing/ultra/l1b/badtimes.py +35 -11
- imap_processing/ultra/l1b/de.py +95 -31
- imap_processing/ultra/l1b/extendedspin.py +31 -16
- imap_processing/ultra/l1b/goodtimes.py +112 -0
- 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.py +7 -7
- imap_processing/ultra/l1b/ultra_l1b_culling.py +169 -7
- imap_processing/ultra/l1b/ultra_l1b_extended.py +311 -69
- imap_processing/ultra/l1c/helio_pset.py +139 -37
- imap_processing/ultra/l1c/l1c_lookup_utils.py +289 -0
- imap_processing/ultra/l1c/spacecraft_pset.py +140 -29
- imap_processing/ultra/l1c/ultra_l1c.py +33 -24
- imap_processing/ultra/l1c/ultra_l1c_culling.py +92 -0
- imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +400 -292
- imap_processing/ultra/l2/ultra_l2.py +54 -11
- imap_processing/ultra/utils/ultra_l1_utils.py +37 -7
- imap_processing/utils.py +3 -4
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/METADATA +2 -2
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/RECORD +118 -109
- imap_processing/idex/idex_l2c.py +0 -84
- imap_processing/spice/kernels.py +0 -187
- imap_processing/ultra/l1b/cullingmask.py +0 -87
- imap_processing/ultra/l1c/histogram.py +0 -36
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/LICENSE +0 -0
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/WHEEL +0 -0
- {imap_processing-0.18.0.dist-info → imap_processing-0.19.2.dist-info}/entry_points.txt +0 -0
|
@@ -1,27 +1,47 @@
|
|
|
1
1
|
"""Calculate Pointing Set Grids."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import astropy_healpix.healpy as hp
|
|
3
6
|
import numpy as np
|
|
4
|
-
import pandas as pd
|
|
5
7
|
import xarray as xr
|
|
6
8
|
|
|
7
|
-
from imap_processing.
|
|
9
|
+
from imap_processing.quality_flags import ImapPSETUltraFlags
|
|
10
|
+
from imap_processing.spice.repoint import get_pointing_times
|
|
11
|
+
from imap_processing.spice.time import (
|
|
12
|
+
et_to_met,
|
|
13
|
+
met_to_ttj2000ns,
|
|
14
|
+
ttj2000ns_to_et,
|
|
15
|
+
)
|
|
16
|
+
from imap_processing.ultra.l1b.ultra_l1b_culling import get_de_rejection_mask
|
|
17
|
+
from imap_processing.ultra.l1c.l1c_lookup_utils import (
|
|
18
|
+
calculate_fwhm_spun_scattering,
|
|
19
|
+
get_spacecraft_pointing_lookup_tables,
|
|
20
|
+
)
|
|
21
|
+
from imap_processing.ultra.l1c.ultra_l1c_culling import compute_culling_mask
|
|
8
22
|
from imap_processing.ultra.l1c.ultra_l1c_pset_bins import (
|
|
9
23
|
build_energy_bins,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
24
|
+
get_efficiencies_and_geometric_function,
|
|
25
|
+
get_energy_delta_minus_plus,
|
|
26
|
+
get_helio_adjusted_data,
|
|
27
|
+
get_spacecraft_exposure_times,
|
|
13
28
|
get_spacecraft_histogram,
|
|
14
29
|
)
|
|
15
30
|
from imap_processing.ultra.utils.ultra_l1_utils import create_dataset
|
|
16
31
|
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
17
34
|
|
|
18
35
|
def calculate_helio_pset(
|
|
19
36
|
de_dataset: xr.Dataset,
|
|
20
|
-
|
|
21
|
-
|
|
37
|
+
goodtimes_dataset: xr.Dataset,
|
|
38
|
+
rates_dataset: xr.Dataset,
|
|
39
|
+
params_dataset: xr.Dataset,
|
|
22
40
|
name: str,
|
|
23
41
|
ancillary_files: dict,
|
|
24
|
-
|
|
42
|
+
instrument_id: int,
|
|
43
|
+
species_id: list,
|
|
44
|
+
) -> xr.Dataset | None:
|
|
25
45
|
"""
|
|
26
46
|
Create dictionary with defined datatype for Pointing Set Grid Data.
|
|
27
47
|
|
|
@@ -29,14 +49,20 @@ def calculate_helio_pset(
|
|
|
29
49
|
----------
|
|
30
50
|
de_dataset : xarray.Dataset
|
|
31
51
|
Dataset containing de data.
|
|
32
|
-
|
|
33
|
-
Dataset containing
|
|
34
|
-
|
|
35
|
-
Dataset containing
|
|
52
|
+
goodtimes_dataset : xarray.Dataset
|
|
53
|
+
Dataset containing goodtimes data.
|
|
54
|
+
rates_dataset : xarray.Dataset
|
|
55
|
+
Dataset containing image rates data.
|
|
56
|
+
params_dataset : xarray.Dataset
|
|
57
|
+
Dataset containing image parameters data.
|
|
36
58
|
name : str
|
|
37
59
|
Name of the dataset.
|
|
38
60
|
ancillary_files : dict
|
|
39
61
|
Ancillary files.
|
|
62
|
+
instrument_id : int
|
|
63
|
+
Instrument ID, either 45 or 90.
|
|
64
|
+
species_id : List
|
|
65
|
+
Species ID.
|
|
40
66
|
|
|
41
67
|
Returns
|
|
42
68
|
-------
|
|
@@ -44,56 +70,132 @@ def calculate_helio_pset(
|
|
|
44
70
|
Dataset containing the data.
|
|
45
71
|
"""
|
|
46
72
|
pset_dict: dict[str, np.ndarray] = {}
|
|
73
|
+
# Select only the species we are interested in.
|
|
74
|
+
indices = np.where(np.isin(de_dataset["e_bin"].values, species_id))[0]
|
|
75
|
+
species_dataset = de_dataset.isel(epoch=indices)
|
|
76
|
+
|
|
77
|
+
rejected = get_de_rejection_mask(
|
|
78
|
+
species_dataset["quality_scattering"].values,
|
|
79
|
+
species_dataset["quality_outliers"].values,
|
|
80
|
+
)
|
|
81
|
+
species_dataset = species_dataset.isel(epoch=~rejected)
|
|
47
82
|
|
|
48
83
|
v_mag_helio_spacecraft = np.linalg.norm(
|
|
49
|
-
|
|
84
|
+
species_dataset["velocity_dps_helio"].values, axis=1
|
|
50
85
|
)
|
|
51
86
|
vhat_dps_helio = (
|
|
52
|
-
|
|
87
|
+
species_dataset["velocity_dps_helio"].values
|
|
88
|
+
/ v_mag_helio_spacecraft[:, np.newaxis]
|
|
53
89
|
)
|
|
54
90
|
intervals, _, energy_bin_geometric_means = build_energy_bins()
|
|
91
|
+
# Get lookup table for FOR indices by spin phase step
|
|
92
|
+
(
|
|
93
|
+
for_indices_by_spin_phase,
|
|
94
|
+
theta_vals,
|
|
95
|
+
phi_vals,
|
|
96
|
+
ra_and_dec,
|
|
97
|
+
boundary_scale_factors,
|
|
98
|
+
) = get_spacecraft_pointing_lookup_tables(ancillary_files, instrument_id)
|
|
99
|
+
|
|
100
|
+
logger.info("calculating spun FWHM scattering values.")
|
|
101
|
+
pixels_below_scattering, scattering_theta, scattering_phi, scattering_thresholds = (
|
|
102
|
+
calculate_fwhm_spun_scattering(
|
|
103
|
+
for_indices_by_spin_phase,
|
|
104
|
+
theta_vals,
|
|
105
|
+
phi_vals,
|
|
106
|
+
ancillary_files,
|
|
107
|
+
instrument_id,
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
nside = hp.npix2nside(for_indices_by_spin_phase.shape[0])
|
|
55
112
|
counts, latitude, longitude, n_pix = get_spacecraft_histogram(
|
|
56
113
|
vhat_dps_helio,
|
|
57
|
-
|
|
114
|
+
species_dataset["energy_heliosphere"].values,
|
|
58
115
|
intervals,
|
|
59
|
-
nside=
|
|
116
|
+
nside=nside,
|
|
117
|
+
)
|
|
118
|
+
helio_pset_quality_flags = np.full(
|
|
119
|
+
n_pix, ImapPSETUltraFlags.NONE.value, dtype=np.uint16
|
|
60
120
|
)
|
|
61
|
-
|
|
62
121
|
healpix = np.arange(n_pix)
|
|
63
122
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
123
|
+
logger.info("Calculating spacecraft exposure times with deadtime correction.")
|
|
124
|
+
exposure_time, deadtime_ratios = get_spacecraft_exposure_times(
|
|
125
|
+
rates_dataset,
|
|
126
|
+
params_dataset,
|
|
127
|
+
pixels_below_scattering,
|
|
128
|
+
boundary_scale_factors,
|
|
129
|
+
n_pix=n_pix,
|
|
130
|
+
)
|
|
131
|
+
logger.info("Calculating spun efficiencies and geometric function.")
|
|
132
|
+
# calculate efficiency and geometric function as a function of energy
|
|
133
|
+
efficiencies, geometric_function = get_efficiencies_and_geometric_function(
|
|
134
|
+
pixels_below_scattering,
|
|
135
|
+
boundary_scale_factors,
|
|
136
|
+
theta_vals,
|
|
137
|
+
phi_vals,
|
|
138
|
+
n_pix,
|
|
139
|
+
ancillary_files,
|
|
140
|
+
)
|
|
141
|
+
# Get midpoint timestamp for pointing.
|
|
142
|
+
pointing_start, pointing_stop = get_pointing_times(
|
|
143
|
+
et_to_met(species_dataset["event_times"].data[0])
|
|
144
|
+
)
|
|
145
|
+
mid_time = ttj2000ns_to_et(met_to_ttj2000ns((pointing_start + pointing_stop) / 2))
|
|
69
146
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
mid_time = sct_to_et(np.median(de_dataset["event_times"].data))
|
|
73
|
-
sensitivity = get_helio_sensitivity(
|
|
147
|
+
logger.info("Adjusting data for helio frame.")
|
|
148
|
+
exposure_time, efficiency, geometric_function = get_helio_adjusted_data(
|
|
74
149
|
mid_time,
|
|
75
|
-
|
|
76
|
-
|
|
150
|
+
exposure_time,
|
|
151
|
+
geometric_function,
|
|
152
|
+
efficiencies,
|
|
153
|
+
ra_and_dec[:, 0],
|
|
154
|
+
ra_and_dec[:, 1],
|
|
155
|
+
nside=nside,
|
|
77
156
|
)
|
|
157
|
+
sensitivity = efficiencies * geometric_function
|
|
78
158
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
df_exposure = pd.read_csv(constant_exposure)
|
|
82
|
-
exposure_pointing = get_helio_exposure_times(mid_time, df_exposure)
|
|
159
|
+
start: float = np.min(species_dataset["event_times"].values)
|
|
160
|
+
end: float = np.max(species_dataset["event_times"].values)
|
|
83
161
|
|
|
84
|
-
#
|
|
85
|
-
|
|
162
|
+
# Time bins in 30 minute intervals
|
|
163
|
+
time_bins = np.arange(start, end + 1800, 1800)
|
|
164
|
+
|
|
165
|
+
# Compute mask for culling the Earth
|
|
166
|
+
compute_culling_mask(
|
|
167
|
+
time_bins,
|
|
168
|
+
6378.1, # Earth radius
|
|
169
|
+
helio_pset_quality_flags,
|
|
170
|
+
nside=nside,
|
|
171
|
+
)
|
|
172
|
+
pointing_start = met_to_ttj2000ns(pointing_start)
|
|
173
|
+
# Epoch should be the start of the pointing
|
|
174
|
+
pset_dict["epoch"] = np.atleast_1d(pointing_start).astype(np.int64)
|
|
86
175
|
pset_dict["counts"] = counts[np.newaxis, ...]
|
|
87
176
|
pset_dict["latitude"] = latitude[np.newaxis, ...]
|
|
88
177
|
pset_dict["longitude"] = longitude[np.newaxis, ...]
|
|
89
178
|
pset_dict["energy_bin_geometric_mean"] = energy_bin_geometric_means
|
|
90
|
-
pset_dict["
|
|
91
|
-
pset_dict["helio_exposure_factor"] = exposure_pointing[np.newaxis, ...]
|
|
179
|
+
pset_dict["helio_exposure_factor"] = exposure_time
|
|
92
180
|
pset_dict["pixel_index"] = healpix
|
|
93
181
|
pset_dict["energy_bin_delta"] = np.diff(intervals, axis=1).squeeze()[
|
|
94
182
|
np.newaxis, ...
|
|
95
183
|
]
|
|
96
|
-
pset_dict["sensitivity"] = sensitivity
|
|
184
|
+
pset_dict["sensitivity"] = sensitivity
|
|
185
|
+
pset_dict["efficiency"] = efficiencies
|
|
186
|
+
pset_dict["geometric_function"] = geometric_function
|
|
187
|
+
pset_dict["dead_time_ratio"] = deadtime_ratios
|
|
188
|
+
pset_dict["spin_phase_step"] = np.arange(len(deadtime_ratios))
|
|
189
|
+
pset_dict["quality_flags"] = helio_pset_quality_flags[np.newaxis, ...]
|
|
190
|
+
|
|
191
|
+
pset_dict["scatter_theta"] = scattering_theta
|
|
192
|
+
pset_dict["scatter_phi"] = scattering_phi
|
|
193
|
+
pset_dict["scatter_threshold"] = scattering_thresholds
|
|
194
|
+
|
|
195
|
+
# Add the energy delta plus/minus to the dataset
|
|
196
|
+
energy_delta_minus, energy_delta_plus = get_energy_delta_minus_plus()
|
|
197
|
+
pset_dict["energy_delta_minus"] = energy_delta_minus
|
|
198
|
+
pset_dict["energy_delta_plus"] = energy_delta_plus
|
|
97
199
|
|
|
98
200
|
dataset = create_dataset(pset_dict, name, "l1c")
|
|
99
201
|
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"""Contains tools for lookup tables for l1c."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
from numpy._typing import NDArray
|
|
8
|
+
|
|
9
|
+
from imap_processing.ultra.l1b.lookup_utils import (
|
|
10
|
+
get_scattering_coefficients,
|
|
11
|
+
get_scattering_thresholds,
|
|
12
|
+
load_scattering_lookup_tables,
|
|
13
|
+
)
|
|
14
|
+
from imap_processing.ultra.l1c.ultra_l1c_pset_bins import build_energy_bins
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def mask_below_fwhm_scattering_threshold(
|
|
20
|
+
theta_coeffs: np.ndarray,
|
|
21
|
+
phi_coeffs: np.ndarray,
|
|
22
|
+
energy: np.ndarray,
|
|
23
|
+
scattering_thresholds: np.ndarray,
|
|
24
|
+
) -> tuple[NDArray, NDArray, NDArray]:
|
|
25
|
+
"""
|
|
26
|
+
Determine indices of theta and phi values below the FWHM scattering threshold.
|
|
27
|
+
|
|
28
|
+
For each phi and theta, calculate the FWHM using the formula:
|
|
29
|
+
FWHM = A*E^g
|
|
30
|
+
If Phi FWHM or Theta FWHM > the scattering requirements from the table above,
|
|
31
|
+
mask the instrument frame pixel.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
theta_coeffs : NDArray
|
|
36
|
+
Coefficients for theta FWHM calculation (a and g) for each pixel.
|
|
37
|
+
phi_coeffs : NDArray
|
|
38
|
+
Coefficients for phi FWHM calculation (a and g) for each pixel.
|
|
39
|
+
energy : NDArray
|
|
40
|
+
Energy corresponding to each theta and phi val in keV.
|
|
41
|
+
scattering_thresholds : dict
|
|
42
|
+
Scattering thresholds corresponding to each energy.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
scattering_mask : numpy.ndarray
|
|
47
|
+
Boolean array indicating indices below the scattering threshold.
|
|
48
|
+
fwhm_theta : numpy.ndarray
|
|
49
|
+
Calculated FWHM values for theta.
|
|
50
|
+
fwhm_phi : numpy.ndarray
|
|
51
|
+
Calculated FWHM values for phi.
|
|
52
|
+
"""
|
|
53
|
+
# Calculate FWHM for all pixels and all energies
|
|
54
|
+
fwhm_theta = theta_coeffs[..., 0:1] * (
|
|
55
|
+
energy ** theta_coeffs[..., 1:2]
|
|
56
|
+
) # (npix, energy.shape[1])
|
|
57
|
+
fwhm_phi = phi_coeffs[..., 0:1] * (
|
|
58
|
+
energy ** phi_coeffs[..., 1:2]
|
|
59
|
+
) # (npix, energy.shape[1])
|
|
60
|
+
|
|
61
|
+
thresholds = scattering_thresholds[np.newaxis, :] # (1, energy.shape[1])
|
|
62
|
+
|
|
63
|
+
# Combine conditions for both theta and phi.
|
|
64
|
+
# shape = (npix, energy.shape[1])
|
|
65
|
+
scattering_mask = np.logical_and(fwhm_theta <= thresholds, fwhm_phi <= thresholds)
|
|
66
|
+
return scattering_mask, fwhm_theta, fwhm_phi
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def calculate_fwhm_spun_scattering(
|
|
70
|
+
for_indices_by_spin_phase: np.ndarray,
|
|
71
|
+
theta_vals: np.ndarray,
|
|
72
|
+
phi_vals: np.ndarray,
|
|
73
|
+
ancillary_files: dict,
|
|
74
|
+
instrument_id: int,
|
|
75
|
+
) -> tuple[list, NDArray, NDArray, NDArray]:
|
|
76
|
+
"""
|
|
77
|
+
Calculate FWHM scattering values for each pixel, energy bin, and spin phase step.
|
|
78
|
+
|
|
79
|
+
This function also calculates a mask for pixels that are below the FWHM threshold.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
for_indices_by_spin_phase : np.ndarray
|
|
84
|
+
A 2D boolean array where cols are spin phase steps are rows are HEALPix pixels.
|
|
85
|
+
True indicates pixels that are within the Field of Regard (FOR) at that
|
|
86
|
+
spin phase.
|
|
87
|
+
theta_vals : np.ndarray
|
|
88
|
+
A 2D array of theta values for each HEALPix pixel at each spin phase step.
|
|
89
|
+
phi_vals : np.ndarray
|
|
90
|
+
A 2D array of phi values for each HEALPix pixel at each spin phase step.
|
|
91
|
+
ancillary_files : dict
|
|
92
|
+
Dictionary containing ancillary files.
|
|
93
|
+
instrument_id : int,
|
|
94
|
+
Instrument ID, either 45 or 90.
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
pixels_below_scattering : list
|
|
99
|
+
A Nested list of arrays indicating pixels within the scattering threshold.
|
|
100
|
+
The outer list indicates spin phase steps, the middle list indicates energy
|
|
101
|
+
bins, and the inner arrays contain indices indicating pixels that are below
|
|
102
|
+
the FWHM scattering threshold.
|
|
103
|
+
scattering_fwhm_theta : NDArray
|
|
104
|
+
Calculated FWHM scatting values for theta at each energy bin and averaged
|
|
105
|
+
over spin phase.
|
|
106
|
+
scattering_fwhm_phi : NDArray
|
|
107
|
+
Calculated FWHM scatting values for theta at each energy bin and averaged
|
|
108
|
+
over spin phase.
|
|
109
|
+
scattering_thresholds_for_energy_mean : NDArray
|
|
110
|
+
Scattering thresholds corresponding to each energy bin.
|
|
111
|
+
"""
|
|
112
|
+
# Load scattering coefficient lookup table
|
|
113
|
+
scattering_luts = load_scattering_lookup_tables(ancillary_files, instrument_id)
|
|
114
|
+
pixels_below_scattering = []
|
|
115
|
+
# Get energy bin geometric means
|
|
116
|
+
energy_bin_geometric_means = build_energy_bins()[2]
|
|
117
|
+
# Load scattering thresholds for the energy bin geometric means
|
|
118
|
+
scattering_thresholds_for_energy_mean = get_scattering_thresholds_for_energy(
|
|
119
|
+
energy_bin_geometric_means, ancillary_files
|
|
120
|
+
)
|
|
121
|
+
# Initialize arrays to accumulate FWHM values for averaging
|
|
122
|
+
fwhm_theta_sum = np.zeros(
|
|
123
|
+
(len(energy_bin_geometric_means), for_indices_by_spin_phase.shape[0])
|
|
124
|
+
)
|
|
125
|
+
fwhm_phi_sum = np.zeros_like(fwhm_theta_sum)
|
|
126
|
+
sample_count = np.zeros_like(fwhm_theta_sum)
|
|
127
|
+
|
|
128
|
+
steps = for_indices_by_spin_phase.shape[1]
|
|
129
|
+
energies = energy_bin_geometric_means[np.newaxis, :]
|
|
130
|
+
# The "for_indices_by_spin_phase" lookup table contains the boolean values of each
|
|
131
|
+
# pixel at each spin phase step, indicating whether the pixel is inside the FOR.
|
|
132
|
+
# It starts at Spin-phase = 0, and increments in fine steps (1 ms), spinning the
|
|
133
|
+
# spacecraft in the despun frame. At each iteration, query for the pixels in the
|
|
134
|
+
# FOR, and calculate whether the FWHM value is below the threshold at the energy.
|
|
135
|
+
for i in range(steps):
|
|
136
|
+
# Calculate spin phase for the current iteration
|
|
137
|
+
for_inds = for_indices_by_spin_phase[:, i]
|
|
138
|
+
|
|
139
|
+
# Skip if no pixels in FOR
|
|
140
|
+
if not np.any(for_inds):
|
|
141
|
+
logger.info(f"No pixels found in FOR at spin phase step {i}")
|
|
142
|
+
pixels_below_scattering.append(
|
|
143
|
+
[
|
|
144
|
+
np.array([], dtype=int)
|
|
145
|
+
for _ in range(len(energy_bin_geometric_means))
|
|
146
|
+
]
|
|
147
|
+
)
|
|
148
|
+
continue
|
|
149
|
+
# Using the lookup table, get the indices of the pixels inside the FOR at
|
|
150
|
+
# the current spin phase step.
|
|
151
|
+
theta = theta_vals[for_inds, i]
|
|
152
|
+
phi = phi_vals[for_inds, i]
|
|
153
|
+
theta_coeffs, phi_coeffs = get_scattering_coefficients(
|
|
154
|
+
theta, phi, lookup_tables=scattering_luts
|
|
155
|
+
)
|
|
156
|
+
# Get a mask for pixels below the FWHM scattering threshold
|
|
157
|
+
scattering_mask, fwhm_theta, fwhm_phi = mask_below_fwhm_scattering_threshold(
|
|
158
|
+
theta_coeffs,
|
|
159
|
+
phi_coeffs,
|
|
160
|
+
energies,
|
|
161
|
+
scattering_thresholds=scattering_thresholds_for_energy_mean,
|
|
162
|
+
)
|
|
163
|
+
# Extract pixel indices for each energy
|
|
164
|
+
for_pixel_indices = np.where(for_inds)[0]
|
|
165
|
+
pixels_below_scattering_for_energy = []
|
|
166
|
+
|
|
167
|
+
for energy_idx in range(len(energy_bin_geometric_means)):
|
|
168
|
+
valid_pixels = scattering_mask[:, energy_idx]
|
|
169
|
+
pixels_below_scattering_for_energy.append(for_pixel_indices[valid_pixels])
|
|
170
|
+
|
|
171
|
+
pixels_below_scattering.append(pixels_below_scattering_for_energy)
|
|
172
|
+
# Accumulate FWHM values for averaging
|
|
173
|
+
fwhm_theta_sum[:, for_inds] += fwhm_theta.T
|
|
174
|
+
fwhm_phi_sum[:, for_inds] += fwhm_phi.T
|
|
175
|
+
sample_count[:, for_inds] += 1
|
|
176
|
+
|
|
177
|
+
fwhm_phi_avg = np.divide(fwhm_theta_sum, sample_count, where=sample_count != 0)
|
|
178
|
+
fwhm_theta_avg = np.divide(fwhm_theta_sum, sample_count, where=sample_count != 0)
|
|
179
|
+
return (
|
|
180
|
+
pixels_below_scattering,
|
|
181
|
+
fwhm_theta_avg,
|
|
182
|
+
fwhm_phi_avg,
|
|
183
|
+
scattering_thresholds_for_energy_mean,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def get_spacecraft_pointing_lookup_tables(
|
|
188
|
+
ancillary_files: dict, instrument_id: int
|
|
189
|
+
) -> tuple[NDArray, NDArray, NDArray, NDArray, NDArray]:
|
|
190
|
+
"""
|
|
191
|
+
Get indices of pixels in the nominal FOR as a function of spin phase.
|
|
192
|
+
|
|
193
|
+
This function also returns the theta / phi values in the instrument frame per spin
|
|
194
|
+
phase, right ascension / declination values in the SC frame, and boundary scale
|
|
195
|
+
factors for each pixel at each spin phase.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
ancillary_files : dict[Path]
|
|
200
|
+
Ancillary files.
|
|
201
|
+
instrument_id : int
|
|
202
|
+
Instrument ID, either 45 or 90.
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
for_indices_by_spin_phase : NDArray
|
|
207
|
+
A 2D boolean array of shape (npix, n_spin_phase_steps).
|
|
208
|
+
True indicates pixels that are within the Field of Regard (FOR) at that
|
|
209
|
+
spin phase.
|
|
210
|
+
theta_vals : NDArray
|
|
211
|
+
A 2D array of theta values for each HEALPix pixel at each spin phase step.
|
|
212
|
+
phi_vals : NDArray
|
|
213
|
+
A 2D array of phi values for each HEALPix pixel at each spin phase step.
|
|
214
|
+
ra_and_dec : NDArray
|
|
215
|
+
A 2D array of right ascension and declination values for each HEALPix pixel.
|
|
216
|
+
boundary_scale_factors : NDArray
|
|
217
|
+
A 2D array of boundary scale factors for each HEALPix pixel at each spin phase
|
|
218
|
+
step.
|
|
219
|
+
"""
|
|
220
|
+
theta_descriptor = f"l1c-{instrument_id}sensor-sc-pointing-theta"
|
|
221
|
+
phi_descriptor = f"l1c-{instrument_id}sensor-sc-pointing-phi"
|
|
222
|
+
index_descriptor = f"l1c-{instrument_id}sensor-sc-pointing-index"
|
|
223
|
+
bsf_descriptor = f"l1c-{instrument_id}sensor-sc-pointing-bsf"
|
|
224
|
+
|
|
225
|
+
theta_vals = pd.read_csv(
|
|
226
|
+
ancillary_files[theta_descriptor], header=None, skiprows=1
|
|
227
|
+
).to_numpy(dtype=float)[:, 2:]
|
|
228
|
+
phi_vals = pd.read_csv(
|
|
229
|
+
ancillary_files[phi_descriptor], header=None, skiprows=1
|
|
230
|
+
).to_numpy(dtype=float)[:, 2:]
|
|
231
|
+
index_grid = pd.read_csv(
|
|
232
|
+
ancillary_files[index_descriptor], header=None, skiprows=1
|
|
233
|
+
).to_numpy(dtype=float)
|
|
234
|
+
boundary_scale_factors = pd.read_csv(
|
|
235
|
+
ancillary_files[bsf_descriptor], header=None, skiprows=1
|
|
236
|
+
).to_numpy(dtype=float)[:, 2:]
|
|
237
|
+
|
|
238
|
+
ra_and_dec = index_grid[:, :2] # Shape (npix, 2)
|
|
239
|
+
# This array indicates whether each pixel is in the nominal FOR at each spin phase
|
|
240
|
+
# step (15000 steps for a full rotation with 1 ms resolution).
|
|
241
|
+
for_indices_by_spin_phase = np.nan_to_num(index_grid[:, 2:], nan=0).astype(
|
|
242
|
+
bool
|
|
243
|
+
) # Shape (npix, 15000)
|
|
244
|
+
return (
|
|
245
|
+
for_indices_by_spin_phase,
|
|
246
|
+
theta_vals,
|
|
247
|
+
phi_vals,
|
|
248
|
+
ra_and_dec,
|
|
249
|
+
boundary_scale_factors,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def get_scattering_thresholds_for_energy(
|
|
254
|
+
energy: np.ndarray, ancillary_files: dict
|
|
255
|
+
) -> np.ndarray:
|
|
256
|
+
"""
|
|
257
|
+
Find the scattering thresholds for each energy bin.
|
|
258
|
+
|
|
259
|
+
Parameters
|
|
260
|
+
----------
|
|
261
|
+
energy : np.ndarray
|
|
262
|
+
Array of energy values in keV.
|
|
263
|
+
ancillary_files : dict
|
|
264
|
+
Dictionary containing ancillary files.
|
|
265
|
+
|
|
266
|
+
Returns
|
|
267
|
+
-------
|
|
268
|
+
np.ndarray
|
|
269
|
+
Array of scattering thresholds for each energy bin.
|
|
270
|
+
"""
|
|
271
|
+
scattering_thresholds = get_scattering_thresholds(ancillary_files)
|
|
272
|
+
# Get thresholds for all energies
|
|
273
|
+
thresholds = []
|
|
274
|
+
for e in energy:
|
|
275
|
+
try:
|
|
276
|
+
threshold = next(
|
|
277
|
+
threshold
|
|
278
|
+
for energy_range, threshold in scattering_thresholds.items()
|
|
279
|
+
if energy_range[0] <= e < energy_range[1]
|
|
280
|
+
)
|
|
281
|
+
except StopIteration:
|
|
282
|
+
logger.warning(
|
|
283
|
+
f"Energy {e} keV is out of bounds for scattering thresholds. Using"
|
|
284
|
+
f" zero for as threshold."
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
threshold = 0
|
|
288
|
+
thresholds.append(threshold)
|
|
289
|
+
return np.array(thresholds)
|