Skip to content

Commit c1bd415

Browse files
Eliminate xarray->numpy->xarray conversion in lo_l2
* Also had to add mypy error suppression in an unrelated class definition in lo_l2.py, because mypy is set to ignore typing from out-of-module imports
1 parent 3a371d5 commit c1bd415

1 file changed

Lines changed: 25 additions & 29 deletions

File tree

imap_processing/lo/l2/lo_l2.py

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ def _get_esa_mode(histrates: xr.Dataset) -> int:
438438
# =============================================================================
439439

440440

441-
class LoSpinAnglePointingSet(PointingSet):
441+
class LoSpinAnglePointingSet(PointingSet): # type: ignore[misc]
442442
"""
443443
The spin-angle bins of one pointing, as an in-memory pointing set.
444444
@@ -809,7 +809,7 @@ def _esa_calibration(species: str, esa_mode: int) -> EsaCalibration:
809809

810810
def _calculate_rates_and_intensities(
811811
sky_map: RectangularSkyMap, calibration: EsaCalibration
812-
) -> dict[str, np.ndarray]:
812+
) -> dict[str, xr.DataArray]:
813813
"""
814814
Turn the accumulated counts and exposure into rates and intensities.
815815
@@ -825,43 +825,40 @@ def _calculate_rates_and_intensities(
825825
826826
Returns
827827
-------
828-
dict[str, np.ndarray]
828+
dict[str, xr.DataArray]
829829
The map variables, each of shape (epoch, esa level, pixel).
830830
"""
831-
counts = sky_map.data_1d["ena_count"].values
832-
exposure = sky_map.data_1d["exposure_factor"].values
833-
bg_rate_exposure = sky_map.data_1d["bg_rate_exposure"].values
831+
counts = sky_map.data_1d["ena_count"]
832+
exposure = sky_map.data_1d["exposure_factor"]
833+
bg_rate_exposure = sky_map.data_1d["bg_rate_exposure"]
834834

835-
# Every ESA level quantity gets a pixel axis to broadcast over the map.
836-
energy = calibration.energy[:, np.newaxis]
837-
geometric_factor = calibration.geometric_factor[:, np.newaxis]
838-
gf_low = calibration.geometric_factor_low[:, np.newaxis]
839-
gf_high = calibration.geometric_factor_high[:, np.newaxis]
835+
# Naming the energy dimension lets xarray broadcast during division
836+
# without the need to introduce new dimensions
837+
energy_dim = CoordNames.ENERGY_L2.value
838+
energy = xr.DataArray(calibration.energy, dims=[energy_dim])
839+
geometric_factor = xr.DataArray(calibration.geometric_factor, dims=[energy_dim])
840+
gf_low = xr.DataArray(calibration.geometric_factor_low, dims=[energy_dim])
841+
gf_high = xr.DataArray(calibration.geometric_factor_high, dims=[energy_dim])
840842

841843
exposed = exposure > 0
842844

843-
def _divide(numerator: np.ndarray, denominator: np.ndarray) -> np.ndarray:
845+
def _divide(numerator: xr.DataArray, denominator: xr.DataArray) -> xr.DataArray:
844846
"""
845847
Divide only where the map was exposed, zero elsewhere.
846848
847849
Parameters
848850
----------
849-
numerator : np.ndarray
851+
numerator : xr.DataArray
850852
The array being divided.
851-
denominator : np.ndarray
853+
denominator : xr.DataArray
852854
The array to divide it by.
853855
854856
Returns
855857
-------
856-
np.ndarray
858+
xr.DataArray
857859
The quotient, zero in the pixels that were never exposed.
858860
"""
859-
return np.divide(
860-
numerator,
861-
denominator,
862-
out=np.zeros_like(exposure),
863-
where=exposed,
864-
)
861+
return (numerator / denominator.where(exposed)).where(exposed, 0)
865862

866863
count_rate = _divide(counts, exposure)
867864
# Poisson uncertainty on the counts, propagated to the rate
@@ -878,13 +875,13 @@ def _divide(numerator: np.ndarray, denominator: np.ndarray) -> np.ndarray:
878875
if not valid.all():
879876
logger.warning(
880877
"The geometric factor of ESA levels "
881-
f"{(np.flatnonzero(~valid[:, 0]) + 1).tolist()} is below its lower "
878+
f"{(np.flatnonzero(~valid.values) + 1).tolist()} is below its lower "
882879
f"error bound; their systematic errors are left at zero."
883880
)
884-
intensity_upper = _divide(count_rate, np.where(valid, gf_low, 1.0) * energy)
881+
intensity_upper = _divide(count_rate, gf_low.where(valid, 1.0) * energy)
885882
intensity_lower = _divide(count_rate, gf_high * energy)
886-
intensity_sys_err_plus = np.where(valid, intensity_upper - intensity, 0.0)
887-
intensity_sys_err_minus = np.where(valid, intensity - intensity_lower, 0.0)
883+
intensity_sys_err_plus = (intensity_upper - intensity).where(valid, 0.0)
884+
intensity_sys_err_minus = (intensity - intensity_lower).where(valid, 0.0)
888885

889886
bg_rate = _divide(bg_rate_exposure, exposure)
890887
bg_rate_stat_uncert = np.sqrt(_divide(bg_rate, exposure))
@@ -912,7 +909,7 @@ def _divide(numerator: np.ndarray, denominator: np.ndarray) -> np.ndarray:
912909

913910
def _build_map_dataset(
914911
sky_map: RectangularSkyMap,
915-
variables: dict[str, np.ndarray],
912+
variables: dict[str, xr.DataArray],
916913
calibration: EsaCalibration,
917914
) -> xr.Dataset:
918915
"""
@@ -925,7 +922,7 @@ def _build_map_dataset(
925922
----------
926923
sky_map : RectangularSkyMap
927924
The map being built.
928-
variables : dict[str, np.ndarray]
925+
variables : dict[str, xr.DataArray]
929926
The map variables, each of shape (epoch, esa level, pixel).
930927
calibration : EsaCalibration
931928
The energy response the map is binned in, read for the widths of the
@@ -937,9 +934,8 @@ def _build_map_dataset(
937934
The map variables on the (epoch, energy, longitude, latitude) grid,
938935
with the energy coordinate and its widths.
939936
"""
940-
dims = sky_map.data_1d["ena_count"].dims
941937
for name, values in variables.items():
942-
sky_map.data_1d[name] = xr.DataArray(values.astype(np.float32), dims=dims)
938+
sky_map.data_1d[name] = values.astype(np.float32)
943939
# `bg_rate_exposure` is an accumulator, not a map variable.
944940
sky_map.data_1d = sky_map.data_1d.drop_vars("bg_rate_exposure")
945941

0 commit comments

Comments
 (0)