Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
03028ce
Added dependency on xarray module
ElMartes Jun 24, 2020
c28ee84
Move devices module to services package to avoid circular import with…
philsmt Jun 24, 2020
9b283f5
Add metro.init_mp_support to fix missing initialization with spawn me…
philsmt Jul 6, 2020
0a8b661
No longer import metro.services.devices into the metro namespace as a…
philsmt Jul 6, 2020
941fb57
Rename GenericDevice.__ge__ to isSubDevice and simplify the implement…
philsmt Jul 6, 2020
4c1b712
Restructures the initialization routines in the metro module for clea…
ElMartes Jul 9, 2020
65acd17
Change global flag load_GUI
ElMartes Jul 10, 2020
1df2a31
Fix Python version checking and 'die' routine initialization
ElMartes Jul 10, 2020
a39c1b2
Remove obsolete comments
ElMartes Jul 10, 2020
18dae0c
Style adjustments for flake8 and consistence
ElMartes Jul 13, 2020
0b8181e
Restructures the initialization routines in the metro module for clea…
ElMartes Jul 9, 2020
ad98c83
Change global flag load_GUI
ElMartes Jul 10, 2020
602346a
Fix Python version checking and 'die' routine initialization
ElMartes Jul 10, 2020
f4a3091
Remove obsolete comments
ElMartes Jul 10, 2020
1544455
Style adjustments for flake8 and consistence
ElMartes Jul 13, 2020
0db0795
Merge branch 'change_import' of https://github.com/philsmt/metro-sci …
ElMartes Jul 13, 2020
cb6a784
Add source root path of the metro package to the globals
ElMartes Jul 14, 2020
15ff21d
Added plot_xy plot for 1D plots with defined X axis
Aug 4, 2020
347f240
Fix to the new import path
Aug 4, 2020
93b2f22
Add entry point for new display device plot_xy
Aug 17, 2020
daa6c41
Fix taskbar icon in Windows
ElMartes Apr 9, 2021
cce8554
Fix problem with loading invisible devices from a profile
ElMartes Apr 9, 2021
60c0fff
Merge branch 'change_import' into plotxy
ElMartes Apr 13, 2021
2130b41
Fix problem with time-dependent plots
ElMartes Nov 20, 2021
231fce4
Merge remote-tracking branch 'origin/master' into plotxy
ElMartes Nov 20, 2021
0ee4f8a
Merge remote-tracking branch 'origin/master' into change_import
ElMartes Nov 25, 2021
803e2b4
Merge branch 'change_import' into plotxy
ElMartes Nov 25, 2021
c9470cf
Add default value for path to the FileArgument text box
ElMartes Feb 3, 2022
96a158c
Rework time mode for waveform plots
ElMartes Feb 8, 2022
48ad46b
Merge branch 'plotxy' of https://github.com/philsmt/metro-sci into pl…
ElMartes Feb 8, 2022
a889ec4
Fix storage path for LogChannels
ElMartes Feb 8, 2022
20c48f4
Fix typo in tooltip and add comments
ElMartes Feb 9, 2022
4fcf6fb
Merge remote-tracking branch 'origin/master' into plotxy
ElMartes Feb 11, 2022
c223b8f
Implement a log window based on the logging library
ElMartes Feb 14, 2022
e5c49c3
Move logger so services and improve functionality, also add it to gen…
ElMartes Feb 15, 2022
fd39629
Change design of log window using HTML style and include logging into…
ElMartes Feb 17, 2022
a077934
Include logging into some basic routines for more output in the log
ElMartes Feb 17, 2022
417af96
Fix retrieval of metro git version and add some more logging
ElMartes Feb 25, 2022
65ef9c4
Minor fix to time/date display in measurement list und logging
ElMartes Mar 1, 2022
de483f9
Fix minor mistake in measurement browsing display
ElMartes Aug 14, 2022
8a555a1
Merge branch 'master' into logging
ElMartes Aug 19, 2022
3c738c2
Fix renaming of stored data
ElMartes Aug 20, 2022
8904582
Merge branch 'logging' of https://github.com/philsmt/metro-sci into l…
ElMartes Aug 20, 2022
938824e
Fix bug with faulty image files in metro2hdf convert script
Dec 12, 2022
68b267b
Revert style changes in __init__ code, fix minor import bug, rename i…
ElMartes Apr 8, 2024
b4dc16c
Fix parallel_operator to account for renaming of initialization routine
ElMartes Apr 8, 2024
3be52f9
Merge branch 'change_import' into logging
ElMartes Apr 8, 2024
aef80c7
Fix plot device with active stepMode, broke with newer pyqtgraph version
ElMartes Apr 8, 2024
fdffb7f
Catch exceptions when a subprocess crashes so that Metro can continue…
ElMartes Apr 12, 2024
1674149
Merge branch 'main' into logging
adryyan Mar 15, 2026
d87a6a1
fix: explicit int conversion
adryyan May 11, 2026
27ca4a6
remove custom pyqtgraph
adryyan May 11, 2026
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
8 changes: 4 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@

