imap-processing 0.19.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.

Files changed (64) 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 +11 -0
  6. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +3 -1
  7. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +5 -4
  8. imap_processing/cdf/config/imap_ultra_global_cdf_attrs.yaml +20 -8
  9. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +33 -31
  10. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +61 -1
  11. imap_processing/cli.py +62 -71
  12. imap_processing/codice/codice_l0.py +2 -1
  13. imap_processing/codice/codice_l1a.py +47 -49
  14. imap_processing/codice/codice_l1b.py +42 -32
  15. imap_processing/codice/codice_l2.py +105 -7
  16. imap_processing/codice/constants.py +50 -8
  17. imap_processing/codice/data/lo_stepping_values.csv +1 -1
  18. imap_processing/ena_maps/ena_maps.py +39 -18
  19. imap_processing/ena_maps/utils/corrections.py +291 -0
  20. imap_processing/ena_maps/utils/map_utils.py +20 -4
  21. imap_processing/glows/l1b/glows_l1b.py +38 -23
  22. imap_processing/glows/l1b/glows_l1b_data.py +10 -11
  23. imap_processing/hi/hi_l1c.py +4 -109
  24. imap_processing/hi/hi_l2.py +34 -23
  25. imap_processing/hi/utils.py +109 -0
  26. imap_processing/ialirt/l0/ialirt_spice.py +1 -0
  27. imap_processing/ialirt/utils/create_xarray.py +1 -1
  28. imap_processing/lo/ancillary_data/imap_lo_hydrogen-geometric-factor_v001.csv +75 -0
  29. imap_processing/lo/ancillary_data/imap_lo_oxygen-geometric-factor_v001.csv +75 -0
  30. imap_processing/lo/l1b/lo_l1b.py +90 -16
  31. imap_processing/lo/l1c/lo_l1c.py +164 -50
  32. imap_processing/lo/l2/lo_l2.py +941 -127
  33. imap_processing/mag/l1d/mag_l1d_data.py +36 -3
  34. imap_processing/mag/l2/mag_l2.py +2 -0
  35. imap_processing/mag/l2/mag_l2_data.py +4 -3
  36. imap_processing/quality_flags.py +14 -0
  37. imap_processing/spice/geometry.py +15 -8
  38. imap_processing/spice/pointing_frame.py +4 -2
  39. imap_processing/spice/repoint.py +49 -0
  40. imap_processing/ultra/constants.py +29 -0
  41. imap_processing/ultra/l1b/badtimes.py +35 -11
  42. imap_processing/ultra/l1b/de.py +15 -9
  43. imap_processing/ultra/l1b/extendedspin.py +24 -12
  44. imap_processing/ultra/l1b/goodtimes.py +112 -0
  45. imap_processing/ultra/l1b/lookup_utils.py +1 -1
  46. imap_processing/ultra/l1b/ultra_l1b.py +7 -7
  47. imap_processing/ultra/l1b/ultra_l1b_culling.py +8 -4
  48. imap_processing/ultra/l1b/ultra_l1b_extended.py +79 -43
  49. imap_processing/ultra/l1c/helio_pset.py +68 -39
  50. imap_processing/ultra/l1c/l1c_lookup_utils.py +45 -12
  51. imap_processing/ultra/l1c/spacecraft_pset.py +81 -37
  52. imap_processing/ultra/l1c/ultra_l1c.py +27 -22
  53. imap_processing/ultra/l1c/ultra_l1c_culling.py +7 -0
  54. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +41 -41
  55. imap_processing/ultra/l2/ultra_l2.py +54 -10
  56. imap_processing/ultra/utils/ultra_l1_utils.py +10 -5
  57. {imap_processing-0.19.0.dist-info → imap_processing-0.19.2.dist-info}/METADATA +1 -1
  58. {imap_processing-0.19.0.dist-info → imap_processing-0.19.2.dist-info}/RECORD +62 -60
  59. imap_processing/ultra/l1b/cullingmask.py +0 -90
  60. imap_processing/ultra/l1c/histogram.py +0 -36
  61. /imap_processing/glows/ancillary/{imap_glows_pipeline_settings_20250923_v002.json → imap_glows_pipeline-settings_20250923_v002.json} +0 -0
  62. {imap_processing-0.19.0.dist-info → imap_processing-0.19.2.dist-info}/LICENSE +0 -0
  63. {imap_processing-0.19.0.dist-info → imap_processing-0.19.2.dist-info}/WHEEL +0 -0
  64. {imap_processing-0.19.0.dist-info → imap_processing-0.19.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,112 @@
1
+ """Calculate Goodtimes."""
2
+
3
+ import numpy as np
4
+ import xarray as xr
5
+
6
+ from imap_processing.ultra.l1b.quality_flag_filters import SPIN_QUALITY_FLAG_FILTERS
7
+ from imap_processing.ultra.utils.ultra_l1_utils import create_dataset, extract_data_dict
8
+
9
+ FILLVAL_UINT16 = 65535
10
+ FILLVAL_FLOAT32 = -1.0e31
11
+ FILLVAL_FLOAT64 = -1.0e31
12
+ FILLVAL_UINT32 = 4294967295
13
+
14
+
15
+ def calculate_goodtimes(extendedspin_dataset: xr.Dataset, name: str) -> xr.Dataset:
16
+ """
17
+ Create dataset with defined datatype for Goodtimes Data.
18
+
19
+ Parameters
20
+ ----------
21
+ extendedspin_dataset : xarray.Dataset
22
+ Dataset containing the data.
23
+ name : str
24
+ Name of the dataset.
25
+
26
+ Returns
27
+ -------
28
+ goodtimes_dataset : xarray.Dataset
29
+ Dataset containing the extendedspin data that remains after culling.
30
+ """
31
+ n_bins = extendedspin_dataset.dims["energy_bin_geometric_mean"]
32
+ # If the spin rate was too high or low then the spin should be thrown out.
33
+ # If the rates at any energy level are too high then throw out the entire spin.
34
+ good_mask = (
35
+ (
36
+ extendedspin_dataset["quality_attitude"]
37
+ & sum(flag.value for flag in SPIN_QUALITY_FLAG_FILTERS["quality_attitude"])
38
+ )
39
+ == 0
40
+ ) & (
41
+ (
42
+ (
43
+ extendedspin_dataset["quality_ena_rates"]
44
+ & sum(
45
+ flag.value
46
+ for flag in SPIN_QUALITY_FLAG_FILTERS["quality_ena_rates"]
47
+ )
48
+ )
49
+ == 0
50
+ ).all(dim="energy_bin_geometric_mean")
51
+ )
52
+ filtered_dataset = extendedspin_dataset.sel(
53
+ spin_number=extendedspin_dataset["spin_number"][good_mask]
54
+ )
55
+
56
+ data_dict = extract_data_dict(filtered_dataset)
57
+
58
+ goodtimes_dataset = create_dataset(data_dict, name, "l1b")
59
+
60
+ if goodtimes_dataset["spin_number"].size == 0:
61
+ goodtimes_dataset = goodtimes_dataset.drop_dims("spin_number")
62
+ goodtimes_dataset = goodtimes_dataset.expand_dims(spin_number=[FILLVAL_UINT32])
63
+ goodtimes_dataset["spin_start_time"] = xr.DataArray(
64
+ np.array([FILLVAL_FLOAT64], dtype="float64"), dims=["spin_number"]
65
+ )
66
+ goodtimes_dataset["spin_period"] = xr.DataArray(
67
+ np.array([FILLVAL_FLOAT64], dtype="float64"), dims=["spin_number"]
68
+ )
69
+ goodtimes_dataset["spin_rate"] = xr.DataArray(
70
+ np.array([FILLVAL_FLOAT64], dtype="float64"), dims=["spin_number"]
71
+ )
72
+ goodtimes_dataset["start_pulses_per_spin"] = xr.DataArray(
73
+ np.array([FILLVAL_FLOAT32], dtype="float32"),
74
+ dims=["spin_number"],
75
+ )
76
+ goodtimes_dataset["stop_pulses_per_spin"] = xr.DataArray(
77
+ np.array([FILLVAL_FLOAT32], dtype="float32"),
78
+ dims=["spin_number"],
79
+ )
80
+ goodtimes_dataset["coin_pulses_per_spin"] = xr.DataArray(
81
+ np.array([FILLVAL_FLOAT32], dtype="float32"),
82
+ dims=["spin_number"],
83
+ )
84
+ goodtimes_dataset["rejected_events_per_spin"] = xr.DataArray(
85
+ np.array([FILLVAL_UINT32], dtype="uint32"),
86
+ dims=["spin_number"],
87
+ )
88
+ goodtimes_dataset["quality_attitude"] = xr.DataArray(
89
+ np.array([FILLVAL_UINT16], dtype="uint16"), dims=["spin_number"]
90
+ )
91
+ goodtimes_dataset["quality_hk"] = xr.DataArray(
92
+ np.array([FILLVAL_UINT16], dtype="uint16"),
93
+ dims=["spin_number"],
94
+ )
95
+ goodtimes_dataset["quality_instruments"] = xr.DataArray(
96
+ np.array([FILLVAL_UINT16], dtype="uint16"),
97
+ dims=["spin_number"],
98
+ )
99
+ goodtimes_dataset["quality_ena_rates"] = (
100
+ ("energy_bin_geometric_mean", "spin_number"),
101
+ np.full((n_bins, 1), FILLVAL_UINT16, dtype="uint16"),
102
+ )
103
+ goodtimes_dataset["ena_rates"] = (
104
+ ("energy_bin_geometric_mean", "spin_number"),
105
+ np.full((n_bins, 1), FILLVAL_FLOAT64, dtype="float64"),
106
+ )
107
+ goodtimes_dataset["ena_rates_threshold"] = (
108
+ ("energy_bin_geometric_mean", "spin_number"),
109
+ np.full((n_bins, 1), FILLVAL_FLOAT32, dtype="float32"),
110
+ )
111
+
112
+ return goodtimes_dataset
@@ -345,7 +345,7 @@ def load_scattering_lookup_tables(ancillary_files: dict, instrument_id: int) ->
345
345
  # TODO remove the line below when the 45 sensor scattering coefficients are
346
346
  # delivered.
347
347
  instrument_id = 90
348
- descriptor = f"l1b-{instrument_id}sensor-scattering-calibration"
348
+ descriptor = f"l1b-{instrument_id}sensor-scattering-calibration-data"
349
349
  theta_grid = pd.read_csv(
350
350
  ancillary_files[descriptor], header=None, skiprows=7, nrows=241
351
351
  ).to_numpy(dtype=float)
@@ -3,9 +3,9 @@
3
3
  import xarray as xr
4
4
 
5
5
  from imap_processing.ultra.l1b.badtimes import calculate_badtimes
6
- from imap_processing.ultra.l1b.cullingmask import calculate_cullingmask
7
6
  from imap_processing.ultra.l1b.de import calculate_de
8
7
  from imap_processing.ultra.l1b.extendedspin import calculate_extendedspin
8
+ from imap_processing.ultra.l1b.goodtimes import calculate_goodtimes
9
9
 
10
10
 
11
11
  def ultra_l1b(data_dict: dict, ancillary_files: dict) -> list[xr.Dataset]:
@@ -29,7 +29,7 @@ def ultra_l1b(data_dict: dict, ancillary_files: dict) -> list[xr.Dataset]:
29
29
  General flow:
30
30
  1. l1a data products are created (upstream to this code)
31
31
  2. l1b de is created here and dropped in s3 kicking off processing again
32
- 3. l1b extended, culling, badtimes created here
32
+ 3. l1b extended, goodtimes, badtimes created here
33
33
  """
34
34
  output_datasets = []
35
35
 
@@ -72,22 +72,22 @@ def ultra_l1b(data_dict: dict, ancillary_files: dict) -> list[xr.Dataset]:
72
72
  output_datasets.append(extendedspin_dataset)
73
73
  elif (
74
74
  f"imap_ultra_l1b_{instrument_id}sensor-extendedspin" in data_dict
75
- and f"imap_ultra_l1b_{instrument_id}sensor-cullingmask" in data_dict
75
+ and f"imap_ultra_l1b_{instrument_id}sensor-goodtimes" in data_dict
76
76
  ):
77
77
  badtimes_dataset = calculate_badtimes(
78
78
  data_dict[f"imap_ultra_l1b_{instrument_id}sensor-extendedspin"],
79
- data_dict[f"imap_ultra_l1b_{instrument_id}sensor-cullingmask"][
79
+ data_dict[f"imap_ultra_l1b_{instrument_id}sensor-goodtimes"][
80
80
  "spin_number"
81
81
  ].values,
82
82
  f"imap_ultra_l1b_{instrument_id}sensor-badtimes",
83
83
  )
84
84
  output_datasets.append(badtimes_dataset)
85
85
  elif f"imap_ultra_l1b_{instrument_id}sensor-extendedspin" in data_dict:
86
- cullingmask_dataset = calculate_cullingmask(
86
+ goodtimes_dataset = calculate_goodtimes(
87
87
  data_dict[f"imap_ultra_l1b_{instrument_id}sensor-extendedspin"],
88
- f"imap_ultra_l1b_{instrument_id}sensor-cullingmask",
88
+ f"imap_ultra_l1b_{instrument_id}sensor-goodtimes",
89
89
  )
90
- output_datasets.append(cullingmask_dataset)
90
+ output_datasets.append(goodtimes_dataset)
91
91
  if not output_datasets:
92
92
  raise ValueError("Data dictionary does not contain the expected keys.")
93
93
 
@@ -31,6 +31,7 @@ SPIN_DURATION = 15 # Default spin duration in seconds.
31
31
  RateResult = namedtuple(
32
32
  "RateResult",
33
33
  [
34
+ "unique_spins",
34
35
  "start_per_spin",
35
36
  "stop_per_spin",
36
37
  "coin_per_spin",
@@ -249,8 +250,8 @@ def flag_rates(
249
250
  Quality flags.
250
251
  spin : NDArray
251
252
  Spin data.
252
- energy_midpoints : NDArray
253
- Energy midpoint data.
253
+ energy_bin_geometric_mean : NDArray
254
+ Energy bin geometric mean.
254
255
  n_sigma_per_energy_reshape : NDArray
255
256
  N sigma per energy.
256
257
  """
@@ -264,7 +265,7 @@ def flag_rates(
264
265
  threshold = get_n_sigma(count_rates, duration, sigma=sigma)
265
266
 
266
267
  bin_edges = np.array(UltraConstants.CULLING_ENERGY_BIN_EDGES)
267
- energy_midpoints = np.sqrt(bin_edges[:-1] * bin_edges[1:])
268
+ energy_bin_geometric_mean = np.sqrt(bin_edges[:-1] * bin_edges[1:])
268
269
  spin = np.unique(spin_number)
269
270
 
270
271
  # Indices where the counts exceed the threshold
@@ -275,7 +276,7 @@ def flag_rates(
275
276
  quality_flags[:, 0] |= ImapRatesUltraFlags.FIRSTSPIN.value
276
277
  quality_flags[:, -1] |= ImapRatesUltraFlags.LASTSPIN.value
277
278
 
278
- return quality_flags, spin, energy_midpoints, threshold
279
+ return quality_flags, spin, energy_bin_geometric_mean, threshold
279
280
 
280
281
 
281
282
  def compare_aux_univ_spin_table(
@@ -424,6 +425,8 @@ def get_pulses_per_spin(rates: xr.Dataset) -> RateResult:
424
425
 
425
426
  Returns
426
427
  -------
428
+ unique_spins : NDArray
429
+ Unique spin numbers.
427
430
  start_per_spin : NDArray
428
431
  Total start pulses per spin.
429
432
  stop_per_spin : NDArray
@@ -474,6 +477,7 @@ def get_pulses_per_spin(rates: xr.Dataset) -> RateResult:
474
477
  coin_per_spin = np.bincount(spin_idx, weights=coin_pulses)
475
478
 
476
479
  return RateResult(
480
+ unique_spins=unique_spins,
477
481
  start_per_spin=start_per_spin,
478
482
  stop_per_spin=stop_per_spin,
479
483
  coin_per_spin=coin_per_spin,
@@ -12,7 +12,9 @@ from numpy import ndarray
12
12
  from numpy.typing import NDArray
13
13
  from scipy.interpolate import LinearNDInterpolator, RegularGridInterpolator
14
14
 
15
+ from imap_processing.quality_flags import ImapDEOutliersUltraFlags
15
16
  from imap_processing.spice.spin import get_spin_data
17
+ from imap_processing.spice.time import sct_to_et
16
18
  from imap_processing.ultra.constants import UltraConstants
17
19
  from imap_processing.ultra.l1b.lookup_utils import (
18
20
  get_angular_profiles,
@@ -552,7 +554,7 @@ def get_de_velocity(
552
554
  v_hat = velocities / np.linalg.norm(velocities, axis=1)[:, None]
553
555
  r_hat = -v_hat
554
556
 
555
- return velocities, v_hat, r_hat
557
+ return velocities, -v_hat, -r_hat
556
558
 
557
559
 
558
560
  def get_ssd_tof(
@@ -837,25 +839,16 @@ def get_ctof(
837
839
  return ctof, magnitude_v
838
840
 
839
841
 
840
- def determine_species(tof: np.ndarray, path_length: np.ndarray, type: str) -> NDArray:
842
+ def determine_species(e_bin: np.ndarray, type: str) -> NDArray:
841
843
  """
842
844
  Determine the species for pulse-height events.
843
845
 
844
- Species is determined from the particle velocity.
845
- For velocity, the particle TOF is normalized with respect
846
- to a fixed distance dmin between the front and back detectors.
847
- The normalized TOF is termed the corrected TOF (ctof).
848
- Particle species are determined from ctof using thresholds.
849
-
850
- Further description is available on pages 42-44 of
851
- IMAP-Ultra Flight Software Specification document.
846
+ Species is determined using the computed e_bin.
852
847
 
853
848
  Parameters
854
849
  ----------
855
- tof : np.ndarray
856
- Time of flight of the SSD event (tenths of a nanosecond).
857
- path_length : np.ndarray
858
- Path length (r) (hundredths of a millimeter).
850
+ e_bin : np.ndarray
851
+ Computed e_bin.
859
852
  type : str
860
853
  Type of data (PH or SSD).
861
854
 
@@ -864,11 +857,17 @@ def determine_species(tof: np.ndarray, path_length: np.ndarray, type: str) -> ND
864
857
  species_bin : np.array
865
858
  Species bin.
866
859
  """
867
- # Event TOF normalization to Z axis
868
- ctof, _ = get_ctof(tof, path_length, type)
869
- # Assign Species 1 ("H") to bins
870
- # TODO: this is a placeholder for future species assignments.
871
- species_bin = np.full(len(ctof), 1, dtype=np.uint8)
860
+ if type == "PH":
861
+ species_groups = UltraConstants.TOFXPH_SPECIES_GROUPS
862
+ if type == "SSD":
863
+ species_groups = UltraConstants.TOFXE_SPECIES_GROUPS
864
+
865
+ non_proton_bins = species_groups["non_proton"]
866
+ proton_bins = species_groups["proton"]
867
+
868
+ species_bin = np.full(e_bin.shape, fill_value=2, dtype=int)
869
+ species_bin[np.isin(e_bin, non_proton_bins)] = 0
870
+ species_bin[np.isin(e_bin, proton_bins)] = 1
872
871
 
873
872
  return species_bin
874
873
 
@@ -984,9 +983,9 @@ def get_eventtimes(
984
983
  Returns
985
984
  -------
986
985
  event_times : np.ndarray
987
- Event times.
986
+ Event times in et.
988
987
  spin_starts : np.ndarray
989
- Spin start times.
988
+ Spin start times in et.
990
989
  spin_period_sec : np.ndarray
991
990
  Spin period in seconds.
992
991
 
@@ -1007,7 +1006,7 @@ def get_eventtimes(
1007
1006
 
1008
1007
  event_times = spin_starts + spin_period_sec * (phase_angle / 720)
1009
1008
 
1010
- return event_times, spin_starts, spin_period_sec
1009
+ return sct_to_et(event_times), sct_to_et(spin_starts), spin_period_sec
1011
1010
 
1012
1011
 
1013
1012
  def interpolate_fwhm(
@@ -1375,54 +1374,91 @@ def is_coin_ph_valid(
1375
1374
  etof: NDArray,
1376
1375
  xc: NDArray,
1377
1376
  xb: NDArray,
1377
+ stop_north_tdc: NDArray,
1378
+ stop_south_tdc: NDArray,
1379
+ stop_east_tdc: NDArray,
1380
+ stop_west_tdc: NDArray,
1378
1381
  sensor: str,
1379
1382
  ancillary_files: dict,
1383
+ quality_flags: NDArray,
1380
1384
  ) -> NDArray:
1381
1385
  """
1382
- Determine whether Coincidence-PH data are valid.
1383
-
1384
- This is based on thresholds defined in the IMAP-Ultra Flight Software Specification
1385
- (see page 36).
1386
+ Determine event validity.
1386
1387
 
1387
1388
  Parameters
1388
1389
  ----------
1389
1390
  etof : NDArray
1390
- Electron TOF (tenths of a nanosecond).
1391
+ Time for the electrons to travel back to the coincidence
1392
+ anode (tenths of a nanosecond).
1391
1393
  xc : NDArray
1392
- Coincidence X position (hundredths of a mm).
1394
+ X coincidence position (hundredths of a millimeter).
1393
1395
  xb : NDArray
1394
- Back X position (hundredths of a mm).
1396
+ Back positions in x direction (hundredths of a millimeter).
1397
+ stop_north_tdc : NDArray
1398
+ Stop North Time to Digital Converter.
1399
+ stop_south_tdc : NDArray
1400
+ Stop South Time to Digital Converter.
1401
+ stop_east_tdc : NDArray
1402
+ Stop East Time to Digital Converter.
1403
+ stop_west_tdc : NDArray
1404
+ Stop West Time to Digital Converter.
1395
1405
  sensor : str
1396
1406
  Sensor name: "ultra45" or "ultra90".
1397
1407
  ancillary_files : dict
1398
1408
  Ancillary files for lookup.
1409
+ quality_flags : NDArray
1410
+ Quality flag to set when there is an outlier.
1399
1411
 
1400
1412
  Returns
1401
1413
  -------
1402
- valid_mask : NDArray
1403
- Boolean array indicating Coin-PH validity.
1414
+ combined_mask : NDArray
1415
+ Boolean array indicating whether back TOF is valid.
1404
1416
 
1405
1417
  Notes
1406
1418
  -----
1407
- Logic derived from page 36 of the IMAP-Ultra Flight Software Specification document.
1419
+ From page 36 of the IMAP-Ultra Flight Software Specification document.
1408
1420
  """
1409
- etof_min = get_image_params("eTOFMin", sensor, ancillary_files)
1410
- etof_max = get_image_params("eTOFMax", sensor, ancillary_files)
1411
-
1412
- etof_valid = (etof >= etof_min) & (etof <= etof_max)
1421
+ # Make certain etof is within range for tenths of a nanosecond.
1422
+ etof_valid = (etof >= UltraConstants.ETOFMIN_EVENTFILTER) & (
1423
+ etof <= UltraConstants.ETOFMAX_EVENTFILTER
1424
+ )
1413
1425
 
1426
+ # Hundredths of a mm.
1414
1427
  diff_x = xc - xb
1415
- etof_offset1 = get_image_params("eTOFOff1", sensor, ancillary_files)
1416
- etof_offset2 = get_image_params("eTOFOff2", sensor, ancillary_files)
1417
- etof_slope1 = get_image_params("eTOFSlope1", sensor, ancillary_files)
1418
- etof_slope2 = get_image_params("eTOFSlope2", sensor, ancillary_files)
1419
1428
 
1420
- t1 = (etof - etof_offset1) * etof_slope1 / 1024
1421
- t2 = (etof - etof_offset2) * etof_slope2 / 1024
1429
+ t1 = (
1430
+ (etof - UltraConstants.ETOFOFF1_EVENTFILTER)
1431
+ * UltraConstants.ETOFSLOPE1_EVENTFILTER
1432
+ / 1024
1433
+ )
1434
+ t2 = (
1435
+ (etof - UltraConstants.ETOFOFF2_EVENTFILTER)
1436
+ * UltraConstants.ETOFSLOPE2_EVENTFILTER
1437
+ / 1024
1438
+ )
1422
1439
 
1423
1440
  condition_1 = (diff_x >= t1) & (diff_x <= t2)
1424
1441
  condition_2 = (diff_x >= -t2) & (diff_x <= -t1)
1425
1442
 
1426
1443
  spatial_valid = condition_1 | condition_2
1427
1444
 
1428
- return etof_valid & spatial_valid
1445
+ sp_n_norm = get_norm(stop_north_tdc, "SpN", sensor, ancillary_files)
1446
+ sp_s_norm = get_norm(stop_south_tdc, "SpS", sensor, ancillary_files)
1447
+ sp_e_norm = get_norm(stop_east_tdc, "SpE", sensor, ancillary_files)
1448
+ sp_w_norm = get_norm(stop_west_tdc, "SpW", sensor, ancillary_files)
1449
+
1450
+ tofx = sp_n_norm + sp_s_norm
1451
+ tofy = sp_e_norm + sp_w_norm
1452
+
1453
+ # Units in tenths of a nanosecond
1454
+ delta_tof = tofy - tofx
1455
+
1456
+ delta_tof_mask = (delta_tof >= UltraConstants.TOFDIFFTPMIN_EVENTFILTER) & (
1457
+ delta_tof <= UltraConstants.TOFDIFFTPMAX_EVENTFILTER
1458
+ )
1459
+
1460
+ combined_mask = etof_valid & spatial_valid & delta_tof_mask
1461
+
1462
+ quality_flags[~combined_mask] |= ImapDEOutliersUltraFlags.COINPH.value
1463
+
1464
+ return combined_mask
@@ -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