diff --git a/artiq/dashboard/waveform.py b/artiq/dashboard/waveform.py index 679cb1eb9..647154e92 100644 --- a/artiq/dashboard/waveform.py +++ b/artiq/dashboard/waveform.py @@ -113,8 +113,8 @@ class _AddChannelDialog(QtWidgets.QDialog): for select in selection: key = self._channel_model.index_to_key(select) if key is not None: - width = self._channel_model[key].ref - channels.append((key, width)) + width, ty = self._channel_model[key].ref + channels.append((key, width, ty, [])) self.accepted.emit(channels) self.close() @@ -136,7 +136,7 @@ class Waveform(pg.PlotWidget): cursorMoved = QtCore.pyqtSignal(float) - def __init__(self, channel, state, parent=None): + def __init__(self, name, width, parent=None): pg.PlotWidget.__init__(self, parent=parent, x=None, @@ -150,13 +150,12 @@ class Waveform(pg.PlotWidget): self.setMenuEnabled(False) self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) - self.channel = channel - self.name = channel[0] - self.width = channel[1][0] + self.name = name + self.width = width - self.state = state - self.x_data = [] - self.y_data = [] + #self.state = state + #self.x_data = [] + #self.y_data = [] self.plot_item = self.getPlotItem() self.plot_item.hideButtons() @@ -193,8 +192,8 @@ class Waveform(pg.PlotWidget): self.label_bg = BackgroundItem(parent=self.plot_item, rect=rect) self.label_bg.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, 0)) - def update_x_max(self): - self.view_box.setLimits(xMax=self.state["stopped_x"]) + #def update_x_max(self): + # self.view_box.setLimits(xMax=self.state["stopped_x"]) def set_cursor_visible(self, visible): if visible: @@ -202,31 +201,28 @@ class Waveform(pg.PlotWidget): else: self.removeItem(self.cursor) - def on_cursor_move(self, x): - self.cursor.setValue(x) - if len(self.x_data) < 1: - return - ind = bisect.bisect_left(self.x_data, x) - 1 - dr = self.plot_data_item.dataRect() - if dr is None: - self.cursor_y = None - elif dr.left() <= x \ - and 0 <= ind < len(self.y_data): - self.cursor_y = self.y_data[ind] - elif x >= dr.right(): - self.cursor_y = self.y_data[-1] - else: - self.cursor_y = None - self.format_cursor_label() + #def on_cursor_move(self, x): + # self.cursor.setValue(x) + # if len(self.x_data) < 1: + # return + # ind = bisect.bisect_left(self.x_data, x) - 1 + # dr = self.plot_data_item.dataRect() + # if dr is None: + # self.cursor_y = None + # elif dr.left() <= x \ + # and 0 <= ind < len(self.y_data): + # self.cursor_y = self.y_data[ind] + # elif x >= dr.right(): + # self.cursor_y = self.y_data[-1] + # else: + # self.cursor_y = None + # self.format_cursor_label() - def extract_data_from_state(self): + def onDataChange(self): raise NotImplementedError - def display(self): - raise NotImplementedError - - def format_cursor_label(self): - raise NotImplementedError + #def format_cursor_label(self): + # raise NotImplementedError # override def mouseMoveEvent(self, e): @@ -254,24 +250,19 @@ class Waveform(pg.PlotWidget): class LogWaveform(Waveform): - def __init__(self, channel, state, parent=None): - Waveform.__init__(self, channel, state, parent) + def __init__(self, name, width, parent=None): + Waveform.__init__(self, name, width, parent) - def extract_data_from_state(self): - try: - self.x_data, self.y_data = zip(*self.state['logs'][self.name]) - except: - logger.debug('Error caught when loading waveform: {}'.format(self.name), exc_info=True) - - def display(self): + def onDataChange(self, data): try: + x_data = zip(*data)[0] self.plot_data_item.setData( - x=self.x_data, y=np.ones(len(self.x_data))) + x=x_data, y=np.ones(len(x_data))) self.plot_data_item.opts.update( {"connect": np.zeros(2), "symbol": "x"}) old_msg = "" old_x = 0 - for x, msg in zip(self.x_data, self.y_data): + for x, msg in data: if x == old_x: old_msg += "\n" + msg else: @@ -292,23 +283,17 @@ class LogWaveform(Waveform): class BitWaveform(Waveform): - def __init__(self, channel, state, parent=None): - Waveform.__init__(self, channel, state, parent) + def __init__(self, name, width, parent=None): + Waveform.__init__(self, name, width, parent) self._arrows = [] #self.plot_data_item.setDownsampling(ds=1000, method="peak", auto=False) - def extract_data_from_state(self): - try: - self.x_data, self.y_data = zip(*self.state['data'][self.name]) - except: - logger.debug('Error caught when loading waveform data: {}'.format(self.name), exc_info=True) - - def display(self): + def onDataChange(self, data): try: display_y = [] display_x = [] previous_y = None - for x, y in zip(self.x_data, self.y_data): + for x, y in data: if y == "X": # TODO: replace with dictionary mapping dis_y = DISPLAY_MID elif y == "1": @@ -335,8 +320,8 @@ class BitWaveform(Waveform): class BitVectorWaveform(Waveform): - def __init__(self, channel, state, parent=None): - Waveform.__init__(self, channel, state, parent) + def __init__(self, name, width, parent=None): + Waveform.__init__(self, name, width, parent) self._labels = [] hx = math.ceil(self.width / 4) self._format_string = "{:0=" + str(hx) + "X}" # TODO: change method.. @@ -350,26 +335,21 @@ class BitVectorWaveform(Waveform): right_label_i = bisect.bisect_right(self.x_data, xmax) + 1 for i, j in itertools.pairwise(range(left_label_i, right_label_i)): x1 = self.x_data[i] - x2 = self.x_data[j] if j < len(self.x_data) else self.state["stopped_x"] + x2 = self.x_data[j] if j < len(self.x_data) else self.x_data[-1]*2 lbl = self._labels[i] bounds = lbl.boundingRect() bounds_view = self.view_box.mapSceneToView(bounds) if bounds_view.boundingRect().width() < x2 - x1: self.addItem(lbl) - - def extract_data_from_state(self): - try: - self.x_data, self.y_data = zip(*self.state['data'][self.name]) - except: - logger.debug('Error caught when loading waveform data: {}'.format(self.name), exc_info=True) - - def display(self): + + # TODO this may need to block sigTransformChanged while changing the labels + def onDataChange(self, data): try: display_x, display_y = [], [] - for x, y in zip(self.x_data, self.y_data): + for x, y in data: display_x.append(x) display_y.append(DISPLAY_LOW) - if "X" in y: # TODO change to using a dictionary + if "X" in y: # TODO change to using a dictionary, remove any ref to "X" it does not happen in current impl display_x.append(x) display_y.append(DISPLAY_MID) elif int(y) != 0: @@ -396,28 +376,23 @@ class BitVectorWaveform(Waveform): class AnalogWaveform(Waveform): - def __init__(self, channel, state, parent=None): - Waveform.__init__(self, channel, state, parent) + def __init__(self, name, width, parent=None): + Waveform.__init__(self, name, width, parent) self.plot_data_item.setDownsampling(ds=10, method="peak", auto=True) # TODO: experiment with downsampling values for best performance # TODO: potentially switch to not using a step connect - def extract_data_from_state(self): - try: - self.x_data, self.y_data = zip(*self.state['data'][self.name]) - except: - logger.debug('Error caught when loading waveform data: {}'.format(self.name), exc_info=True) - # TODO: change to using max_, min_ - def display(self): + def onDataChange(self, data): try: - self.plot_data_item.setData(x=self.x_data, y=self.y_data) - mx = max(self.y_data) - mn = min(self.y_data) - self.plot_item.setRange(yRange=(mn, mx), padding=0.1) + x_data, y_data = zip(*data) + self.plot_data_item.setData(x=x_data, y=y_data) + max_y = max(y_data) + min_y = min(y_data) + self.plot_item.setRange(yRange=(min_y, max_y), padding=0.1) except: logger.debug('Error caught when displaying waveform: {}'.format(self.name), exc_info=True) - self.plot_data_item.setData(x=[0], y=[0]) + self.plot_data_item.setData(x=[], y=[]) # TODO: can also just leave it as None -> simpler def format_cursor_label(self): @@ -505,7 +480,7 @@ class _WaveformView(QtWidgets.QWidget): WaveformType.ANALOG: AnalogWaveform, WaveformType.LOG: LogWaveform }[ty] - return waveform_cls(name, width) + return waveform_cls(name, width, parent=self._splitter) def _resize(self): self._splitter.setMinimumHeight(Waveform.MIN_HEIGHT * self._model.rowCount()) @@ -785,11 +760,12 @@ class WaveformDock(QtWidgets.QDockWidget): QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable) self._channel_model = Model({}) + self._waveform_model = _WaveformModel() - self._devices = None + self._ddb = None self._dump = None - self._state = { + self._waveform_data = { "timescale": 1, "stopped_x": None, "logs": dict(), @@ -821,12 +797,9 @@ class WaveformDock(QtWidgets.QDockWidget): grid.addWidget(self._request_dump_btn, 0, 1) self._request_dump_btn.clicked.connect( lambda: asyncio.ensure_future(self.rpc_client.trigger_proxy_task())) - - self._waveform_area = WaveformArea(self, self._state, - self._channel_model) - self.traceDataChanged.connect(self._waveform_area.on_trace_update) - self.traceDataChanged.connect(self._update_log_channels) - grid.addWidget(self._waveform_area, 2, 0, colspan=12) + + self._waveform_view = _WaveformView(self) + grid.addWidget(self._waveform_view, 2, 0, colspan=12) self._add_btn = QtWidgets.QToolButton() self._add_btn.setToolTip("Add channels...") @@ -834,13 +807,15 @@ class WaveformDock(QtWidgets.QDockWidget): QtWidgets.QApplication.style().standardIcon( QtWidgets.QStyle.SP_FileDialogListView)) grid.addWidget(self._add_btn, 0, 2) - self._add_btn.clicked.connect(self._waveform_area.on_add_channel_click) + self._add_btn.clicked.connect(self.on_add_channel_click) - self._cursor_control = _CursorTimeControl(parent=self, state=self._state) - grid.addWidget(self._cursor_control, 0, 3, colspan=3) - self._cursor_control.submit.connect( - self._waveform_area.on_cursor_move) - self._waveform_area.cursorMoved.connect(self._cursor_control.display_value) + #self._cursor_control = _CursorTimeControl(parent=self, state=self._state) + #grid.addWidget(self._cursor_control, 0, 3, colspan=3) + #self._cursor_control.submit.connect( + # self._waveform_area.on_cursor_move) + #self._waveform_area.cursorMoved.connect(self._cursor_control.display_value) + + self.traceDataChanged.connect(self._update_log_channels) self._file_menu = QtWidgets.QMenu() self._add_async_action("Open trace...", self.load_trace) @@ -867,6 +842,21 @@ class WaveformDock(QtWidgets.QDockWidget): self._dump = data self.traceDataChanged.emit() + def on_add_channel_click(self): + asyncio.ensure_future(self._add_waveform_task()) + + async def _add_waveform_task(self): + dialog = _AddChannelDialog(self, self._channel_model) + fut = asyncio.Future() + + def on_accept(s): + fut.set_result(s) + dialog.accepted.connect(on_accept) + dialog.open() + channels = await fut + self._waveform_model.extend(channels) + self._waveform_model.update_data(self._waveform_data) # TODO use a specific function for this + async def load_trace(self): try: filename = await get_open_file_name(