"""
The control panel above the plotting area allows to manipulate the following
attributes specific to the :class:`~pyrpl.hardware_modules.scope.Scope`:
* :attr:`~.Scope.ch1_active`/:attr:`~.Scope.ch2_active`: Hide/show the trace
corresponding to ch1/ch2.
* :attr:`~.Scope.input1`/:attr:`~.Scope.input2`: Choose the input among a
list of possible signals. Internal signals can be referenced by their
symbolic name e.g. :code:`lockbox.outputs.output1`.
* :attr:`~.Scope.threshold`: The voltage threshold for the scope trigger.
* :attr:`~.Scope.hysteresis`: Hysteresis for the scope trigger, i.e. the scope
input signal must exceed the :attr:`~.Scope.threshold` value by more than
the hysteresis value to generate a trigger event.
* :attr:`~.Scope.duration`: The full duration of the scope trace to acquire,
in units of seconds.
* :attr:`~.Scope.trigger_delay`: The delay beteween trigger event and the
center of the trace.
* :attr:`~.Scope.trigger_source`: The channel to use as trigger input.
* :attr:`~.Scope.average`: Enables "averaging" a.k.a. "high-resolution" mode,
which averages all data samples acquired at the full sampling rate between
two successive points of the trace. If disabled, only a sample of the
full-rate signal is shown as the trace. The averaging mode corresponds to a
moving-average filter with a cutoff frequency of
:attr:`~.pyrpl.hardware_modules.scope.Scope.sampling_time` :math:`^{-1} = 2^{14}/\\mathrm{duration}`
in units of Hz.
* :attr:`~.Scope.trigger_mode`: Multiple options are available.
* :code:`Normal` is used for triggered acquisition.
* :code:`Untriggered (rolling)` is used for continuous acquisition without
requiring a trigger signal, where the traces "roll" through the plotting
area from right to left in real-time. The rolling mode does not allow for
trace averaging nor durations below 0.1 s.
"""
import pyqtgraph as pg
from qtpy import QtCore, QtGui, QtWidgets
import numpy as np
from ...errors import NotReadyError
from .base_module_widget import ModuleWidget
from .acquisition_module_widget import AcquisitionModuleWidget
[docs]class ScopeWidget(AcquisitionModuleWidget):
"""
Widget for scope
"""
[docs] def init_gui(self):
"""
sets up all the gui for the scope.
"""
self.datas = [None, None]
self.times = None
self.ch_color = ('green', 'red')
self.ch_transparency = (255, 255) # 0 is transparent, 255 is not # deactivated transparency for speed reasons
#self.module.__dict__['curve_name'] = 'scope'
#self.main_layout = QtWidgets.QVBoxLayout()
self.init_main_layout(orientation="vertical")
self.init_attribute_layout()
aws = self.attribute_widgets
self.layout_channels = QtWidgets.QVBoxLayout()
self.layout_ch1 = QtWidgets.QHBoxLayout()
self.layout_ch2 = QtWidgets.QHBoxLayout()
self.layout_channels.addLayout(self.layout_ch1)
self.layout_channels.addLayout(self.layout_ch2)
self.attribute_layout.removeWidget(aws['xy_mode'])
self.attribute_layout.removeWidget(aws['ch1_active'])
self.attribute_layout.removeWidget(aws['input1'])
self.attribute_layout.removeWidget(aws['threshold'])
self.layout_ch1.addWidget(aws['ch1_active'])
self.layout_ch1.addWidget(aws['input1'])
self.layout_ch1.addWidget(aws['threshold'])
aws['ch1_active'].setStyleSheet("color: %s" % self.ch_color[0])
self.attribute_layout.removeWidget(aws['ch2_active'])
self.attribute_layout.removeWidget(aws['input2'])
self.attribute_layout.removeWidget(aws['hysteresis'])
aws['ch2_active'].setStyleSheet("color: %s" % self.ch_color[1])
self.layout_ch2.addWidget(aws['ch2_active'])
self.layout_ch2.addWidget(aws['input2'])
self.layout_ch2.addWidget(aws['hysteresis'])
self.attribute_layout.addLayout(self.layout_channels)
self.attribute_layout.removeWidget(aws['duration'])
self.attribute_layout.removeWidget(aws['trigger_delay'])
self.layout_duration = QtWidgets.QVBoxLayout()
self.layout_duration.addWidget(aws['duration'])
self.layout_duration.addWidget(aws['trigger_delay'])
self.attribute_layout.addLayout(self.layout_duration)
self.attribute_layout.removeWidget(aws['trigger_source'])
self.attribute_layout.removeWidget(aws['average'])
self.layout_misc = QtWidgets.QVBoxLayout()
self.layout_misc.addWidget(aws['trigger_source'])
self.layout_misc.addWidget(aws['average'])
self.attribute_layout.addLayout(self.layout_misc)
#self.attribute_layout.removeWidget(aws['curve_name'])
self.button_layout = QtWidgets.QHBoxLayout()
aws = self.attribute_widgets
self.attribute_layout.removeWidget(aws["trace_average"])
self.attribute_layout.removeWidget(aws["curve_name"])
self.button_layout.addWidget(aws["xy_mode"])
self.button_layout.addWidget(aws["trace_average"])
self.button_layout.addWidget(aws["curve_name"])
#self.setLayout(self.main_layout)
self.setWindowTitle("Scope")
self.win = pg.GraphicsWindow(title="Scope")
self.plot_item = self.win.addPlot(title="Scope")
self.plot_item.showGrid(y=True, alpha=1.)
#self.button_single = QtWidgets.QPushButton("Run single")
#self.button_continuous = QtWidgets.QPushButton("Run continuous")
#self.button_save = QtWidgets.QPushButton("Save curve")
self.curves = [self.plot_item.plot(pen=(QtGui.QColor(color).red(),
QtGui.QColor(color).green(),
QtGui.QColor(color).blue()
))
#,trans)) \
for color, trans in zip(self.ch_color,
self.ch_transparency)]
self.main_layout.addWidget(self.win, stretch=10)
#self.button_layout.addWidget(self.button_single)
#self.button_layout.addWidget(self.button_continuous)
#self.button_layout.addWidget(self.button_save)
#self.button_layout.addWidget(aws['curve_name'])
#aws['curve_name'].setMaximumWidth(250)
self.main_layout.addLayout(self.button_layout)
#self.button_single.clicked.connect(self.run_single_clicked)
#self.button_continuous.clicked.connect(self.run_continuous_clicked)
#self.button_save.clicked.connect(self.save_clicked)
self.rolling_group = QtWidgets.QGroupBox("Trigger mode")
self.checkbox_normal = QtWidgets.QRadioButton("Normal")
self.checkbox_untrigged = QtWidgets.QRadioButton("Untrigged (rolling)")
self.checkbox_normal.setChecked(True)
self.lay_radio = QtWidgets.QVBoxLayout()
self.lay_radio.addWidget(self.checkbox_normal)
self.lay_radio.addWidget(self.checkbox_untrigged)
self.rolling_group.setLayout(self.lay_radio)
self.attribute_layout.insertWidget(
list(self.attribute_widgets.keys()).index("trigger_source"),
self.rolling_group)
self.checkbox_normal.clicked.connect(self.rolling_mode_toggled)
self.checkbox_untrigged.clicked.connect(self.rolling_mode_toggled)
#self.update_rolling_mode_visibility()
self.attribute_widgets['duration'].value_changed.connect(
self.update_rolling_mode_visibility)
super(ScopeWidget, self).init_gui()
# since trigger_mode radiobuttons is not a regular attribute_widget,
# it is not synced with the module at creation time.
self.update_running_buttons()
self.update_rolling_mode_visibility()
self.rolling_mode = self.module.rolling_mode
self.attribute_layout.addStretch(1)
# Not sure why the stretch factors in button_layout are not good by
# default...
#self.button_layout.setStretchFactor(self.button_single, 1)
#self.button_layout.setStretchFactor(self.button_continuous, 1)
#self.button_layout.setStretchFactor(self.button_save, 1)
[docs] def update_attribute_by_name(self, name, new_value_list):
"""
Updates all attributes on the gui when their values have changed.
"""
super(ScopeWidget, self).update_attribute_by_name(name, new_value_list)
if name in ['rolling_mode', 'duration']:
self.rolling_mode = self.module.rolling_mode
self.update_rolling_mode_visibility()
if name in ['running_state',]:
self.update_running_buttons()
[docs] def display_channel(self, ch):
"""
Displays channel ch (1 or 2) on the graph
:param ch:
"""
try:
self.datas[ch-1] = self.module.curve(ch)
self.times = self.module.times
self.curves[ch-1].setData(self.times,
self.datas[ch-1])
except NotReadyError:
pass
[docs] def change_ownership(self):
"""
For some reason the visibility of the rolling mode panel is not updated
when the scope becomes free again unless we ask for it explicitly...
"""
super(ScopeWidget, self).change_ownership()
self.update_rolling_mode_visibility()
[docs] def display_curve(self, list_of_arrays):
"""
Displays all active channels on the graph.
"""
times, (ch1, ch2) = list_of_arrays
disp = [(ch1, self.module.ch1_active), (ch2, self.module.ch2_active)]
if self.module.xy_mode:
self.curves[0].setData(ch1, ch2)
self.curves[0].setVisible(True)
self.curves[1].setVisible(False)
else:
for ch, (data, active) in enumerate(disp):
if active:
self.curves[ch].setData(times, data)
self.curves[ch].setVisible(True)
else:
self.curves[ch].setVisible(False)
self.update_current_average() # to update the number of averages
[docs] def set_rolling_mode(self):
"""
Set rolling mode on or off based on the module's attribute
"rolling_mode"
"""
self.rolling_mode = self.module.rolling_mode
@property
def rolling_mode(self):
return ((self.checkbox_untrigged.isChecked()) and self.rolling_group.isEnabled())
@rolling_mode.setter
def rolling_mode(self, val):
if val:
self.checkbox_untrigged.setChecked(True)
else:
self.checkbox_normal.setChecked(True)
return val
[docs] def update_rolling_mode_visibility(self):
"""
Hide rolling mode checkbox for duration < 100 ms
"""
self.rolling_group.setEnabled(self.module._rolling_mode_allowed())
self.attribute_widgets['trigger_source'].widget.setEnabled(
not self.rolling_mode)
self.attribute_widgets['threshold'].widget.setEnabled(
not self.rolling_mode)
self.attribute_widgets['hysteresis'].widget.setEnabled(
not self.rolling_mode)
self.button_single.setEnabled(not self.rolling_mode)
[docs] def autoscale_x(self):
"""Autoscale pyqtgraph. The current behavior is to autoscale x axis
and set y axis to [-1, +1]"""
if self.module.xy_mode:
return
if self.module._is_rolling_mode_active():
mini = -self.module.duration
maxi = 0
else:
mini = min(self.module.times)
maxi = max(self.module.times)
self.plot_item.setRange(xRange=[mini, maxi])
self.plot_item.setRange(yRange=[-1,1])
# self.plot_item.autoRange()