imap-processing 0.14.0__py3-none-any.whl → 0.16.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.

Files changed (81) hide show
  1. imap_processing/_version.py +2 -2
  2. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +60 -35
  3. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +765 -287
  4. imap_processing/cdf/config/imap_codice_l1b_variable_attrs.yaml +1577 -288
  5. imap_processing/cdf/config/imap_codice_l2_variable_attrs.yaml +1004 -0
  6. imap_processing/cdf/config/imap_enamaps_l2-common_variable_attrs.yaml +28 -0
  7. imap_processing/cdf/config/imap_enamaps_l2-healpix_variable_attrs.yaml +1 -1
  8. imap_processing/cdf/config/imap_enamaps_l2-rectangular_variable_attrs.yaml +18 -0
  9. imap_processing/cdf/config/imap_glows_l2_variable_attrs.yaml +39 -3
  10. imap_processing/cdf/config/imap_ialirt_global_cdf_attrs.yaml +18 -0
  11. imap_processing/cdf/config/imap_ialirt_l1_variable_attrs.yaml +353 -0
  12. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +7 -0
  13. imap_processing/cdf/config/imap_idex_l1b_variable_attrs.yaml +11 -0
  14. imap_processing/cdf/config/imap_idex_l2a_variable_attrs.yaml +4 -0
  15. imap_processing/cdf/config/imap_idex_l2c_variable_attrs.yaml +7 -3
  16. imap_processing/cdf/config/imap_lo_global_cdf_attrs.yaml +6 -0
  17. imap_processing/cdf/config/imap_mag_l2_variable_attrs.yaml +114 -0
  18. imap_processing/cdf/config/imap_swe_global_cdf_attrs.yaml +11 -5
  19. imap_processing/cdf/config/imap_swe_l1b_variable_attrs.yaml +23 -1
  20. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +4 -0
  21. imap_processing/cdf/config/imap_ultra_l1c_variable_attrs.yaml +2 -2
  22. imap_processing/cli.py +145 -80
  23. imap_processing/codice/codice_l1a.py +140 -84
  24. imap_processing/codice/codice_l1b.py +91 -18
  25. imap_processing/codice/codice_l2.py +81 -0
  26. imap_processing/codice/constants.py +68 -0
  27. imap_processing/ena_maps/ena_maps.py +43 -1
  28. imap_processing/glows/l2/glows_l2_data.py +3 -6
  29. imap_processing/hi/hi_l1a.py +447 -0
  30. imap_processing/hi/{l1b/hi_l1b.py → hi_l1b.py} +1 -1
  31. imap_processing/hi/{l1c/hi_l1c.py → hi_l1c.py} +21 -21
  32. imap_processing/hi/{l2/hi_l2.py → hi_l2.py} +13 -13
  33. imap_processing/hi/utils.py +6 -6
  34. imap_processing/hit/l1b/hit_l1b.py +30 -11
  35. imap_processing/ialirt/constants.py +38 -0
  36. imap_processing/ialirt/l0/parse_mag.py +1 -1
  37. imap_processing/ialirt/l0/process_codice.py +91 -0
  38. imap_processing/ialirt/l0/process_hit.py +12 -21
  39. imap_processing/ialirt/l0/process_swapi.py +172 -23
  40. imap_processing/ialirt/l0/process_swe.py +3 -10
  41. imap_processing/ialirt/utils/constants.py +62 -0
  42. imap_processing/ialirt/utils/create_xarray.py +135 -0
  43. imap_processing/idex/idex_l2c.py +9 -9
  44. imap_processing/lo/l1b/lo_l1b.py +6 -1
  45. imap_processing/lo/l1c/lo_l1c.py +22 -13
  46. imap_processing/lo/l2/lo_l2.py +213 -0
  47. imap_processing/mag/l1c/mag_l1c.py +8 -1
  48. imap_processing/mag/l2/mag_l2.py +6 -2
  49. imap_processing/mag/l2/mag_l2_data.py +7 -5
  50. imap_processing/swe/l1a/swe_l1a.py +6 -6
  51. imap_processing/swe/l1b/swe_l1b.py +70 -11
  52. imap_processing/ultra/l0/decom_ultra.py +1 -1
  53. imap_processing/ultra/l0/ultra_utils.py +0 -4
  54. imap_processing/ultra/l1b/badtimes.py +7 -3
  55. imap_processing/ultra/l1b/cullingmask.py +7 -2
  56. imap_processing/ultra/l1b/de.py +26 -12
  57. imap_processing/ultra/l1b/lookup_utils.py +8 -7
  58. imap_processing/ultra/l1b/ultra_l1b.py +59 -48
  59. imap_processing/ultra/l1b/ultra_l1b_culling.py +50 -18
  60. imap_processing/ultra/l1b/ultra_l1b_extended.py +4 -4
  61. imap_processing/ultra/l1c/helio_pset.py +53 -0
  62. imap_processing/ultra/l1c/spacecraft_pset.py +20 -12
  63. imap_processing/ultra/l1c/ultra_l1c.py +49 -26
  64. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +40 -2
  65. imap_processing/ultra/l2/ultra_l2.py +47 -2
  66. imap_processing/ultra/lookup_tables/Angular_Profiles_FM90_RightSlit.csv +524 -526
  67. imap_processing/ultra/utils/ultra_l1_utils.py +51 -10
  68. {imap_processing-0.14.0.dist-info → imap_processing-0.16.0.dist-info}/METADATA +2 -2
  69. {imap_processing-0.14.0.dist-info → imap_processing-0.16.0.dist-info}/RECORD +72 -69
  70. imap_processing/hi/l1a/__init__.py +0 -0
  71. imap_processing/hi/l1a/hi_l1a.py +0 -98
  72. imap_processing/hi/l1a/histogram.py +0 -152
  73. imap_processing/hi/l1a/science_direct_event.py +0 -214
  74. imap_processing/hi/l1b/__init__.py +0 -0
  75. imap_processing/hi/l1c/__init__.py +0 -0
  76. imap_processing/hi/l2/__init__.py +0 -0
  77. imap_processing/ialirt/l0/process_codicehi.py +0 -156
  78. imap_processing/ialirt/l0/process_codicelo.py +0 -41
  79. {imap_processing-0.14.0.dist-info → imap_processing-0.16.0.dist-info}/LICENSE +0 -0
  80. {imap_processing-0.14.0.dist-info → imap_processing-0.16.0.dist-info}/WHEEL +0 -0
  81. {imap_processing-0.14.0.dist-info → imap_processing-0.16.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,53 @@
1
+ """Calculate Pointing Set Grids."""
2
+
3
+ import astropy_healpix.healpy as hp
4
+ import numpy as np
5
+ import xarray as xr
6
+
7
+ from imap_processing.ultra.l1c.ultra_l1c_pset_bins import build_energy_bins
8
+ from imap_processing.ultra.utils.ultra_l1_utils import create_dataset
9
+
10
+
11
+ def calculate_helio_pset(
12
+ de_dataset: xr.Dataset,
13
+ extendedspin_dataset: xr.Dataset,
14
+ cullingmask_dataset: xr.Dataset,
15
+ name: str,
16
+ ancillary_files: dict,
17
+ ) -> xr.Dataset:
18
+ """
19
+ Create dictionary with defined datatype for Pointing Set Grid Data.
20
+
21
+ Parameters
22
+ ----------
23
+ de_dataset : xarray.Dataset
24
+ Dataset containing de data.
25
+ extendedspin_dataset : xarray.Dataset
26
+ Dataset containing extendedspin data.
27
+ cullingmask_dataset : xarray.Dataset
28
+ Dataset containing cullingmask data.
29
+ name : str
30
+ Name of the dataset.
31
+ ancillary_files : dict
32
+ Ancillary files.
33
+
34
+ Returns
35
+ -------
36
+ dataset : xarray.Dataset
37
+ Dataset containing the data.
38
+ """
39
+ # TODO: Fill in the rest of this later.
40
+ pset_dict: dict[str, np.ndarray] = {}
41
+ healpix = np.arange(hp.nside2npix(128))
42
+ _, _, energy_bin_geometric_means = build_energy_bins()
43
+
44
+ pset_dict["epoch"] = de_dataset.epoch.data[:1].astype(np.int64)
45
+ pset_dict["pixel_index"] = healpix
46
+ pset_dict["energy_bin_geometric_mean"] = energy_bin_geometric_means
47
+ pset_dict["exposure_factor"] = np.zeros(len(healpix), dtype=np.uint8)[
48
+ np.newaxis, ...
49
+ ]
50
+
51
+ dataset = create_dataset(pset_dict, name, "l1c")
52
+
53
+ return dataset
@@ -4,24 +4,22 @@ import numpy as np
4
4
  import pandas as pd
5
5
  import xarray as xr
6
6
 
7
- from imap_processing import imap_module_directory
8
7
  from imap_processing.ultra.l1c.ultra_l1c_pset_bins import (
9
8
  build_energy_bins,
10
9
  get_spacecraft_background_rates,
11
10
  get_spacecraft_exposure_times,
12
11
  get_spacecraft_histogram,
12
+ interpolate_sensitivity,
13
13
  )
14
14
  from imap_processing.ultra.utils.ultra_l1_utils import create_dataset
15
15
 
16
- # TODO: This is a placeholder for the API lookup table directory.
17
- TEST_PATH = imap_module_directory / "tests" / "ultra" / "data" / "l1"
18
-
19
16
 
20
17
  def calculate_spacecraft_pset(
21
18
  de_dataset: xr.Dataset,
22
19
  extendedspin_dataset: xr.Dataset,
23
20
  cullingmask_dataset: xr.Dataset,
24
21
  name: str,
22
+ ancillary_files: dict,
25
23
  ) -> xr.Dataset:
26
24
  """
27
25
  Create dictionary with defined datatype for Pointing Set Grid Data.
@@ -36,6 +34,8 @@ def calculate_spacecraft_pset(
36
34
  Dataset containing cullingmask data.
37
35
  name : str
38
36
  Name of the dataset.
37
+ ancillary_files : dict
38
+ Ancillary files.
39
39
 
40
40
  Returns
41
41
  -------
@@ -61,23 +61,31 @@ def calculate_spacecraft_pset(
61
61
  # calculate background rates
62
62
  background_rates = get_spacecraft_background_rates()
63
63
 
64
- # TODO: calculate sensitivity and interpolate based on energy.
64
+ efficiencies = ancillary_files["l1c-90sensor-efficiencies"]
65
+ geometric_function = ancillary_files["l1c-90sensor-gf"]
66
+
67
+ df_efficiencies = pd.read_csv(efficiencies)
68
+ df_geometric_function = pd.read_csv(geometric_function)
69
+ sensitivity = interpolate_sensitivity(df_efficiencies, df_geometric_function)
65
70
 
66
71
  # Calculate exposure
67
- constant_exposure = TEST_PATH / "ultra_90_dps_exposure.csv"
72
+ constant_exposure = ancillary_files["l1c-90sensor-dps-exposure"]
68
73
  df_exposure = pd.read_csv(constant_exposure)
69
74
  exposure_pointing = get_spacecraft_exposure_times(df_exposure)
70
75
 
71
76
  # For ISTP, epoch should be the center of the time bin.
72
77
  pset_dict["epoch"] = de_dataset.epoch.data[:1].astype(np.int64)
73
- pset_dict["counts"] = counts
74
- pset_dict["latitude"] = latitude
75
- pset_dict["longitude"] = longitude
78
+ pset_dict["counts"] = counts[np.newaxis, ...]
79
+ pset_dict["latitude"] = latitude[np.newaxis, ...]
80
+ pset_dict["longitude"] = longitude[np.newaxis, ...]
76
81
  pset_dict["energy_bin_geometric_mean"] = energy_bin_geometric_means
77
- pset_dict["background_rates"] = background_rates
78
- pset_dict["exposure_factor"] = exposure_pointing
82
+ pset_dict["background_rates"] = background_rates[np.newaxis, ...]
83
+ pset_dict["exposure_factor"] = exposure_pointing.to_numpy()[np.newaxis, ...]
79
84
  pset_dict["pixel_index"] = healpix
80
- pset_dict["energy_bin_delta"] = np.diff(intervals, axis=1).squeeze()
85
+ pset_dict["energy_bin_delta"] = np.diff(intervals, axis=1).squeeze()[
86
+ np.newaxis, ...
87
+ ]
88
+ pset_dict["sensitivity"] = sensitivity[np.newaxis, ...]
81
89
 
82
90
  dataset = create_dataset(pset_dict, name, "l1c")
83
91
 
@@ -2,11 +2,14 @@
2
2
 
3
3
  import xarray as xr
4
4
 
5
+ from imap_processing.ultra.l1c.helio_pset import calculate_helio_pset
5
6
  from imap_processing.ultra.l1c.histogram import calculate_histogram
6
7
  from imap_processing.ultra.l1c.spacecraft_pset import calculate_spacecraft_pset
7
8
 
8
9
 
9
- def ultra_l1c(data_dict: dict) -> list[xr.Dataset]:
10
+ def ultra_l1c(
11
+ data_dict: dict, ancillary_files: dict, has_spice: bool
12
+ ) -> list[xr.Dataset]:
10
13
  """
11
14
  Will process ULTRA L1A and L1B data into L1C CDF files at output_filepath.
12
15
 
@@ -14,37 +17,57 @@ def ultra_l1c(data_dict: dict) -> list[xr.Dataset]:
14
17
  ----------
15
18
  data_dict : dict
16
19
  The data itself and its dependent data.
20
+ ancillary_files : dict
21
+ Ancillary files.
22
+ has_spice : bool
23
+ Whether to use SPICE data.
17
24
 
18
25
  Returns
19
26
  -------
20
27
  output_datasets : list[xarray.Dataset]
21
28
  List of xarray.Dataset.
22
29
  """
23
- instrument_id = 45 if any("45" in key for key in data_dict.keys()) else 90
24
-
25
- if (
26
- f"imap_ultra_l1a_{instrument_id}sensor-histogram" in data_dict
27
- and f"imap_ultra_l1b_{instrument_id}sensor-cullingmask" in data_dict
28
- ):
29
- histogram_dataset = calculate_histogram(
30
- data_dict[f"imap_ultra_l1a_{instrument_id}sensor-histogram"],
31
- f"imap_ultra_l1c_{instrument_id}sensor-histogram",
32
- )
33
- output_datasets = [histogram_dataset]
34
- elif (
35
- f"imap_ultra_l1b_{instrument_id}sensor-cullingmask" in data_dict
36
- and f"imap_ultra_l1b_{instrument_id}sensor-de" in data_dict
37
- and f"imap_ultra_l1b_{instrument_id}sensor-extendedspin" in data_dict
38
- ):
39
- spacecraft_pset = calculate_spacecraft_pset(
40
- data_dict[f"imap_ultra_l1b_{instrument_id}sensor-de"],
41
- data_dict[f"imap_ultra_l1b_{instrument_id}sensor-extendedspin"],
42
- data_dict[f"imap_ultra_l1b_{instrument_id}sensor-cullingmask"],
43
- f"imap_ultra_l1c_{instrument_id}sensor-spacecraftpset",
44
- )
45
- # TODO: add calculate_helio_pset here
46
- output_datasets = [spacecraft_pset]
47
- else:
30
+ output_datasets = []
31
+
32
+ # Account for possibility of having 45 and 90 in dictionary.
33
+ for instrument_id in [45, 90]:
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
45
+ 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
48
+ ):
49
+ helio_pset = calculate_helio_pset(
50
+ 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"],
53
+ f"imap_ultra_l1c_{instrument_id}sensor-heliopset",
54
+ ancillary_files,
55
+ )
56
+ output_datasets = [helio_pset]
57
+ elif (
58
+ f"imap_ultra_l1b_{instrument_id}sensor-cullingmask" in data_dict
59
+ and f"imap_ultra_l1b_{instrument_id}sensor-de" in data_dict
60
+ and f"imap_ultra_l1b_{instrument_id}sensor-extendedspin" in data_dict
61
+ ):
62
+ spacecraft_pset = calculate_spacecraft_pset(
63
+ data_dict[f"imap_ultra_l1b_{instrument_id}sensor-de"],
64
+ data_dict[f"imap_ultra_l1b_{instrument_id}sensor-extendedspin"],
65
+ data_dict[f"imap_ultra_l1b_{instrument_id}sensor-cullingmask"],
66
+ f"imap_ultra_l1c_{instrument_id}sensor-spacecraftpset",
67
+ ancillary_files,
68
+ )
69
+ output_datasets = [spacecraft_pset]
70
+ if not output_datasets:
48
71
  raise ValueError("Data dictionary does not contain the expected keys.")
49
72
 
50
73
  return output_datasets
@@ -15,6 +15,7 @@ from imap_processing.spice.geometry import (
15
15
  from imap_processing.ultra.constants import UltraConstants
16
16
 
17
17
  # TODO: add species binning.
18
+ FILLVAL_FLOAT32 = -1.0e31
18
19
 
19
20
 
20
21
  def build_energy_bins() -> tuple[list[tuple[float, float]], np.ndarray, np.ndarray]:
@@ -478,8 +479,6 @@ def grid_sensitivity(
478
479
  Efficiencies at different energy levels.
479
480
  geometric_function : pandas.DataFrame
480
481
  Geometric function.
481
- energy : np.ndarray
482
- The particle energy.
483
482
  energy : float
484
483
  Energy to which we are interpolating.
485
484
 
@@ -503,10 +502,49 @@ def grid_sensitivity(
503
502
 
504
503
  # Interpolate to energy
505
504
  interpolated = interp_func(energy)
505
+ interpolated = np.where(np.isnan(interpolated), FILLVAL_FLOAT32, interpolated)
506
506
 
507
507
  return interpolated
508
508
 
509
509
 
510
+ def interpolate_sensitivity(
511
+ efficiencies: pd.DataFrame,
512
+ geometric_function: pd.DataFrame,
513
+ nside: int = 128,
514
+ ) -> NDArray:
515
+ """
516
+ Interpolate the sensitivity and bin it in HEALPix space.
517
+
518
+ Parameters
519
+ ----------
520
+ efficiencies : pandas.DataFrame
521
+ Efficiencies at different energy levels.
522
+ geometric_function : pandas.DataFrame
523
+ Geometric function.
524
+ nside : int, optional
525
+ Healpix nside resolution (default is 128).
526
+
527
+ Returns
528
+ -------
529
+ interpolated_sensitivity : np.ndarray
530
+ Array of shape (n_energy_bins, n_healpix_pixels).
531
+ """
532
+ _, _, energy_bin_geometric_means = build_energy_bins()
533
+ npix = hp.nside2npix(nside)
534
+
535
+ interpolated_sensitivity = np.full(
536
+ (len(energy_bin_geometric_means), npix), FILLVAL_FLOAT32
537
+ )
538
+
539
+ for i, energy in enumerate(energy_bin_geometric_means):
540
+ pixel_sensitivity = grid_sensitivity(
541
+ efficiencies, geometric_function, energy
542
+ ).flatten()
543
+ interpolated_sensitivity[i, :] = pixel_sensitivity
544
+
545
+ return interpolated_sensitivity
546
+
547
+
510
548
  def get_helio_sensitivity(
511
549
  time: np.ndarray,
512
550
  efficiencies: pandas.DataFrame,
@@ -77,12 +77,18 @@ VARIABLES_TO_DROP_AFTER_INTENSITY_CALCULATION = [
77
77
  "pointing_set_exposure_times_solid_angle",
78
78
  "num_pointing_set_pixel_members",
79
79
  "corrected_count_rate",
80
+ "obs_date_for_std",
81
+ "obs_date_squared_for_std",
80
82
  ]
81
83
 
82
84
  # These variables may or may not be energy dependent, depending on the
83
85
  # input data. They must be handled slightly differently when it comes to adding
84
86
  # metadata to the map dataset.
85
- INCONSISTENTLY_ENERGY_DEPENDENT_VARIABLES = ["obs_date", "exposure_factor"]
87
+ INCONSISTENTLY_ENERGY_DEPENDENT_VARIABLES = [
88
+ "obs_date",
89
+ "exposure_factor",
90
+ "obs_date_range",
91
+ ]
86
92
 
87
93
 
88
94
  def get_variable_attributes_optional_energy_dependence(
@@ -200,6 +206,8 @@ def generate_ultra_healpix_skymap(
200
206
  output_map_structure.values_to_push_project.extend(
201
207
  [
202
208
  "num_pointing_set_pixel_members",
209
+ "obs_date_for_std",
210
+ "obs_date_squared_for_std",
203
211
  ]
204
212
  )
205
213
  output_map_structure.values_to_pull_project.extend(
@@ -254,6 +262,13 @@ def generate_ultra_healpix_skymap(
254
262
  fill_value=pointing_set.epoch,
255
263
  dtype=np.int64,
256
264
  )
265
+ pointing_set.data["obs_date_for_std"] = pointing_set.data["obs_date"].astype(
266
+ np.float64
267
+ )
268
+ pointing_set.data["obs_date_squared_for_std"] = (
269
+ pointing_set.data["obs_date_for_std"] ** 2
270
+ )
271
+
257
272
  # Add solid_angle * exposure of pointing set as data_var
258
273
  # so this quantity is projected to map pixels for use in weighted averaging
259
274
  pointing_set.data["pointing_set_exposure_times_solid_angle"] = (
@@ -291,6 +306,10 @@ def generate_ultra_healpix_skymap(
291
306
 
292
307
  # Get the energy bin widths from a PointingSet (they will all be the same)
293
308
  delta_energy = pointing_set.data["energy_bin_delta"]
309
+ if CoordNames.TIME.value in delta_energy.dims:
310
+ delta_energy = delta_energy.mean(
311
+ dim=CoordNames.TIME.value,
312
+ )
294
313
 
295
314
  # Core calculations of ena_intensity and its statistical uncertainty for L2
296
315
  # Exposure time may contain 0s, producing NaNs in the corrected count rate
@@ -317,6 +336,27 @@ def generate_ultra_healpix_skymap(
317
336
  * delta_energy
318
337
  )
319
338
 
339
+ # Calculate the standard deviation of the observation date as:
340
+ # sqrt((sum(obs_date^2) / N) - (sum(obs_date) / N)^2)
341
+ # where sum here refers to the projection process
342
+ # summing over N pset pixels across different psets
343
+ skymap.data_1d["obs_date_range"] = (
344
+ (
345
+ (
346
+ skymap.data_1d["obs_date_squared_for_std"]
347
+ / (skymap.data_1d["num_pointing_set_pixel_members"])
348
+ )
349
+ - (
350
+ (
351
+ skymap.data_1d["obs_date_for_std"]
352
+ / (skymap.data_1d["num_pointing_set_pixel_members"])
353
+ )
354
+ ** 2
355
+ )
356
+ )
357
+ ** 0.5
358
+ ).astype(np.int64)
359
+
320
360
  # Drop the variables that are no longer needed
321
361
  skymap.data_1d = skymap.data_1d.drop_vars(
322
362
  VARIABLES_TO_DROP_AFTER_INTENSITY_CALCULATION,
@@ -425,7 +465,6 @@ def ultra_l2(
425
465
  data=healpix_skymap.az_el_points[:, i],
426
466
  dims=(CoordNames.GENERIC_PIXEL.value,),
427
467
  )
428
-
429
468
  map_dataset = healpix_skymap.to_dataset()
430
469
  # Add attributes related to the map
431
470
  map_attrs = {
@@ -517,6 +556,12 @@ def ultra_l2(
517
556
  name=f"{coord_var}_label",
518
557
  )
519
558
 
559
+ # Add systematic error as all zeros with shape matching statistical unc
560
+ # TODO: update once we have information from the instrument team
561
+ map_dataset["ena_intensity_sys_err"] = xr.zeros_like(
562
+ map_dataset["ena_intensity_stat_unc"],
563
+ )
564
+
520
565
  # Add epoch_delta
521
566
  map_dataset.coords["epoch_delta"] = xr.DataArray(
522
567
  [