'display.fast_plot', 'display.hist1d', 'display.hist2d',
'display.image', 'display.ogl_plot', 'display.plot',
'display.polar_plot', 'display.sorted', 'display.value',
'display.waveform',
'display.plot_xy', 'display.polar_plot', 'display.sorted',
'display.value', 'display.waveform',

'project.generic2d', 'project.window',

Expand All @@ -68,7 +68,7 @@
f'display.{entry_point} = devices.display.{entry_point}:Device'
for entry_point
in [
'fast_plot', 'hist1d', 'hist2d', 'image', 'plot',
'fast_plot', 'hist1d', 'hist2d', 'image', 'plot', 'plot_xy',
'polar_plot', 'sorted', 'value', 'waveform'
]
]
Expand All @@ -86,7 +86,7 @@
], language_level=3, build_dir='build'),

python_requires='>=3.11',
install_requires=['typing', 'PyQt5', 'numpy', 'scipy', 'h5py', 'xarray'],
install_requires=['typing', 'PyQt5', 'numpy', 'scipy', 'h5py', 'xarray', 'pyqtgraph'],

classifiers=[
'Development Status :: 5 - Production/Stable',
Expand Down
15 changes: 9 additions & 6 deletions src/metro/devices/abstract/async_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@

import metro

from metro.services import logger
log = logger.log(__name__)


class Operator(metro.QObject):
_ready = metro.QSignal(object)
Expand Down Expand Up @@ -41,11 +44,11 @@ def _on_finished(self):
self.showError('Unchecked exception in Operator.finalize, please '
'see details.', e)

def showError(self, text, details=None):
self._error.emit((text, details))
def showError(self, text, details=None, log=log):
self._error.emit((text, details, log))

def showException(self, e):
self._error.emit(e)
def showException(self, e, log=log):
self._error.emit((e, log))

def prepare(self, args):
pass
Expand Down Expand Up @@ -87,8 +90,8 @@ def _on_ready(self, res):

@metro.QSlot(object)
def _on_error(self, err):
if isinstance(err, Exception):
self.showException(err)
if isinstance(err[0], Exception):
self.showException(*err)
else:
self.showError(*err)

Expand Down
7 changes: 6 additions & 1 deletion src/metro/devices/abstract/parallel_event_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,12 @@ def prepare(self, operator_cls, operator_args, state, raw_column_count,
stopped=self.rate_timer.stop)

def finalize(self):
super().finalize()
# Catch the BrokenPipeError in case the process crashed, so that we
# can still clean up the channels
try:
super().finalize()
except BrokenPipeError as bpe:
self.showError(str(bpe), bpe)

# Close the channels last, since the thread might still add
# some data
Expand Down
8 changes: 7 additions & 1 deletion src/metro/devices/abstract/parallel_operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,13 @@ def __init__(self, ctrl_pipe, data_pipe, pre_filter):
# Called by thread.started signal
@metro.QSlot()
def waitForOperatorReady(self):
reply = self.ctrl_pipe.recv()
# An EOFError is raised when the process crashes, so this has to be
# treated here, as it will not be raised in the process to be returned
try:
reply = self.ctrl_pipe.recv()
except EOFError as e:
reply = (f"{type(e).__name__} while waiting for the operator",
traceback.format_exc())

if reply is True:
self.operatorReady.emit()
Expand Down
6 changes: 5 additions & 1 deletion src/metro/devices/analyze/scan_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ def dataAdded(self, spec):
return

self.mtx[self.current_idx, :] = spec
self.current_idx += 1

if self.current_idx < len(self.x)-1:
self.current_idx += 1
else:
self.current_idx = 0

self.ch_out.addData(self.mtx)

Expand Down
2 changes: 2 additions & 0 deletions src/metro/devices/display/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ def getDefault(channel):
if channel.hint == channel.INDICATOR_HINT:
if channel.shape == 1:
return 'display.plot'
elif channel.shape == 2:
return 'display.plot_xy'
else:
return 'display.value'
elif channel.shape == 0 and channel.hint == channel.WAVEFORM_HINT:
Expand Down
2 changes: 1 addition & 1 deletion src/metro/devices/display/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def prepare(self, args, state):
self.plot_item.enableAutoRange('x', True)

if args['steps']:
self.curve = self.plot_item.plot(stepMode=True, pen='y')
self.curve = self.plot_item.plot([0], [], stepMode=True, pen='y')
self.x_len_offset = 1
self.x_val_offset = -0.5
else:
Expand Down
47 changes: 47 additions & 0 deletions src/metro/devices/display/plot_xy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@

# Copyright 2019 Philipp Schmidt <phil.smt@gmail.com>.
# All Rights Reserved.
#
# This file is part of the METRO measurement environment and may not be
# copied and/or distributed via any medium without the express
# permission of the author.

import numpy as np

import metro

from metro.devices.display import plot


class Device(plot.Device):
def prepare(self, args, state):
self.buffer = dict()

super().prepare(args, state)

def dataAdded(self, d):

# d[.,0] and d[.,1] are the x and respective y value(s)
d = d[self.index, :]

# We store them in a dictionary buffer as point pairs
self.buffer[d[0, 0]] = d[0, 1]
self.items = np.array(list(self.buffer.items()))

if len(self.items) <= 1:
x = self.items[:, 0]
y = self.items[:, 1]
else:
items = self.items[self.items[:, 0].argsort(axis=0)]
x = items[:, 0]
y = items[:, 1]

self.curve.setData(x, y, autoDownsample=False, antialias=False)
self._notifyFittingCallbacks(x, y)

@staticmethod
def isChannelSupported(channel):
if isinstance(channel, metro.StreamChannel) and channel.shape != 2:
raise ValueError('plot_xy only supports StreamChannel in 2d')

return True
2 changes: 1 addition & 1 deletion src/metro/devices/display/sorted.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def paintEvent(self, event):

for entry in self.data[::-1]:
qp.drawText(20, cur_y, self.base_func(entry[0], max_index))
qp.fillRect(80, cur_y-9, (entry[1]/max_value)*(width-100), 10,
qp.fillRect(80, cur_y-9, int((entry[1]/max_value)*(width-100)), 10,
QtCore.Qt.white)

cur_y += 15
96 changes: 77 additions & 19 deletions src/metro/devices/display/waveform.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import collections

import numpy
import time
import xarray as xr

import metro
Expand All @@ -23,6 +24,7 @@ class Device(single_plot.Device, metro.DisplayDevice):
'wave_points': 40,
'ma_points': 5,
'ma_enabled': False,
'time_enabled': False,
'bg_text': '',
}

Expand All @@ -36,6 +38,7 @@ class Device(single_plot.Device, metro.DisplayDevice):
'ma_points': 'Number of samples used for calculating the moving '
'average',
'ma_enabled': 'Whether moving average is enabled by default.',
'time_enabled': 'Whether the time is shown on the x-axis by default.',
'bg_text': 'Initial background text, chosen automatically if empty.'
}

