imap-processing 0.7.0__py3-none-any.whl → 0.8.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 (124) hide show
  1. imap_processing/__init__.py +1 -1
  2. imap_processing/_version.py +2 -2
  3. imap_processing/ccsds/excel_to_xtce.py +34 -2
  4. imap_processing/cdf/config/imap_codice_global_cdf_attrs.yaml +1 -1
  5. imap_processing/cdf/config/imap_codice_l1a_variable_attrs.yaml +145 -30
  6. imap_processing/cdf/config/imap_glows_l1b_variable_attrs.yaml +36 -36
  7. imap_processing/cdf/config/imap_hi_variable_attrs.yaml +36 -8
  8. imap_processing/cdf/config/imap_hit_l1b_variable_attrs.yaml +9 -0
  9. imap_processing/cdf/config/imap_idex_global_cdf_attrs.yaml +7 -7
  10. imap_processing/cdf/config/imap_idex_l1a_variable_attrs.yaml +32 -33
  11. imap_processing/cdf/config/imap_mag_l1_variable_attrs.yaml +24 -28
  12. imap_processing/cdf/config/imap_ultra_l1a_variable_attrs.yaml +1 -0
  13. imap_processing/cdf/config/imap_ultra_l1b_variable_attrs.yaml +133 -78
  14. imap_processing/cdf/config/imap_variable_schema.yaml +13 -0
  15. imap_processing/cdf/imap_cdf_manager.py +31 -27
  16. imap_processing/cli.py +12 -10
  17. imap_processing/codice/codice_l1a.py +151 -61
  18. imap_processing/codice/constants.py +1 -1
  19. imap_processing/codice/decompress.py +4 -9
  20. imap_processing/codice/utils.py +1 -0
  21. imap_processing/glows/l1b/glows_l1b.py +3 -3
  22. imap_processing/glows/l1b/glows_l1b_data.py +59 -37
  23. imap_processing/glows/l2/glows_l2_data.py +123 -0
  24. imap_processing/hi/l1a/histogram.py +1 -1
  25. imap_processing/hi/l1a/science_direct_event.py +1 -1
  26. imap_processing/hi/l1b/hi_l1b.py +85 -11
  27. imap_processing/hi/l1c/hi_l1c.py +23 -1
  28. imap_processing/hi/utils.py +1 -1
  29. imap_processing/hit/hit_utils.py +221 -0
  30. imap_processing/hit/l0/constants.py +118 -0
  31. imap_processing/hit/l0/decom_hit.py +186 -153
  32. imap_processing/hit/l1a/hit_l1a.py +20 -175
  33. imap_processing/hit/l1b/hit_l1b.py +33 -153
  34. imap_processing/idex/idex_l1a.py +10 -9
  35. imap_processing/lo/l0/decompression_tables/decompression_tables.py +1 -1
  36. imap_processing/lo/l0/lo_science.py +1 -1
  37. imap_processing/lo/packet_definitions/lo_xtce.xml +1 -3296
  38. imap_processing/mag/l0/decom_mag.py +4 -3
  39. imap_processing/mag/l1a/mag_l1a.py +11 -11
  40. imap_processing/mag/l1b/mag_l1b.py +89 -7
  41. imap_processing/spice/geometry.py +126 -4
  42. imap_processing/swapi/l1/swapi_l1.py +1 -1
  43. imap_processing/swapi/l2/swapi_l2.py +1 -1
  44. imap_processing/swe/l1b/swe_l1b_science.py +8 -8
  45. imap_processing/tests/ccsds/test_data/expected_output.xml +1 -0
  46. imap_processing/tests/ccsds/test_excel_to_xtce.py +4 -4
  47. imap_processing/tests/cdf/test_imap_cdf_manager.py +0 -10
  48. imap_processing/tests/codice/conftest.py +1 -17
  49. imap_processing/tests/codice/data/imap_codice_l0_raw_20241110_v001.pkts +0 -0
  50. imap_processing/tests/codice/test_codice_l0.py +8 -2
  51. imap_processing/tests/codice/test_codice_l1a.py +127 -107
  52. imap_processing/tests/codice/test_codice_l1b.py +1 -0
  53. imap_processing/tests/codice/test_decompress.py +7 -7
  54. imap_processing/tests/conftest.py +54 -15
  55. imap_processing/tests/glows/conftest.py +6 -0
  56. imap_processing/tests/glows/test_glows_l1b.py +9 -9
  57. imap_processing/tests/glows/test_glows_l1b_data.py +9 -9
  58. imap_processing/tests/glows/test_glows_l2_data.py +0 -0
  59. imap_processing/tests/hi/test_data/l1a/imap_hi_l1a_45sensor-de_20250415_v000.cdf +0 -0
  60. imap_processing/tests/hi/test_hi_l1b.py +71 -1
  61. imap_processing/tests/hi/test_hi_l1c.py +10 -2
  62. imap_processing/tests/hi/test_utils.py +4 -3
  63. imap_processing/tests/hit/{test_hit_decom.py → test_decom_hit.py} +84 -35
  64. imap_processing/tests/hit/test_hit_l1a.py +2 -197
  65. imap_processing/tests/hit/test_hit_l1b.py +156 -25
  66. imap_processing/tests/hit/test_hit_utils.py +218 -0
  67. imap_processing/tests/idex/conftest.py +1 -1
  68. imap_processing/tests/idex/imap_idex_l0_raw_20231214_v001.pkts +0 -0
  69. imap_processing/tests/idex/impact_14_tof_high_data.txt +4444 -4444
  70. imap_processing/tests/idex/test_idex_l0.py +3 -3
  71. imap_processing/tests/idex/test_idex_l1a.py +1 -1
  72. imap_processing/tests/lo/test_lo_science.py +2 -2
  73. imap_processing/tests/mag/imap_mag_l1a_norm-magi_20251017_v001.cdf +0 -0
  74. imap_processing/tests/mag/test_mag_l1b.py +59 -3
  75. imap_processing/tests/spice/test_data/imap_ena_sim_metakernel.template +3 -1
  76. imap_processing/tests/spice/test_geometry.py +84 -4
  77. imap_processing/tests/swe/conftest.py +33 -0
  78. imap_processing/tests/swe/l1_validation/swe_l0_unpacked-data_20240510_v001_VALIDATION_L1B_v3.dat +4332 -0
  79. imap_processing/tests/swe/test_swe_l1b.py +29 -8
  80. imap_processing/tests/test_utils.py +1 -1
  81. imap_processing/tests/ultra/test_data/l1/dps_exposure_helio_45_E12.cdf +0 -0
  82. imap_processing/tests/ultra/test_data/l1/dps_exposure_helio_45_E24.cdf +0 -0
  83. imap_processing/tests/ultra/unit/test_de.py +108 -0
  84. imap_processing/tests/ultra/unit/test_ultra_l1b.py +27 -3
  85. imap_processing/tests/ultra/unit/test_ultra_l1b_annotated.py +31 -10
  86. imap_processing/tests/ultra/unit/test_ultra_l1b_extended.py +21 -11
  87. imap_processing/tests/ultra/unit/test_ultra_l1c_pset_bins.py +9 -44
  88. imap_processing/ultra/constants.py +8 -3
  89. imap_processing/ultra/l1b/de.py +174 -30
  90. imap_processing/ultra/l1b/ultra_l1b_annotated.py +24 -10
  91. imap_processing/ultra/l1b/ultra_l1b_extended.py +21 -14
  92. imap_processing/ultra/l1c/ultra_l1c_pset_bins.py +70 -119
  93. {imap_processing-0.7.0.dist-info → imap_processing-0.8.0.dist-info}/METADATA +15 -14
  94. {imap_processing-0.7.0.dist-info → imap_processing-0.8.0.dist-info}/RECORD +98 -113
  95. imap_processing/cdf/cdf_attribute_manager.py +0 -322
  96. imap_processing/cdf/config/shared/default_global_cdf_attrs_schema.yaml +0 -246
  97. imap_processing/cdf/config/shared/default_variable_cdf_attrs_schema.yaml +0 -466
  98. imap_processing/hit/l0/data_classes/housekeeping.py +0 -240
  99. imap_processing/hit/l0/data_classes/science_packet.py +0 -259
  100. imap_processing/hit/l0/utils/hit_base.py +0 -57
  101. imap_processing/tests/cdf/shared/default_global_cdf_attrs_schema.yaml +0 -246
  102. imap_processing/tests/cdf/shared/default_variable_cdf_attrs_schema.yaml +0 -466
  103. imap_processing/tests/cdf/test_cdf_attribute_manager.py +0 -353
  104. imap_processing/tests/codice/data/imap_codice_l0_hi-counters-aggregated_20240429_v001.pkts +0 -0
  105. imap_processing/tests/codice/data/imap_codice_l0_hi-counters-singles_20240429_v001.pkts +0 -0
  106. imap_processing/tests/codice/data/imap_codice_l0_hi-omni_20240429_v001.pkts +0 -0
  107. imap_processing/tests/codice/data/imap_codice_l0_hi-pha_20240429_v001.pkts +0 -0
  108. imap_processing/tests/codice/data/imap_codice_l0_hi-sectored_20240429_v001.pkts +0 -0
  109. imap_processing/tests/codice/data/imap_codice_l0_hskp_20100101_v001.pkts +0 -0
  110. imap_processing/tests/codice/data/imap_codice_l0_lo-counters-aggregated_20240429_v001.pkts +0 -0
  111. imap_processing/tests/codice/data/imap_codice_l0_lo-counters-singles_20240429_v001.pkts +0 -0
  112. imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-angular_20240429_v001.pkts +0 -0
  113. imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-priority_20240429_v001.pkts +0 -0
  114. imap_processing/tests/codice/data/imap_codice_l0_lo-nsw-species_20240429_v001.pkts +0 -0
  115. imap_processing/tests/codice/data/imap_codice_l0_lo-pha_20240429_v001.pkts +0 -0
  116. imap_processing/tests/codice/data/imap_codice_l0_lo-sw-angular_20240429_v001.pkts +0 -0
  117. imap_processing/tests/codice/data/imap_codice_l0_lo-sw-priority_20240429_v001.pkts +0 -0
  118. imap_processing/tests/codice/data/imap_codice_l0_lo-sw-species_20240429_v001.pkts +0 -0
  119. imap_processing/tests/idex/imap_idex_l0_raw_20230725_v001.pkts +0 -0
  120. imap_processing/tests/mag/imap_mag_l1a_burst-magi_20231025_v001.cdf +0 -0
  121. /imap_processing/tests/hit/test_data/{imap_hit_l0_hk_20100105_v001.pkts → imap_hit_l0_raw_20100105_v001.pkts} +0 -0
  122. {imap_processing-0.7.0.dist-info → imap_processing-0.8.0.dist-info}/LICENSE +0 -0
  123. {imap_processing-0.7.0.dist-info → imap_processing-0.8.0.dist-info}/WHEEL +0 -0
  124. {imap_processing-0.7.0.dist-info → imap_processing-0.8.0.dist-info}/entry_points.txt +0 -0
