-
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.
This is not carefully tested.
- Loading branch information
Showing
4 changed files
with
666 additions
and
200 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,367 @@ | ||
# -*- coding: utf-8 -*- | ||
__all__ = ['TerascanGui'] | ||
|
||
import numpy as np | ||
import os | ||
from PySide2 import QtCore, QtGui, QtWidgets | ||
import pyqtgraph as pg | ||
from typing import List | ||
from time import sleep | ||
|
||
from qudi.util.datastorage import TextDataStorage | ||
from qudi.core.module import GuiBase | ||
from qudi.core.connector import Connector | ||
from qudi.core.configoption import ConfigOption | ||
from qudi.core.statusvariable import StatusVar | ||
# from qudi.gui.terascan.terascan_main_window import TerascanMainWindow | ||
from qudi.util.paths import get_artwork_dir | ||
from qudi.util.colordefs import QudiPalettePale as palette | ||
from enum import Enum | ||
|
||
# TODO: This is a copy of the one in the logic module. We should probably move this to a common place. | ||
class TeraScanType(Enum): | ||
SCAN_TYPE_MEDIUM = 1 | ||
SCAN_TYPE_FINE = 2 | ||
SCAN_TYPE_LINE = 3 | ||
|
||
class TeraScanRate(Enum): | ||
SCAN_RATE_MEDIUM_100_GHZ = 4 | ||
SCAN_RATE_MEDIUM_50_GHZ = 5 | ||
SCAN_RATE_MEDIUM_20_GHZ = 6 | ||
SCAN_RATE_MEDIUM_15_GHZ = 7 | ||
SCAN_RATE_MEDIUM_10_GHZ = 8 | ||
SCAN_RATE_MEDIUM_5_GHZ = 9 | ||
SCAN_RATE_MEDIUM_2_GHZ = 10 | ||
SCAN_RATE_MEDIUM_1_GHZ = 11 | ||
SCAN_RATE_FINE_LINE_20_GHZ = 12 | ||
SCAN_RATE_FINE_LINE_10_GHZ = 13 | ||
SCAN_RATE_FINE_LINE_5_GHZ = 14 | ||
SCAN_RATE_FINE_LINE_2_GHZ = 15 | ||
SCAN_RATE_FINE_LINE_1_GHZ = 16 | ||
SCAN_RATE_FINE_LINE_500_MHZ = 17 | ||
SCAN_RATE_FINE_LINE_200_MHZ = 18 | ||
SCAN_RATE_FINE_LINE_100_MHZ = 19 | ||
SCAN_RATE_FINE_LINE_50_MHZ = 20 | ||
SCAN_RATE_FINE_LINE_20_MHZ = 21 | ||
SCAN_RATE_FINE_LINE_10_MHZ = 22 | ||
SCAN_RATE_FINE_LINE_5_MHZ = 23 | ||
SCAN_RATE_FINE_LINE_2_MHZ = 24 | ||
SCAN_RATE_FINE_LINE_1_MHZ = 25 | ||
SCAN_RATE_LINE_500_KHZ = 26 | ||
SCAN_RATE_LINE_200_KHZ = 27 | ||
SCAN_RATE_LINE_100_KHZ = 28 | ||
SCAN_RATE_LINE_50_KHZ = 29 | ||
|
||
pg.setConfigOption('useOpenGL', True) # Add this at the top of your file | ||
|
||
|
||
# TODO: No status variables. Just grab the ones from the logic module. | ||
class TerascanGui(GuiBase): | ||
""" Terascan Measurement GUI | ||
example config for copy-paste: | ||
terascan_gui: | ||
module.Class: 'terascan.terascan_gui.TerascanGui' | ||
connect: | ||
terascan_logic: terascan_logic | ||
""" | ||
|
||
####################### SIGNALS FROM GUI TO LOGIC #################### | ||
sigStartMeasurement = QtCore.Signal() | ||
sigStopMeasurement = QtCore.Signal() | ||
sigSetStartWavelength = QtCore.Signal(float) | ||
sigSetStopWavelength = QtCore.Signal(float) | ||
sigSetScanType = QtCore.Signal(int) | ||
sigSetScanRate = QtCore.Signal(int) | ||
sigSaveData = QtCore.Signal() | ||
|
||
#################### CONNECTOR TO LOGIC #################### | ||
_terascan_logic = Connector(name='terascan_logic', interface='TerascanLogic') | ||
|
||
#################### STATUS VARIABLES #################### | ||
_running_avg_points = StatusVar(name='running_avg_points', default=5) | ||
|
||
# TODO: operate in terascan_logic instead of terascan_logic() | ||
def on_activate(self) -> None: | ||
# Initialize the main window and set wavelength controls: | ||
self._mw = TerascanMainWindow() | ||
|
||
for txt, scan_type in self._terascan_logic().scan_types.items(): | ||
self._mw.scan_type.addItem(txt, scan_type) | ||
for txt, scan_rate in self._terascan_logic().scan_rates.items(): | ||
self._mw.scan_rate.addItem(txt, scan_rate) | ||
|
||
################# CONNECT SIGNALS FROM GUI TO GUI ############################ | ||
self._mw.start_wavelength.valueChanged.connect(self._start_changed) | ||
self._mw.stop_wavelength.valueChanged.connect(self._stop_changed) | ||
self._mw.start_stop_button.clicked.connect(self._start_stop_pressed) | ||
self._mw.action_save_data.triggered.connect(self._save_data) | ||
self._mw.scan_type.currentIndexChanged.connect(self._scan_type_changed) | ||
self._mw.scan_rate.currentIndexChanged.connect(self._scan_rate_changed) | ||
|
||
#################### CONNECT SIGNALS FROM LOGIC TO GUI #################### | ||
self._terascan_logic().sigNewData.connect(self._update_data) # Update the GUI with new data | ||
self._terascan_logic().sigSetScanType.connect(self._set_scan_type) # Update the GUI with new scan type | ||
self._terascan_logic().sigSetScanRate.connect(self._set_scan_rate) # Update the GUI with new scan rate | ||
self._terascan_logic().sigScanStarted.connect(self._scan_started) # Update the GUI when the scan starts | ||
self._terascan_logic().sigScanStopped.connect(self._scan_stopped) # Update the GUI when the scan stops | ||
|
||
|
||
################### CONNECT SIGNALS FROM GUI TO LOGIC #################### | ||
self.sigStartMeasurement.connect( | ||
self._terascan_logic().start_scan, QtCore.Qt.QueuedConnection | ||
) | ||
self.sigStopMeasurement.connect( | ||
self._terascan_logic().stop_scan, QtCore.Qt.QueuedConnection | ||
) | ||
self.sigSetStartWavelength.connect( | ||
self._terascan_logic().set_start_wavelength, QtCore.Qt.QueuedConnection | ||
) | ||
self.sigSetStopWavelength.connect( | ||
self._terascan_logic().set_stop_wavelength, QtCore.Qt.QueuedConnection | ||
) | ||
self.sigSetScanType.connect( | ||
self._terascan_logic().set_scan_type, QtCore.Qt.DirectConnection | ||
) | ||
self.sigSetScanRate.connect( | ||
self._terascan_logic().set_scan_rate, QtCore.Qt.QueuedConnection | ||
) | ||
self.sigSaveData.connect( | ||
self._terascan_logic().save_data, QtCore.Qt.QueuedConnection | ||
) | ||
|
||
####################### REFERENCE TO TERASCAN DATA #################### | ||
self.wavelength_data: List[float] = [] # Wavelength data from the logic module | ||
self.counts_data: List[float] = [] # Counts data from the logic module | ||
|
||
# Set up update timer for plot updates | ||
self.__timer = QtCore.QTimer() | ||
self.__timer.setSingleShot(False) | ||
self.__timer.timeout.connect(self.__update_gui) | ||
self.__timer.start(250) # Update every 250 ms | ||
# TODO: Make this more configurable and maybe more efficient? | ||
|
||
# Restore running average points from the StatusVar: | ||
self._mw.spin_avg_points.setValue(self._running_avg_points) | ||
# Connect changes of the spin box to update our status variable | ||
self._mw.spin_avg_points.valueChanged.connect(self._update_running_avg_points) | ||
|
||
self.show() | ||
|
||
def on_deactivate(self) -> None: | ||
self._terascan_logic().sigNewData.disconnect(self._update_data) | ||
|
||
self._mw.start_wavelength.valueChanged.disconnect() | ||
self._mw.stop_wavelength.valueChanged.disconnect() | ||
self._mw.start_stop_button.clicked.disconnect() | ||
self.sigStartMeasurement.disconnect() | ||
self.sigStopMeasurement.disconnect() | ||
self.sigSetWavelengths.disconnect() | ||
|
||
self.__timer.stop() | ||
self.__timer.timeout.disconnect() | ||
self.__timer = None | ||
|
||
self._mw.spin_avg_points.valueChanged.disconnect(self._update_running_avg_points) | ||
self._mw.close() | ||
|
||
def show(self) -> None: | ||
self._mw.show() | ||
self._mw.raise_() | ||
|
||
def _save_data(self) -> None: | ||
""" Tell the logic to save the data """ | ||
self.sigSaveData.emit() | ||
|
||
# Handlers from the UI: | ||
@QtCore.Slot(float) | ||
def _start_changed(self, wave: float) -> None: | ||
self.sigSetStartWavelength.emit(wave) | ||
|
||
@QtCore.Slot(float) | ||
def _stop_changed(self, wave: float) -> None: | ||
self.sigSetStopWavelength.emit(wave) | ||
|
||
@QtCore.Slot() | ||
def _start_stop_pressed(self) -> None: | ||
if self._mw.start_stop_button.text() == 'Start Measurement': | ||
self.sigStartMeasurement.emit() # Tell the logic to start the scan | ||
|
||
# TODO: This should be a signal | ||
self._terascan_logic().clear_data() | ||
start_wl = self._terascan_logic().start_wavelength | ||
stop_wl = self._terascan_logic().stop_wavelength | ||
self._mw.plot_widget.setXRange(start_wl, stop_wl) | ||
else: | ||
self.sigStopMeasurement.emit() # Tell the logic to stop the scan | ||
|
||
|
||
|
||
|
||
# @QtCore.Slot(bool) | ||
# def _laser_lock_ui(self, locked: bool) -> None: | ||
# icon = 'network-connect' if locked else 'network-disconnect' | ||
# pix = QtGui.QPixmap(os.path.join(get_artwork_dir(), 'icons', icon)) | ||
# self._mw._locked_indicator.setPixmap(pix.scaled(16, 16)) | ||
|
||
# Private internal functions: | ||
def _scan_started(self) -> None: | ||
""" Update the GUI when the scan starts """ | ||
self._mw.start_stop_button.setText('Stop Measurement') | ||
self._mw._statusbar.clearMessage() | ||
self._mw._progress_bar.setValue(0) | ||
|
||
def _scan_stopped(self) -> None: | ||
""" Update the GUI when the scan stops """ | ||
self._mw.start_stop_button.setText('Start Measurement') | ||
self._mw._statusbar.showMessage('Ready') | ||
self._mw._progress_bar.setValue(100) | ||
|
||
@QtCore.Slot() | ||
def __update_gui(self): | ||
x_array = self.wavelength_data | ||
y_array = self.counts_data | ||
|
||
if len(x_array) == 0 or len(y_array) == 0: | ||
return | ||
|
||
# If running average is enabled, apply a rolling average | ||
if self._mw.checkbox_running_avg.isChecked(): | ||
window_size = self._mw.spin_avg_points.value() | ||
if window_size > 1 and window_size <= len(y_array): | ||
kernel = np.ones(window_size) / float(window_size) | ||
y_array = np.convolve(y_array, kernel, mode='same') | ||
|
||
|
||
self._mw.data_item.setData( | ||
x=x_array, | ||
y=y_array, | ||
) | ||
|
||
|
||
@QtCore.Slot(int) | ||
def _update_running_avg_points(self, points: int) -> None: | ||
self._running_avg_points = points | ||
|
||
@QtCore.Slot() | ||
def _update_data(self, wavelength_data, counts_data) -> None: | ||
""" Update the data from the logic module """ | ||
self.wavelength_data = wavelength_data | ||
self.counts_data = counts_data | ||
|
||
|
||
|
||
class TerascanMainWindow(QtWidgets.QMainWindow): | ||
""" Main window for Terascan measurement """ | ||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.setWindowTitle('Terascan Measurement') | ||
self.resize(1250, 500) | ||
|
||
# Create menu bar | ||
menu_bar = QtWidgets.QMenuBar() | ||
menu = menu_bar.addMenu('File') | ||
self.action_save_data = QtWidgets.QAction('Save Data') | ||
path = os.path.join(get_artwork_dir(), 'icons', 'document-save') | ||
self.action_save_data.setIcon(QtGui.QIcon(path)) | ||
menu.addAction(self.action_save_data) | ||
menu.addSeparator() | ||
|
||
self.action_close = QtWidgets.QAction('Close') | ||
path = os.path.join(get_artwork_dir(), 'icons', 'application-exit') | ||
self.action_close.setIcon(QtGui.QIcon(path)) | ||
self.action_close.triggered.connect(self.close) | ||
menu.addAction(self.action_close) | ||
self.setMenuBar(menu_bar) | ||
|
||
# Create statusbar and indicators | ||
self._statusbar = self.statusBar() | ||
self._progress_bar = QtWidgets.QProgressBar() | ||
self._progress_bar.setRange(0, 100) | ||
self._progress_bar.setValue(0) | ||
|
||
self._locked_indicator = QtWidgets.QLabel() | ||
self._locked_indicator.setPixmap( | ||
QtGui.QPixmap(os.path.join(get_artwork_dir(), 'icons', 'network-disconnect')).scaled(16, 16) | ||
) | ||
self._statusbar.addWidget(self._locked_indicator) | ||
self._statusbar.addWidget(self._progress_bar) | ||
|
||
# Initialize widgets for wavelengths, scan, etc. | ||
self.start_wavelength_label = QtWidgets.QLabel('Start Wavelength (um)') | ||
self.start_wavelength = QtWidgets.QDoubleSpinBox() | ||
self.start_wavelength.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) | ||
self.start_wavelength.setAlignment(QtCore.Qt.AlignHCenter) | ||
self.start_wavelength.setRange(0.3, 2) | ||
self.start_wavelength.setDecimals(6) | ||
|
||
self.stop_wavelength_label = QtWidgets.QLabel('Stop Wavelength (um)') | ||
self.stop_wavelength = QtWidgets.QDoubleSpinBox() | ||
self.stop_wavelength.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) | ||
self.stop_wavelength.setAlignment(QtCore.Qt.AlignHCenter) | ||
self.stop_wavelength.setRange(0.3, 2) | ||
self.stop_wavelength.setDecimals(6) | ||
|
||
self.scan_rate_label = QtWidgets.QLabel('Scan Rate') | ||
self.scan_rate = QtWidgets.QComboBox() | ||
|
||
self.scan_type_label = QtWidgets.QLabel('Scan Type') | ||
self.scan_type = QtWidgets.QComboBox() | ||
|
||
self.plot_widget = pg.PlotWidget() | ||
self.plot_widget.setAntialiasing(False) | ||
self.plot_widget.getPlotItem().setContentsMargins(1, 1, 1, 1) | ||
self.plot_widget.setSizePolicy(QtWidgets.QSizePolicy.Expanding, | ||
QtWidgets.QSizePolicy.Expanding) | ||
self.plot_widget.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus) | ||
self.plot_widget.setLabel('bottom', text='Wavelength', units='um') | ||
self.plot_widget.setLabel('left', text='Counts') | ||
|
||
self.data_item = pg.PlotDataItem( | ||
pen=pg.mkPen(palette.c1, style=QtCore.Qt.SolidLine), | ||
# downsampling if you want | ||
# downsample=10, # Render 1 out of every 10 points | ||
# downsampleMethod='mean' # Average points for smoother results | ||
) | ||
self.plot_widget.addItem(self.data_item) | ||
|
||
# Running Average controls | ||
self.checkbox_running_avg = QtWidgets.QCheckBox("Enable Running Average") | ||
self.checkbox_running_avg.setChecked(False) | ||
self.label_avg_points = QtWidgets.QLabel("Points in Rolling Average:") | ||
self.spin_avg_points = QtWidgets.QSpinBox() | ||
self.spin_avg_points.setRange(1, 9999) | ||
self.spin_avg_points.setValue(5) # default value; this will be set from a StatusVar in the GUI | ||
|
||
# The Start Measurement button (we want this at the very bottom) | ||
self.start_stop_button = QtWidgets.QPushButton('Start Measurement') | ||
|
||
# Arrange widgets in layout | ||
layout = QtWidgets.QGridLayout() | ||
layout.addWidget(self.plot_widget, 0, 0, 4, 4) | ||
|
||
control_layout = QtWidgets.QVBoxLayout() | ||
control_layout.addWidget(self.scan_type_label, 0, QtCore.Qt.AlignBottom) | ||
control_layout.addWidget(self.scan_type, 0, QtCore.Qt.AlignTop) | ||
control_layout.addWidget(self.scan_rate_label, 0, QtCore.Qt.AlignBottom) | ||
control_layout.addWidget(self.scan_rate, 0, QtCore.Qt.AlignTop) | ||
control_layout.addWidget(self.start_wavelength_label, 0, QtCore.Qt.AlignBottom) | ||
control_layout.addWidget(self.start_wavelength, 0, QtCore.Qt.AlignTop) | ||
control_layout.addWidget(self.stop_wavelength_label, 0, QtCore.Qt.AlignBottom) | ||
control_layout.addWidget(self.stop_wavelength, 0, QtCore.Qt.AlignTop) | ||
|
||
# Place Running Average controls ABOVE the Start Measurement button: | ||
control_layout.addWidget(self.checkbox_running_avg) | ||
control_layout.addWidget(self.label_avg_points) | ||
control_layout.addWidget(self.spin_avg_points) | ||
|
||
# Add stretch to push the start button to the bottom: | ||
control_layout.addStretch() | ||
control_layout.addWidget(self.start_stop_button) | ||
|
||
layout.addLayout(control_layout, 0, 5, 5, 1) | ||
layout.setColumnStretch(1, 1) | ||
|
||
central_widget = QtWidgets.QWidget() | ||
central_widget.setLayout(layout) | ||
self.setCentralWidget(central_widget) |
Oops, something went wrong.