imap-processing 0.19.0__py3-none-any.whl → 0.19.3__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.

Files changed (73) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +6 -0
  3. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +31 -894
  4. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +279 -255
  5. imap_processing/cdf/config/imap_enamaps_l2-common_variable_attrs.yaml +55 -0
  6. imap_processing/cdf/config/imap_enamaps_l2-healpix_variable_attrs.yaml +29 -0
  7. imap_processing/cdf/config/imap_enamaps_l2-rectangular_variable_attrs.yaml +32 -0
  8. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +3 -1
  9. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +5 -4
  10. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +28 -16
  11. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +33 -31
  12. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +61 -1
  13. imap_processing/cli.py +62 -71
  14. imap_processing/codice/codice_l0.py +2 -1
  15. imap_processing/codice/codice_l1a.py +47 -49
  16. imap_processing/codice/codice_l1b.py +42 -32
  17. imap_processing/codice/codice_l2.py +105 -7
  18. imap_processing/codice/constants.py +50 -8
  19. imap_processing/codice/data/lo_stepping_values.csv +1 -1
  20. imap_processing/ena_maps/ena_maps.py +39 -18
  21. imap_processing/ena_maps/utils/corrections.py +291 -0
  22. imap_processing/ena_maps/utils/map_utils.py +20 -4
  23. imap_processing/glows/l1b/glows_l1b.py +38 -23
  24. imap_processing/glows/l1b/glows_l1b_data.py +10 -11
  25. imap_processing/hi/hi_l1c.py +4 -109
  26. imap_processing/hi/hi_l2.py +34 -23
  27. imap_processing/hi/utils.py +109 -0
  28. imap_processing/ialirt/l0/ialirt_spice.py +1 -1
  29. imap_processing/ialirt/l0/parse_mag.py +18 -4
  30. imap_processing/ialirt/l0/process_hit.py +9 -4
  31. imap_processing/ialirt/l0/process_swapi.py +9 -4
  32. imap_processing/ialirt/l0/process_swe.py +9 -4
  33. imap_processing/ialirt/utils/create_xarray.py +1 -1
  34. imap_processing/lo/ancillary_data/imap_lo_hydrogen-geometric-factor_v001.csv +75 -0
  35. imap_processing/lo/ancillary_data/imap_lo_oxygen-geometric-factor_v001.csv +75 -0
  36. imap_processing/lo/l1b/lo_l1b.py +90 -16
  37. imap_processing/lo/l1c/lo_l1c.py +164 -50
  38. imap_processing/lo/l2/lo_l2.py +941 -127
  39. imap_processing/mag/l1d/mag_l1d_data.py +36 -3
  40. imap_processing/mag/l2/mag_l2.py +2 -0
  41. imap_processing/mag/l2/mag_l2_data.py +4 -3
  42. imap_processing/quality_flags.py +14 -0
  43. imap_processing/spice/geometry.py +13 -8
  44. imap_processing/spice/pointing_frame.py +4 -2
  45. imap_processing/spice/repoint.py +49 -0
  46. imap_processing/ultra/constants.py +29 -0
  47. imap_processing/ultra/l0/decom_tools.py +58 -46
  48. imap_processing/ultra/l0/decom_ultra.py +21 -9
  49. imap_processing/ultra/l0/ultra_utils.py +4 -4
  50. imap_processing/ultra/l1b/badtimes.py +35 -11
  51. imap_processing/ultra/l1b/de.py +15 -9
  52. imap_processing/ultra/l1b/extendedspin.py +24 -12
  53. imap_processing/ultra/l1b/goodtimes.py +112 -0
  54. imap_processing/ultra/l1b/lookup_utils.py +1 -1
  55. imap_processing/ultra/l1b/ultra_l1b.py +7 -7
  56. imap_processing/ultra/l1b/ultra_l1b_culling.py +8 -4
  57. imap_processing/ultra/l1b/ultra_l1b_extended.py +79 -43
  58. imap_processing/ultra/l1c/helio_pset.py +68 -39
  59. imap_processing/ultra/l1c/l1c_lookup_utils.py +45 -12
  60. imap_processing/ultra/l1c/spacecraft_pset.py +81 -37
  61. imap_processing/ultra/l1c/ultra_l1c.py +27 -22
  62. imap_processing/ultra/l1c/ultra_l1c_culling.py +7 -0
  63. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +41 -41
  64. imap_processing/ultra/l2/ultra_l2.py +75 -18
  65. imap_processing/ultra/utils/ultra_l1_utils.py +10 -5
  66. {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/METADATA +2 -2
  67. {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/RECORD +71 -69
  68. imap_processing/ultra/l1b/cullingmask.py +0 -90
  69. imap_processing/ultra/l1c/histogram.py +0 -36
  70. /imap_processing/glows/ancillary/{imap_glows_pipeline_settings_20250923_v002.json → imap_glows_pipeline-settings_20250923_v002.json} +0 -0
  71. {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/LICENSE +0 -0
  72. {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/WHEEL +0 -0
  73. {imap_processing-0.19.0.dist-info → imap_processing-0.19.3.dist-info}/entry_points.txt +0 -0
@@ -2,25 +2,27 @@
2
2
 
3
3
  import logging
4
4
 
5
+ import astropy_healpix.healpy as hp
5
6
  import numpy as np
6
- import pandas as pd
7
7
  import xarray as xr
8
8
 
9
+ from imap_processing.quality_flags import ImapPSETUltraFlags
9
10
  from imap_processing.spice.repoint import get_pointing_times
10
11
  from imap_processing.spice.time import (
11
12
  et_to_met,
12
13
  met_to_ttj2000ns,
13
- sct_to_et,
14
14
  ttj2000ns_to_et,
15
15
  )
16
16
  from imap_processing.ultra.l1b.ultra_l1b_culling import get_de_rejection_mask
17
17
  from imap_processing.ultra.l1c.l1c_lookup_utils import (
18
- calculate_pixels_within_scattering_threshold,
18
+ calculate_fwhm_spun_scattering,
19
19
  get_spacecraft_pointing_lookup_tables,
20
20
  )
21
+ from imap_processing.ultra.l1c.ultra_l1c_culling import compute_culling_mask
21
22
  from imap_processing.ultra.l1c.ultra_l1c_pset_bins import (
22
23
  build_energy_bins,
23
24
  get_efficiencies_and_geometric_function,
25
+ get_energy_delta_minus_plus,
24
26
  get_helio_adjusted_data,
25
27
  get_spacecraft_exposure_times,
26
28
  get_spacecraft_histogram,
@@ -32,15 +34,14 @@ logger = logging.getLogger(__name__)
32
34
 
33
35
  def calculate_helio_pset(
34
36
  de_dataset: xr.Dataset,
35
- extendedspin_dataset: xr.Dataset,
36
- cullingmask_dataset: xr.Dataset,
37
+ goodtimes_dataset: xr.Dataset,
37
38
  rates_dataset: xr.Dataset,
38
39
  params_dataset: xr.Dataset,
39
40
  name: str,
40
41
  ancillary_files: dict,
41
42
  instrument_id: int,
42
- species_id: int = 1,
43
- ) -> xr.Dataset:
43
+ species_id: list,
44
+ ) -> xr.Dataset | None:
44
45
  """
45
46
  Create dictionary with defined datatype for Pointing Set Grid Data.
46
47
 
@@ -48,10 +49,8 @@ def calculate_helio_pset(
48
49
  ----------
49
50
  de_dataset : xarray.Dataset
50
51
  Dataset containing de data.
51
- extendedspin_dataset : xarray.Dataset
52
- Dataset containing extendedspin data.
53
- cullingmask_dataset : xarray.Dataset
54
- Dataset containing cullingmask data.
52
+ goodtimes_dataset : xarray.Dataset
53
+ Dataset containing goodtimes data.
55
54
  rates_dataset : xarray.Dataset
56
55
  Dataset containing image rates data.
57
56
  params_dataset : xarray.Dataset
@@ -62,8 +61,8 @@ def calculate_helio_pset(
62
61
  Ancillary files.
63
62
  instrument_id : int
64
63
  Instrument ID, either 45 or 90.
65
- species_id : int
66
- Species ID, default of 1 refers to Hydrogen.
64
+ species_id : List
65
+ Species ID.
67
66
 
68
67
  Returns
69
68
  -------
@@ -72,14 +71,14 @@ def calculate_helio_pset(
72
71
  """
73
72
  pset_dict: dict[str, np.ndarray] = {}
74
73
  # Select only the species we are interested in.
75
- indices = np.where(de_dataset["species"].values == species_id)[0]
74
+ indices = np.where(np.isin(de_dataset["e_bin"].values, species_id))[0]
76
75
  species_dataset = de_dataset.isel(epoch=indices)
77
76
 
78
77
  rejected = get_de_rejection_mask(
79
78
  species_dataset["quality_scattering"].values,
80
79
  species_dataset["quality_outliers"].values,
81
80
  )
82
- de_dataset = species_dataset.isel(epoch=~rejected)
81
+ species_dataset = species_dataset.isel(epoch=~rejected)
83
82
 
84
83
  v_mag_helio_spacecraft = np.linalg.norm(
85
84
  species_dataset["velocity_dps_helio"].values, axis=1
@@ -89,15 +88,6 @@ def calculate_helio_pset(
89
88
  / v_mag_helio_spacecraft[:, np.newaxis]
90
89
  )
91
90
  intervals, _, energy_bin_geometric_means = build_energy_bins()
92
- counts, latitude, longitude, n_pix = get_spacecraft_histogram(
93
- vhat_dps_helio,
94
- species_dataset["energy_heliosphere"].values,
95
- intervals,
96
- nside=128,
97
- )
98
-
99
- healpix = np.arange(n_pix)
100
-
101
91
  # Get lookup table for FOR indices by spin phase step
102
92
  (
103
93
  for_indices_by_spin_phase,
@@ -106,26 +96,39 @@ def calculate_helio_pset(
106
96
  ra_and_dec,
107
97
  boundary_scale_factors,
108
98
  ) = get_spacecraft_pointing_lookup_tables(ancillary_files, instrument_id)
109
- # Check that the number of rows in the lookup table matches the number of pixels
110
- if for_indices_by_spin_phase.shape[0] != n_pix:
111
- logger.warning(
112
- "The lookup table is expected to have the same number of rows as "
113
- "the number of HEALPix pixels."
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,
114
108
  )
109
+ )
115
110
 
116
- pixels_below_scattering = calculate_pixels_within_scattering_threshold(
117
- for_indices_by_spin_phase, theta_vals, phi_vals, ancillary_files, instrument_id
111
+ nside = hp.npix2nside(for_indices_by_spin_phase.shape[0])
112
+ counts, latitude, longitude, n_pix = get_spacecraft_histogram(
113
+ vhat_dps_helio,
114
+ species_dataset["energy_heliosphere"].values,
115
+ intervals,
116
+ nside=nside,
117
+ )
118
+ helio_pset_quality_flags = np.full(
119
+ n_pix, ImapPSETUltraFlags.NONE.value, dtype=np.uint16
118
120
  )
119
- # Calculate exposure
120
- constant_exposure = ancillary_files["l1c-90sensor-dps-exposure"]
121
- df_exposure = pd.read_csv(constant_exposure)
121
+ healpix = np.arange(n_pix)
122
+
123
+ logger.info("Calculating spacecraft exposure times with deadtime correction.")
122
124
  exposure_time, deadtime_ratios = get_spacecraft_exposure_times(
123
- df_exposure,
124
125
  rates_dataset,
125
126
  params_dataset,
126
127
  pixels_below_scattering,
127
128
  boundary_scale_factors,
129
+ n_pix=n_pix,
128
130
  )
131
+ logger.info("Calculating spun efficiencies and geometric function.")
129
132
  # calculate efficiency and geometric function as a function of energy
130
133
  efficiencies, geometric_function = get_efficiencies_and_geometric_function(
131
134
  pixels_below_scattering,
@@ -136,11 +139,12 @@ def calculate_helio_pset(
136
139
  ancillary_files,
137
140
  )
138
141
  # Get midpoint timestamp for pointing.
139
- # TODO remove sct_to_et conversion
140
142
  pointing_start, pointing_stop = get_pointing_times(
141
- et_to_met(sct_to_et(species_dataset["event_times"].data[0]))
143
+ et_to_met(species_dataset["event_times"].data[0])
142
144
  )
143
145
  mid_time = ttj2000ns_to_et(met_to_ttj2000ns((pointing_start + pointing_stop) / 2))
146
+
147
+ logger.info("Adjusting data for helio frame.")
144
148
  exposure_time, efficiency, geometric_function = get_helio_adjusted_data(
145
149
  mid_time,
146
150
  exposure_time,
@@ -148,11 +152,26 @@ def calculate_helio_pset(
148
152
  efficiencies,
149
153
  ra_and_dec[:, 0],
150
154
  ra_and_dec[:, 1],
155
+ nside=nside,
151
156
  )
152
157
  sensitivity = efficiencies * geometric_function
153
158
 
154
- # For ISTP, epoch should be the center of the time bin.
155
- pset_dict["epoch"] = de_dataset.epoch.data[:1].astype(np.int64)
159
+ start: float = np.min(species_dataset["event_times"].values)
160
+ end: float = np.max(species_dataset["event_times"].values)
161
+
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)
156
175
  pset_dict["counts"] = counts[np.newaxis, ...]
157
176
  pset_dict["latitude"] = latitude[np.newaxis, ...]
158
177
  pset_dict["longitude"] = longitude[np.newaxis, ...]
@@ -167,6 +186,16 @@ def calculate_helio_pset(
167
186
  pset_dict["geometric_function"] = geometric_function
168
187
  pset_dict["dead_time_ratio"] = deadtime_ratios
169
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
170
199
 
171
200
  dataset = create_dataset(pset_dict, name, "l1c")
172
201
 
@@ -21,7 +21,7 @@ def mask_below_fwhm_scattering_threshold(
21
21
  phi_coeffs: np.ndarray,
22
22
  energy: np.ndarray,
23
23
  scattering_thresholds: np.ndarray,
24
- ) -> np.ndarray:
24
+ ) -> tuple[NDArray, NDArray, NDArray]:
25
25
  """
26
26
  Determine indices of theta and phi values below the FWHM scattering threshold.
27
27
 
@@ -43,8 +43,12 @@ def mask_below_fwhm_scattering_threshold(
43
43
 
44
44
  Returns
45
45
  -------
46
- numpy.ndarray
46
+ scattering_mask : numpy.ndarray
47
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.
48
52
  """
49
53
  # Calculate FWHM for all pixels and all energies
50
54
  fwhm_theta = theta_coeffs[..., 0:1] * (
@@ -58,18 +62,21 @@ def mask_below_fwhm_scattering_threshold(
58
62
 
59
63
  # Combine conditions for both theta and phi.
60
64
  # shape = (npix, energy.shape[1])
61
- return np.logical_and(fwhm_theta <= thresholds, fwhm_phi <= thresholds)
65
+ scattering_mask = np.logical_and(fwhm_theta <= thresholds, fwhm_phi <= thresholds)
66
+ return scattering_mask, fwhm_theta, fwhm_phi
62
67
 
63
68
 
64
- def calculate_pixels_within_scattering_threshold(
69
+ def calculate_fwhm_spun_scattering(
65
70
  for_indices_by_spin_phase: np.ndarray,
66
71
  theta_vals: np.ndarray,
67
72
  phi_vals: np.ndarray,
68
73
  ancillary_files: dict,
69
74
  instrument_id: int,
70
- ) -> list:
75
+ ) -> tuple[list, NDArray, NDArray, NDArray]:
71
76
  """
72
- Calculate pixels within the FWHM scattering threshold for each spin phase step.
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.
73
80
 
74
81
  Parameters
75
82
  ----------
@@ -93,6 +100,14 @@ def calculate_pixels_within_scattering_threshold(
93
100
  The outer list indicates spin phase steps, the middle list indicates energy
94
101
  bins, and the inner arrays contain indices indicating pixels that are below
95
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.
96
111
  """
97
112
  # Load scattering coefficient lookup table
98
113
  scattering_luts = load_scattering_lookup_tables(ancillary_files, instrument_id)
@@ -103,6 +118,13 @@ def calculate_pixels_within_scattering_threshold(
103
118
  scattering_thresholds_for_energy_mean = get_scattering_thresholds_for_energy(
104
119
  energy_bin_geometric_means, ancillary_files
105
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
+
106
128
  steps = for_indices_by_spin_phase.shape[1]
107
129
  energies = energy_bin_geometric_means[np.newaxis, :]
108
130
  # The "for_indices_by_spin_phase" lookup table contains the boolean values of each
@@ -132,7 +154,7 @@ def calculate_pixels_within_scattering_threshold(
132
154
  theta, phi, lookup_tables=scattering_luts
133
155
  )
134
156
  # Get a mask for pixels below the FWHM scattering threshold
135
- scattering_mask = mask_below_fwhm_scattering_threshold(
157
+ scattering_mask, fwhm_theta, fwhm_phi = mask_below_fwhm_scattering_threshold(
136
158
  theta_coeffs,
137
159
  phi_coeffs,
138
160
  energies,
@@ -147,8 +169,19 @@ def calculate_pixels_within_scattering_threshold(
147
169
  pixels_below_scattering_for_energy.append(for_pixel_indices[valid_pixels])
148
170
 
149
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
150
176
 
151
- return pixels_below_scattering
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
+ )
152
185
 
153
186
 
154
187
  def get_spacecraft_pointing_lookup_tables(
@@ -184,10 +217,10 @@ def get_spacecraft_pointing_lookup_tables(
184
217
  A 2D array of boundary scale factors for each HEALPix pixel at each spin phase
185
218
  step.
186
219
  """
187
- theta_descriptor = f"l1c-{instrument_id}sensor-sc-pointing-theta-n32"
188
- phi_descriptor = f"l1c-{instrument_id}sensor-sc-pointing-phi-n32"
189
- index_descriptor = f"l1c-{instrument_id}sensor-sc-pointing-index-n32"
190
- bsf_descriptor = f"l1c-{instrument_id}sensor-sc-pointing-bsf-n32"
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"
191
224
 
192
225
  theta_vals = pd.read_csv(
193
226
  ancillary_files[theta_descriptor], header=None, skiprows=1
@@ -2,19 +2,27 @@
2
2
 
3
3
  import logging
4
4
 
5
+ import astropy_healpix.healpy as hp
5
6
  import numpy as np
6
- import pandas as pd
7
7
  import xarray as xr
8
8
 
9
9
  from imap_processing.cdf.utils import parse_filename_like
10
+ from imap_processing.quality_flags import ImapPSETUltraFlags
11
+ from imap_processing.spice.repoint import get_pointing_times
12
+ from imap_processing.spice.time import (
13
+ et_to_met,
14
+ met_to_ttj2000ns,
15
+ )
10
16
  from imap_processing.ultra.l1b.ultra_l1b_culling import get_de_rejection_mask
11
17
  from imap_processing.ultra.l1c.l1c_lookup_utils import (
12
- calculate_pixels_within_scattering_threshold,
18
+ calculate_fwhm_spun_scattering,
13
19
  get_spacecraft_pointing_lookup_tables,
14
20
  )
21
+ from imap_processing.ultra.l1c.ultra_l1c_culling import compute_culling_mask
15
22
  from imap_processing.ultra.l1c.ultra_l1c_pset_bins import (
16
23
  build_energy_bins,
17
24
  get_efficiencies_and_geometric_function,
25
+ get_energy_delta_minus_plus,
18
26
  get_spacecraft_background_rates,
19
27
  get_spacecraft_exposure_times,
20
28
  get_spacecraft_histogram,
@@ -26,14 +34,13 @@ logger = logging.getLogger(__name__)
26
34
 
27
35
  def calculate_spacecraft_pset(
28
36
  de_dataset: xr.Dataset,
29
- extendedspin_dataset: xr.Dataset,
30
- cullingmask_dataset: xr.Dataset,
37
+ goodtimes_dataset: xr.Dataset,
31
38
  rates_dataset: xr.Dataset,
32
39
  params_dataset: xr.Dataset,
33
40
  name: str,
34
41
  ancillary_files: dict,
35
42
  instrument_id: int,
36
- species_id: int = 1,
43
+ species_id: list,
37
44
  ) -> xr.Dataset:
38
45
  """
39
46
  Create dictionary with defined datatype for Pointing Set Grid Data.
@@ -42,10 +49,8 @@ def calculate_spacecraft_pset(
42
49
  ----------
43
50
  de_dataset : xarray.Dataset
44
51
  Dataset containing de data.
45
- extendedspin_dataset : xarray.Dataset
46
- Dataset containing extendedspin data.
47
- cullingmask_dataset : xarray.Dataset
48
- Dataset containing cullingmask data.
52
+ goodtimes_dataset : xarray.Dataset
53
+ Dataset containing goodtimes data.
49
54
  rates_dataset : xarray.Dataset
50
55
  Dataset containing image rates data.
51
56
  params_dataset : xarray.Dataset
@@ -56,8 +61,8 @@ def calculate_spacecraft_pset(
56
61
  Ancillary files.
57
62
  instrument_id : int
58
63
  Instrument ID, either 45 or 90.
59
- species_id : int
60
- Species ID, default of 1 refers to Hydrogen.
64
+ species_id : List
65
+ Species ID.
61
66
 
62
67
  Returns
63
68
  -------
@@ -65,11 +70,15 @@ def calculate_spacecraft_pset(
65
70
  Dataset containing the data.
66
71
  """
67
72
  pset_dict: dict[str, np.ndarray] = {}
73
+
68
74
  sensor = parse_filename_like(name)["sensor"][0:2]
69
- # Select only the species we are interested in.
70
- indices = np.where(de_dataset["species"].values == species_id)[0]
75
+ indices = np.where(np.isin(de_dataset["e_bin"].values, species_id))[0]
71
76
  species_dataset = de_dataset.isel(epoch=indices)
72
77
 
78
+ # If there are no species return None.
79
+ if indices.size == 0:
80
+ return None
81
+
73
82
  # Before we use the de_dataset to calculate the pointing set grid we need to filter.
74
83
  rejected = get_de_rejection_mask(
75
84
  species_dataset["quality_scattering"].values,
@@ -85,13 +94,6 @@ def calculate_spacecraft_pset(
85
94
  )
86
95
 
87
96
  intervals, _, energy_bin_geometric_means = build_energy_bins()
88
- counts, latitude, longitude, n_pix = get_spacecraft_histogram(
89
- vhat_dps_spacecraft,
90
- species_dataset["energy_spacecraft"].values,
91
- intervals,
92
- nside=128,
93
- )
94
- healpix = np.arange(n_pix)
95
97
 
96
98
  # Get lookup table for FOR indices by spin phase step
97
99
  (
@@ -101,16 +103,28 @@ def calculate_spacecraft_pset(
101
103
  ra_and_dec,
102
104
  boundary_scale_factors,
103
105
  ) = get_spacecraft_pointing_lookup_tables(ancillary_files, instrument_id)
104
- # Check that the number of rows in the lookup table matches the number of pixels
105
- if for_indices_by_spin_phase.shape[0] != n_pix:
106
- logger.warning(
107
- "The lookup table is expected to have the same number of rows as "
108
- "the number of HEALPix pixels."
109
- )
110
106
 
111
- pixels_below_scattering = calculate_pixels_within_scattering_threshold(
112
- for_indices_by_spin_phase, theta_vals, phi_vals, ancillary_files, instrument_id
107
+ logger.info("calculating spun FWHM scattering values.")
108
+ pixels_below_scattering, scattering_theta, scattering_phi, scattering_thresholds = (
109
+ calculate_fwhm_spun_scattering(
110
+ for_indices_by_spin_phase,
111
+ theta_vals,
112
+ phi_vals,
113
+ ancillary_files,
114
+ instrument_id,
115
+ )
113
116
  )
117
+ # Determine nside from the lookup table
118
+ nside = hp.npix2nside(len(for_indices_by_spin_phase))
119
+ counts, latitude, longitude, n_pix = get_spacecraft_histogram(
120
+ vhat_dps_spacecraft,
121
+ species_dataset["energy_spacecraft"].values,
122
+ intervals,
123
+ nside=nside,
124
+ )
125
+ healpix = np.arange(n_pix)
126
+
127
+ logger.info("Calculating spun efficiencies and geometric function.")
114
128
  # calculate efficiency and geometric function as a function of energy
115
129
  efficiencies, geometric_function = get_efficiencies_and_geometric_function(
116
130
  pixels_below_scattering,
@@ -122,29 +136,49 @@ def calculate_spacecraft_pset(
122
136
  )
123
137
  sensitivity = efficiencies * geometric_function
124
138
 
125
- # Calculate exposure
126
- constant_exposure = ancillary_files["l1c-90sensor-dps-exposure"]
127
- df_exposure = pd.read_csv(constant_exposure)
128
-
139
+ # Calculate exposure times
140
+ logger.info("Calculating spacecraft exposure times with deadtime correction.")
129
141
  exposure_pointing, deadtime_ratios = get_spacecraft_exposure_times(
130
- df_exposure,
131
142
  rates_dataset,
132
143
  params_dataset,
133
144
  pixels_below_scattering,
134
145
  boundary_scale_factors,
146
+ n_pix=n_pix,
135
147
  )
136
-
148
+ logger.info("Calculating background rates.")
137
149
  # Calculate background rates
138
150
  background_rates = get_spacecraft_background_rates(
139
151
  rates_dataset,
140
152
  sensor,
141
153
  ancillary_files,
142
154
  intervals,
143
- cullingmask_dataset["spin_number"].values,
155
+ goodtimes_dataset["spin_number"].values,
156
+ nside=nside,
157
+ )
158
+ spacecraft_pset_quality_flags = np.full(
159
+ n_pix, ImapPSETUltraFlags.NONE.value, dtype=np.uint16
144
160
  )
145
161
 
146
- # For ISTP, epoch should be the center of the time bin.
147
- pset_dict["epoch"] = de_dataset.epoch.data[:1].astype(np.int64)
162
+ start: float = np.min(species_dataset["event_times"].values)
163
+ end: float = np.max(species_dataset["event_times"].values)
164
+
165
+ # Time bins in 30 minute intervals
166
+ time_bins = np.arange(start, end + 1800, 1800)
167
+
168
+ # Compute mask for culling the Earth
169
+ compute_culling_mask(
170
+ time_bins,
171
+ 6378.1, # Earth radius
172
+ spacecraft_pset_quality_flags,
173
+ nside=nside,
174
+ )
175
+ # Get pointing start and stop times and convert to ttj2000ns
176
+ pointing_start, pointing_stop = get_pointing_times(
177
+ float(et_to_met(species_dataset["event_times"].data[0]))
178
+ )
179
+ pointing_start = met_to_ttj2000ns(pointing_start)
180
+ # Epoch should be the start of the pointing
181
+ pset_dict["epoch"] = np.atleast_1d(pointing_start).astype(np.int64)
148
182
  pset_dict["counts"] = counts[np.newaxis, ...]
149
183
  pset_dict["latitude"] = latitude[np.newaxis, ...]
150
184
  pset_dict["longitude"] = longitude[np.newaxis, ...]
@@ -155,6 +189,7 @@ def calculate_spacecraft_pset(
155
189
  pset_dict["energy_bin_delta"] = np.diff(intervals, axis=1).squeeze()[
156
190
  np.newaxis, ...
157
191
  ]
192
+ pset_dict["quality_flags"] = spacecraft_pset_quality_flags[np.newaxis, ...]
158
193
 
159
194
  pset_dict["sensitivity"] = sensitivity
160
195
  pset_dict["efficiency"] = efficiencies
@@ -162,6 +197,15 @@ def calculate_spacecraft_pset(
162
197
  pset_dict["dead_time_ratio"] = deadtime_ratios
163
198
  pset_dict["spin_phase_step"] = np.arange(len(deadtime_ratios))
164
199
 
200
+ pset_dict["scatter_theta"] = scattering_theta
201
+ pset_dict["scatter_phi"] = scattering_phi
202
+ pset_dict["scatter_threshold"] = scattering_thresholds
203
+
204
+ # Add the energy delta plus/minus to the dataset
205
+ energy_delta_minus, energy_delta_plus = get_energy_delta_minus_plus()
206
+ pset_dict["energy_delta_minus"] = energy_delta_minus
207
+ pset_dict["energy_delta_plus"] = energy_delta_plus
208
+
165
209
  dataset = create_dataset(pset_dict, name, "l1c")
166
210
 
167
211
  return dataset
@@ -2,13 +2,13 @@
2
2
 
3
3
  import xarray as xr
4
4
 
5
+ from imap_processing.ultra.constants import UltraConstants
5
6
  from imap_processing.ultra.l1c.helio_pset import calculate_helio_pset
6
- from imap_processing.ultra.l1c.histogram import calculate_histogram
7
7
  from imap_processing.ultra.l1c.spacecraft_pset import calculate_spacecraft_pset
8
8
 
9
9
 
10
10
  def ultra_l1c(
11
- data_dict: dict, ancillary_files: dict, has_spice: bool
11
+ data_dict: dict, ancillary_files: dict, imap_frames: bool
12
12
  ) -> list[xr.Dataset]:
13
13
  """
14
14
  Will process ULTRA L1A and L1B data into L1C CDF files at output_filepath.
@@ -19,8 +19,8 @@ def ultra_l1c(
19
19
  The data itself and its dependent data.
20
20
  ancillary_files : dict
21
21
  Ancillary files.
22
- has_spice : bool
23
- Whether to use SPICE data.
22
+ imap_frames : bool
23
+ Whether to use IMAP frames.
24
24
 
25
25
  Returns
26
26
  -------
@@ -32,47 +32,52 @@ def ultra_l1c(
32
32
  # Account for possibility of having 45 and 90 in dictionary.
33
33
  for instrument_id in [45, 90]:
34
34
  if (
35
- f"imap_ultra_l1a_{instrument_id}sensor-histogram" in data_dict
36
- and f"imap_ultra_l1b_{instrument_id}sensor-cullingmask" in data_dict
37
- ):
38
- histogram_dataset = calculate_histogram(
39
- data_dict[f"imap_ultra_l1a_{instrument_id}sensor-histogram"],
40
- f"imap_ultra_l1c_{instrument_id}sensor-histogram",
41
- )
42
- output_datasets = [histogram_dataset]
43
- elif (
44
- f"imap_ultra_l1b_{instrument_id}sensor-cullingmask" in data_dict
35
+ f"imap_ultra_l1b_{instrument_id}sensor-goodtimes" in data_dict
45
36
  and f"imap_ultra_l1b_{instrument_id}sensor-de" in data_dict
46
- and f"imap_ultra_l1b_{instrument_id}sensor-extendedspin" in data_dict
47
- and has_spice
37
+ and f"imap_ultra_l1a_{instrument_id}sensor-rates" in data_dict
38
+ and f"imap_ultra_l1a_{instrument_id}sensor-params" in data_dict
39
+ and imap_frames
48
40
  ):
49
41
  helio_pset = calculate_helio_pset(
50
42
  data_dict[f"imap_ultra_l1b_{instrument_id}sensor-de"],
51
- data_dict[f"imap_ultra_l1b_{instrument_id}sensor-extendedspin"],
52
- data_dict[f"imap_ultra_l1b_{instrument_id}sensor-cullingmask"],
43
+ data_dict[f"imap_ultra_l1b_{instrument_id}sensor-goodtimes"],
53
44
  data_dict[f"imap_ultra_l1a_{instrument_id}sensor-rates"],
54
45
  data_dict[f"imap_ultra_l1a_{instrument_id}sensor-params"],
55
46
  f"imap_ultra_l1c_{instrument_id}sensor-heliopset",
56
47
  ancillary_files,
57
48
  instrument_id,
49
+ UltraConstants.TOFXPH_SPECIES_GROUPS["proton"],
58
50
  )
59
51
  output_datasets = [helio_pset]
60
52
  elif (
61
- f"imap_ultra_l1b_{instrument_id}sensor-cullingmask" in data_dict
53
+ f"imap_ultra_l1b_{instrument_id}sensor-goodtimes" in data_dict
62
54
  and f"imap_ultra_l1b_{instrument_id}sensor-de" in data_dict
63
- and f"imap_ultra_l1b_{instrument_id}sensor-extendedspin" in data_dict
55
+ and f"imap_ultra_l1a_{instrument_id}sensor-rates" in data_dict
56
+ and f"imap_ultra_l1a_{instrument_id}sensor-params" in data_dict
64
57
  ):
65
58
  spacecraft_pset = calculate_spacecraft_pset(
66
59
  data_dict[f"imap_ultra_l1b_{instrument_id}sensor-de"],
67
- data_dict[f"imap_ultra_l1b_{instrument_id}sensor-extendedspin"],
68
- data_dict[f"imap_ultra_l1b_{instrument_id}sensor-cullingmask"],
60
+ data_dict[f"imap_ultra_l1b_{instrument_id}sensor-goodtimes"],
69
61
  data_dict[f"imap_ultra_l1a_{instrument_id}sensor-rates"],
70
62
  data_dict[f"imap_ultra_l1a_{instrument_id}sensor-params"],
71
63
  f"imap_ultra_l1c_{instrument_id}sensor-spacecraftpset",
72
64
  ancillary_files,
73
65
  instrument_id,
66
+ UltraConstants.TOFXPH_SPECIES_GROUPS["proton"],
74
67
  )
75
68
  output_datasets = [spacecraft_pset]
69
+ spacecraft_pset_non_proton = calculate_spacecraft_pset(
70
+ data_dict[f"imap_ultra_l1b_{instrument_id}sensor-de"],
71
+ data_dict[f"imap_ultra_l1b_{instrument_id}sensor-goodtimes"],
72
+ data_dict[f"imap_ultra_l1a_{instrument_id}sensor-rates"],
73
+ data_dict[f"imap_ultra_l1a_{instrument_id}sensor-params"],
74
+ f"imap_ultra_l1c_{instrument_id}sensor-spacecraftpset-nonproton",
75
+ ancillary_files,
76
+ instrument_id,
77
+ UltraConstants.TOFXPH_SPECIES_GROUPS["non_proton"],
78
+ )
79
+ if spacecraft_pset_non_proton is not None:
80
+ output_datasets.append(spacecraft_pset_non_proton)
76
81
  if not output_datasets:
77
82
  raise ValueError("Data dictionary does not contain the expected keys.")
78
83