Source code for arpes.endstations.plugin.DSNP_UMCS

"""Implements data loading for the ARPES at DSNP UMCS.

(Department of Surface and Nanostructures Physics)
Institute of Physics, Maria Curie-Sklodowska University in Lublin.
"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, ClassVar

import numpy as np
import xarray as xr

from arpes.endstations import (
    HemisphericalEndstation,
    SingleFileEndstation,
    add_endstation,
)
from arpes.endstations.prodigy_xy import load_xy
from arpes.provenance import Provenance, provenance_from_file

if TYPE_CHECKING:
    from collections.abc import Callable

    from arpes._typing.attrs_property import ScanDesc, Spectrometer


__all__ = ["DSNP_UMCSEndstation"]


[docs] class DSNP_UMCSEndstation( # noqa: N801 HemisphericalEndstation, SingleFileEndstation, ): """Implements loading xy text files from the Specs Prodigy software.""" PRINCIPAL_NAME = "DSNP_UMCS" ALIASES: ClassVar[list[str]] = ["DSNP_UMCS", "IF_UMCS", "LubARPES", "LublinARPRES"] _TOLERATED_EXTENSIONS: ClassVar[set[str]] = {".xy", ".itx"} # Mapping of lens modes to dispersion or magnification modes _LENS_MAPPING: ClassVar[dict[str, bool]] = { "HighAngularDispersion": True, "MediumAngularDispersion": True, "LowAngularDispersion": True, "WideAngleMode": True, "LowMagnification": False, "MediumMagnification": False, "HighMagnification": False, } RENAME_KEYS: ClassVar[dict[str, str]] = { "eff_workfunction": "workfunction", "acquisition_date": "aquisition_date_utc", "analyzer_slit": "slit", "analyzer_lens": "lens_mode", "detector_voltage": "mcp_voltage", "excitation_energy": "hv", "region": "id", } ATTR_TRANSFORMS: ClassVar[dict[str, Callable[..., dict[str, float | list[str] | str]]]] = { "aquisition_date_utc": lambda _: { "date": _.split()[0], "time": _.split()[1], }, "slit": lambda _: { "slit_number": int(_.split(":")[0]), "slit_width": float(_.split(":")[1].split("x")[0]), }, } MERGE_ATTRS: ClassVar[Spectrometer] = { "analyzer": "Specs PHOIBOS 150", "analyzer_name": "Specs PHOIBOS 150", "parallel_deflectors": False, "perpendicular_deflectors": False, "analyzer_radius": 150, "analyzer_type": "hemispherical", } def load_single_frame( self, frame_path: str | Path = "", scan_desc: ScanDesc | None = None, **kwargs: str | float, ) -> xr.Dataset: """Load single frame from Specs Prodiy generated xy file. - Calculate phi or x values depending on the lens mode. - Rename the third dimension (polar) to theta angle. - Convert degrees to radians. Args: frame_path(str | Path): _description_, by default "" scan_desc(ScanDesc | None): _description_, by default None kwargs(str | int | float): Pass to load_xy Returns: xr.Datast: pyARPES is not compatible at this stage. (postprocess_final is needed.) """ provenance_context: Provenance = { "what": "Loaded xy dataset", "by": "load_single_frame", } if scan_desc is None: scan_desc = {} file = Path(frame_path) if file.suffix in self._TOLERATED_EXTENSIONS: if file.suffix == ".xy": data = load_xy(frame_path, **kwargs) # Calculate phi or x values depending on the lens mode lens_mode = data.attrs["analyzer_lens"].split(":")[0] if lens_mode in self._LENS_MAPPING: dispersion_mode = self._LENS_MAPPING[lens_mode] if "nonenergy" in data.coords: if dispersion_mode: data = data.rename({"nonenergy": "phi"}) data = data.assign_coords(phi=np.deg2rad(data.phi)) else: data = data.rename({"nonenergy": "x"}) else: msg = f"Unknown Analyzer Lens: {lens_mode}" raise ValueError(msg) # Convert polar manipulator angle to theta in radians if "polar" in data.coords: data = data.rename({"polar": "theta"}) data = data.assign_coords(theta=np.deg2rad(data.theta)) dataset = xr.Dataset({"spectrum": data}, attrs=data.attrs) provenance_from_file( child_arr=dataset["spectrum"], file=str(frame_path), record=provenance_context, ) dataset.attrs["location"] = self.PRINCIPAL_NAME return dataset if file.suffix == ".itx": msg = "Not supported yet..." raise RuntimeError(msg) msg = "Data file must be ended with .xy or .itx" raise RuntimeError(msg) def postprocess_final( self, data: xr.Dataset, scan_desc: ScanDesc | None = None, ) -> xr.Dataset: """Perform final processing on the ARPES data. - Add missing parameters. Args: data(xr.Dataset): ARPES data scan_desc(SCANDESC | None): scan_description. Not used currently Returns: xr.Dataset: pyARPES compatible. """ # Convert to binding energy binding_energies = data.coords["eV"].values - data.attrs["hv"] data = data.assign_coords({"eV": binding_energies}) # Add missing parameters if scan_desc is None: scan_desc = {} defaults = { "x": 78.0, "y": 0.5, "z": 2.5, "beta": 0.0, "chi": 0.0, "psi": 0.0, "theta": 0.0, "alpha": np.deg2rad(90), "energy_notation": "Binding", } for k, v in defaults.items(): data.attrs[k] = v for s in [dv for dv in data.data_vars.values() if "eV" in dv.dims]: s.attrs[k] = v data = super().postprocess_final(data, scan_desc) data.S.spectrum.attrs["location"] = self.PRINCIPAL_NAME return data
add_endstation(DSNP_UMCSEndstation)