-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
I changed the powermeter module from clade to pymeasure. I wrote a gui and logic module also.
- Loading branch information
Showing
5 changed files
with
288 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from PySide2 import QtCore | ||
from qudi.core.module import GuiBase | ||
from qudi.core.connector import Connector | ||
from qudi.core.statusvariable import StatusVar | ||
|
||
from .powermeter_main_window import PowermeterMainWindow | ||
|
||
class PowermeterGui(GuiBase): | ||
""" | ||
GUI module to display power meter readings with a multiplication factor. | ||
Example config: | ||
powermeter_gui: | ||
module.Class: 'powermeter.powermeter_gui.PowermeterGui' | ||
connect: | ||
powermeter_logic: powermeter_logic | ||
""" | ||
# Connector to the logic module (which emits power readings) | ||
_powermeter_logic = Connector(name='powermeter_logic', interface='PowermeterLogic') | ||
# StatusVar to store the multiplication factor between sessions | ||
_multiplication_factor = StatusVar(name='multiplication_factor', default=1.0) | ||
|
||
def on_activate(self): | ||
self._mw = PowermeterMainWindow() | ||
# Initialize the main window's multiplication factor from the status variable. | ||
self._mw.set_multiplication_factor(self._multiplication_factor) | ||
# Set the spinbox value accordingly. | ||
self._mw.mult_factor_spinbox.setValue(self._multiplication_factor) | ||
# Connect spinbox changes to our handler. | ||
self._mw.mult_factor_spinbox.valueChanged.connect(self._on_mult_factor_changed) | ||
# Connect logic's power update signal to the main window update slot. | ||
self._powermeter_logic().sigPowerUpdated.connect( | ||
self._mw.update_power, QtCore.Qt.QueuedConnection | ||
) | ||
self.show() | ||
|
||
def on_deactivate(self): | ||
self._powermeter_logic().sigPowerUpdated.disconnect(self._mw.update_power) | ||
self._mw.mult_factor_spinbox.valueChanged.disconnect(self._on_mult_factor_changed) | ||
self._mw.close() | ||
|
||
def show(self): | ||
self._mw.show() | ||
self._mw.raise_() | ||
|
||
@QtCore.Slot(float) | ||
def _on_mult_factor_changed(self, factor: float): | ||
""" | ||
Slot called when the multiplication factor is changed via the spinbox. | ||
Updates the StatusVar and informs the main window. | ||
""" | ||
self._multiplication_factor = factor | ||
self._mw.set_multiplication_factor(factor) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
from PySide2 import QtWidgets, QtCore, QtGui | ||
|
||
class PowermeterMainWindow(QtWidgets.QMainWindow): | ||
""" | ||
Main window for displaying the Thorlabs Power Meter reading. | ||
Features: | ||
- Automatic unit conversion. | ||
- A multiplication factor control (for correcting losses) in the lower right, | ||
where the user types in the value (no increment arrows). | ||
- Dynamic font scaling: as the window is resized, the power reading text scales. | ||
""" | ||
def __init__(self, parent=None): | ||
super().__init__(parent) | ||
self.setWindowTitle('Thorlabs Power Meter') | ||
self.resize(400, 180) | ||
|
||
# Initialize multiplication factor (default 1.0) | ||
self._mult_factor = 1.0 | ||
|
||
# Set up central widget and main layout | ||
central_widget = QtWidgets.QWidget() | ||
self.setCentralWidget(central_widget) | ||
main_layout = QtWidgets.QVBoxLayout(central_widget) | ||
main_layout.setContentsMargins(10, 10, 10, 10) | ||
main_layout.setSpacing(5) | ||
|
||
# Create power reading label with initial font | ||
self.power_label = QtWidgets.QLabel("--", self) | ||
self.power_label.setAlignment(QtCore.Qt.AlignCenter) | ||
initial_font = QtGui.QFont("Arial", 24) | ||
self.power_label.setFont(initial_font) | ||
main_layout.addWidget(self.power_label) | ||
|
||
# Add a stretch to push the following controls to the bottom | ||
main_layout.addStretch() | ||
|
||
# Create a horizontal layout for the multiplication factor controls | ||
h_layout = QtWidgets.QHBoxLayout() | ||
h_layout.addStretch() # Pushes items to the right side | ||
|
||
self.mult_factor_label = QtWidgets.QLabel("Multiplication Factor:", self) | ||
self.mult_factor_spinbox = QtWidgets.QDoubleSpinBox(self) | ||
self.mult_factor_spinbox.setDecimals(4) | ||
self.mult_factor_spinbox.setRange(0.0, 1000.0) | ||
self.mult_factor_spinbox.setSingleStep(0.1) | ||
# Remove the up/down arrows so the user types the value | ||
self.mult_factor_spinbox.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) | ||
self.mult_factor_spinbox.setValue(self._mult_factor) | ||
|
||
h_layout.addWidget(self.mult_factor_label) | ||
h_layout.addWidget(self.mult_factor_spinbox) | ||
main_layout.addLayout(h_layout) | ||
|
||
@QtCore.Slot(float) | ||
def update_power(self, power: float): | ||
""" | ||
Update the label text with the new power reading. | ||
The raw power (in Watts) is multiplied by the multiplication factor, then | ||
converted into a suitable unit (pW, nW, µW, mW, or W). | ||
""" | ||
corrected_power = power * self._mult_factor | ||
abs_pwr = abs(corrected_power) | ||
|
||
if abs_pwr < 1e-12: | ||
display_val = corrected_power | ||
display_unit = "W" | ||
elif abs_pwr < 1e-9: | ||
display_val = corrected_power * 1e12 | ||
display_unit = "pW" | ||
elif abs_pwr < 1e-6: | ||
display_val = corrected_power * 1e9 | ||
display_unit = "nW" | ||
elif abs_pwr < 1e-3: | ||
display_val = corrected_power * 1e6 | ||
display_unit = "µW" | ||
elif abs_pwr < 1: | ||
display_val = corrected_power * 1e3 | ||
display_unit = "mW" | ||
else: | ||
display_val = corrected_power | ||
display_unit = "W" | ||
|
||
self.power_label.setText(f"{display_val:.4f} {display_unit}") | ||
|
||
def set_multiplication_factor(self, factor: float): | ||
""" | ||
Update the multiplication factor. | ||
""" | ||
self._mult_factor = factor | ||
|
||
def resizeEvent(self, event): | ||
""" | ||
Dynamically scale the font size of the power reading label based on the | ||
available width of the central widget. | ||
Adjust the divisor (here, 10) to fine-tune scaling. | ||
""" | ||
available_width = self.centralWidget().width() | ||
new_font_size = max(12, int(available_width / 10)) | ||
font = self.power_label.font() | ||
font.setPointSize(new_font_size) | ||
self.power_label.setFont(font) | ||
super().resizeEvent(event) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,72 +1,111 @@ | ||
from PySide2 import QtCore | ||
|
||
from ThorlabsPM100 import ThorlabsPM100, USBTMC | ||
# ref: https://github.com/clade/ThorlabsPM100 | ||
import pyvisa | ||
from pymeasure.instruments.thorlabs import ThorlabsPM100USB | ||
|
||
from qudi.core.configoption import ConfigOption | ||
from qudi.util.mutex import RecursiveMutex | ||
from qudi.interface.simple_powermeter_interface import SimplePowermeterInterface | ||
|
||
|
||
class ThorlabsPowerMeter(SimplePowermeterInterface): | ||
""" Hardware class for Thorlabs power meter. Assumes only one meter is attached | ||
Example config for copy-paste: | ||
""" | ||
Hardware class for a Thorlabs power meter (via pymeasure). | ||
Assumes exactly one meter is attached. | ||
powermeter_thorlabs: | ||
# This module assumes only one thorlabs meter is attached | ||
module.Class: 'powermeter.thorlabs_power_meter.ThorlabsPowerMeter' | ||
Example config for copy-paste: | ||
options: | ||
average_count: 5 # Internal samples to average in the power meter | ||
update_interval: 0 # Period in ms to check for data updates. Integers only. 0 is as fast as possible | ||
powermeter_thorlabs: | ||
module.Class: 'powermeter.thorlabs_power_meter.ThorlabsPowerMeter' | ||
options: | ||
average_count: 5 # Internal samples to average in the power meter | ||
update_period: 0 # Timer interval in ms; 0 = as fast as possible | ||
""" | ||
|
||
_averages = ConfigOption( | ||
name='average_count', | ||
default=5 | ||
) | ||
|
||
_update_interval = ConfigOption(name='update_period', | ||
default=0, | ||
missing='info') | ||
|
||
|
||
# Run this in its own thread: | ||
_averages = ConfigOption(name='average_count', default=5) | ||
_update_interval = ConfigOption(name='update_period', default=0, missing='info') | ||
|
||
# This module runs in its own thread: | ||
_threaded = True | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.__timer = None | ||
self._thread_lock = RecursiveMutex() | ||
self._meter = None | ||
|
||
def on_activate(self): | ||
inst = USBTMC() | ||
self._meter = ThorlabsPM100(inst=inst) | ||
self._meter.sense.power.dc.range.auto = "ON" # auto-range | ||
self._meter.input.pdiode.filter.lpass.state = 0 # high bandwidth, 1 for low | ||
""" | ||
Called when Qudi activates the module. We open the first VISA resource | ||
that appears, instantiate the PM100USB driver from pymeasure, | ||
and set up a QTimer to poll periodically. | ||
""" | ||
rm = pyvisa.ResourceManager() | ||
devices = rm.list_resources() | ||
|
||
if not devices: | ||
self.log.error("No VISA resources found. Cannot open Thorlabs PM100.") | ||
return | ||
|
||
# Open the first available device (adjust if you have multiple meters) | ||
device = devices[0] | ||
self.log.info(f"Opening resource: {device}") | ||
self._meter = ThorlabsPM100USB(device) | ||
|
||
# Example: set auto-range if supported | ||
self._meter.auto_range = True | ||
|
||
# Set the averaging if supported | ||
self.set_average_count(self._averages) | ||
|
||
|
||
# Start a timer to periodically read data | ||
self.__timer = QtCore.QTimer() | ||
self.__timer.timeout.connect(self.__data_update) | ||
self.__timer.setSingleShot(False) | ||
self.__timer.start(int(self._update_interval)) | ||
|
||
|
||
def on_deactivate(self): | ||
if (self.__timer is not None): | ||
""" | ||
Called when Qudi deactivates the module. | ||
Clean up the timer and close the device. | ||
""" | ||
if self.__timer is not None: | ||
self.__timer.stop() | ||
self.__timer.timeout.disconnect() | ||
self.__timer = None | ||
|
||
def get_power(self): | ||
return self._meter.read | ||
|
||
# If needed, close the device | ||
if self._meter is not None: | ||
try: | ||
self._meter.shutdown() # Only if the pymeasure driver supports it | ||
except AttributeError: | ||
pass | ||
self._meter = None | ||
|
||
def get_power(self) -> float: | ||
""" | ||
Retrieve the current power reading from the device (in Watts). | ||
""" | ||
if self._meter is None: | ||
return 0.0 | ||
return self._meter.power # from the pymeasure PM100USB driver | ||
|
||
def set_average_count(self, count: int): | ||
""" | ||
Set the sample averaging on the power meter, if supported. | ||
""" | ||
self._averages = count | ||
self._meter.sense.average.count = self._averages | ||
|
||
|
||
if self._meter is not None: | ||
# The actual pymeasure property might differ; adjust as needed. | ||
try: | ||
self._meter.average_count = count | ||
except AttributeError: | ||
pass # Not implemented by the driver? | ||
|
||
def __data_update(self): | ||
""" | ||
Called by the QTimer to emit new data for any logic or GUI module | ||
connected to sigNewData. | ||
""" | ||
with self._thread_lock: | ||
data = self.get_power() | ||
self.sigNewData.emit(data) | ||
self.sigNewData.emit(data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
from PySide2 import QtCore | ||
from qudi.core.module import LogicBase | ||
from qudi.core.connector import Connector | ||
from qudi.util.mutex import RecursiveMutex | ||
|
||
class PowermeterLogic(LogicBase): | ||
""" | ||
Logic module for displaying the Thorlabs power meter readings. | ||
It connects to the hardware module, listens for new data, and re-emits | ||
a dedicated signal to any GUI module. | ||
""" | ||
|
||
# Connector to the hardware module that implements SimplePowermeterInterface | ||
_powermeter = Connector(name='powermeter', interface='SimplePowermeterInterface') | ||
|
||
# Signal that the GUI can connect to for new power readings | ||
sigPowerUpdated = QtCore.Signal(float) | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self._thread_lock = RecursiveMutex() | ||
|
||
def on_activate(self): | ||
""" Called when this logic module is activated by Qudi. """ | ||
# Connect hardware's new-data signal to our internal slot | ||
self._powermeter().sigNewData.connect(self._on_new_data, QtCore.Qt.QueuedConnection) | ||
|
||
def on_deactivate(self): | ||
""" Called when this logic module is deactivated. """ | ||
# Safely disconnect signals | ||
self._powermeter().sigNewData.disconnect(self._on_new_data) | ||
|
||
@QtCore.Slot(float) | ||
def _on_new_data(self, power: float): | ||
""" Internal slot that receives new power readings from the hardware. """ | ||
with self._thread_lock: | ||
# Simply re-emit the data for the GUI | ||
self.sigPowerUpdated.emit(power) |