Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New trigger interface #629

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions config/example/default.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,14 @@ hardware:
mydummyswitch2:
module.Class: 'switches.switch_dummy.SwitchDummy'

mydummytrigger:
module.Class: 'trigger.dummy_trigger.DummyTrigger'

mydummytrigger2:
module.Class: 'trigger.dummy_trigger.DummyTrigger'
names_of_triggers: ['bang', 'one']
trigger_length: 0

myspectrometer:
module.Class: 'spectrometer.spectrometer_dummy.SpectrometerInterfaceDummy'
connect:
Expand Down Expand Up @@ -185,6 +193,17 @@ logic:
switch1: 'mydummyswitch1'
switch2: 'mydummyswitch2'

triggerlogic:
module.Class: 'trigger_logic.TriggerLogic'
connect:
trigger_hardware: 'triggerinterfuse'

triggerinterfuse:
module.Class: 'interfuse.trigger_combiner_interfuse.TriggerCombinerInterfuse'
connect:
trigger1: 'mydummytrigger'
trigger2: 'mydummytrigger2'

scannerlogic:
module.Class: 'confocal_logic.ConfocalLogic'
connect:
Expand Down Expand Up @@ -401,6 +420,11 @@ gui:
connect:
switchlogic: 'switchlogic'

trigger:
module.Class: 'trigger.trigger_gui.TriggerGui'
connect:
trigger_logic: 'triggerlogic'

taskrunner:
module.Class: 'taskrunner.taskgui.TaskGui'
connect:
Expand Down
1 change: 1 addition & 0 deletions documentation/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ please use _ni_x_series_in_streamer.py_ as hardware module.
* Added a hardware file to interface Thorlabs filter wheels via scripts
* Bug fixes to core: made error messages sticky, respecting dependencies when restarting.
* Added a config option to regulate pid logic timestep length
* Added a new trigger interface and a logic and GUI as demonstration
*


Expand Down
134 changes: 134 additions & 0 deletions gui/trigger/trigger_gui.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# -*- coding: utf-8 -*-
"""
This file contains the Qudi console GUI module.

Qudi 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, either version 3 of the License, or
(at your option) any later version.

Qudi 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 Qudi. If not, see <http://www.gnu.org/licenses/>.

Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the
top-level directory of this distribution and at <https://github.com/Ulm-IQO/qudi/>
"""

from core.connector import Connector
from gui.guibase import GUIBase
from qtpy import QtWidgets, QtCore, QtGui


class TriggerMainWindow(QtWidgets.QMainWindow):
""" Main Window for the TriggerGui module """

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setWindowTitle('qudi: Trigger GUI')
# Create main layout and central widget
self.main_layout = QtWidgets.QVBoxLayout()
self.main_layout.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
widget = QtWidgets.QWidget()
widget.setLayout(self.main_layout)
widget.setMinimumWidth(50)
widget.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
self.setCentralWidget(widget)

# Create QActions and menu bar
menu_bar = QtWidgets.QMenuBar()
self.setMenuBar(menu_bar)

menu = menu_bar.addMenu('Menu')
self.action_close = QtWidgets.QAction('Close Window')
self.action_close.setCheckable(False)
self.action_close.setIcon(QtGui.QIcon('artwork/icons/oxygen/22x22/application-exit.png'))
self.addAction(self.action_close)
menu.addAction(self.action_close)

# close window upon triggering close action
self.action_close.triggered.connect(self.close)
return


class TriggerGui(GUIBase):
""" A graphical interface to trigger a hardware by hand.
"""

# declare connectors
trigger_logic = Connector(interface='TriggerLogic')

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._mw = TriggerMainWindow()
self._widgets = list()

def on_activate(self):
""" Create all UI objects and show the window.
"""
self.restoreWindowPos(self._mw)
self._populate_triggers()
self.show()

def on_deactivate(self):
""" Hide window empty the GUI and disconnect signals
"""

self._delete_triggers()

self.saveWindowPos(self._mw)
self._mw.close()

def show(self):
""" Make sure that the window is visible and at the top.
"""
self._mw.show()

def _populate_triggers(self):
""" Dynamically build the gui.
@return: None
"""
self._widgets = list()
for trigger in self.trigger_logic().names_of_triggers:
widget = QtWidgets.QPushButton(trigger)
widget.setMinimumWidth(50)
widget.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
widget.setCheckable(True)
widget.setChecked(False)
widget.setFocusPolicy(QtCore.Qt.NoFocus)

