Source code for ewoksxas.converters.larch

from typing import Any

import numpy as np
import numpy.typing as npt
from larch import Group


[docs] def create_groups( x: npt.NDArray[np.float64], y: npt.NDArray[np.float64], mode: str = "energy", **kwargs, ) -> list[Group]: """Create one Larch group per spectrum from paired x and y arrays. Three input shapes are accepted: 1. Both x and y are 1D with the same length: produces a single group. 2. When x is 1D and y is 2D: one group is created per row of y, all sharing the same x. 3. Both x and y are 2D with the same shape: rows are paired positionally, producing one group per row. The group attribute names are determined by ``mode``. Args: x: 1D array shared across all groups, or 2D array with one row per group. y: 1D array for a single group, or 2D array with one row per group. mode: Selects the x/y attribute names written to each group. kwargs: Additional attributes passed unchanged to every group. Raises: TypeError: If x or y is not a NumPy array. ValueError: If mode is invalid or the shapes of x and y are incompatible. """ attribute_names = { "energy": ("energy", "mu"), "k": ("k", "chi"), "r": ("r", "chir"), } if mode not in attribute_names: raise ValueError( f"The mode should be one of {list(attribute_names)}, but got {mode}" ) if not isinstance(x, np.ndarray): raise TypeError(f"Expected NumPy array for x, but got {type(x)}.") if not isinstance(y, np.ndarray): raise TypeError(f"Expected NumPy array for y, but got {type(y)}.") if mode == "energy": x = convert_kev_to_ev(x) x_name, y_name = attribute_names[mode] if x.ndim == 1 and y.ndim == 1 and x.shape == y.shape: return [Group(**{x_name: x, y_name: y}, **kwargs)] if x.ndim == 1 and y.ndim == 2 and x.shape[0] == y.shape[1]: return [Group(**{x_name: x, y_name: yi}, **kwargs) for yi in y] if x.ndim == 2 and y.ndim == 2 and x.shape == y.shape: return [ Group(**{x_name: xi, y_name: yi}, **kwargs) for xi, yi in zip(x, y, strict=True) ] raise ValueError( f"The x values shape {x.shape} is incompatible with " f"the y values shape {y.shape}" )
[docs] def convert_kev_to_ev( energy: npt.NDArray[np.float64], threshold: float = 0.01 ) -> npt.NDArray[np.float64]: """Convert energy values from keV to eV when the data appear to be in keV. Detection relies on the median step size between consecutive points. X-ray spectroscopy data in eV typically has steps of 0.1-1 eV; the same data in keV has steps of 0.0001-0.001 keV. A threshold of 0.01 sits cleanly between the two ranges for all realistic scans. Args: energy: Energy axis values (1D or 2D array). threshold: If the median step size is below this value the data are assumed to be in keV and multiplied by 1000. Raises: ValueError: If energy has more than two dimensions. """ if energy.ndim not in (1, 2): raise ValueError(f"Expected 1D or 2D energy array, got ndim={energy.ndim}.") # np.diff needs at least 2 points along the last axis. if energy.shape[-1] < 2: return energy step = float(np.median(np.abs(np.diff(energy, axis=-1)))) if step < threshold: return energy * 1000 return energy
[docs] def get_attribute_values( groups: list[Group], attributes: str | list[str] ) -> dict[str, Any]: """Collect attribute values from a list of Larch groups into arrays. Args: groups: Larch groups to read from. attributes: One or more attribute names to extract. Returns: Dictionary mapping each attribute name to a NumPy array of its values across all groups. Raises: KeyError: If an attribute is not found in a group. """ if isinstance(attributes, str): attributes = [attributes] result = {} for attribute in attributes: values = [] for i, group in enumerate(groups): if not hasattr(group, attribute): raise KeyError( f"Attribute '{attribute}' not found in group at index {i}" ) values.append(getattr(group, attribute)) result[attribute] = np.array(values) return result