diff --git a/arpes/_typing.py b/arpes/_typing.py index 4f4e6502..ce752055 100644 --- a/arpes/_typing.py +++ b/arpes/_typing.py @@ -146,17 +146,17 @@ class ANALYZERINFO(TypedDict, total=False): lens_mode_name: str | None acquisition_mode: str pass_energy: float - slit_shape: str + slit_shape: str | None slit_width: float slit_number: str | int lens_table: None - analyzer_type: str + analyzer_type: str | None mcp_voltage: float work_function: float # analyzer_radius: float - analyzer: str - analyzer_name: str + analyzer: str | None + analyzer_name: str | None parallel_deflectors: bool perpendicular_deflectors: bool @@ -174,10 +174,10 @@ class _PUMPINFO(TypedDict, total=False): pump_spot_size: float | tuple[float, float] pump_spot_size_x: float pump_spot_size_y: float - pump_profile: None + pump_profile: Incomplete pump_linewidth: float pump_duration: float - pump_polarization: str | tuple[float | None, float | None] + pump_polarization: str | tuple[float, float] pump_polarization_theta: float pump_polarization_alpha: float @@ -198,7 +198,7 @@ class _PROBEINFO(TypedDict, total=False): probe_profile: None probe_linewidth: float probe_duration: float - probe_polarization: str | tuple[float | None, float | None] + probe_polarization: str | tuple[float, float] probe_polarization_theta: float probe_polarization_alpha: float @@ -211,21 +211,21 @@ class _BEAMLINEINFO(TypedDict, total=False): hv: float | xr.DataArray linewidth: float - photon_polarization: tuple[float | None, float | None] + photon_polarization: tuple[float, float] undulation_info: Incomplete repetition_rate: float beam_current: float - entrance_slit: float - exit_slit: float - monochrometer_info: dict[str, None | float] + entrance_slit: float | str + exit_slit: float | str + monochrometer_info: dict[str, float] class LIGHTSOURCEINFO(_PROBEINFO, _PUMPINFO, _BEAMLINEINFO, total=False): polarization: float | tuple[float, float] | str photon_flux: float photocurrent: float - probe: None - probe_detail: None + probe: Incomplete + probe_detail: Incomplete class SAMPLEINFO(TypedDict, total=False): @@ -234,9 +234,9 @@ class SAMPLEINFO(TypedDict, total=False): see sample_info in xarray_extensions """ - id: int | str - sample_name: str - source: str + id: int | str | None + sample_name: str | None + source: str | None reflectivity: float @@ -258,7 +258,7 @@ class DAQINFO(TypedDict, total=False): see daq_info in xarray_extensions.py """ - daq_type: str + daq_type: str | None region: str | None region_name: str | None center_energy: float diff --git a/arpes/plotting/annotations.py b/arpes/plotting/annotations.py index 019ea209..484420b7 100644 --- a/arpes/plotting/annotations.py +++ b/arpes/plotting/annotations.py @@ -11,7 +11,6 @@ from mpl_toolkits.mplot3d import Axes3D from arpes.constants import TWO_DIMENSION -from arpes.utilities.conversion.forward import convert_coordinates_to_kspace_forward from .utils import name_for_dim, unit_for_dim @@ -181,6 +180,8 @@ def annotate_cuts( include_text_labels: Whether to include text labels kwargs: Defines the coordinates of the cut location """ + from arpes.utilities.conversion.forward import convert_coordinates_to_kspace_forward + converted_coordinates = convert_coordinates_to_kspace_forward(data) assert converted_coordinates, xr.Dataset | xr.DataArray assert len(plotted_axes) == TWO_DIMENSION diff --git a/arpes/plotting/dispersion.py b/arpes/plotting/dispersion.py index 2c0aa7fe..fe37ef46 100644 --- a/arpes/plotting/dispersion.py +++ b/arpes/plotting/dispersion.py @@ -364,7 +364,6 @@ class LabeledFermiSurfaceParam(TypedDict, total=False): include_symmetry_points: bool include_bz: bool fermi_energy: float - out: str @save_plot_provenance diff --git a/arpes/plotting/qt_tool/__init__.py b/arpes/plotting/qt_tool/__init__.py index 29ef611a..e597e012 100644 --- a/arpes/plotting/qt_tool/__init__.py +++ b/arpes/plotting/qt_tool/__init__.py @@ -1,10 +1,12 @@ """Provides a Qt based implementation of Igor's ImageTool.""" + # pylint: disable=import-error from __future__ import annotations import contextlib import warnings import weakref +from collections.abc import Sequence from logging import DEBUG, INFO, Formatter, StreamHandler, getLogger from typing import TYPE_CHECKING, reveal_type @@ -31,7 +33,9 @@ from .BinningInfoWidget import BinningInfoWidget if TYPE_CHECKING: + import xarray as xr from _typeshed import Incomplete + from PySide6.QtCore import QEvent from PySide6.QtWidgets import QWidget from arpes._typing import DataType @@ -113,20 +117,20 @@ def compile_key_bindings(self) -> list[KeyBinding]: ), ] - def center_cursor(self, event) -> None: + def center_cursor(self, event: QEvent) -> None: logger.debug(f"method: center_cursor {event!s}") self.app().center_cursor() - def transpose_roll(self, event) -> None: + def transpose_roll(self, event: QEvent) -> None: logger.debug(f"method: transpose_roll {event!s}") self.app().transpose_to_front(-1) - def transpose_swap(self, event) -> None: + def transpose_swap(self, event: QEvent) -> None: logger.debug(f"method: transpose_swap {event!s}") self.app().transpose_to_front(1) @staticmethod - def _update_scroll_delta(delta, event: QtGui.QKeyEvent) -> tuple: + def _update_scroll_delta(delta: tuple[float, ...], event: QtGui.QKeyEvent) -> tuple: logger.debug(f"method: _update_scroll_delta {event!s}") if event.nativeModifiers() & 1: # shift key delta = (delta[0], delta[1] * 5) @@ -190,7 +194,7 @@ class QtTool(SimpleApp): def __init__(self) -> None: """Initialize attributes to safe empty values.""" super().__init__() - self.data = None + self.data: xr.Dataset | xr.DataArray self.content_layout = None self.main_layout: QtWidgets.QGridLayout | None = None @@ -419,13 +423,13 @@ def safe_slice(vlow: float, vhigh: float, axis: int = 0) -> slice: ), ) if isinstance(reactive.view, DataArrayImageView): - image_data = self.data.isel(**select_coord) + image_data = self.data.isel(select_coord) if select_coord: image_data = image_data.mean(list(select_coord.keys())) reactive.view.setImage(image_data, keep_levels=keep_levels) elif isinstance(reactive.view, pg.PlotWidget): - for_plot = self.data.isel(**select_coord) + for_plot = self.data.isel(select_coord) if select_coord: for_plot = for_plot.mean(list(select_coord.keys())) @@ -449,7 +453,7 @@ def safe_slice(vlow: float, vhigh: float, axis: int = 0) -> slice: except IndexError: pass - def construct_axes_tab(self) -> tuple[QtWidgets, list[AxisInfoWidget]]: + def construct_axes_tab(self) -> tuple[QWidget, list[AxisInfoWidget]]: """Controls for axis order and transposition.""" inner_items = [ AxisInfoWidget(axis_index=i, root=weakref.ref(self)) for i in range(len(self.data.dims)) diff --git a/arpes/plotting/stack_plot.py b/arpes/plotting/stack_plot.py index 415ece84..d51a8e27 100644 --- a/arpes/plotting/stack_plot.py +++ b/arpes/plotting/stack_plot.py @@ -329,7 +329,7 @@ def stack_dispersion_plot( # noqa: PLR0913 negate(bool): _description_ **kwargs: set figsize to change the default figisize=(7,7) - set title, if not specified the attrs[description] (or S.scan_name) is used. + set title, if not specified the attrs[description] (or S.label) is used. other kwargs is passed to ax.plot (or ax.scatter). Can set linewidth/s etc., here. """ data_arr, stack_axis, other_axis = _rebinning( diff --git a/arpes/utilities/normalize.py b/arpes/utilities/normalize.py index 748848e1..99ad2e91 100644 --- a/arpes/utilities/normalize.py +++ b/arpes/utilities/normalize.py @@ -17,6 +17,7 @@ def normalize_to_spectrum(data: DataType | str) -> xr.DataArray: """Tries to extract the actual ARPES spectrum from a dataset containing other variables.""" + import arpes.xarray_extensions # noqa: F401 from arpes.io import load_data if isinstance(data, xr.Dataset): diff --git a/arpes/utilities/qt/__init__.py b/arpes/utilities/qt/__init__.py index 7872ac1e..fad4b0b0 100644 --- a/arpes/utilities/qt/__init__.py +++ b/arpes/utilities/qt/__init__.py @@ -1,13 +1,17 @@ """Infrastructure code for Qt based analysis tools.""" + from __future__ import annotations import functools +from logging import DEBUG, INFO, Formatter, StreamHandler, getLogger from multiprocessing import Process from typing import TYPE_CHECKING import dill import pyqtgraph as pg from pyqtgraph import ViewBox +from PySide6.QtCore import QCoreApplication +from PySide6.QtWidgets import QWidget from arpes._typing import xr_types @@ -35,6 +39,18 @@ "run_tool_in_daemon_process", ) +LOGLEVELS = (DEBUG, INFO) +LOGLEVEL = LOGLEVELS[1] +logger = getLogger(__name__) +fmt = "%(asctime)s %(levelname)s %(name)s :%(message)s" +formatter = Formatter(fmt) +handler = StreamHandler() +handler.setLevel(LOGLEVEL) +logger.setLevel(LOGLEVEL) +handler.setFormatter(formatter) +logger.addHandler(handler) +logger.propagate = False + def run_tool_in_daemon_process(tool_handler: Callable) -> Callable: """Starts a Qt based tool as a daemon process. @@ -89,7 +105,7 @@ def remove_dangling_viewboxes() -> None: * ViewBox.AllViews * ViewBox.NamedViews """ - import sip + import sipbuild # TODO: CHECK. for_deletion = set() @@ -97,7 +113,7 @@ def remove_dangling_viewboxes() -> None: # a list before we iterate because we are modifying the # underlying collection for v in list(ViewBox.AllViews): - if sip.isdeleted(v): + if sipbuild.isdeleted(v): # first remove it from the ViewBox references # and then we will delete it later to prevent an # error @@ -107,7 +123,7 @@ def remove_dangling_viewboxes() -> None: for vname in list(ViewBox.NamedViews): v = ViewBox.NamedViews[vname] - if sip.isdeleted(v): + if sipbuild.isdeleted(v): for_deletion.add(v) del ViewBox.NamedViews[vname] @@ -128,25 +144,28 @@ def init_from_app(self, app: QApplication) -> None: self._inited = True dpis = [screen.physicalDotsPerInch() for screen in app.screens()] - self.screen_dpi = sum(dpis) / len(dpis) + self.screen_dpi = int(sum(dpis) / len(dpis)) def apply_settings_to_app(self, app: QApplication) -> None: # Adjust the font size based on screen DPI font = app.font() - font.setPointSize(self.inches_to_px(0.1)) + logger.debug(f"Type of app {type(app)}") + font_size = self.inches_to_px(0.1) + assert isinstance(font_size, int) + font.setPointSize(font_size) app.instance().setFont(font) def inches_to_px( self, arg: float | tuple[float, ...], - ) -> int | Generator[int, None, None]: + ) -> int | tuple[int, ...]: if isinstance( arg, int | float, ): return int(self.screen_dpi * arg) - return (int(x * self.screen_dpi) for x in arg) + return tuple(int(x * self.screen_dpi) for x in arg) def setup_pyqtgraph(self) -> None: """Does any patching required on PyQtGraph and configures options.""" @@ -177,7 +196,7 @@ def patchedLinkedViewChanged( We also don't handle inverted axes for now. """ - if self.linksBlocked or view is None: + if view is None: return vr = view.viewRect() diff --git a/arpes/utilities/qt/app.py b/arpes/utilities/qt/app.py index 6e46f4ba..42b9f0c5 100644 --- a/arpes/utilities/qt/app.py +++ b/arpes/utilities/qt/app.py @@ -1,9 +1,11 @@ """Application infrastructure for apps/tools which browse a data volume.""" + from __future__ import annotations import sys import weakref from collections import defaultdict +from logging import DEBUG, INFO, Formatter, StreamHandler, getLogger from typing import TYPE_CHECKING import matplotlib as mpl @@ -21,10 +23,25 @@ if TYPE_CHECKING: from _typeshed import Incomplete from matplotlib.colors import Colormap + from PySide6.QtWidgets import QGridLayout + + from .windows import SimpleWindow __all__ = ["SimpleApp"] +LOGLEVELS = (DEBUG, INFO) +LOGLEVEL = LOGLEVELS[1] +logger = getLogger(__name__) +fmt = "%(asctime)s %(levelname)s %(name)s :%(message)s" +formatter = Formatter(fmt) +handler = StreamHandler() +handler.setLevel(LOGLEVEL) +logger.setLevel(LOGLEVEL) +handler.setFormatter(formatter) +logger.addHandler(handler) +logger.propagate = False + class SimpleApp: """Layout information and business logic for an interactive data browsing. @@ -32,23 +49,23 @@ class SimpleApp: utility using PySide6. """ - WINDOW_CLS: type[SimpleApp] | None = None + WINDOW_CLS: type[SimpleWindow] | None = None WINDOW_SIZE: tuple[float, float] = (4, 4) TITLE = "Untitled Tool" def __init__(self) -> None: """Only interesting thing on init is to make a copy of the user settings.""" self._ninety_eight_percentile: float | None = None - self._data: xr.DataArray | None = None + self._data: xr.DataArray self.settings = None - self._window: SimpleApp | None = None - self._layout = None + self._window: SimpleWindow + self._layout: QGridLayout - self.context = {} + self.context: dict[str, Incomplete] = {} - self.views = {} - self.reactive_views = [] - self.registered_cursors: dict[str, list[CursorRegion]] = defaultdict(list) + self.views: dict[str, DataArrayImageView] = {} + self.reactive_views: list[ReactivePlotRecord] = [] + self.registered_cursors: dict[int, list[CursorRegion]] = defaultdict(list) self.settings = arpes.config.SETTINGS.copy() @@ -128,14 +145,14 @@ def set_colormap(self, colormap: Colormap) -> None: def generate_marginal_for( self, - dimensions: tuple[int, ...] | list[int], + dimensions: tuple[int, ...], column: int, row: int, - name: str | None = None, + name: str = "", orientation: PlotOrientation = PlotOrientation.Horizontal, *, cursors: bool = False, - layout: Incomplete = None, + layout: QGridLayout | None = None, ) -> DataArrayImageView | DataArrayPlot: """Generates a marginal plot for the applications's data after selecting along `dimensions`. @@ -157,9 +174,11 @@ def generate_marginal_for( if cursors: cursor = CursorRegion( - orientation=CursorRegion.Horizontal - if orientation == PlotOrientation.Vertical - else CursorRegion.Vertical, + orientation=( + CursorRegion.Horizontal + if orientation == PlotOrientation.Vertical + else CursorRegion.Vertical + ), movable=True, ) widget.addItem(cursor, ignoreBounds=False) @@ -195,12 +214,13 @@ def connect_cursor(self, dimension: int, the_line: CursorRegion) -> None: providing self to a closure which is retained by `the_line`. """ self.registered_cursors[dimension].append(the_line) - owner = weakref.ref(self) def connected_cursor(line: CursorRegion) -> None: - new_cursor = list(owner().context["cursor"]) + simple_app = weakref.ref(self)() + assert isinstance(simple_app, SimpleApp) + new_cursor = list(simple_app.context["cursor"]) new_cursor[dimension] = line.getRegion()[0] - owner().update_cursor_position(new_cursor) + simple_app.update_cursor_position(new_cursor) the_line.sigRegionChanged.connect(connected_cursor) @@ -210,7 +230,20 @@ def before_show(self) -> None: def after_show(self) -> None: """Lifecycle hook.""" - def layout(self) -> None: + def update_cursor_position( + self, + new_cursor: list[float], + *, + force: bool = False, + keep_levels: bool = True, + ) -> None: + """Hook for defining the application layout. + + This needs to be provided by subclasses. + """ + raise NotImplementedError + + def layout(self) -> QGridLayout: """Hook for defining the application layout. This needs to be provided by subclasses. @@ -218,7 +251,7 @@ def layout(self) -> None: raise NotImplementedError @property - def window(self) -> SimpleApp: + def window(self) -> SimpleWindow: """Gets the window instance on the current application.""" assert self._window is not None return self._window @@ -230,10 +263,7 @@ def start(self, *, no_exec: bool = False, app: QtWidgets.QApplication | None = N if arpes.config.DOCS_BUILD: return - if app is None: - app = QtWidgets.QApplication.instance() - if not app: app = QtWidgets.QApplication(sys.argv) app.owner = self @@ -243,7 +273,8 @@ def start(self, *, no_exec: bool = False, app: QtWidgets.QApplication | None = N qt_info.init_from_app(app) assert self.WINDOW_CLS is not None self._window = self.WINDOW_CLS() - win_size = tuple(qt_info.inches_to_px(self.WINDOW_SIZE)) + win_size = qt_info.inches_to_px(self.WINDOW_SIZE) + assert isinstance(win_size, tuple) self.window.resize(int(win_size[0]), int(win_size[1])) self.window.setWindowTitle(self.TITLE) @@ -263,4 +294,4 @@ def start(self, *, no_exec: bool = False, app: QtWidgets.QApplication | None = N if no_exec: return - QtWidgets.QApplication.instance().exec() + QtWidgets.QApplication.exec() diff --git a/arpes/utilities/qt/data_array_image_view.py b/arpes/utilities/qt/data_array_image_view.py index ae2a0ee8..56f57471 100644 --- a/arpes/utilities/qt/data_array_image_view.py +++ b/arpes/utilities/qt/data_array_image_view.py @@ -1,20 +1,22 @@ """Provides xarray aware pyqtgraph plotting widgets.""" + from __future__ import annotations -from collections.abc import Callable, Sequence, Sized from typing import TYPE_CHECKING import numpy as np import pyqtgraph as pg +import xarray as xr from scipy import interpolate from .utils import PlotOrientation if TYPE_CHECKING: + from collections.abc import Sequence + from _typeshed import Incomplete - from numpy.typing import NDArray + from numpy._typing import NDArray - from arpes._typing import DataType __all__ = ( "DataArrayImageView", @@ -25,13 +27,13 @@ class CoordAxis(pg.AxisItem): def __init__(self, dim_index: int, *args: Incomplete, **kwargs: Incomplete) -> None: self.dim_index = dim_index - self.coord = None - self.interp: Callable[[float | Sequence[float]], float | NDArray[np.float_]] | None = None + self.coord: NDArray[np.float_] + self.interp: interpolate.interp1d super().__init__(*args, **kwargs) - def setImage(self, image: DataType) -> None: + def setImage(self, image: xr.DataArray) -> None: + assert isinstance(image, xr.DataArray) self.coord = image.coords[image.dims[self.dim_index]].values - assert isinstance(self.coord, Sized) self.interp = interpolate.interp1d( np.arange(0, len(self.coord)), self.coord, @@ -64,7 +66,7 @@ def __init__( def plot( self, - data: DataType, + data: xr.DataArray, *args: Incomplete, **kwargs: Incomplete, ) -> pg.PlotDataItem: @@ -73,6 +75,7 @@ def plot( Data also needs to be forwarded to the coordinate axis in case of transpose or changed range of data. """ + assert isinstance(data, xr.DataArray) y = data.values self._coord_axis.setImage(data) @@ -118,12 +121,13 @@ def __init__( def setImage( self, - img: DataType, + img: xr.DataArray, *args: Incomplete, keep_levels: bool = False, **kwargs: Incomplete, ) -> None: """Accepts an xarray.DataArray instead of a numpy array.""" + assert isinstance(img, xr.DataArray) if keep_levels: levels = self.getLevels() diff --git a/arpes/utilities/qt/help_dialogs.py b/arpes/utilities/qt/help_dialogs.py index 537545c9..be2880b3 100644 --- a/arpes/utilities/qt/help_dialogs.py +++ b/arpes/utilities/qt/help_dialogs.py @@ -1,4 +1,5 @@ """A help dialog showing keyboard shortcuts for Qt application.""" + # pylint: disable=import-error from __future__ import annotations @@ -39,7 +40,7 @@ def __init__(self, shortcuts: list[KeyBinding] | None = None) -> None: keyboard_shortcuts_info.setLayout(keyboard_shortcuts_layout) - aboutInfo = QtWidgets.QGroupBox(title="About") + aboutInfo: QtWidgets.QGroupBox = QtWidgets.QGroupBox(title="About") vertical( label( "QtTool is the work of Conrad Stansbury, with much inspiration " @@ -55,7 +56,9 @@ def __init__(self, shortcuts: list[KeyBinding] | None = None) -> None: from . import qt_info - aboutInfo.setFixedHeight(qt_info.inches_to_px(1)) + height = qt_info.inches_to_px(1) + assert isinstance(height, int) + aboutInfo.setFixedHeight(height) self.layout.addWidget(keyboard_shortcuts_info) self.layout.addWidget(aboutInfo) diff --git a/arpes/utilities/qt/windows.py b/arpes/utilities/qt/windows.py index 4e538587..0ac77281 100644 --- a/arpes/utilities/qt/windows.py +++ b/arpes/utilities/qt/windows.py @@ -1,8 +1,9 @@ """Infrastructure code for Qt application windows.""" + from __future__ import annotations import sys -from logging import INFO, Formatter, StreamHandler, getLogger +from logging import DEBUG, INFO, Formatter, StreamHandler, getLogger from typing import TYPE_CHECKING from PySide6 import QtCore, QtGui, QtWidgets @@ -12,7 +13,6 @@ from arpes.utilities.ui import KeyBinding if TYPE_CHECKING: - from weakref import ReferenceType from _typeshed import Incomplete from PySide6.QtCore import QObject @@ -23,7 +23,8 @@ __all__ = ("SimpleWindow",) -LOGLEVEL = INFO +LOGLEVELS = (DEBUG, INFO) +LOGLEVEL = LOGLEVELS[0] logger = getLogger(__name__) fmt = "%(asctime)s %(levelname)s %(name)s :%(message)s" formatter = Formatter(fmt) @@ -58,9 +59,7 @@ def __init__(self) -> None: * install filters to drop unnecessary Qt events """ super().__init__() - self.app: ReferenceType | None = ( - None # this will eventually be a weakref to the application - ) + self.app: Incomplete = None self._help_dialog: BasicHelpDialog | None = None self._old_excepthook = sys.excepthook @@ -91,17 +90,17 @@ def closeEvent(self, event: QCloseEvent) -> None: def do_close(self, event: QCloseEvent) -> None: """Handler for closing accepting an unused event arg.""" - msg = f"unused {event!s} is detected" - logger.debug(msg) + logger.debug(f"unused {event!s} is detected") + self.close() - def close(self) -> None: + def close(self) -> bool: """If we need to close, give the application a chance to clean up first.""" sys.excepthook = self._old_excepthook self.app().close() super().close() - def eventFilter(self, source: QObject, event: QKeyEvent) -> bool: + def eventFilter(self, source: QObject, event: QKeyEvent) -> bool: # type:ignore[override] """Neglect Qt events which do not relate to key presses for now.""" special_keys = [ QtCore.Qt.Key.Key_Down, @@ -132,8 +131,7 @@ def handleKeyPressEvent(self, event: QKeyEvent) -> None: binding.handler(event) if not handled and arpes.config.SETTINGS.get("DEBUG", False): - logger_info = f"{event.key()} @ {type(self)}:{event}" - logger.info(logger_info) + logger.debug(f"{event.key()} @ {type(self)}:{event}") def toggle_help(self, event: QKeyEvent) -> None: """Open and close (toggle) the help panel for the application.""" diff --git a/arpes/utilities/ui.py b/arpes/utilities/ui.py index 6ab8108a..fe26c419 100644 --- a/arpes/utilities/ui.py +++ b/arpes/utilities/ui.py @@ -37,6 +37,7 @@ with the most recent values of the inputs {'check','slider','file'} as a dictionary with these keys. This allows building PySide6 "forms" without effort. """ + from __future__ import annotations import enum @@ -173,7 +174,7 @@ def pretty_key_event(event: QKeyEvent) -> list[str]: ACTIVE_UI = None -def ui_builder(f): +def ui_builder(f: Callable) -> Callable: """Decorator synergistic with CollectUI to make widgets which register themselves.""" @functools.wraps(f) diff --git a/arpes/utilities/widgets.py b/arpes/utilities/widgets.py index a0d863f5..f6380e00 100644 --- a/arpes/utilities/widgets.py +++ b/arpes/utilities/widgets.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: from _typeshed import Incomplete - from PySide6.QtCore import CheckState + from PySide6.QtCore.Qt import CheckState __all__ = ( "SubjectivePushButton", @@ -113,7 +113,7 @@ def __init__(self, *args: Incomplete) -> None: """Wrap signals in ``rx.BehaviorSubject``s.""" super().__init__(*args) self.subject = BehaviorSubject(self.text()) - self.textChanged[str].connect(self.subject.on_next) + self.textChanged.connect(self.subject.on_next) self.subject.subscribe(self.update_ui) def update_ui(self, value: str) -> None: @@ -173,14 +173,14 @@ def __init__( def get_file(self) -> None: """Opens a dialog allowing a single from the user.""" - filename = QFileDialog.getOpenFileName(self, "Open File", self.dialog_root) + filename = QFileDialog.getOpenFileName(self, "Open File", str(self.dialog_root)) self.subject.on_next(filename[0]) def get_files(self) -> None: """Opens a dialog allowing multiple selections from the user.""" dialog = QFileDialog() - dialog.setFileMode(QFileDialog.AnyFile) + dialog.setFileMode(QFileDialog.FileMode.AnyFile) if dialog.exec_(): filenames = dialog.selectedFiles() diff --git a/arpes/workflow.py b/arpes/workflow.py index 2365fe32..91582fb8 100644 --- a/arpes/workflow.py +++ b/arpes/workflow.py @@ -33,7 +33,7 @@ from functools import wraps from pathlib import Path from pprint import pprint -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from logging import INFO, Formatter, StreamHandler, getLogger diff --git a/arpes/xarray_extensions.py b/arpes/xarray_extensions.py index 3a1fa386..cab5ef06 100644 --- a/arpes/xarray_extensions.py +++ b/arpes/xarray_extensions.py @@ -47,6 +47,7 @@ from collections import OrderedDict, defaultdict from collections.abc import Collection, Hashable, Mapping, Sequence from logging import DEBUG, INFO, Formatter, StreamHandler, getLogger +from pathlib import Path from typing import TYPE_CHECKING, Any, Literal, TypeAlias, Unpack import matplotlib.pyplot as plt @@ -82,7 +83,6 @@ if TYPE_CHECKING: from collections.abc import Callable, Generator - from pathlib import Path import lmfit from _typeshed import Incomplete @@ -812,7 +812,7 @@ def scan_name(self) -> str: """ for option in ["scan", "file"]: if option in self._obj.attrs: - return self._obj.attrs[option] + return Path(self._obj.attrs[option]).name id_code = self._obj.attrs.get("id") @@ -856,10 +856,7 @@ def lookup_coord(self, name: str) -> xr.DataArray | float: if name in self._obj.coords: return unwrap_xarray_item(self._obj.coords[name]) - if name in self._obj.attrs: - return unwrap_xarray_item(self._obj.attrs[name]) - - msg = f"Could not find coordinate {name}." + msg = f"Could not find coordinate {name}. Check your endstation module." raise ValueError(msg) def lookup_offset(self, attr_name: str) -> float: @@ -1132,6 +1129,7 @@ def find_spectrum_angular_edges( angular_dim = "pixel" if "pixel" in self._obj.dims else "phi" energy_edge = self.find_spectrum_energy_edges() energy_slice = slice(np.max(energy_edge) - 0.1, np.max(energy_edge)) + assert isinstance(self._obj, xr.DataArray) near_ef = self._obj.sel(eV=energy_slice).sum( [d for d in self._obj.dims if d not in [angular_dim]], ) diff --git a/tests/test_xarray_extensions.py b/tests/test_xarray_extensions.py index 04a36607..7ec5799d 100644 --- a/tests/test_xarray_extensions.py +++ b/tests/test_xarray_extensions.py @@ -116,6 +116,10 @@ def test_property_sample_angles(self, dataarray_cut: xr.Dataset) -> None: assert dataarray_cut.S.sample_angles[4] == 0 assert dataarray_cut.S.sample_angles[5] == 0 + def test_is_subtracted(self, dataarray_cut: xr.DataArray) -> None: + """Test property is_subtracted.""" + assert dataarray_cut.S.is_subtracted is False + def test_property_is_kspace(self, dataset_cut: xr.Dataset) -> None: """Test property is_kspace.""" assert dataset_cut.S.is_kspace is False @@ -269,6 +273,10 @@ def test_spectrum_type(self, dataarray_cut: xr.DataArray) -> None: del dataarray_cut.attrs["spectrum_type"] assert dataarray_cut.S.spectrum_type == "cut" + def test_label(self, dataarray_cut: xr.DataArray) -> None: + """Test scan_name.""" + assert dataarray_cut.S.label == "cut.fits" + class TestGeneralforDataArray: """Test class for "G"."""