widget.clicked.connect(lambda button_state, trigger_origin=trigger:
self._button_toggled(trigger_origin, button_state))

self._widgets.append([trigger, widget])

self._mw.main_layout.addWidget(widget)

def _delete_triggers(self):
""" Delete all the buttons from the group box and remove the layout.
@return: None
"""

for index in reversed(range(len(self._widgets))):
trigger, widget = self._widgets[index]
widget.clicked.disconnect()
self._mw.main_layout.removeWidget(widget)
widget.setParent(None)
del self._widgets[index]
widget.deleteLater()

def _button_toggled(self, trigger, is_set):
""" Helper function that is connected to the GUI interaction.
A GUI change is transmitted to the logic and the visual indicators are changed.
@param str trigger: name of the trigger toggled
@param bool is_set: indicator for the state of the button, ignored in this case
@return: None
"""
self.trigger_logic().trigger(trigger)
for widget in self._widgets:
if trigger == widget[0]:
widget[1].setChecked(False)
123 changes: 123 additions & 0 deletions hardware/trigger/dummy_trigger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-

"""
Qudi 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, either version 3 of the License, or
(at your option) any later version.

Qudi 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 Qudi. If not, see <http://www.gnu.org/licenses/>.

Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the
top-level directory of this distribution and at <https://github.com/Ulm-IQO/qudi/>
"""

from core.module import Base
from core.configoption import ConfigOption
from core.statusvariable import StatusVar
import numpy as np
import time

from interface.trigger_interface import TriggerInterface


class DummyTrigger(Base, TriggerInterface):
""" This is a dummy to simulate a simple trigger.

Example config for copy-paste:

dummy_trigger:
module.Class: 'trigger.dummy_trigger.DummyTrigger'
names_of_triggers: ['bang', 'one']
trigger_length: 0
"""

# ConfigOptions
# names_of_triggers defined as list of strings
_names_of_triggers = ConfigOption(name='names_of_triggers', default=['one', 'two'], missing='nothing')

# StatusVariables
# trigger_length in seconds
_trigger_length = StatusVar(name='trigger_length', default=0.5)

def on_activate(self):
""" Activate the module and fill status variables.
"""
pass

def on_deactivate(self):
""" Deactivate the module and clean up.
"""
pass

@property
def number_of_triggers(self):
""" The number of triggers provided by this hardware file.
@return int: number of triggers
"""
return len(self._names_of_triggers)

@property
def names_of_triggers(self):
""" Names of the triggers as list of strings.
@return list(str): names of the triggers
"""
return self._names_of_triggers.copy()

def trigger(self, trigger=None):
""" Triggers the hardware.
The trigger is performed either
on all channels, if trigger is None,
on a single channel, if trigger is a single channel name or
on a list of channels, if trigger is a list of channel names.
@param [None/str/list(str)] trigger: trigger name to be triggered
@return int: negative error code or 0 at success
"""
if trigger is None:
trigger = self.names_of_triggers
elif isinstance(trigger, str):
if trigger in self.names_of_triggers:
trigger = [trigger]
else:
self.log.error(f'trigger name "{trigger}" was requested, but the options are: {self.names_of_triggers}')
return -1
elif isinstance(trigger, (list, tuple, np.ndarray, set)):
for index, item in enumerate(trigger):
if item not in self.names_of_triggers:
self.log.warning(f'trigger name "{item}" was requested, '
f'but the options are: {self.names_of_triggers}')
del trigger[index]
else:
self.log.error(f'The trigger name was {trigger} but either has to be one of {self.names_of_triggers} '
f'or a list of trigger names.')
return -2

for item in trigger:
self.log.info(f'Trigger on channel: {self._name}.{item}.')

time.sleep(self.trigger_length)
return 0

@property
def trigger_length(self):
""" Returns the length of all the triggers of this hardware in seconds.
@return int: length of the trigger
"""
return self._trigger_length

@trigger_length.setter
def trigger_length(self, value):
""" Sets the trigger length in seconds.
@param float value: length of the trigger to be set
"""
if not isinstance(value, (int, float)):
self.log.error(f'trigger_length has to be of type float but was {value}.')
return
self._trigger_length = float(value)
self.log.info(f'trigger_length set to {self._trigger_length}')
Loading