@@ -7,7 +7,7 @@ from imap_processing import imap_module_directory
7
7
 
8
8
 
9
9
  def test_idex_decom_length(decom_test_data: xr.Dataset):
10
- """Verify that there are 6 data variables in the output.
10
+ """Verify that the output data has the expected number of data variables.
11
11
 
12
12
  Parameters
13
13
  ----------
@@ -18,7 +18,7 @@ def test_idex_decom_length(decom_test_data: xr.Dataset):
18
18
 
19
19
 
20
20
  def test_idex_decom_event_num(decom_test_data: xr.Dataset):
21
- """Verify that 19 impacts were gathered by the test data.
21
+ """Verify that 14 impacts were gathered by the test data.
22
22
 
23
23
  Parameters
24
24
  ----------
@@ -26,7 +26,7 @@ def test_idex_decom_event_num(decom_test_data: xr.Dataset):
26
26
  The dataset to test with
27
27
  """
28
28
  for var in decom_test_data:
29
- assert len(decom_test_data[var]) == 19
29
+ assert len(decom_test_data[var]) == 14
30
30
 
31
31
 
32
32
  def test_idex_tof_high_data(decom_test_data: xr.Dataset):
@@ -21,7 +21,7 @@ def test_idex_cdf_file(decom_test_data: xr.Dataset):
21
21
  file_name = write_cdf(decom_test_data)
22
22
 
23
23
  assert file_name.exists()
24
- assert file_name.name == "imap_idex_l1_sci_20230725_v001.cdf"
24
+ assert file_name.name == "imap_idex_l1a_sci_20231214_v001.cdf"
25
25
 
26
26
 
27
27
  def test_bad_cdf_attributes(decom_test_data: xr.Dataset):
@@ -195,9 +195,9 @@ def test_combine_segmented_packets(segmented_pkts_fake_data):
195
195
  dataset["events"].values,
196
196
  np.array(
197
197
  [
198
- "0000000001,0000000010,0000000100,0000001000",
198
+ "0000000001000000001000000001000000001000",
199
199
  "0000010000",
200
- "0100000000,1000000000",
200
+ "01000000001000000000",
201
201
  ]
202
202
  ),
203
203
  )
@@ -12,18 +12,38 @@ from imap_processing.mag.l1b.mag_l1b import mag_l1b, mag_l1b_processing
12
12
  def mag_l1a_dataset():
13
13
  epoch = xr.DataArray(np.arange(20), name="epoch", dims=["epoch"])
14
14
  direction = xr.DataArray(np.arange(4), name="direction", dims=["direction"])
15
+ compression = xr.DataArray(np.arange(2), name="compression", dims=["compression"])
16
+
17
+ direction_label = xr.DataArray(
18
+ direction.values.astype(str),
19
+ name="direction_label",
20
+ dims=["direction_label"],
21
+ )
22
+
23
+ compression_label = xr.DataArray(
24
+ compression.values.astype(str),
25
+ name="compression_label",
26
+ dims=["compression_label"],
27
+ )
28
+
15
29
  vectors = xr.DataArray(
16
30
  np.zeros((20, 4)),
17
31
  dims=["epoch", "direction"],
18
32
  coords={"epoch": epoch, "direction": direction},
19
33
  )
34
+ compression_flags = xr.DataArray(
35
+ np.zeros((20, 2), dtype=np.int8), dims=["epoch", "compression"]
36
+ )
20
37
 
21
38
  vectors[0, :] = np.array([1, 1, 1, 0])
22
39
 
23
40
  output_dataset = xr.Dataset(
24
- coords={"epoch": epoch, "direction": direction},
41
+ coords={"epoch": epoch, "direction": direction, "compression": compression},
25
42
  )
26
43
  output_dataset["vectors"] = vectors
44
+ output_dataset["compression_flags"] = compression_flags
45
+ output_dataset["direction_label"] = direction_label
46
+ output_dataset["compression_label"] = compression_label
27
47
 
28
48
  return output_dataset
29
49
 
@@ -66,13 +86,49 @@ def test_mag_attributes(mag_l1a_dataset):
66
86
  assert output.attrs["Data_level"] == "L1B"
67
87
 
68
88
 
69
- @pytest.mark.skip(reason="Epoch variable data need to be monotonically increasing")
70
89
  def test_cdf_output():
71
90
  l1a_cdf = load_cdf(
72
- Path(__file__).parent / "imap_mag_l1a_burst-magi_20231025_v001.cdf"
91
+ Path(__file__).parent / "imap_mag_l1a_norm-magi_20251017_v001.cdf"
73
92
  )
74
93
  l1b_dataset = mag_l1b(l1a_cdf, "v001")
75
94
 
76
95
  output_path = write_cdf(l1b_dataset)
77
96
 
78
97
  assert Path.exists(output_path)
98
+
99
+
100
+ def test_mag_compression_scale(mag_l1a_dataset):
101
+ test_calibration = np.array(
102
+ [
103
+ [2.2972202, 0.0, 0.0],
104
+ [0.00348625, 2.23802879, 0.0],
105
+ [-0.00250788, -0.00888437, 2.24950008],
106
+ ]
107
+ )
108
+ mag_l1a_dataset["vectors"][0, :] = np.array([1, 1, 1, 0])
109
+ mag_l1a_dataset["vectors"][1, :] = np.array([1, 1, 1, 0])
110
+ mag_l1a_dataset["vectors"][2, :] = np.array([1, 1, 1, 0])
111
+ mag_l1a_dataset["vectors"][3, :] = np.array([1, 1, 1, 0])
112
+
113
+ mag_l1a_dataset["compression_flags"][0, :] = np.array([1, 16], dtype=np.int8)
114
+ mag_l1a_dataset["compression_flags"][1, :] = np.array([0, 0], dtype=np.int8)
115
+ mag_l1a_dataset["compression_flags"][2, :] = np.array([1, 18], dtype=np.int8)
116
+ mag_l1a_dataset["compression_flags"][3, :] = np.array([1, 14], dtype=np.int8)
117
+
118
+ mag_l1a_dataset.attrs["Logical_source"] = ["imap_mag_l1a_norm-mago"]
119
+ output = mag_l1b(mag_l1a_dataset, "v001")
120
+
121
+ calibrated_vectors = np.matmul(np.array([1, 1, 1]), test_calibration)
122
+ # 16 bit width is the standard
123
+ assert np.allclose(output["vectors"].data[0][:3], calibrated_vectors)
124
+ # uncompressed data is uncorrected
125
+ assert np.allclose(output["vectors"].data[1][:3], calibrated_vectors)
126
+
127
+ # width of 18 should be multiplied by 1/4
128
+ scaled_vectors = calibrated_vectors * 1 / 4
129
+ # should be corrected
130
+ assert np.allclose(output["vectors"].data[2][:3], scaled_vectors)
131
+
132
+ # width of 14 should be multiplied by 4
133
+ scaled_vectors = calibrated_vectors * 4
134
+ assert np.allclose(output["vectors"].data[3][:3], scaled_vectors)
@@ -3,4 +3,6 @@
3
3
  {SPICE_TEST_DATA_PATH}/imap_spk_demo.bsp
4
4
  {SPICE_TEST_DATA_PATH}/sim_1yr_imap_attitude.bc
5
5
  {SPICE_TEST_DATA_PATH}/imap_wkcp.tf
6
- {SPICE_TEST_DATA_PATH}/de440s.bsp
6
+ {SPICE_TEST_DATA_PATH}/de440s.bsp
7
+ {SPICE_TEST_DATA_PATH}/imap_science_0001.tf
8
+ {SPICE_TEST_DATA_PATH}/sim_1yr_imap_pointing_frame.bc
@@ -8,6 +8,8 @@ import spiceypy as spice
8
8
  from imap_processing.spice.geometry import (
9
9
  SpiceBody,
10
10
  SpiceFrame,
11
+ basis_vectors,
12
+ cartesian_to_spherical,
11
13
  frame_transform,
12
14
  get_instrument_spin_phase,
13
15
  get_rotation_matrix,
@@ -16,7 +18,9 @@ from imap_processing.spice.geometry import (
16
18
  get_spin_data,
17
19
  imap_state,
18
20
  instrument_pointing,
21
+ spherical_to_cartesian,
19
22
  )
23
+ from imap_processing.spice.kernels import ensure_spice
20
24
 
21
25
 
22
26
  @pytest.mark.parametrize(
@@ -99,10 +103,10 @@ def test_get_spacecraft_spin_phase_value_error(query_met_times, fake_spin_data):
99
103
  _ = get_spacecraft_spin_phase(query_met_times)
100
104
 
101
105
 
102
- @pytest.mark.usefixtures("_set_spin_data_filepath")
103
- def test_get_spin_data():
106
+ @pytest.mark.usefixtures("use_fake_spin_data_for_time")
107
+ def test_get_spin_data(use_fake_spin_data_for_time):
104
108
  """Test get_spin_data() with generated spin data."""
105
-
109
+ use_fake_spin_data_for_time(453051323.0 - 56120)
106
110
  spin_data = get_spin_data()
107
111
 
108
112
  (
@@ -250,7 +254,7 @@ def test_frame_transform_exceptions():
250
254
  match="Mismatch in number of position vectors and Ephemeris times provided.",
251
255
  ):
252
256
  frame_transform(
253
- np.arange(2),
257
+ 1,
254
258
  np.arange(9).reshape((3, 3)),
255
259
  SpiceFrame.ECLIPJ2000,
256
260
  SpiceFrame.IMAP_HIT,
@@ -306,3 +310,79 @@ def test_instrument_pointing(furnish_kernels):
306
310
  et, SpiceFrame.IMAP_HI_90, SpiceFrame.ECLIPJ2000, cartesian=True
307
311
  )
308
312
  assert ins_pointing.shape == (3, 3)
313
+
314
+
315
+ @pytest.mark.external_kernel()
316
+ @pytest.mark.use_test_metakernel("imap_ena_sim_metakernel.template")
317
+ def test_basis_vectors():
318
+ """Test coverage for basis_vectors()."""
319
+ # This call to SPICE needs to be wrapped with `ensure_spice` so that kernels
320
+ # get furnished automatically
321
+ et = ensure_spice(spice.utc2et)("2025-09-30T12:00:00.000")
322
+ # test input of float
323
+ sc_axes = basis_vectors(et, SpiceFrame.IMAP_SPACECRAFT, SpiceFrame.IMAP_SPACECRAFT)
324
+ np.testing.assert_array_equal(sc_axes, np.eye(3))
325
+ # test array of et input
326
+ et_array = np.arange(10) + et
327
+ sc_axes = basis_vectors(et_array, SpiceFrame.IMAP_SPACECRAFT, SpiceFrame.ECLIPJ2000)
328
+ assert sc_axes.shape == (10, 3, 3)
329
+ # Verify that for each time, the basis vectors are correct
330
+ for et, basis_matrix in zip(et_array, sc_axes):
331
+ np.testing.assert_array_equal(
332
+ basis_matrix,
333
+ frame_transform(
334
+ et * np.ones(3),
335
+ np.eye(3),
336
+ SpiceFrame.IMAP_SPACECRAFT,
337
+ SpiceFrame.ECLIPJ2000,
338
+ ),
339
+ )
340
+
341
+
342
+ def test_cartesian_to_spherical():
343
+ """Tests cartesian_to_spherical function."""
344
+
345
+ step = 0.05
346
+ x = np.arange(-1, 1 + step, step)
347
+ y = np.arange(-1, 1 + step, step)
348
+ z = np.arange(-1, 1 + step, step)
349
+ x, y, z = np.meshgrid(x, y, z)
350
+
351
+ cartesian_points = np.stack((x.ravel(), y.ravel(), z.ravel()), axis=-1)
352
+
353
+ for point in cartesian_points:
354
+ r, az, el = cartesian_to_spherical(point)
355
+ r_spice, colat_spice, slong_spice = spice.recsph(point)
356
+
357
+ # Convert SPICE co-latitude to elevation
358
+ el_spice = 90 - np.degrees(colat_spice)
359
+ az_spice = np.degrees(slong_spice)
360
+
361
+ # Normalize azimuth to [0, 360]
362
+ az_spice = az_spice % 360
363
+
364
+ np.testing.assert_allclose(r, r_spice, atol=1e-5)
365
+ np.testing.assert_allclose(az, az_spice, atol=1e-5)
366
+ np.testing.assert_allclose(el, el_spice, atol=1e-5)
367
+
368
+
369
+ def test_spherical_to_cartesian():
370
+ """Tests spherical_to_cartesian function."""
371
+
372
+ azimuth = np.linspace(0, 2 * np.pi, 50)
373
+ elevation = np.linspace(-np.pi / 2, np.pi / 2, 50)
374
+ theta, elev = np.meshgrid(azimuth, elevation)
375
+ r = 1.0
376
+
377
+ spherical_points = np.stack(
378
+ (r * np.ones_like(theta).ravel(), theta.ravel(), elev.ravel()), axis=-1
379
+ )
380
+
381
+ # Convert elevation to colatitude for SPICE
382
+ colat = np.pi / 2 - spherical_points[:, 2]
383
+
384
+ for i in range(len(colat)):
385
+ cartesian_coords = spherical_to_cartesian(np.array([spherical_points[i]]))
386
+ spice_coords = spice.sphrec(r, colat[i], spherical_points[i, 1])
387
+
388
+ np.testing.assert_allclose(cartesian_coords[0], spice_coords, atol=1e-5)
@@ -74,3 +74,36 @@ def l1a_validation_df():
74
74
  # Fill NaNs with the previous value
75
75
  df["shcoarse"] = df["shcoarse"].ffill()
76
76
  return df
77
+
78
+
79
+ @pytest.fixture(scope="session")
80
+ def l1b_validation_df():
81
+ """Read validation data from file"""
82
+ l1_val_path = imap_module_directory / "tests/swe/l1_validation"
83
+ filename = "swe_l0_unpacked-data_20240510_v001_VALIDATION_L1B_v3.dat"
84
+
85
+ # Define column names for validation data
86
+ column_names = [
87
+ "shcoarse",
88
+ "cem_1",
89
+ "cem_2",
90
+ "cem_3",
91
+ "cem_4",
92
+ "cem_5",
93
+ "cem_6",
94
+ "cem_7",
95
+ ]
96
+
97
+ # Read the data, specifying na_values and delimiter
98
+ df = pd.read_csv(
99
+ l1_val_path / filename,
100
+ skiprows=12, # Skip the first 10 rows of comments
101
+ sep=r"\s*,\s*", # Regex to handle spaces and commas as delimiters
102
+ names=column_names,
103
+ na_values=["", " "], # Treat empty strings or spaces as NaN
104
+ engine="python",
105
+ )
106
+
107
+ # Fill NaNs with the previous value
108
+ df["shcoarse"] = df["shcoarse"].ffill()
109
+ return df