Expand All @@ -52,12 +55,18 @@ def prepare(self, args, state):
if state is not None:
self.raw_enabled = state[0]
self.ma_enabled = state[1]
self.time_enabled = state[2]
else:
self.raw_enabled = True
self.ma_enabled = args['ma_enabled']
self.time_enabled = args['time_enabled']

self.time_enabled = False

self.y_label = ''
self.x_data = None
self.time_data = []
self.start_time = None
self.y_data = []
self.ma_buffer = collections.deque(maxlen=self.ma_points)
self.ma_data = []
Expand Down Expand Up @@ -100,6 +109,14 @@ def prepare(self, args, state):

self._updateCurves()

# Time Mode:
menu.addSeparator()

self.actionTimeMode = menu.addAction('Time Mode')
self.actionTimeMode.triggered.connect(self.on_actionTimeMode_triggered)
self.actionTimeMode.setCheckable(True)
self.actionTimeMode.setChecked(self.time_enabled)

# We configure first to your default values in case the channel
# subscription adds data (some implementation may do) and then
# reconfigure if necessary.
Expand All @@ -116,7 +133,7 @@ def finalize(self):
super().finalize()

def serialize(self):
return self.raw_enabled, self.ma_enabled
return self.raw_enabled, self.ma_enabled, self.time_enabled

def sizeHint(self):
return metro.QtCore.QSize(525, 225)
Expand Down Expand Up @@ -153,16 +170,23 @@ def _configure(self):
self.reset_xaxis_on_add = True
return

