Skip to content

Commit da17246

Browse files
authored
2434 hi lo l2 pull sc velocity and ram mask functions out of cg correction (#2468)
* Pull add_spacecraft_velocity_to_pset and calculate_ram_mask out of CG correction for better commonality between SF and HF map making * Update Hi and Lo L2 pipelines to always call add_spacecraft_velocity_to_pset and calculate_ram_mask * Remove duplicate calculation
1 parent e277753 commit da17246

File tree

5 files changed

+67
-70
lines changed

5 files changed

+67
-70
lines changed

imap_processing/ena_maps/utils/corrections.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -472,11 +472,12 @@ def _calculate_compton_getting_transform(
472472

473473
# Calculate dot product between look directions and spacecraft direction vector
474474
# Use Einstein summation for efficient vectorized dot product
475+
sc_direction_vector = pset["sc_velocity"] / sc_velocity_km_per_sec
475476
dot_product = xr.DataArray(
476477
np.einsum(
477478
"...i,...i->...",
478479
pset["look_direction"],
479-
pset["sc_direction_vector"],
480+
sc_direction_vector,
480481
),
481482
dims=pset["look_direction"].dims[:-1],
482483
)
@@ -543,9 +544,6 @@ def _calculate_compton_getting_transform(
543544
ena_source_direction_helio[..., 2],
544545
)
545546

546-
# Update the PSET ram mask.
547-
pset = calculate_ram_mask(pset)
548-
549547
return pset
550548

551549

@@ -560,7 +558,7 @@ def calculate_ram_mask(pset: xr.Dataset) -> xr.Dataset:
560558
----------
561559
pset : xarray.Dataset
562560
Pointing set dataset. The pset dataset is assumed to have valid
563-
"hae_longitude", "hae_latitude", and "sc_direction_vector" variables.
561+
"hae_longitude", "hae_latitude", and "sc_velocity" variables.
564562
565563
Returns
566564
-------
@@ -569,12 +567,12 @@ def calculate_ram_mask(pset: xr.Dataset) -> xr.Dataset:
569567
"""
570568
logger.debug(
571569
f"Calculating the RAM mask using input spacecraft direction "
572-
f"vector: {pset['sc_direction_vector'].values} and hae coordinates in "
570+
f"vector: {pset['sc_velocity'].values} and hae coordinates in "
573571
f"the dataset hae_longitude and hae_latitude variables."
574572
)
575573
longitude = pset["hae_longitude"]
576574
latitude = pset["hae_latitude"]
577-
spacecraft_direction_vec = pset["sc_direction_vector"].values
575+
spacecraft_velocity = pset["sc_velocity"].values
578576
spherical_coords = np.stack(
579577
[
580578
np.ones_like(longitude.values),
@@ -593,9 +591,7 @@ def calculate_ram_mask(pset: xr.Dataset) -> xr.Dataset:
593591
# ram_mask = (-v⃗_ena · û_sc) >= 0
594592
# Use Einstein summation for efficient vectorized dot product
595593
ram_mask = (
596-
np.einsum(
597-
"...i,...i->...", spacecraft_direction_vec, cartesian_source_direction
598-
)
594+
np.einsum("...i,...i->...", spacecraft_velocity, cartesian_source_direction)
599595
>= 0
600596
)
601597
pset["ram_mask"] = xr.DataArray(
@@ -630,7 +626,9 @@ def apply_compton_getting_correction(
630626
Pointing set dataset. Must contain the following coordinates:
631627
- epoch: start time of the pointing
632628
Must contain the following variables:
633-
- epoch_delta: duration of the pointing in nanoseconds
629+
- sc_velocity: velocity vector of the spacecraft in the HAE frame at
630+
the midpoint time of the pointing [km/s]. See the
631+
`add_spacecraft_velocity_to_pset` function.
634632
- hae_longitude: PSET bin longitudes in the HAE frame (degrees)
635633
- hae_latitude: PSET bin latitudes in the HAE frame (degrees)
636634
energy_hf : xr.DataArray
@@ -647,22 +645,17 @@ def apply_compton_getting_correction(
647645
Notes
648646
-----
649647
This function adds the following variables to the dataset:
650-
- "sc_velocity": Spacecraft velocity vector (km/s)
651-
- "sc_direction_vector": Spacecraft velocity unit vector
652648
- "look_direction": Cartesian unit vectors of observation directions
653649
- "energy_hf": ENA energies in heliosphere frame (eV)
654650
- "energy_sc": ENA energies in spacecraft frame (eV)
655651
This function modifies the following variables in the dataset:
656652
- "hae_longitude": ENA source longitudes in heliosphere frame (degrees)
657653
- "hae_latitude": ENA source latitudes in heliosphere frame (degrees)
658654
"""
659-
# Step 1: Add spacecraft velocity and direction to pset
660-
processed_dataset = add_spacecraft_velocity_to_pset(pset)
661-
662-
# Step 2: Calculate and add look direction vectors to pset
663-
processed_dataset = _add_cartesian_look_direction(processed_dataset)
655+
# Step 1: Calculate and add look direction vectors to pset
656+
processed_dataset = _add_cartesian_look_direction(pset)
664657

665-
# Step 3: Apply Compton-Getting transformation
658+
# Step 2: Apply Compton-Getting transformation
666659
processed_dataset = _calculate_compton_getting_transform(
667660
processed_dataset, energy_hf
668661
)

imap_processing/hi/hi_l2.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,14 @@ def generate_hi_map(
152152
"All PSETs must have the same set of esa_energy_step values."
153153
)
154154

155+
pset_ds = add_spacecraft_velocity_to_pset(pset_ds)
156+
155157
if descriptor.frame_descriptor == "hf":
156158
# convert esa nominal central energy from keV to eV
157159
esa_energy_ev = energy_kev * 1000
158160
pset_ds = apply_compton_getting_correction(pset_ds, esa_energy_ev)
159-
else:
160-
pset_ds = add_spacecraft_velocity_to_pset(pset_ds)
161-
pset_ds = calculate_ram_mask(pset_ds)
161+
162+
pset_ds = calculate_ram_mask(pset_ds)
162163

163164
# Multiply variables that need to be exposure time weighted average by
164165
# exposure factor.

imap_processing/lo/l2/lo_l2.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,10 @@ def process_single_pset(
371371
# Step 3: Calculate efficiency-corrected quantities
372372
pset_processed = calculate_efficiency_corrected_quantities(pset_processed)
373373

374-
# Step 4: CG correction and compute ram mask
374+
# Step 4: Add s/c velocity, optionally apply CG correction, and calculate
375+
# ram-mask
376+
pset_processed = add_spacecraft_velocity_to_pset(pset_processed)
377+
375378
if cg_correct:
376379
# NOTE: Heliospheric frame energy selection for CG correction
377380
# The heliospheric (HF) energies passed to the CG correction algorithm
@@ -390,9 +393,9 @@ def process_single_pset(
390393
pset_processed = apply_compton_getting_correction(
391394
pset_processed, energy_values_ev
392395
)
393-
else:
394-
pset_processed = add_spacecraft_velocity_to_pset(pset_processed)
395-
pset_processed = calculate_ram_mask(pset_processed)
396+
397+
# Always calculate ram-mask to identify ram/anti-ram bins
398+
pset_processed = calculate_ram_mask(pset_processed)
396399

397400
return pset_processed
398401

imap_processing/tests/ena_maps/test_corrections.py

Lines changed: 32 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
import xarray as xr
88

99
from imap_processing.cdf.utils import load_cdf
10-
from imap_processing.ena_maps import ena_maps
10+
from imap_processing.ena_maps.ena_maps import HiPointingSet
11+
from imap_processing.ena_maps.utils.coordinates import CoordNames
1112
from imap_processing.ena_maps.utils.corrections import (
1213
PowerLawFluxCorrector,
1314
_add_cartesian_look_direction,
@@ -439,7 +440,6 @@ def test_calculate_compton_getting_transform(self, mock_imap_state, mock_hi_pset
439440
assert "energy_sc" in mock_hi_pset
440441
assert "hae_longitude" in mock_hi_pset
441442
assert "hae_latitude" in mock_hi_pset
442-
assert "ram_mask" in mock_hi_pset
443443

444444
# Verify energy_hf matches input
445445
np.testing.assert_array_equal(
@@ -463,12 +463,6 @@ def test_calculate_compton_getting_transform(self, mock_imap_state, mock_hi_pset
463463
assert np.all(mock_hi_pset["hae_latitude"].values >= -90)
464464
assert np.all(mock_hi_pset["hae_latitude"].values <= 90)
465465

466-
# Verify ram_mask properties
467-
ram_mask = mock_hi_pset["ram_mask"]
468-
assert isinstance(ram_mask, xr.DataArray)
469-
assert ram_mask.dtype == bool
470-
assert ram_mask.shape == mock_hi_pset["energy_sc"].shape
471-
472466
@mock.patch("imap_processing.ena_maps.utils.corrections.geometry.imap_state")
473467
def test_apply_compton_getting_correction(self, mock_imap_state, mock_hi_pset):
474468
"""Test full Compton-Getting correction pipeline."""
@@ -483,6 +477,9 @@ def test_apply_compton_getting_correction(self, mock_imap_state, mock_hi_pset):
483477
coords={"esa_energy_step": [1, 2, 3]},
484478
)
485479

480+
# add the required sc_velocity to the pointing set
481+
mock_hi_pset = add_spacecraft_velocity_to_pset(mock_hi_pset)
482+
486483
# Apply the full correction
487484
mock_hi_pset = apply_compton_getting_correction(mock_hi_pset, energy_hf)
488485

@@ -494,22 +491,21 @@ def test_apply_compton_getting_correction(self, mock_imap_state, mock_hi_pset):
494491
assert "energy_sc" in mock_hi_pset
495492
assert "hae_longitude" in mock_hi_pset
496493
assert "hae_latitude" in mock_hi_pset
497-
assert "ram_mask" in mock_hi_pset
498494

499495
@pytest.mark.external_test_data
500-
@mock.patch("imap_processing.ena_maps.utils.corrections.geometry.imap_state")
501-
def test_compton_getting_with_real_pset(self, mock_imap_state, hi_pset_cdf_path):
496+
def test_compton_getting_with_real_pset(self, hi_pset_cdf_path):
502497
"""Test Compton-Getting correction with real Hi PSET data."""
503498
# Load real pointing set
504-
pset_ds = load_cdf(hi_pset_cdf_path)
505-
hi_pset = ena_maps.HiPointingSet(pset_ds)
499+
pset = load_cdf(hi_pset_cdf_path)
500+
pset = pset.rename(HiPointingSet.l1c_to_l2_var_mapping)
506501

507502
# Store original coordinates for comparison
508-
original_lon = hi_pset.data["hae_longitude"].copy()
503+
original_lon = pset["hae_longitude"].copy()
509504

510505
# Mock spacecraft state
511-
mock_sc_state = np.array([1e8, 2e8, 3e8, 12.0, -27.0, 0.02]) # km and km/s
512-
mock_imap_state.return_value = mock_sc_state
506+
pset["sc_velocity"] = xr.DataArray(
507+
np.array([12.0, -27.0, 0.02]), dims=[CoordNames.CARTESIAN_VECTOR.value]
508+
)
513509

514510
# Create energy array (Hi has 9 energy steps)
515511
energy_hf = xr.DataArray(
@@ -521,11 +517,11 @@ def test_compton_getting_with_real_pset(self, mock_imap_state, hi_pset_cdf_path)
521517
)
522518

523519
# Apply correction (pass the dataset, not the pointing set object)
524-
hi_pset.data = apply_compton_getting_correction(hi_pset.data, energy_hf)
520+
pset = apply_compton_getting_correction(pset, energy_hf)
525521

526522
# Verify coordinates were modified
527-
corrected_lon = hi_pset.data["hae_longitude"]
528-
corrected_lat = hi_pset.data["hae_latitude"]
523+
corrected_lon = pset["hae_longitude"]
524+
corrected_lat = pset["hae_latitude"]
529525

530526
# Shape should now include energy dimension
531527
assert "esa_energy_step" in corrected_lon.dims
@@ -542,17 +538,6 @@ def test_compton_getting_with_real_pset(self, mock_imap_state, hi_pset_cdf_path)
542538
assert np.all(corrected_lat.values >= -90)
543539
assert np.all(corrected_lat.values <= 90)
544540

545-
# Verify az_el_points was updated
546-
assert hi_pset.az_el_points is not None
547-
assert isinstance(hi_pset.az_el_points, xr.DataArray)
548-
549-
# Verify ram_mask was added and has correct properties
550-
assert "ram_mask" in hi_pset.data
551-
ram_mask = hi_pset.data["ram_mask"]
552-
assert isinstance(ram_mask, xr.DataArray)
553-
assert ram_mask.dtype == bool
554-
assert ram_mask.shape == hi_pset.data["energy_sc"].shape
555-
556541
def test_compton_getting_physical_consistency(self, mock_hi_pset):
557542
"""Test physical consistency of Compton-Getting correction."""
558543
# Set up a known spacecraft velocity
@@ -586,6 +571,10 @@ def test_compton_getting_physical_consistency(self, mock_hi_pset):
586571
# 4. Energy variation should exist across different look directions
587572
assert energy_sc.values.std() > 0
588573

574+
575+
class TestRamMask:
576+
"""Test suite for calculate_ram_mask function."""
577+
589578
def test_ram_mask_calculation(self):
590579
"""Test ram_mask correctly identifies ram and anti-ram directions."""
591580
# Create a simple mock pset with specific look directions
@@ -613,9 +602,6 @@ def test_ram_mask_calculation(self):
613602
# Set up spacecraft velocity in +X direction
614603
sc_velocity = np.array([30.0, 0.0, 0.0]) # km/s
615604
dataset["sc_velocity"] = xr.DataArray(sc_velocity, dims=["x_y_z"])
616-
dataset["sc_direction_vector"] = xr.DataArray(
617-
sc_velocity / np.linalg.norm(sc_velocity), dims=["x_y_z"]
618-
)
619605

620606
# Add look directions
621607
dataset = _add_cartesian_look_direction(dataset)
@@ -626,6 +612,8 @@ def test_ram_mask_calculation(self):
626612
# Calculate CG transform
627613
dataset = _calculate_compton_getting_transform(dataset, energy_hf)
628614

615+
dataset = calculate_ram_mask(dataset)
616+
629617
# Verify ram_mask exists
630618
assert "ram_mask" in dataset
631619
ram_mask = dataset["ram_mask"]
@@ -690,7 +678,7 @@ def test_update_ram_mask_plus_x_direction(self):
690678
pset = self.create_synthetic_pset_with_hae_coords()
691679

692680
# Spacecraft velocity in +X direction (HAE frame)
693-
pset["sc_direction_vector"] = np.array([1.0, 0.0, 0.0])
681+
pset["sc_velocity"] = np.array([1.0, 0.0, 0.0])
694682
pset = calculate_ram_mask(pset)
695683

696684
lon = pset["hae_longitude"].values
@@ -714,7 +702,7 @@ def test_update_ram_mask_minus_x_direction(self):
714702
pset = self.create_synthetic_pset_with_hae_coords()
715703

716704
# Spacecraft velocity in -X direction (HAE frame)
717-
pset["sc_direction_vector"] = np.array([-1.0, 0.0, 0.0])
705+
pset["sc_velocity"] = np.array([-1.0, 0.0, 0.0])
718706
pset = calculate_ram_mask(pset)
719707

720708
lon = pset["hae_longitude"].values
@@ -737,7 +725,7 @@ def test_update_ram_mask_plus_y_direction(self):
737725
pset = self.create_synthetic_pset_with_hae_coords()
738726

739727
# Spacecraft velocity in +Y direction (HAE frame)
740-
pset["sc_direction_vector"] = np.array([0.0, 1.0, 0.0])
728+
pset["sc_velocity"] = np.array([0.0, 1.0, 0.0])
741729
pset = calculate_ram_mask(pset)
742730

743731
lon = pset["hae_longitude"].values
@@ -758,11 +746,11 @@ def test_update_ram_mask_magnitude_invariance(self):
758746
pset = self.create_synthetic_pset_with_hae_coords()
759747

760748
# Test with two different magnitudes in the same direction
761-
pset["sc_direction_vector"] = np.array([1.0, 0.0, 0.0])
749+
pset["sc_velocity"] = np.array([1.0, 0.0, 0.0])
762750
pset = calculate_ram_mask(pset)
763751
ram_mask_1 = pset["ram_mask"].values.copy()
764752

765-
pset["sc_direction_vector"] = np.array([100.0, 0.0, 0.0])
753+
pset["sc_velocity"] = np.array([100.0, 0.0, 0.0])
766754
pset = calculate_ram_mask(pset)
767755
ram_mask_2 = pset["ram_mask"].values.copy()
768756

@@ -774,7 +762,7 @@ def test_update_ram_mask_dot_product_correctness(self):
774762
pset = self.create_synthetic_pset_with_hae_coords()
775763

776764
# Use a simple spacecraft velocity vector
777-
pset["sc_direction_vector"] = np.array([1.0, 0.0, 0.0])
765+
pset["sc_velocity"] = np.array([1.0, 0.0, 0.0])
778766
pset = calculate_ram_mask(pset)
779767

780768
# Manually verify a few specific pixels
@@ -802,7 +790,7 @@ def test_update_ram_mask_dimensions_preserved(self):
802790
original_dims_2d = pset_2d["hae_longitude"].dims
803791

804792
# Update ram_mask
805-
pset_2d["sc_direction_vector"] = np.array([1.0, 1.0, 0.0])
793+
pset_2d["sc_velocity"] = np.array([1.0, 1.0, 0.0])
806794
pset_2d = calculate_ram_mask(pset_2d)
807795

808796
# Verify dimensions are preserved
@@ -833,7 +821,7 @@ def test_update_ram_mask_dimensions_preserved(self):
833821
original_dims_1d = dataset_1d["hae_longitude"].dims
834822

835823
# Update ram_mask
836-
dataset_1d["sc_direction_vector"] = np.array([1.0, 1.0, 0.0])
824+
dataset_1d["sc_velocity"] = np.array([1.0, 1.0, 0.0])
837825
dataset_1d = calculate_ram_mask(dataset_1d)
838826

839827
# Verify dimensions are preserved
@@ -844,12 +832,12 @@ def test_update_ram_mask_replaces_existing(self):
844832
pset = self.create_synthetic_pset_with_hae_coords()
845833

846834
# Set initial mask with +X direction
847-
pset["sc_direction_vector"] = np.array([1.0, 0.0, 0.0])
835+
pset["sc_velocity"] = np.array([1.0, 0.0, 0.0])
848836
pset = calculate_ram_mask(pset)
849837
ram_mask_1 = pset["ram_mask"].values.copy()
850838

851839
# Update mask with opposite direction
852-
pset["sc_direction_vector"] = np.array([-1.0, 0.0, 0.0])
840+
pset["sc_velocity"] = np.array([-1.0, 0.0, 0.0])
853841
pset = calculate_ram_mask(pset)
854842
ram_mask_2 = pset["ram_mask"].values.copy()
855843

@@ -861,7 +849,7 @@ def test_update_ram_mask_arbitrary_direction(self):
861849
pset = self.create_synthetic_pset_with_hae_coords(shape=(36, 18))
862850

863851
# Use an arbitrary direction (not aligned with axes)
864-
pset["sc_direction_vector"] = np.array([1.0, 1.0, 0.5])
852+
pset["sc_velocity"] = np.array([1.0, 1.0, 0.5])
865853
pset = calculate_ram_mask(pset)
866854

867855
# Verify the mask was created

0 commit comments

Comments
 (0)