Source code for autoemx.microscope_drivers

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Electron Microscope Driver Loader

This module provides a function to dynamically load and inject
microscope-specific driver parameters and functions for SEM operation
from the 'microscope_drivers' directory.

Usage from other modules:
    from autoemx import EM_driver
    EM_driver.load_microscope_driver(microscope_ID='PhenomXL')

Author: Andrea Giunto
Created on: Mon Jan 20 15:40:42 2025
"""

import os
import importlib
from types import ModuleType
from typing import Any, Optional

_active_driver_module: Optional[ModuleType] = None
_active_driver_id: Optional[str] = None

[docs] def load_microscope_driver(microscope_ID: str) -> None: """ Dynamically load driver parameters and functions for a given microscope. This function imports all public attributes from the driver file of the microscope (located inside the microscope_drivers directory) and injects them into the current module's namespace. The driver file is '{microscope_ID}.py', unless the detector model of the microscope (autoemx/calibrations/<microscope_ID>/detector_model.json) names another driver, shared by instruments of the same model. Args ---- microscope_ID (str): The name of the microscope. Raises ------ ValueError: If the driver file cannot be found or imported. Warning ------- This function injects variables and functions into the module namespace. Use with care to avoid name collisions. """ from autoemx.calibrations import microscope_driver_name # Build the path to the driver file driver_name = microscope_driver_name(microscope_ID) drivers_dir = os.path.join(os.path.dirname(__file__)) driver_file = os.path.join(drivers_dir, f"{driver_name}.py") if not os.path.isfile(driver_file): raise ValueError( f"Could not find the microscope driver file at '{driver_file}'.\n" f"Please ensure the driver of microscope '{microscope_ID}' ('{driver_name}') matches a .py file in " "'microscope_drivers' (the driver is the microscope ID, unless set as 'driver' in its detector_model.json)." ) # Import the driver module dynamically module_name = f"autoemx.microscope_drivers.{driver_name}" pkg = __package__ if __package__ else __name__ try: mod = importlib.import_module(module_name, package=pkg) except ModuleNotFoundError as e: raise ValueError( f"Could not import driver module '{module_name}'.\n" f"Tried to import: {module_name} (relative to package '{pkg}')." ) from e global _active_driver_module, _active_driver_id _active_driver_module = mod _active_driver_id = microscope_ID
[docs] def get_active_driver_module() -> ModuleType: """Return currently loaded microscope driver module.""" if _active_driver_module is None: raise RuntimeError("No microscope driver loaded. Call load_microscope_driver first.") return _active_driver_module
[docs] def connect_to_microscope(warn_if_unavailable: bool = True) -> bool: """Connect using the active driver, if supported.""" mod = get_active_driver_module() connect_fn = getattr(mod, "connect_to_microscope", None) if connect_fn is None: raise AttributeError( f"Driver '{_active_driver_id}' does not define connect_to_microscope()." ) return bool(connect_fn(warn_if_unavailable=warn_if_unavailable))
[docs] def is_microscope_connected() -> bool: """Return True if the active driver reports a live microscope connection.""" if _active_driver_module is None: return False is_connected_fn = getattr(_active_driver_module, "is_microscope_connected", None) if is_connected_fn is not None: try: return bool(is_connected_fn()) except Exception: return False return bool(getattr(_active_driver_module, "is_at_EM", False))
def __getattr__(name: str) -> Any: """Delegate unresolved attributes to the active microscope driver module. Raises AttributeError (not RuntimeError) when no driver is loaded, so that hasattr() and getattr() with a default keep working, e.g. for introspection tools. """ if _active_driver_module is None or name.startswith("__"): raise AttributeError( f"module '{__name__}' has no attribute '{name}' (no microscope driver loaded: " "call load_microscope_driver first)." ) mod = _active_driver_module try: return getattr(mod, name) except AttributeError as exc: raise AttributeError(f"Active driver '{_active_driver_id}' has no attribute '{name}'.") from exc def __dir__() -> list[str]: attrs = set(globals().keys()) if _active_driver_module is not None: attrs.update(attr for attr in dir(_active_driver_module) if not attr.startswith("_")) return sorted(attrs)