Skip to content

Commit

Permalink
Add running average
Browse files Browse the repository at this point in the history
I want the capability to do a running average while scanning
  • Loading branch information
lange50 committed Apr 4, 2025
1 parent 869d11e commit 3ad1f91
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 110 deletions.
2 changes: 1 addition & 1 deletion src/qudi/gui/powermeter/powermeter_main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def resizeEvent(self, event):
Adjust the divisor (here, 10) to fine-tune scaling.
"""
available_width = self.centralWidget().width()
new_font_size = max(12, int(available_width / 10))
new_font_size = max(12, int(available_width / 8))
font = self.power_label.font()
font.setPointSize(new_font_size)
self.power_label.setFont(font)
Expand Down
136 changes: 55 additions & 81 deletions src/qudi/gui/terascan/terascan_gui.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-

__all__ = ['TerascanGui']

import numpy as np
Expand All @@ -17,39 +16,35 @@

from qudi.logic.terascan_logic import TerascanData


# qudi GUI measurement modules must inherit qudi.core.module.GuiBase or other GUI modules.
class TerascanGui(GuiBase):
""" Terascan Measurement GUI
example config for copy-paste:
terascan_gui:
module.Class: 'terascan.terascan_gui.TerascanGui'
connect:
terascan_logic: terascan_logic
"""
# Signal declaration for outgoing control signals to logic
# Signals for outgoing control signals to logic
sigStartMeasurement = QtCore.Signal()
sigStopMeasurement = QtCore.Signal()
sigSetWavelengths = QtCore.Signal(float, float)
sigSetScanType = QtCore.Signal(int)
sigSetScanRate = QtCore.Signal(int)
sigSaveData = QtCore.Signal()

# One logic module
# Connector to the logic module
_terascan_logic = Connector(name='terascan_logic', interface='TerascanLogic')

# Declare static parameters that can/must be declared in the qudi configuration
# _my_config_option = ConfigOption(name='my_config_option', default=1, missing='warn')

# Declare status variables that are saved in the AppStatus upon deactivation of the module and
# are initialized to the saved value again upon activation.
# Status variables saved in the AppStatus:
_start_wavelength = StatusVar(name='start_wavelength', default=0.775)
_stop_wavelength = StatusVar(name='stop_wavelength', default=0.790)
_current_wavelength = StatusVar('current_wavelength', default=0.75)
_current_wavelength = StatusVar('current_wavelength', default=0.785)
# New status variable for the running average window size:
_running_avg_points = StatusVar(name='running_avg_points', default=5)

def on_activate(self) -> None:
# initialize the main window
# Initialize the main window and set wavelength controls:
self._mw = TerascanMainWindow()
self._mw.start_wavelength.setValue(self._start_wavelength)
self._mw.stop_wavelength.setValue(self._stop_wavelength)
Expand All @@ -59,146 +54,121 @@ def on_activate(self) -> None:
for txt, scan_rate in self._terascan_logic().scan_rates.items():
self._mw.scan_rate.addItem(txt, scan_rate)

# connect all GUI internal signals
# Connect GUI internal signals
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 all signals to and from the logic. Make sure the connections are QueuedConnection.
# Inputs:
# Connect signals from the logic module
self._terascan_logic().sigWavelengthUpdated.connect(
self._wavelength_changed, QtCore.Qt.QueuedConnection
)

self._terascan_logic().sigCountsUpdated.connect(
self._receive_data, QtCore.Qt.QueuedConnection
)

self._terascan_logic().sigScanFinished.connect(
self._scan_finished, QtCore.Qt.QueuedConnection
)

)
self._terascan_logic().sigLaserLocked.connect(
self._laser_lock_ui, QtCore.Qt.QueuedConnection
)


# Outputs:
# Connect output signals 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.sigSetWavelengths.connect(
self._terascan_logic().configure_scan, QtCore.Qt.QueuedConnection
)

self.sigSetScanType.connect(
self._terascan_logic().set_scan_type, QtCore.Qt.DirectConnection # this is direct on purpose
self._terascan_logic().set_scan_type, QtCore.Qt.DirectConnection
)

self.sigSetScanRate.connect(
self._terascan_logic().set_scan_rate, QtCore.Qt.QueuedConnection
)

self._data = []

# Turn on update timer:
# Set up update timer for plot updates
self.__timer = QtCore.QTimer()
self.__timer.setSingleShot(False)
self.__timer.timeout.connect(self._update_plot)

# Restore saved wavelengths:
self.sigSetWavelengths.emit(self._start_wavelength, self._stop_wavelength)

# Show the main window and raise it above all others
# 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:
# Disconnect all connections done in "on_activate" that are inputs
self._terascan_logic().sigWavelengthUpdated.disconnect(self._wavelength_changed)
self._terascan_logic().sigCountsUpdated.disconnect(self._receive_data)
self._terascan_logic().sigScanFinished.disconnect(self._scan_finished)
self._terascan_logic().sigLaserLocked.disconnect(self._laser_lock_ui)

# Use "plain" disconnects (without argument) only on signals owned by this module
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()

# disable update timer:
self.__timer.stop()
self.__timer.timeout.disconnect()
self.__timer = None
# Close main window

self._mw.spin_avg_points.valueChanged.disconnect(self._update_running_avg_points)
self._mw.close()


def show(self) -> None:
""" Mandatory method to show the main window """
self._mw.show()
self._mw.raise_()


### Handlers from the UI ###
# Handlers from the UI:
@QtCore.Slot(float)
def _start_changed(self, wave: float) -> None:
""" Qt slot to be called upon wavelength change """
self._start_wavelength = wave
self.sigSetWavelengths.emit(self._start_wavelength, self._stop_wavelength)


@QtCore.Slot(float)
def _stop_changed(self, wave: float) -> None:
""" Qt slot to be called upon wavelength change """
self._stop_wavelength = wave
self.sigSetWavelengths.emit(self._start_wavelength, self._stop_wavelength)


@QtCore.Slot()
def _start_stop_pressed(self) -> None:
""" Qt slot to be called upon wavelength change """
if self._mw.start_stop_button.text() == 'Start Measurement':
self._update_ui(True)
self.__timer.start(250)
self.__timer.start(250)
self.sigStartMeasurement.emit()
else:
self._update_ui(False)
self.__timer.stop()
self.sigStopMeasurement.emit()


@QtCore.Slot(int)
def _scan_type_changed(self, _: int):
""" Qt slot to be called upon scan type change """

self.sigSetScanType.emit(self._mw.scan_type.currentData().value)
# When type changes, update UI for allowed rates
self._mw.scan_rate.clear() # Clear previous rates
self._mw.scan_rate.clear()
for txt, scan_rate in self._terascan_logic().scan_rates.items():
self._mw.scan_rate.addItem(txt, scan_rate)


@QtCore.Slot(int)
def _scan_rate_changed(self, _: int):
""" Qt slot to be called upon scan rate change """
if self._mw.scan_rate.currentData() is not None:
self.sigSetScanRate.emit(self._mw.scan_rate.currentData().value)

### End Handlers from UI ###


### Begin Handlers from Logic ###

# Handlers from the Logic:
@QtCore.Slot()
def _scan_finished(self) -> None:
self._mw.start_stop_button.setText('Start Measurement')
Expand All @@ -207,58 +177,62 @@ def _scan_finished(self) -> None:
def _receive_data(self, data: List[TerascanData]) -> None:
self._data = data


@QtCore.Slot(float)
def _wavelength_changed(self, wave: float) -> None:
self._current_wavelength = wave
percent = 100 * (wave*1e-3 - self._start_wavelength) / (self._stop_wavelength - self._start_wavelength);
percent = 100 * (wave*1e-3 - self._start_wavelength) / (self._stop_wavelength - self._start_wavelength)
self._mw._progress_bar.setValue(int(round(percent)))

@QtCore.Slot()
def _save_data(self) -> None:
ds = TextDataStorage(
root_dir=self.module_default_data_dir,
column_formats='.15e'
)

root_dir=self.module_default_data_dir,
column_formats='.15e'
)
array = np.array([(d.wavelength, d.counts) for d in self._data])
ds.save_data(array)

@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))

### End Handlers from Logic ###


### Begin Private internal Functions ###

# Private internal functions:
def _update_ui(self, running: bool) -> None:
if running:
self._mw.start_stop_button.setText('Stop Measurement')
self._mw.plot_widget.setXRange(self._start_wavelength, self._stop_wavelength)
self._mw._statusbar.clearMessage()
self._mw._progress_bar.setValue(0)

else:
self._mw.start_stop_button.setText('Start Measurement')
self._mw._statusbar.showMessage('Ready')

# TODO: Make this more efficient. There is no reason to redraw the entire plot each time.
def _update_plot(self) -> None:
if (len(self._data) == 0):
def _update_plot(self):
# Make a local snapshot of the data
local_data = self._data[:]
if not local_data:
return


# x, y = self._mw.data_item.getData()
# new_count = len(self._data) - len(x)

img = np.zeros((len(self._data),2))
for i in range(len(self._data) - 1):
img[i][0] = self._data[i].wavelength*1e-3
img[i][1] = self._data[i].counts

self._mw.data_item.setData(x = img[:, 0], y = img[:, 1])
# Convert data to arrays
x_array = np.array([d.wavelength*1e-3 for d in local_data])
y_array = np.array([d.counts for d in local_data])

# 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')

# Final sanity-check
if x_array.shape[0] != y_array.shape[0]:
return # skip this update

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
Loading

0 comments on commit 3ad1f91

Please sign in to comment.