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
|
@@ -3,17 +3,30 @@
|
|
|
3
3
|
import astropy_healpix.healpy as hp
|
|
4
4
|
import numpy as np
|
|
5
5
|
import pandas
|
|
6
|
-
import pandas as pd
|
|
7
6
|
import xarray as xr
|
|
8
7
|
from numpy.typing import NDArray
|
|
9
|
-
from scipy
|
|
8
|
+
from scipy import interpolate
|
|
10
9
|
|
|
11
10
|
from imap_processing.spice.geometry import (
|
|
12
11
|
SpiceFrame,
|
|
13
12
|
cartesian_to_spherical,
|
|
14
13
|
imap_state,
|
|
15
14
|
)
|
|
15
|
+
from imap_processing.spice.spin import get_spacecraft_spin_phase, get_spin_angle
|
|
16
16
|
from imap_processing.ultra.constants import UltraConstants
|
|
17
|
+
from imap_processing.ultra.l1b.lookup_utils import (
|
|
18
|
+
get_geometric_factor,
|
|
19
|
+
get_image_params,
|
|
20
|
+
load_geometric_factor_tables,
|
|
21
|
+
)
|
|
22
|
+
from imap_processing.ultra.l1b.ultra_l1b_culling import (
|
|
23
|
+
get_pulses_per_spin,
|
|
24
|
+
get_spin_and_duration,
|
|
25
|
+
)
|
|
26
|
+
from imap_processing.ultra.l1b.ultra_l1b_extended import (
|
|
27
|
+
get_efficiency,
|
|
28
|
+
get_efficiency_interpolator,
|
|
29
|
+
)
|
|
17
30
|
|
|
18
31
|
# TODO: add species binning.
|
|
19
32
|
FILLVAL_FLOAT32 = -1.0e31
|
|
@@ -32,15 +45,8 @@ def build_energy_bins() -> tuple[list[tuple[float, float]], np.ndarray, np.ndarr
|
|
|
32
45
|
energy_bin_geometric_means : np.ndarray
|
|
33
46
|
Array of geometric means of energy bins.
|
|
34
47
|
"""
|
|
35
|
-
# Calculate energy step
|
|
36
|
-
energy_step = (1 + UltraConstants.ALPHA / 2) / (1 - UltraConstants.ALPHA / 2)
|
|
37
|
-
|
|
38
48
|
# Create energy bins.
|
|
39
|
-
energy_bin_edges =
|
|
40
|
-
UltraConstants.N_BINS + 1
|
|
41
|
-
)
|
|
42
|
-
# Add a zero to the left side for outliers and round to nearest 3 decimal places.
|
|
43
|
-
energy_bin_edges = np.around(np.insert(energy_bin_edges, 0, 0), 3)
|
|
49
|
+
energy_bin_edges = np.array(UltraConstants.CULLING_ENERGY_BIN_EDGES)
|
|
44
50
|
energy_midpoints = (energy_bin_edges[:-1] + energy_bin_edges[1:]) / 2
|
|
45
51
|
|
|
46
52
|
intervals = [
|
|
@@ -73,7 +79,7 @@ def get_energy_delta_minus_plus() -> tuple[NDArray, NDArray]:
|
|
|
73
79
|
"""
|
|
74
80
|
bins, _, bin_geom_means = build_energy_bins()
|
|
75
81
|
bins_energy_delta_plus, bins_energy_delta_minus = [], []
|
|
76
|
-
for bin_edges, bin_geom_mean in zip(bins, bin_geom_means):
|
|
82
|
+
for bin_edges, bin_geom_mean in zip(bins, bin_geom_means, strict=False):
|
|
77
83
|
bins_energy_delta_plus.append(bin_edges[1] - bin_geom_mean)
|
|
78
84
|
bins_energy_delta_minus.append(bin_geom_mean - bin_edges[0])
|
|
79
85
|
return abs(np.array(bins_energy_delta_minus)), abs(np.array(bins_energy_delta_plus))
|
|
@@ -87,7 +93,7 @@ def get_spacecraft_histogram(
|
|
|
87
93
|
nested: bool = False,
|
|
88
94
|
) -> tuple[NDArray, NDArray, NDArray, NDArray]:
|
|
89
95
|
"""
|
|
90
|
-
Compute a
|
|
96
|
+
Compute a 2D histogram of the particle data using HEALPix binning.
|
|
91
97
|
|
|
92
98
|
Parameters
|
|
93
99
|
----------
|
|
@@ -106,7 +112,7 @@ def get_spacecraft_histogram(
|
|
|
106
112
|
Returns
|
|
107
113
|
-------
|
|
108
114
|
hist : np.ndarray
|
|
109
|
-
A
|
|
115
|
+
A 2D histogram array with shape (n_pix, n_energy_bins).
|
|
110
116
|
latitude : np.ndarray
|
|
111
117
|
Array of latitude values.
|
|
112
118
|
longitude : np.ndarray
|
|
@@ -152,66 +158,44 @@ def get_spacecraft_histogram(
|
|
|
152
158
|
return hist, latitude, longitude, n_pix
|
|
153
159
|
|
|
154
160
|
|
|
155
|
-
def
|
|
156
|
-
nside: int = 128,
|
|
157
|
-
) -> NDArray:
|
|
161
|
+
def get_spacecraft_count_rate_uncertainty(hist: NDArray, exposure: NDArray) -> NDArray:
|
|
158
162
|
"""
|
|
159
|
-
Calculate
|
|
163
|
+
Calculate the count rate uncertainty for HEALPix-binned data.
|
|
160
164
|
|
|
161
165
|
Parameters
|
|
162
166
|
----------
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
hist : NDArray
|
|
168
|
+
A 2D histogram array with shape (n_pix, n_energy_bins).
|
|
169
|
+
exposure : NDArray
|
|
170
|
+
A 2D array of exposure times with shape (n_pix, n_energy_bins).
|
|
165
171
|
|
|
166
172
|
Returns
|
|
167
173
|
-------
|
|
168
|
-
|
|
169
|
-
|
|
174
|
+
count_rate_uncertainty : NDArray
|
|
175
|
+
Rate uncertainty with shape (n_pix, n_energy_bins) (counts/sec).
|
|
170
176
|
|
|
171
177
|
Notes
|
|
172
178
|
-----
|
|
173
|
-
|
|
174
|
-
TODO: background rates to be provided by IT.
|
|
179
|
+
These calculations were based on Eqn 15 from the IMAP-Ultra Algorithm Document.
|
|
175
180
|
"""
|
|
176
|
-
|
|
177
|
-
_, energy_midpoints, _ = build_energy_bins()
|
|
178
|
-
background = np.zeros((len(energy_midpoints), npix))
|
|
179
|
-
return background
|
|
181
|
+
count_uncertainty = np.sqrt(hist)
|
|
180
182
|
|
|
183
|
+
rate_uncertainty = np.zeros_like(hist)
|
|
184
|
+
valid = exposure > 0
|
|
185
|
+
rate_uncertainty[valid] = count_uncertainty[valid] / exposure[valid]
|
|
181
186
|
|
|
182
|
-
|
|
183
|
-
nside: int = 128,
|
|
184
|
-
) -> NDArray:
|
|
185
|
-
"""
|
|
186
|
-
Calculate background rates.
|
|
187
|
+
return rate_uncertainty
|
|
187
188
|
|
|
188
|
-
Parameters
|
|
189
|
-
----------
|
|
190
|
-
nside : int, optional
|
|
191
|
-
The nside parameter of the Healpix tessellation (default is 128).
|
|
192
189
|
|
|
193
|
-
|
|
194
|
-
-------
|
|
195
|
-
background_rates : np.ndarray
|
|
196
|
-
Array of background rates.
|
|
197
|
-
|
|
198
|
-
Notes
|
|
199
|
-
-----
|
|
200
|
-
This is a placeholder.
|
|
201
|
-
TODO: background rates to be provided by IT.
|
|
190
|
+
def get_deadtime_ratios(sectored_rates_ds: xr.Dataset) -> xr.DataArray:
|
|
202
191
|
"""
|
|
203
|
-
|
|
204
|
-
_, energy_midpoints, _ = build_energy_bins()
|
|
205
|
-
background = np.zeros((len(energy_midpoints), npix))
|
|
206
|
-
return background
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def get_deadtime_correction_factors(sectored_rates_ds: xr.Dataset) -> xr.DataArray:
|
|
210
|
-
"""
|
|
211
|
-
Compute the dead time correction factor at each sector.
|
|
192
|
+
Compute the dead time ratio at each sector.
|
|
212
193
|
|
|
213
|
-
|
|
214
|
-
|
|
194
|
+
A reduction in exposure time (duty cycle) is caused by the flight hardware listening
|
|
195
|
+
for coincidence events that never occur, due to singles starts predominantly from UV
|
|
196
|
+
radiation. The static exposure time for a given Pointing should be reduced by this
|
|
197
|
+
spatially dependent exposure time reduction factor (the dead time). Further
|
|
198
|
+
description is available in section 3.4.3 of the IMAP-Ultra Algorithm Document.
|
|
215
199
|
|
|
216
200
|
Parameters
|
|
217
201
|
----------
|
|
@@ -286,18 +270,142 @@ def get_sectored_rates(rates_ds: xr.Dataset, params_ds: xr.Dataset) -> xr.Datase
|
|
|
286
270
|
# Build a list of conditions for each sector mode time range
|
|
287
271
|
conditions = [
|
|
288
272
|
(rates_ds["epoch"] >= start) & (rates_ds["epoch"] < end)
|
|
289
|
-
for start, end in zip(mode_3_start, mode_3_end)
|
|
273
|
+
for start, end in zip(mode_3_start, mode_3_end, strict=False)
|
|
290
274
|
]
|
|
291
275
|
|
|
292
276
|
sector_mode_mask = np.logical_or.reduce(conditions)
|
|
293
277
|
return rates_ds.isel(epoch=sector_mode_mask)
|
|
294
278
|
|
|
295
279
|
|
|
280
|
+
def get_deadtime_ratios_by_spin_phase(
|
|
281
|
+
sectored_rates: xr.Dataset,
|
|
282
|
+
) -> np.ndarray:
|
|
283
|
+
"""
|
|
284
|
+
Calculate nominal deadtime ratios at every spin phase step (1ms res).
|
|
285
|
+
|
|
286
|
+
Parameters
|
|
287
|
+
----------
|
|
288
|
+
sectored_rates : xarray.Dataset
|
|
289
|
+
Dataset containing sector mode image rates data.
|
|
290
|
+
|
|
291
|
+
Returns
|
|
292
|
+
-------
|
|
293
|
+
numpy.ndarray
|
|
294
|
+
Nominal deadtime ratios at every spin phase step (1ms res).
|
|
295
|
+
"""
|
|
296
|
+
deadtime_ratios = get_deadtime_ratios(sectored_rates)
|
|
297
|
+
# Get the spin phase at the start of each sector rate measurement
|
|
298
|
+
spin_phases = np.asarray(
|
|
299
|
+
get_spin_angle(
|
|
300
|
+
get_spacecraft_spin_phase(np.array(sectored_rates.epoch.data)), degrees=True
|
|
301
|
+
)
|
|
302
|
+
)
|
|
303
|
+
# Assume the sectored rate data is evenly spaced in time, and find the middle spin
|
|
304
|
+
# phase value for each sector.
|
|
305
|
+
# The center spin phase is the closest / most accurate spin phase.
|
|
306
|
+
# There are 24 spin phases per sector so the nominal middle sector spin phases
|
|
307
|
+
# would be: array([ 12., 36., ..., 300., 324.]) for 15 sectors.
|
|
308
|
+
spin_phases_centered = (spin_phases[:-1] + spin_phases[1:]) / 2
|
|
309
|
+
# Assume the last sector is nominal because we dont have enough data to determine
|
|
310
|
+
# the spin phase at the end of the last sector.
|
|
311
|
+
# TODO: is this assumption valid?
|
|
312
|
+
# Add the last spin phase value + half of a nominal sector.
|
|
313
|
+
spin_phases_centered = np.append(spin_phases_centered, spin_phases[-1] + 12)
|
|
314
|
+
# Wrap any spin phases > 360 back to [0, 360]
|
|
315
|
+
spin_phases_centered = spin_phases_centered % 360
|
|
316
|
+
# Create a dataset with spin phases and dead time ratios
|
|
317
|
+
deadtime_by_spin_phase = xr.Dataset(
|
|
318
|
+
{"deadtime_ratio": deadtime_ratios},
|
|
319
|
+
coords={
|
|
320
|
+
"spin_phase": xr.DataArray(np.array(spin_phases_centered), dims="epoch")
|
|
321
|
+
},
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Sort the dataset by spin phase (ascending order)
|
|
325
|
+
deadtime_by_spin_phase = deadtime_by_spin_phase.sortby("spin_phase")
|
|
326
|
+
# Group by spin phase and calculate the median dead time ratio for each phase
|
|
327
|
+
deadtime_medians = deadtime_by_spin_phase.groupby("spin_phase").median(skipna=True)
|
|
328
|
+
|
|
329
|
+
if np.any(np.isnan(deadtime_medians["deadtime_ratio"].values)):
|
|
330
|
+
raise ValueError(
|
|
331
|
+
"Dead time ratios contain NaN values, cannot create interpolator."
|
|
332
|
+
)
|
|
333
|
+
interpolator = interpolate.PchipInterpolator(
|
|
334
|
+
deadtime_medians["spin_phase"].values, deadtime_medians["deadtime_ratio"].values
|
|
335
|
+
)
|
|
336
|
+
# Calculate the nominal spin phases at 1 ms resolution and query the pchip
|
|
337
|
+
# interpolator to get the deadtime ratios.
|
|
338
|
+
steps = 15 * 1000 # 15 seconds at 1 ms resolution
|
|
339
|
+
nominal_spin_phases_1ms_res = np.arange(0, 360, 360 / steps)
|
|
340
|
+
return interpolator(nominal_spin_phases_1ms_res)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def apply_deadtime_correction(
|
|
344
|
+
exposure_pointing: pandas.DataFrame,
|
|
345
|
+
deadtime_ratios: np.ndarray,
|
|
346
|
+
pixels_below_scattering: list,
|
|
347
|
+
boundary_scale_factors: NDArray,
|
|
348
|
+
) -> np.ndarray:
|
|
349
|
+
"""
|
|
350
|
+
Adjust the exposure time at each pixel to account for dead time.
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
exposure_pointing : pandas.DataFrame
|
|
355
|
+
Exposure data.
|
|
356
|
+
deadtime_ratios : PchipInterpolator
|
|
357
|
+
Interpolating function for dead time ratios.
|
|
358
|
+
pixels_below_scattering : list
|
|
359
|
+
A Nested list of arrays indicating pixels within the scattering threshold.
|
|
360
|
+
The outer list indicates spin phase steps, the middle list indicates energy
|
|
361
|
+
bins, and the inner arrays contain indices indicating pixels that are below
|
|
362
|
+
the FWHM scattering threshold.
|
|
363
|
+
boundary_scale_factors : np.ndarray
|
|
364
|
+
Boundary scale factors for each pixel at each spin phase.
|
|
365
|
+
|
|
366
|
+
Returns
|
|
367
|
+
-------
|
|
368
|
+
exposure_pointing_adjusted : np.ndarray
|
|
369
|
+
Adjusted exposure times accounting for dead time.
|
|
370
|
+
"""
|
|
371
|
+
# Get energy bin geometric means
|
|
372
|
+
energy_bin_geometric_means = build_energy_bins()[2]
|
|
373
|
+
# Exposure time should now be of shape (npix, energy)
|
|
374
|
+
exposure_pointing = np.repeat(
|
|
375
|
+
exposure_pointing.to_numpy()[np.newaxis, :],
|
|
376
|
+
len(energy_bin_geometric_means),
|
|
377
|
+
axis=0,
|
|
378
|
+
)
|
|
379
|
+
# nominal spin phase step.
|
|
380
|
+
nominal_ms_step = 15 / len(pixels_below_scattering) # time step
|
|
381
|
+
# Query the dead-time ratio and apply the nominal exposure time to pixels in the FOR
|
|
382
|
+
# and below the scattering threshold
|
|
383
|
+
# Loop through the spin phase steps. This is spinning the spacecraft by nominal
|
|
384
|
+
# 1 ms steps in the despun frame.
|
|
385
|
+
for i, pixels_at_spin in enumerate(pixels_below_scattering):
|
|
386
|
+
# Loop through energy bins
|
|
387
|
+
for energy_bin_idx in range(len(energy_bin_geometric_means)):
|
|
388
|
+
pixels_at_energy_and_spin = pixels_at_spin[energy_bin_idx]
|
|
389
|
+
if pixels_at_energy_and_spin.size == 0:
|
|
390
|
+
continue
|
|
391
|
+
# Apply the nominal exposure time (1 ms) scaled by the deadtime ratio to
|
|
392
|
+
# every pixel in the FOR, that is below the FWHM scattering threshold,
|
|
393
|
+
exposure_pointing[energy_bin_idx, pixels_at_energy_and_spin] += (
|
|
394
|
+
nominal_ms_step
|
|
395
|
+
* deadtime_ratios[i]
|
|
396
|
+
* boundary_scale_factors[pixels_at_energy_and_spin, i]
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
return exposure_pointing
|
|
400
|
+
|
|
401
|
+
|
|
296
402
|
def get_spacecraft_exposure_times(
|
|
297
403
|
constant_exposure: pandas.DataFrame,
|
|
298
404
|
rates_dataset: xr.Dataset,
|
|
299
405
|
params_dataset: xr.Dataset,
|
|
300
|
-
|
|
406
|
+
pixels_below_scattering: list[list],
|
|
407
|
+
boundary_scale_factors: NDArray,
|
|
408
|
+
) -> tuple[NDArray, NDArray]:
|
|
301
409
|
"""
|
|
302
410
|
Compute exposure times for HEALPix pixels.
|
|
303
411
|
|
|
@@ -309,6 +417,13 @@ def get_spacecraft_exposure_times(
|
|
|
309
417
|
Dataset containing image rates data.
|
|
310
418
|
params_dataset : xarray.Dataset
|
|
311
419
|
Dataset containing image parameters data.
|
|
420
|
+
pixels_below_scattering : list
|
|
421
|
+
List of lists indicating pixels within the scattering threshold.
|
|
422
|
+
The outer list indicates spin phase steps, the middle list indicates energy
|
|
423
|
+
bins, and the inner list contains pixel indices indicating pixels that are
|
|
424
|
+
below the FWHM scattering threshold.
|
|
425
|
+
boundary_scale_factors : np.ndarray
|
|
426
|
+
Boundary scale factors for each pixel at each spin phase.
|
|
312
427
|
|
|
313
428
|
Returns
|
|
314
429
|
-------
|
|
@@ -316,36 +431,145 @@ def get_spacecraft_exposure_times(
|
|
|
316
431
|
Total exposure times of pixels in a
|
|
317
432
|
Healpix tessellation of the sky
|
|
318
433
|
in the pointing (dps) frame.
|
|
434
|
+
nominal_deadtime_ratios : np.ndarray
|
|
435
|
+
Deadtime ratios at each spin phase step (1ms res).
|
|
319
436
|
"""
|
|
320
|
-
# TODO: uncomment these lines when the deadtime correction is implemented
|
|
321
|
-
# sectored_rates = get_sectored_rates(rates_dataset, params_dataset)
|
|
322
|
-
# get_deadtime_correction_factors(sectored_rates)
|
|
323
|
-
# TODO: calculate the deadtime correction function
|
|
324
|
-
# TODO: Apply the deadtime correction to the exposure times
|
|
325
437
|
# TODO: use the universal spin table and
|
|
326
438
|
# universal pointing table here to determine actual number of spins
|
|
439
|
+
sectored_rates = get_sectored_rates(rates_dataset, params_dataset)
|
|
440
|
+
nominal_deadtime_ratios = get_deadtime_ratios_by_spin_phase(sectored_rates)
|
|
327
441
|
exposure_pointing = (
|
|
328
442
|
constant_exposure["Exposure Time"] * 5760
|
|
329
443
|
) # 5760 spins per pointing (for now)
|
|
444
|
+
exposure_pointing_adjusted = apply_deadtime_correction(
|
|
445
|
+
exposure_pointing,
|
|
446
|
+
nominal_deadtime_ratios,
|
|
447
|
+
pixels_below_scattering,
|
|
448
|
+
boundary_scale_factors,
|
|
449
|
+
)
|
|
450
|
+
return exposure_pointing_adjusted, nominal_deadtime_ratios
|
|
330
451
|
|
|
331
|
-
return exposure_pointing
|
|
332
452
|
|
|
453
|
+
def get_efficiencies_and_geometric_function(
|
|
454
|
+
pixels_below_scattering: list[list],
|
|
455
|
+
boundary_scale_factors: np.ndarray,
|
|
456
|
+
theta_vals: np.ndarray,
|
|
457
|
+
phi_vals: np.ndarray,
|
|
458
|
+
npix: int,
|
|
459
|
+
ancillary_files: dict,
|
|
460
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
461
|
+
"""
|
|
462
|
+
Compute the geometric factor and efficiency for each pixel and energy bin.
|
|
463
|
+
|
|
464
|
+
The results are averaged over all spin phases.
|
|
333
465
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
466
|
+
Parameters
|
|
467
|
+
----------
|
|
468
|
+
pixels_below_scattering : list
|
|
469
|
+
List of lists indicating pixels within the scattering threshold.
|
|
470
|
+
The outer list indicates spin phase steps, the middle list indicates energy
|
|
471
|
+
bins, and the inner list contains pixel indices indicating pixels that are
|
|
472
|
+
below the FWHM scattering threshold.
|
|
473
|
+
boundary_scale_factors : np.ndarray
|
|
474
|
+
Boundary scale factors for each pixel at each spin phase.
|
|
475
|
+
theta_vals : np.ndarray
|
|
476
|
+
A 2D array of theta values for each HEALPix pixel at each spin phase step.
|
|
477
|
+
phi_vals : np.ndarray
|
|
478
|
+
A 2D array of phi values for each HEALPix pixel at each spin phase step.
|
|
479
|
+
npix : int
|
|
480
|
+
Number of HEALPix pixels.
|
|
481
|
+
ancillary_files : dict
|
|
482
|
+
Dictionary containing ancillary files.
|
|
483
|
+
|
|
484
|
+
Returns
|
|
485
|
+
-------
|
|
486
|
+
gf_summation : np.ndarray
|
|
487
|
+
Summation of geometric factors for each pixel and energy bin.
|
|
488
|
+
eff_summation : np.ndarray
|
|
489
|
+
Summation of efficiencies for each pixel and energy bin.
|
|
490
|
+
"""
|
|
491
|
+
# Load callable efficiency interpolator function
|
|
492
|
+
eff_interpolator = get_efficiency_interpolator(ancillary_files)
|
|
493
|
+
# load geometric factor lookup table
|
|
494
|
+
geometric_lookup_table = load_geometric_factor_tables(
|
|
495
|
+
ancillary_files, "l1b-sensor-gf-blades"
|
|
496
|
+
)
|
|
497
|
+
# Get energy bin geometric means
|
|
498
|
+
energy_bin_geometric_means = build_energy_bins()[2]
|
|
499
|
+
energy_bins = len(energy_bin_geometric_means)
|
|
500
|
+
# Initialize summation arrays for geometric factors and efficiencies
|
|
501
|
+
gf_summation = np.zeros((energy_bins, npix))
|
|
502
|
+
eff_summation = np.zeros((energy_bins, npix))
|
|
503
|
+
sample_count = np.zeros((energy_bins, npix))
|
|
504
|
+
# Loop through spin phases
|
|
505
|
+
for i, pixels_at_spin in enumerate(pixels_below_scattering):
|
|
506
|
+
# Loop through energy bins
|
|
507
|
+
# Compute gf and eff for these theta/phi pairs
|
|
508
|
+
theta_at_spin = theta_vals[:, i]
|
|
509
|
+
phi_at_spin = phi_vals[:, i]
|
|
510
|
+
gf_values = get_geometric_factor(
|
|
511
|
+
phi=phi_at_spin,
|
|
512
|
+
theta=theta_at_spin,
|
|
513
|
+
quality_flag=np.zeros(len(phi_at_spin)).astype(np.uint16),
|
|
514
|
+
geometric_factor_tables=geometric_lookup_table,
|
|
515
|
+
)
|
|
516
|
+
for energy_bin_idx in range(energy_bins):
|
|
517
|
+
pixel_inds = pixels_at_spin[energy_bin_idx]
|
|
518
|
+
if pixel_inds.size == 0:
|
|
519
|
+
continue
|
|
520
|
+
energy = energy_bin_geometric_means[energy_bin_idx]
|
|
521
|
+
eff_values = get_efficiency(
|
|
522
|
+
np.full(phi_at_spin[pixel_inds].shape, energy),
|
|
523
|
+
phi_at_spin[pixel_inds],
|
|
524
|
+
theta_at_spin[pixel_inds],
|
|
525
|
+
ancillary_files,
|
|
526
|
+
interpolator=eff_interpolator,
|
|
527
|
+
)
|
|
528
|
+
# Accumulate gf and eff values
|
|
529
|
+
gf_summation[energy_bin_idx, pixel_inds] += (
|
|
530
|
+
gf_values[pixel_inds] * boundary_scale_factors[pixel_inds, i]
|
|
531
|
+
)
|
|
532
|
+
eff_summation[energy_bin_idx, pixel_inds] += (
|
|
533
|
+
eff_values * boundary_scale_factors[pixel_inds, i]
|
|
534
|
+
)
|
|
535
|
+
sample_count[energy_bin_idx, pixel_inds] += 1
|
|
536
|
+
|
|
537
|
+
# return averaged geometric factors and efficiencies across all spin phases
|
|
538
|
+
# These are now energy dependent.
|
|
539
|
+
gf_averaged = np.divide(gf_summation, sample_count, where=sample_count != 0)
|
|
540
|
+
eff_averaged = np.divide(eff_summation, sample_count, where=sample_count != 0)
|
|
541
|
+
return gf_averaged, eff_averaged
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def get_helio_adjusted_data(
|
|
545
|
+
time: float,
|
|
546
|
+
exposure_time: np.ndarray,
|
|
547
|
+
geometric_factor: np.ndarray,
|
|
548
|
+
efficiency: np.ndarray,
|
|
549
|
+
ra: np.ndarray,
|
|
550
|
+
dec: np.ndarray,
|
|
337
551
|
nside: int = 128,
|
|
338
552
|
nested: bool = False,
|
|
339
|
-
) -> NDArray:
|
|
553
|
+
) -> tuple[NDArray, NDArray, NDArray]:
|
|
340
554
|
"""
|
|
341
|
-
Compute
|
|
555
|
+
Compute 2D (Healpix index, energy) arrays for in the helio frame.
|
|
556
|
+
|
|
557
|
+
Build CG corrected exposure, efficiency, and geometric factor arrays.
|
|
342
558
|
|
|
343
559
|
Parameters
|
|
344
560
|
----------
|
|
345
|
-
time :
|
|
561
|
+
time : float
|
|
346
562
|
Median time of pointing in et.
|
|
347
|
-
|
|
348
|
-
Spacecraft exposure
|
|
563
|
+
exposure_time : np.ndarray
|
|
564
|
+
Spacecraft exposure. Shape = (energy, npix).
|
|
565
|
+
geometric_factor : np.ndarray
|
|
566
|
+
Geometric factor values. Shape = (energy, npix).
|
|
567
|
+
efficiency : np.ndarray
|
|
568
|
+
Efficiency values. Shape = (energy, npix).
|
|
569
|
+
ra : np.ndarray
|
|
570
|
+
Right ascension in the spacecraft frame (degrees).
|
|
571
|
+
dec : np.ndarray
|
|
572
|
+
Declination in the spacecraft frame (degrees).
|
|
349
573
|
nside : int, optional
|
|
350
574
|
The nside parameter of the Healpix tessellation (default is 128).
|
|
351
575
|
nested : bool, optional
|
|
@@ -354,18 +578,18 @@ def get_helio_exposure_times(
|
|
|
354
578
|
Returns
|
|
355
579
|
-------
|
|
356
580
|
helio_exposure : np.ndarray
|
|
357
|
-
A 2D array of shape (
|
|
581
|
+
A 2D array of shape (n_energy_bins, npix).
|
|
582
|
+
helio_efficiency : np.ndarray
|
|
583
|
+
A 2D array of shape (n_energy_bins, npix).
|
|
584
|
+
helio_geometric_factors : np.ndarray
|
|
585
|
+
A 2D array of shape (n_energy_bins, npix).
|
|
358
586
|
|
|
359
587
|
Notes
|
|
360
588
|
-----
|
|
361
589
|
These calculations are performed once per pointing.
|
|
362
590
|
"""
|
|
363
591
|
# Get energy midpoints.
|
|
364
|
-
_,
|
|
365
|
-
# Extract (RA/Dec) and exposure from the spacecraft frame.
|
|
366
|
-
ra = df_exposure["Right Ascension (deg)"].values
|
|
367
|
-
dec = df_exposure["Declination (deg)"].values
|
|
368
|
-
exposure_flat = df_exposure["Exposure Time"].values
|
|
592
|
+
_, _, energy_bin_geometric_means = build_energy_bins()
|
|
369
593
|
|
|
370
594
|
# The Cartesian state vector representing the position and velocity of the
|
|
371
595
|
# IMAP spacecraft.
|
|
@@ -376,19 +600,28 @@ def get_helio_exposure_times(
|
|
|
376
600
|
# Convert (RA, Dec) angles into 3D unit vectors.
|
|
377
601
|
# Each unit vector represents a direction in the sky where the spacecraft observed
|
|
378
602
|
# and accumulated exposure time.
|
|
603
|
+
npix = hp.nside2npix(nside)
|
|
379
604
|
unit_dirs = hp.ang2vec(ra, dec, lonlat=True).T # Shape (N, 3)
|
|
380
|
-
|
|
605
|
+
shape = (len(energy_bin_geometric_means), int(npix))
|
|
606
|
+
if np.any(
|
|
607
|
+
[arr.shape != shape for arr in [exposure_time, geometric_factor, efficiency]]
|
|
608
|
+
):
|
|
609
|
+
raise ValueError(
|
|
610
|
+
f"Input arrays must have the same shape {shape}, but got "
|
|
611
|
+
f"{exposure_time.shape}, {geometric_factor.shape}, {efficiency.shape}."
|
|
612
|
+
)
|
|
381
613
|
# Initialize output array.
|
|
382
614
|
# Each row corresponds to a HEALPix pixel, and each column to an energy bin.
|
|
383
|
-
|
|
384
|
-
|
|
615
|
+
helio_exposure = np.zeros(shape)
|
|
616
|
+
helio_efficiency = np.zeros(shape)
|
|
617
|
+
helio_geometric_factors = np.zeros(shape)
|
|
385
618
|
|
|
386
619
|
# Loop through energy bins and compute transformed exposure.
|
|
387
|
-
for i,
|
|
620
|
+
for i, energy_mean in enumerate(energy_bin_geometric_means):
|
|
388
621
|
# Convert the midpoint energy to a velocity (km/s).
|
|
389
622
|
# Based on kinetic energy equation: E = 1/2 * m * v^2.
|
|
390
623
|
energy_velocity = (
|
|
391
|
-
np.sqrt(2 *
|
|
624
|
+
np.sqrt(2 * energy_mean * UltraConstants.KEV_J / UltraConstants.MASS_H)
|
|
392
625
|
/ 1e3
|
|
393
626
|
)
|
|
394
627
|
|
|
@@ -411,222 +644,97 @@ def get_helio_exposure_times(
|
|
|
411
644
|
# Convert azimuth/elevation directions to HEALPix pixel indices.
|
|
412
645
|
hpix_idx = hp.ang2pix(nside, az, el, nest=nested, lonlat=True)
|
|
413
646
|
|
|
414
|
-
# Accumulate exposure values into HEALPix pixels for this energy
|
|
647
|
+
# Accumulate exposure, eff, and gf values into HEALPix pixels for this energy
|
|
648
|
+
# bin.
|
|
415
649
|
helio_exposure[i, :] = np.bincount(
|
|
416
|
-
hpix_idx, weights=
|
|
650
|
+
hpix_idx, weights=exposure_time[i, :], minlength=npix
|
|
651
|
+
)
|
|
652
|
+
helio_efficiency[i, :] = np.bincount(
|
|
653
|
+
hpix_idx, weights=efficiency[i, :], minlength=npix
|
|
654
|
+
)
|
|
655
|
+
helio_geometric_factors[i, :] = np.bincount(
|
|
656
|
+
hpix_idx, weights=geometric_factor[i, :], minlength=npix
|
|
417
657
|
)
|
|
418
658
|
|
|
419
|
-
return helio_exposure
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
def get_spacecraft_sensitivity(
|
|
423
|
-
efficiencies: pandas.DataFrame,
|
|
424
|
-
geometric_function: pandas.DataFrame,
|
|
425
|
-
) -> tuple[pandas.DataFrame, NDArray, NDArray, NDArray]:
|
|
426
|
-
"""
|
|
427
|
-
Compute sensitivity as efficiency * geometric factor.
|
|
428
|
-
|
|
429
|
-
Parameters
|
|
430
|
-
----------
|
|
431
|
-
efficiencies : pandas.DataFrame
|
|
432
|
-
Efficiencies at different energy levels.
|
|
433
|
-
geometric_function : pandas.DataFrame
|
|
434
|
-
Geometric function.
|
|
435
|
-
|
|
436
|
-
Returns
|
|
437
|
-
-------
|
|
438
|
-
pointing_sensitivity : pandas.DataFrame
|
|
439
|
-
Sensitivity with dimensions (HEALPIX pixel_number, energy).
|
|
440
|
-
energy_vals : NDArray
|
|
441
|
-
Energy values of dataframe.
|
|
442
|
-
right_ascension : NDArray
|
|
443
|
-
Right ascension (longitude/azimuth) values of dataframe (0 - 360 degrees).
|
|
444
|
-
declination : NDArray
|
|
445
|
-
Declination (latitude/elevation) values of dataframe (-90 to 90 degrees).
|
|
446
|
-
"""
|
|
447
|
-
# Exclude "Right Ascension (deg)" and "Declination (deg)" from the multiplication
|
|
448
|
-
energy_columns = [
|
|
449
|
-
col
|
|
450
|
-
for col in efficiencies.columns
|
|
451
|
-
if col not in ["Right Ascension (deg)", "Declination (deg)"]
|
|
452
|
-
]
|
|
453
|
-
sensitivity = efficiencies[energy_columns].mul(
|
|
454
|
-
geometric_function["Response (cm2-sr)"].values, axis=0
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
right_ascension = efficiencies["Right Ascension (deg)"]
|
|
458
|
-
declination = efficiencies["Declination (deg)"]
|
|
459
|
-
|
|
460
|
-
energy_vals = np.array([float(col.replace("keV", "")) for col in energy_columns])
|
|
461
|
-
|
|
462
|
-
return sensitivity, energy_vals, right_ascension, declination
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
def grid_sensitivity(
|
|
466
|
-
efficiencies: pandas.DataFrame,
|
|
467
|
-
geometric_function: pandas.DataFrame,
|
|
468
|
-
energy: float,
|
|
469
|
-
) -> NDArray:
|
|
470
|
-
"""
|
|
471
|
-
Grid the sensitivity.
|
|
472
|
-
|
|
473
|
-
Parameters
|
|
474
|
-
----------
|
|
475
|
-
efficiencies : pandas.DataFrame
|
|
476
|
-
Efficiencies at different energy levels.
|
|
477
|
-
geometric_function : pandas.DataFrame
|
|
478
|
-
Geometric function.
|
|
479
|
-
energy : float
|
|
480
|
-
Energy to which we are interpolating.
|
|
481
|
-
|
|
482
|
-
Returns
|
|
483
|
-
-------
|
|
484
|
-
interpolated_sensitivity : np.ndarray
|
|
485
|
-
Sensitivity with dimensions (HEALPIX pixel_number, 1).
|
|
486
|
-
"""
|
|
487
|
-
sensitivity, energy_vals, right_ascension, declination = get_spacecraft_sensitivity(
|
|
488
|
-
efficiencies, geometric_function
|
|
489
|
-
)
|
|
490
|
-
|
|
491
|
-
# Create interpolator over energy dimension for each pixel (axis=1)
|
|
492
|
-
interp_func = interp1d(
|
|
493
|
-
energy_vals,
|
|
494
|
-
sensitivity.values,
|
|
495
|
-
axis=1,
|
|
496
|
-
bounds_error=False,
|
|
497
|
-
fill_value=np.nan,
|
|
498
|
-
)
|
|
499
|
-
|
|
500
|
-
# Interpolate to energy
|
|
501
|
-
interpolated = interp_func(energy)
|
|
502
|
-
interpolated = np.where(np.isnan(interpolated), FILLVAL_FLOAT32, interpolated)
|
|
503
|
-
|
|
504
|
-
return interpolated
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
def interpolate_sensitivity(
|
|
508
|
-
efficiencies: pd.DataFrame,
|
|
509
|
-
geometric_function: pd.DataFrame,
|
|
510
|
-
nside: int = 128,
|
|
511
|
-
) -> NDArray:
|
|
512
|
-
"""
|
|
513
|
-
Interpolate the sensitivity and bin it in HEALPix space.
|
|
514
|
-
|
|
515
|
-
Parameters
|
|
516
|
-
----------
|
|
517
|
-
efficiencies : pandas.DataFrame
|
|
518
|
-
Efficiencies at different energy levels.
|
|
519
|
-
geometric_function : pandas.DataFrame
|
|
520
|
-
Geometric function.
|
|
521
|
-
nside : int, optional
|
|
522
|
-
Healpix nside resolution (default is 128).
|
|
523
|
-
|
|
524
|
-
Returns
|
|
525
|
-
-------
|
|
526
|
-
interpolated_sensitivity : np.ndarray
|
|
527
|
-
Array of shape (n_energy_bins, n_healpix_pixels).
|
|
528
|
-
"""
|
|
529
|
-
_, _, energy_bin_geometric_means = build_energy_bins()
|
|
530
|
-
npix = hp.nside2npix(nside)
|
|
531
|
-
|
|
532
|
-
interpolated_sensitivity = np.full(
|
|
533
|
-
(len(energy_bin_geometric_means), npix), FILLVAL_FLOAT32
|
|
534
|
-
)
|
|
535
|
-
|
|
536
|
-
for i, energy in enumerate(energy_bin_geometric_means):
|
|
537
|
-
pixel_sensitivity = grid_sensitivity(
|
|
538
|
-
efficiencies, geometric_function, energy
|
|
539
|
-
).flatten()
|
|
540
|
-
interpolated_sensitivity[i, :] = pixel_sensitivity
|
|
541
|
-
|
|
542
|
-
return interpolated_sensitivity
|
|
659
|
+
return helio_exposure, helio_efficiency, helio_geometric_factors
|
|
543
660
|
|
|
544
661
|
|
|
545
|
-
def
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
662
|
+
def get_spacecraft_background_rates(
|
|
663
|
+
rates_dataset: xr.Dataset,
|
|
664
|
+
sensor: str,
|
|
665
|
+
ancillary_files: dict,
|
|
666
|
+
energy_bin_edges: list[tuple[float, float]],
|
|
667
|
+
cullingmask_spin_number: NDArray,
|
|
549
668
|
nside: int = 128,
|
|
550
|
-
nested: bool = False,
|
|
551
669
|
) -> NDArray:
|
|
552
670
|
"""
|
|
553
|
-
|
|
671
|
+
Calculate background rates based on the provided parameters.
|
|
554
672
|
|
|
555
673
|
Parameters
|
|
556
674
|
----------
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
675
|
+
rates_dataset : xr.Dataset
|
|
676
|
+
Rates dataset.
|
|
677
|
+
sensor : str
|
|
678
|
+
Sensor name: "ultra45" or "ultra90".
|
|
679
|
+
ancillary_files : dict[Path]
|
|
680
|
+
Ancillary files containing the lookup tables.
|
|
681
|
+
energy_bin_edges : list[tuple[float, float]]
|
|
682
|
+
Energy bin edges.
|
|
683
|
+
cullingmask_spin_number : NDArray
|
|
684
|
+
Goodtime spins.
|
|
685
|
+
Ex. imap_ultra_l1b_45sensor-cullingmask[0]["spin_number"]
|
|
686
|
+
This is used to determine the number of pulses per spin.
|
|
563
687
|
nside : int, optional
|
|
564
688
|
The nside parameter of the Healpix tessellation (default is 128).
|
|
565
|
-
nested : bool, optional
|
|
566
|
-
Whether the Healpix tessellation is nested (default is False).
|
|
567
689
|
|
|
568
690
|
Returns
|
|
569
691
|
-------
|
|
570
|
-
|
|
571
|
-
|
|
692
|
+
background_rates : NDArray of shape (n_energy_bins, n_HEALPix pixels)
|
|
693
|
+
Calculated background rates.
|
|
572
694
|
|
|
573
695
|
Notes
|
|
574
696
|
-----
|
|
575
|
-
|
|
697
|
+
See Eqn. 3, 8, and 20 in the Algorithm Document for the equation.
|
|
576
698
|
"""
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
# IMAP spacecraft.
|
|
585
|
-
state = imap_state(time, ref_frame=SpiceFrame.IMAP_DPS)
|
|
586
|
-
|
|
587
|
-
# Extract the velocity part of the state vector
|
|
588
|
-
spacecraft_velocity = state[3:6]
|
|
589
|
-
# Convert (RA, Dec) angles into 3D unit vectors.
|
|
590
|
-
# Each unit vector represents a direction in the sky where the spacecraft observed
|
|
591
|
-
# and accumulated sensitivity.
|
|
592
|
-
unit_dirs = hp.ang2vec(ra, dec, lonlat=True).T # Shape (N, 3)
|
|
593
|
-
|
|
594
|
-
# Initialize output array.
|
|
595
|
-
# Each row corresponds to a HEALPix pixel, and each column to an energy bin.
|
|
596
|
-
npix = hp.nside2npix(nside)
|
|
597
|
-
helio_sensitivity = np.zeros((len(energy_midpoints), npix))
|
|
598
|
-
|
|
599
|
-
# Loop through energy bins and compute transformed sensitivity.
|
|
600
|
-
for i, energy in enumerate(energy_midpoints):
|
|
601
|
-
# Convert the midpoint energy to a velocity (km/s).
|
|
602
|
-
# Based on kinetic energy equation: E = 1/2 * m * v^2.
|
|
603
|
-
energy_velocity = (
|
|
604
|
-
np.sqrt(2 * energy * UltraConstants.KEV_J / UltraConstants.MASS_H) / 1e3
|
|
605
|
-
)
|
|
606
|
-
|
|
607
|
-
# Use Galilean Transform to transform the velocity wrt spacecraft
|
|
608
|
-
# to the velocity wrt heliosphere.
|
|
609
|
-
# energy_velocity * cartesian -> apply the magnitude of the velocity
|
|
610
|
-
# to every position on the grid in the despun grid.
|
|
611
|
-
helio_velocity = spacecraft_velocity.reshape(1, 3) + energy_velocity * unit_dirs
|
|
699
|
+
pulses = get_pulses_per_spin(rates_dataset)
|
|
700
|
+
# Pulses for the pointing.
|
|
701
|
+
etof_min = get_image_params("eTOFMin", sensor, ancillary_files)
|
|
702
|
+
etof_max = get_image_params("eTOFMax", sensor, ancillary_files)
|
|
703
|
+
spin_number, _ = get_spin_and_duration(
|
|
704
|
+
rates_dataset["shcoarse"], rates_dataset["spin"]
|
|
705
|
+
)
|
|
612
706
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
helio_velocity, axis=1, keepdims=True
|
|
616
|
-
)
|
|
707
|
+
# Get dmin for PH (mm).
|
|
708
|
+
dmin_ctof = UltraConstants.DMIN_PH_CTOF
|
|
617
709
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
helio_spherical = cartesian_to_spherical(helio_normalized)
|
|
621
|
-
az, el = helio_spherical[:, 1], helio_spherical[:, 2]
|
|
710
|
+
# Compute number of HEALPix pixels that cover the sphere
|
|
711
|
+
n_pix = hp.nside2npix(nside)
|
|
622
712
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
gridded_sensitivity = grid_sensitivity(efficiencies, geometric_function, energy)
|
|
713
|
+
# Initialize background rate array: (n_energy_bins, n_HEALPix pixels)
|
|
714
|
+
background_rates = np.zeros((len(energy_bin_edges), n_pix))
|
|
626
715
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
716
|
+
# Only select pulses from goodtimes.
|
|
717
|
+
goodtime_mask = np.isin(spin_number, cullingmask_spin_number)
|
|
718
|
+
mean_start_pulses = np.mean(pulses.start_pulses[goodtime_mask])
|
|
719
|
+
mean_stop_pulses = np.mean(pulses.stop_pulses[goodtime_mask])
|
|
720
|
+
mean_coin_pulses = np.mean(pulses.coin_pulses[goodtime_mask])
|
|
631
721
|
|
|
632
|
-
|
|
722
|
+
for i, (e_min, e_max) in enumerate(energy_bin_edges):
|
|
723
|
+
# Calculate ctof for the energy bin boundaries by combining Eqn. 3 and 8.
|
|
724
|
+
# Compute speed for min and max energy using E = 1/2mv^2 -> v = sqrt(2E/m)
|
|
725
|
+
vmin = np.sqrt(2 * e_min * UltraConstants.KEV_J / UltraConstants.MASS_H) # m/s
|
|
726
|
+
vmax = np.sqrt(2 * e_max * UltraConstants.KEV_J / UltraConstants.MASS_H) # m/s
|
|
727
|
+
# Compute cTOF = dmin / v
|
|
728
|
+
# Multiply times 1e-3 to convert to m.
|
|
729
|
+
ctof_min = dmin_ctof * 1e-3 / vmax * 1e-9 # Convert to ns
|
|
730
|
+
ctof_max = dmin_ctof * 1e-3 / vmin * 1e-9 # Convert to ns
|
|
731
|
+
|
|
732
|
+
background_rates[i, :] = (
|
|
733
|
+
np.abs(ctof_max - ctof_min)
|
|
734
|
+
* (etof_max - etof_min)
|
|
735
|
+
* mean_start_pulses
|
|
736
|
+
* mean_stop_pulses
|
|
737
|
+
* mean_coin_pulses
|
|
738
|
+
) / 30.0
|
|
739
|
+
|
|
740
|
+
return background_rates
|