if self.x_data is not None:
self.plot_item.setXRange(self.x_data[0], self.x_data[-1])
self.displayed_points = len(self.x_data)
return

# We are only here if all of the above cases failed.
if self.x_data is None:
self.x_data = numpy.arange(self.wave_points)+1
self.displayed_points = self.wave_points
else:
self.displayed_points = len(self.x_data)

self.x_data = numpy.arange(self.wave_points)+1
self.plot_item.setXRange(1, self.wave_points)
self.displayed_points = self.wave_points
if self.start_time is None:
self.start_time = time.monotonic()
self.time_data.clear()

if self.time_enabled:
self.plot_item.setXRange(self.time_data[0], self.time_data[-1])
self.plot_item.setLabel(axis='bottom', text='time', units='s')
else:
self.plot_item.setXRange(self.x_data[0], self.x_data[-1])
self.plot_item.setLabel(axis='bottom', text='', units='')

def _updateCurves(self):
if self.raw_enabled and self.raw_curve is None:
Expand Down Expand Up @@ -204,7 +228,7 @@ def dataSet(self, d):
self.dataCleared()
return

self.y_data = list(d) # Make of copy
self.y_data = list(d) # Make copy

self.ma_buffer.clear()
self.ma_data.clear()
Expand Down Expand Up @@ -244,42 +268,62 @@ def dataAdded(self, d):
pass
else:
self.ma_data.append(sum(self.ma_buffer) / len(self.ma_buffer))
# TODO CHECK!

self.time_data.append(time.monotonic() - self.start_time)

if self.has_metropc_tags:
self.x_data = self.channel._metropc_tags
self.displayed_points = len(self.x_data)
self.plot_item.setXRange(min(self.x_data), max(self.x_data))
if not self.time_enabled:
self.displayed_points = len(self.x_data)
self.plot_item.setXRange(min(self.x_data), max(self.x_data))

elif len(self.y_data) > len(self.x_data):
# This happens for continuous channels that just filled the
# chart, so we just extend our axis to the right.
self.x_data += 1
self.plot_item.setXRange(self.x_data[0], self.x_data[-1])
if not self.time_enabled:
self.plot_item.setXRange(self.x_data[0], self.x_data[-1])

elif self.reset_xaxis_on_add:
# This flag is currently only set for STEP channels that do
# not have a fixed point list.
self.displayed_points = len(self.x_data)
if self.time_enabled:
self.displayed_points = len(self.time_data)
self.plot_item.setXRange(self.time_data[0], self.time_data[-1])
else:
self.displayed_points = len(self.x_data)
self.plot_item.setXRange(self.x_data[0], self.x_data[-1])

else:
self.plot_item.setXRange(self.x_data[0], self.x_data[-1])

if len(self.y_data) > 10*self.displayed_points:
self.x_data = self.x_data[-2*self.displayed_points:]
self.time_data = self.time_data[-2*self.displayed_points:]
self.y_data = self.y_data[-2*self.displayed_points:]
self.ma_data = self.ma_data[-2*self.displayed_points:]

current_x = self.x_data[:min(self.displayed_points, len(self.y_data))]
if self.time_enabled:
length = min(self.displayed_points, len(self.y_data),
len(self.time_data))
current_x = self.time_data[-length:]
self.plot_item.setXRange(current_x[0], current_x[-1])
else:
length = min(self.displayed_points, len(self.y_data),
len(self.x_data))
current_x = self.x_data[:length]

if self.raw_enabled:
self.raw_curve.setData(current_x,
self.y_data[-self.displayed_points:])
self.raw_curve.setData(current_x, self.y_data[-length:])

if self.ma_enabled:
self.ma_curve.setData(current_x,
self.ma_data[-self.displayed_points:])
self.ma_curve.setData(current_x, self.ma_data[-length:])

def dataCleared(self):
self.y_data.clear()
self.ma_data.clear()
self.time_data.clear()

if self.raw_enabled:
self.raw_curve.setData(self.y_data)
Expand Down Expand Up @@ -309,3 +353,17 @@ def on_menuShowCurves_triggered(self, action):

self._updateCurves()
self.dataAdded(None)

def on_actionTimeMode_triggered(self, action):
if self.time_enabled == self.actionTimeMode.isChecked():
return

self.time_enabled = self.actionTimeMode.isChecked()

if self.time_enabled:
self.plot_item.setLabel(axis='bottom', text='time', units='s')
else:
self.plot_item.setLabel(axis='bottom', text='', units='')

self._updateCurves()
self.dataAdded(None)
Empty file removed src/metro/external/__init__.py
Empty file.
Loading