Source code for ORIEL

# Copyright (C) 2026 Bence Göblyös
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see https://www.gnu.org/licenses/.

"""
This module contains the driver for ORIEL Luminator monochromated light sources.

Examples
--------
Below is a minimum working example showing how to use the ORIEL Luminator.

.. code-block:: python

    import pyvisa
    from Devices.ORIEL import Luminator

    rm = pyvisa.ResourceManager()
    device = Luminator(rm, 'GPIB0::4::INSTR')


    device.wavelength = 532 # By default, the first-order wavelength calibration is applied
    print(f'Wavelength: {device.wavelength} nm') # Read back actual wavelength

    # The timeout will prevent new commands from being sent while the monochromator is moving
    device.wavelength = 400
    device.wavelength = 900

    # Everything outside the configured bounds in clamped
    device.wavelength = 0
    device.wavelength = 10000
"""


import numpy as np
import json
import logging
import time
import sys
from pathlib import Path

if "sphinx" in sys.modules:
    PROJECT_ROOT = Path(".")
else:
    PROJECT_ROOT = Path(__file__).resolve().parents[1]

[docs] class Luminator(): """ Driver class for the ORIEL Luminator monochromated light source. Handles instrument communication via PyVISA and applies an optional linear wavelength calibration based on a provided configuration file. """ def __init__(self, rm, address, config = (PROJECT_ROOT / "Config" / "Luminator.json")): """ Parameters ---------- rm: pyvisa.ResourceManager The VISA resource manager instance. address: str The VISA resource address for the device. config: Path, default: "Config/Luminator.json" Path to the JSON configuration file containing calibration data with 'c0' and 'c1' coefficients, 'lower' and 'upper' limits and 'speed' value. """ # Set up logger self.logger = logging.getLogger('instrumpy.Luminator') self.logger.propagate = True self.logger.setLevel(logging.NOTSET) self.logger.debug("Logger initialized.") self.device = rm.open_resource(address) self.device.write_termination = "\r\n" self.device.read_termination = "\r\n" # TODO: Store multiple config entries and fetch them by serial number. d = {} if config is not None: try: with open(config, "r") as file: d = json.load(file) except Exception as e: self.logger.warning(f"Configuration could not be loaded: {e}") d = {} self.a = d.get('c1', 1.0) self.b = d.get('c0', 0.0) self.lower = d.get('lower', 300.0) self.upper = d.get('upper', 1200.0) self.speed = d.get('speed', 50.0)
[docs] def internal2actual(self, internal): """ Converts the internal monochromator setpoint to the actual output wavelength. Parameters ---------- internal: float The internal wavelength setpoint in nm. Returns ------- actual: float The calibrated actual output wavelength in nm. """ return float(self.a*internal + self.b)
[docs] def actual2internal(self, actual): """ Converts a desired actual output wavelength to the required internal setpoint. Parameters ---------- actual: float The desired actual output wavelength in nm. Returns ------- internal: float The necessary internal setpoint in nm to achieve the actual wavelength. """ return float((actual - self.b)/self.a)
[docs] def setWavelength(self, target, convert = True): """ Sets the operating wavelength of the monochromator. Parameters ---------- target: float The target wavelength in nm. convert: bool, default: True If True, applies the linear calibration to the target before sending. """ old = self.getWavelength(convert = convert) if target < self.lower: target = self.lower self.logger.warning(f"Wavelength below minimum level. It has been increased to {self.lower} nm.") if target > self.upper: target = self.upper self.logger.warning(f"Wavelength above maximum level. It has been decreased to {self.upper} nm.") wait = (np.abs(old - target) / self.speed) + 0.05 target = self.actual2internal(target) if convert else target cmd = "GOWAVE " + "{:.3f}".format(target) self.device.write(cmd) time.sleep(wait)
[docs] def getWavelength(self, convert = True): """ Retrieves the current operating wavelength of the monochromator. Parameters ---------- convert: bool, default: True If True, returns the calibrated actual wavelength. If False, returns the raw internal setpoint. Returns ------- wavelength: float The current wavelength in nm. """ result = float(self.device.query("WAVE?")) return self.internal2actual(result) if convert else result
#: float: Get or set the wavelength of the monochromator in nm. Applies calibration if available. wavelength = property(fget=getWavelength, fset=setWavelength)