ezmsg-tools 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/PKG-INFO +7 -2
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/pyproject.toml +7 -1
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/src/ezmsg/tools/__version__.py +2 -2
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/src/ezmsg/tools/sigmon/cli.py +21 -6
- ezmsg_tools-0.3.0/src/ezmsg/tools/viewer/__init__.py +0 -0
- ezmsg_tools-0.3.0/src/ezmsg/tools/viewer/cli.py +256 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/.github/workflows/docs.yml +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/.github/workflows/python-publish.yml +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/.github/workflows/python-tests.yml +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/.gitignore +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/.pre-commit-config.yaml +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/LICENSE +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/README.md +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/docs/Makefile +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/docs/make.bat +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/docs/source/_templates/autosummary/module.rst +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/docs/source/api/index.rst +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/docs/source/conf.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/docs/source/index.md +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/scripts_nbs/generator/eeg_generator_graph.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/scripts_nbs/orphan/run_attach.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/scripts_nbs/profiler/ecog_preproc.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/scripts_nbs/profiler/parse_profiler.ipynb +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/scripts_nbs/profiler/profiler.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/src/ezmsg/tools/__init__.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/src/ezmsg/tools/dag.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/src/ezmsg/tools/perfmon/__init__.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/src/ezmsg/tools/perfmon/cli.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/src/ezmsg/tools/shmem/__init__.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/src/ezmsg/tools/shmem/shmem.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/src/ezmsg/tools/shmem/shmem_mirror.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/src/ezmsg/tools/sigmon/__init__.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/src/ezmsg/tools/sigmon/dag_widget.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/tests/test_shmem_mirror.py +0 -0
- {ezmsg_tools-0.2.0 → ezmsg_tools-0.3.0}/tests/test_shmem_sink.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ezmsg-tools
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: ezmsg namespace package containing tools for working with ezmsg graphs.
|
|
5
5
|
Author-email: Chadwick Boulay <chadwick.boulay@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -21,10 +21,15 @@ Requires-Dist: typer>=0.15.1; extra == 'perfmon'
|
|
|
21
21
|
Provides-Extra: sigmon
|
|
22
22
|
Requires-Dist: ezmsg-qt; extra == 'sigmon'
|
|
23
23
|
Requires-Dist: pandas; extra == 'sigmon'
|
|
24
|
-
Requires-Dist: phosphor>=0.
|
|
24
|
+
Requires-Dist: phosphor>=0.4; extra == 'sigmon'
|
|
25
25
|
Requires-Dist: pygraphviz>=1.14; extra == 'sigmon'
|
|
26
26
|
Requires-Dist: pyside6>=6.7; extra == 'sigmon'
|
|
27
27
|
Requires-Dist: typer>=0.15.1; extra == 'sigmon'
|
|
28
|
+
Provides-Extra: viewer
|
|
29
|
+
Requires-Dist: ezmsg-qt; extra == 'viewer'
|
|
30
|
+
Requires-Dist: phosphor>=0.4; extra == 'viewer'
|
|
31
|
+
Requires-Dist: pyside6>=6.7; extra == 'viewer'
|
|
32
|
+
Requires-Dist: typer>=0.15.1; extra == 'viewer'
|
|
28
33
|
Description-Content-Type: text/markdown
|
|
29
34
|
|
|
30
35
|
# ezmsg-tools
|
|
@@ -53,10 +53,16 @@ sigmon = [
|
|
|
53
53
|
"PySide6>=6.7",
|
|
54
54
|
"pygraphviz>=1.14",
|
|
55
55
|
"typer>=0.15.1",
|
|
56
|
-
"phosphor>=0.
|
|
56
|
+
"phosphor>=0.4",
|
|
57
57
|
"pandas",
|
|
58
58
|
"ezmsg-qt",
|
|
59
59
|
]
|
|
60
|
+
viewer = [
|
|
61
|
+
"PySide6>=6.7",
|
|
62
|
+
"typer>=0.15.1",
|
|
63
|
+
"phosphor>=0.4",
|
|
64
|
+
"ezmsg-qt",
|
|
65
|
+
]
|
|
60
66
|
|
|
61
67
|
[project.scripts]
|
|
62
68
|
ezmsg-performance-monitor = "ezmsg.tools.perfmon.cli:main"
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.3.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 3, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
|
@@ -5,7 +5,7 @@ import sys
|
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import typer
|
|
8
|
-
from ezmsg.qt import
|
|
8
|
+
from ezmsg.qt import EzSession, EzSubscriber
|
|
9
9
|
from phosphor import (
|
|
10
10
|
ScatterConfig,
|
|
11
11
|
ScatterWidget,
|
|
@@ -60,6 +60,7 @@ class SigmonWindow(QMainWindow):
|
|
|
60
60
|
def __init__(
|
|
61
61
|
self,
|
|
62
62
|
graph_address: tuple[str, int],
|
|
63
|
+
session: EzSession,
|
|
63
64
|
parent: QWidget | None = None,
|
|
64
65
|
) -> None:
|
|
65
66
|
super().__init__(parent)
|
|
@@ -67,7 +68,7 @@ class SigmonWindow(QMainWindow):
|
|
|
67
68
|
self._graph_address = graph_address
|
|
68
69
|
|
|
69
70
|
# Dynamic subscriber — switches topics when the user clicks a graph node.
|
|
70
|
-
self._data_sub =
|
|
71
|
+
self._data_sub = EzSubscriber(topic=None, parent=self, session=session)
|
|
71
72
|
self._data_sub.connect(self._on_data)
|
|
72
73
|
|
|
73
74
|
# Layout: splitter with DAG on left, plot on right.
|
|
@@ -97,7 +98,9 @@ class SigmonWindow(QMainWindow):
|
|
|
97
98
|
shortcut.activated.connect(self._toggle_scatter)
|
|
98
99
|
|
|
99
100
|
def _on_node_selected(self, topic: str) -> None:
|
|
100
|
-
|
|
101
|
+
# logger.debug("Switching to topic: %s", topic)
|
|
102
|
+
print(f"Switching to topic: {topic}")
|
|
103
|
+
self._data_sub.set_topic(topic)
|
|
101
104
|
self._first_message = True
|
|
102
105
|
self._channel_labels = None
|
|
103
106
|
self._channel_positions = None
|
|
@@ -146,6 +149,13 @@ class SigmonWindow(QMainWindow):
|
|
|
146
149
|
)
|
|
147
150
|
widget = SpectrumWidget(config)
|
|
148
151
|
|
|
152
|
+
elif "ch" in msg.dims and self._channel_positions is not None:
|
|
153
|
+
# ch but no time or freq; assume scatter
|
|
154
|
+
config = ScatterConfig(
|
|
155
|
+
positions=self._channel_positions,
|
|
156
|
+
channel_labels=labels,
|
|
157
|
+
)
|
|
158
|
+
widget = ScatterWidget(config)
|
|
149
159
|
else:
|
|
150
160
|
logger.warning("Unknown AxisArray dims: %s — defaulting to sweep", msg.dims)
|
|
151
161
|
n_samples = msg.shape[0]
|
|
@@ -158,7 +168,7 @@ class SigmonWindow(QMainWindow):
|
|
|
158
168
|
widget = SweepWidget(config)
|
|
159
169
|
|
|
160
170
|
self._primary_config = config
|
|
161
|
-
self._showing_scatter =
|
|
171
|
+
self._showing_scatter = isinstance(widget, ScatterWidget)
|
|
162
172
|
self._replace_plot_widget(widget)
|
|
163
173
|
|
|
164
174
|
def _toggle_scatter(self) -> None:
|
|
@@ -190,6 +200,10 @@ class SigmonWindow(QMainWindow):
|
|
|
190
200
|
sizes = self._splitter.sizes()
|
|
191
201
|
old = self._splitter.widget(1)
|
|
192
202
|
if old is not None:
|
|
203
|
+
# Stop the render loop before destroying the Qt widget,
|
|
204
|
+
# otherwise fastplotlib keeps painting a deleted canvas.
|
|
205
|
+
if hasattr(old, "_figure"):
|
|
206
|
+
old._figure.close()
|
|
193
207
|
old.setParent(None)
|
|
194
208
|
old.deleteLater()
|
|
195
209
|
self._splitter.insertWidget(1, widget)
|
|
@@ -236,9 +250,10 @@ def _run(
|
|
|
236
250
|
graph_address = (graph_ip, int(graph_port_str))
|
|
237
251
|
|
|
238
252
|
app = QApplication.instance() or QApplication(sys.argv)
|
|
239
|
-
|
|
253
|
+
session = EzSession(graph_address=graph_address)
|
|
254
|
+
window = SigmonWindow(graph_address, session)
|
|
240
255
|
window.showMaximized()
|
|
241
|
-
with
|
|
256
|
+
with session:
|
|
242
257
|
app.exec()
|
|
243
258
|
|
|
244
259
|
|
|
File without changes
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""ezmsg-viewer — plot a specific ezmsg topic without the graph inspector."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import typer
|
|
9
|
+
from ezmsg.qt import EzSession, EzSubscriber
|
|
10
|
+
from ezmsg.util.messages.axisarray import AxisArray
|
|
11
|
+
from phosphor import (
|
|
12
|
+
ScatterConfig,
|
|
13
|
+
ScatterWidget,
|
|
14
|
+
SpectrumConfig,
|
|
15
|
+
SpectrumWidget,
|
|
16
|
+
SweepConfig,
|
|
17
|
+
SweepEvent,
|
|
18
|
+
SweepWidget,
|
|
19
|
+
)
|
|
20
|
+
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
GRAPH_IP = "127.0.0.1"
|
|
25
|
+
GRAPH_PORT = 25978
|
|
26
|
+
|
|
27
|
+
# Event color palette (deterministic by label hash)
|
|
28
|
+
EVENT_COLORS = [
|
|
29
|
+
(1.0, 1.0, 0.4), # yellow
|
|
30
|
+
(0.4, 1.0, 1.0), # cyan
|
|
31
|
+
(1.0, 0.4, 1.0), # magenta
|
|
32
|
+
(1.0, 0.7, 0.3), # orange
|
|
33
|
+
(0.4, 1.0, 0.4), # green
|
|
34
|
+
(1.0, 0.4, 0.4), # red
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class PlotMode(str, Enum):
|
|
39
|
+
timeseries = "timeseries"
|
|
40
|
+
spectral = "spectral"
|
|
41
|
+
scatter = "scatter"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _extract_channel_meta(msg) -> tuple[list[str] | None, np.ndarray | None]:
|
|
45
|
+
"""Extract channel labels and 2D positions from AxisArray channel metadata."""
|
|
46
|
+
if "ch" not in msg.dims:
|
|
47
|
+
return None, None
|
|
48
|
+
|
|
49
|
+
ch_axis = msg.get_axis("ch")
|
|
50
|
+
ch_data = getattr(ch_axis, "data", None)
|
|
51
|
+
if ch_data is None or ch_data.dtype.names is None:
|
|
52
|
+
return None, None
|
|
53
|
+
|
|
54
|
+
labels = None
|
|
55
|
+
if "label" in ch_data.dtype.names:
|
|
56
|
+
labels = [str(v) for v in ch_data["label"]]
|
|
57
|
+
|
|
58
|
+
positions = None
|
|
59
|
+
if "x" in ch_data.dtype.names and "y" in ch_data.dtype.names:
|
|
60
|
+
x = ch_data["x"].astype(np.float32)
|
|
61
|
+
y = ch_data["y"].astype(np.float32)
|
|
62
|
+
if np.any(x != 0) or np.any(y != 0):
|
|
63
|
+
positions = np.column_stack([x, y])
|
|
64
|
+
|
|
65
|
+
return labels, positions
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _event_label(msg) -> str:
|
|
69
|
+
"""Try to extract a human-readable label from an event message."""
|
|
70
|
+
if hasattr(msg, "dims") and hasattr(msg, "get_axis"):
|
|
71
|
+
if "ch" in msg.dims:
|
|
72
|
+
ch_axis = msg.get_axis("ch")
|
|
73
|
+
ch_data = getattr(ch_axis, "data", None)
|
|
74
|
+
if ch_data is not None and ch_data.dtype.names and "label" in ch_data.dtype.names:
|
|
75
|
+
labels = ch_data["label"]
|
|
76
|
+
if len(labels) > 0:
|
|
77
|
+
return str(labels[0])
|
|
78
|
+
return ""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ViewerWindow(QMainWindow):
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
session: EzSession,
|
|
85
|
+
mode: PlotMode,
|
|
86
|
+
data_topic: str,
|
|
87
|
+
event_topic: str | None = None,
|
|
88
|
+
event_filter: str | None = None,
|
|
89
|
+
parent: QWidget | None = None,
|
|
90
|
+
) -> None:
|
|
91
|
+
super().__init__(parent)
|
|
92
|
+
self.setWindowTitle(f"ezmsg Viewer — {data_topic}")
|
|
93
|
+
self._mode = mode
|
|
94
|
+
self._event_filter = event_filter
|
|
95
|
+
|
|
96
|
+
self._data_sub = EzSubscriber(topic=data_topic, parent=self, session=session)
|
|
97
|
+
self._data_sub.connect(self._on_data)
|
|
98
|
+
|
|
99
|
+
self._event_sub: EzSubscriber | None = None
|
|
100
|
+
if event_topic:
|
|
101
|
+
self._event_sub = EzSubscriber(topic=event_topic, parent=self, session=session)
|
|
102
|
+
self._event_sub.connect(self._on_event)
|
|
103
|
+
|
|
104
|
+
self._plot_widget: QWidget | None = None
|
|
105
|
+
self._first_message = True
|
|
106
|
+
self._channel_labels: list[str] | None = None
|
|
107
|
+
self._channel_positions: np.ndarray | None = None
|
|
108
|
+
|
|
109
|
+
# ------------------------------------------------------------------
|
|
110
|
+
# Data handling
|
|
111
|
+
# ------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
def _on_data(self, msg) -> None:
|
|
114
|
+
if self._first_message:
|
|
115
|
+
self._channel_labels, self._channel_positions = _extract_channel_meta(msg)
|
|
116
|
+
self._create_plot_widget(msg)
|
|
117
|
+
self._first_message = False
|
|
118
|
+
self._push_message(msg)
|
|
119
|
+
|
|
120
|
+
def _create_plot_widget(self, msg) -> None:
|
|
121
|
+
labels = self._channel_labels
|
|
122
|
+
|
|
123
|
+
if self._mode == PlotMode.timeseries:
|
|
124
|
+
if "time" in msg.dims:
|
|
125
|
+
time_axis = msg.get_axis("time")
|
|
126
|
+
srate = 1.0 / time_axis.gain
|
|
127
|
+
time_idx = msg.get_axis_idx("time")
|
|
128
|
+
n_samples = msg.shape[time_idx]
|
|
129
|
+
n_channels = msg.data.size // n_samples
|
|
130
|
+
else:
|
|
131
|
+
logger.warning("No 'time' dimension — using shape[0] as time")
|
|
132
|
+
n_samples = msg.shape[0]
|
|
133
|
+
n_channels = msg.data.size // n_samples if n_samples > 0 else 1
|
|
134
|
+
srate = 1000.0
|
|
135
|
+
|
|
136
|
+
config = SweepConfig(n_channels=n_channels, srate=srate, channel_labels=labels)
|
|
137
|
+
widget = SweepWidget(config)
|
|
138
|
+
|
|
139
|
+
elif self._mode == PlotMode.spectral:
|
|
140
|
+
if "freq" not in msg.dims:
|
|
141
|
+
logger.error("Spectral mode requires 'freq' dimension in data")
|
|
142
|
+
sys.exit(1)
|
|
143
|
+
|
|
144
|
+
freq_axis = msg.get_axis("freq")
|
|
145
|
+
freq_idx = msg.get_axis_idx("freq")
|
|
146
|
+
n_bins = msg.shape[freq_idx]
|
|
147
|
+
srate = 2.0 * freq_axis.gain * n_bins
|
|
148
|
+
n_channels = msg.data.size // n_bins
|
|
149
|
+
|
|
150
|
+
config = SpectrumConfig(n_channels=n_channels, srate=srate, n_bins=n_bins, channel_labels=labels)
|
|
151
|
+
widget = SpectrumWidget(config)
|
|
152
|
+
|
|
153
|
+
elif self._mode == PlotMode.scatter:
|
|
154
|
+
if self._channel_positions is None:
|
|
155
|
+
logger.error("Scatter mode requires channel position metadata (x, y fields in ch axis)")
|
|
156
|
+
sys.exit(1)
|
|
157
|
+
|
|
158
|
+
config = ScatterConfig(positions=self._channel_positions, channel_labels=labels)
|
|
159
|
+
widget = ScatterWidget(config)
|
|
160
|
+
|
|
161
|
+
self._plot_widget = widget
|
|
162
|
+
self.setCentralWidget(widget)
|
|
163
|
+
|
|
164
|
+
def _push_message(self, msg) -> None:
|
|
165
|
+
widget = self._plot_widget
|
|
166
|
+
|
|
167
|
+
if isinstance(widget, SweepWidget):
|
|
168
|
+
time_idx = msg.get_axis_idx("time") if "time" in msg.dims else 0
|
|
169
|
+
n_samples = msg.shape[time_idx]
|
|
170
|
+
n_channels = msg.data.size // n_samples if n_samples > 0 else 1
|
|
171
|
+
data_2d = np.moveaxis(msg.data, time_idx, 0).reshape(n_samples, n_channels)
|
|
172
|
+
# Pass the AxisArray time-axis offset so the sweep buffer
|
|
173
|
+
# tracks the same clock as the event timestamps.
|
|
174
|
+
ts = msg.get_axis("time").offset if "time" in msg.dims else None
|
|
175
|
+
widget.push_data(data_2d.astype(np.float32), timestamps=ts)
|
|
176
|
+
|
|
177
|
+
elif isinstance(widget, SpectrumWidget):
|
|
178
|
+
freq_idx = msg.get_axis_idx("freq") if "freq" in msg.dims else 0
|
|
179
|
+
n_bins = msg.shape[freq_idx]
|
|
180
|
+
n_channels = msg.data.size // n_bins if n_bins > 0 else 1
|
|
181
|
+
data_2d = np.moveaxis(msg.data, freq_idx, 0).reshape(n_bins, n_channels)
|
|
182
|
+
widget.push_data(data_2d.astype(np.float32))
|
|
183
|
+
|
|
184
|
+
elif isinstance(widget, ScatterWidget):
|
|
185
|
+
if len(msg.shape) > 1:
|
|
186
|
+
targ_idx = 0
|
|
187
|
+
if "time" in msg.dims or "freq" in msg.dims:
|
|
188
|
+
targ_idx = msg.get_axis_idx("time") if "time" in msg.dims else msg.get_axis_idx("freq")
|
|
189
|
+
n_items = msg.shape[targ_idx]
|
|
190
|
+
n_channels = msg.data.size // n_items if n_items > 0 else 1
|
|
191
|
+
data_2d = np.moveaxis(msg.data, targ_idx, 0).reshape(n_items, n_channels)
|
|
192
|
+
else:
|
|
193
|
+
data_2d = msg.data.reshape(1, msg.data.size)
|
|
194
|
+
widget.push_data(data_2d.astype(np.float32))
|
|
195
|
+
|
|
196
|
+
# ------------------------------------------------------------------
|
|
197
|
+
# Event handling
|
|
198
|
+
# ------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
def _on_event(self, msg: AxisArray) -> None:
|
|
201
|
+
widget = self._plot_widget
|
|
202
|
+
if not isinstance(widget, SweepWidget):
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
if "time" not in msg.dims:
|
|
206
|
+
logger.warning("Event message must have 'time' dimension")
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
time_axis = msg.get_axis("time")
|
|
210
|
+
time_idx = msg.get_axis_idx("time")
|
|
211
|
+
timestamps = time_axis.value(list(range(msg.shape[time_idx])))
|
|
212
|
+
events: list[SweepEvent] = []
|
|
213
|
+
for ev_ix, ts in enumerate(timestamps):
|
|
214
|
+
label = msg.data[ev_ix, 0]
|
|
215
|
+
if self._event_filter and self._event_filter not in label:
|
|
216
|
+
continue
|
|
217
|
+
color = EVENT_COLORS[hash(label) % len(EVENT_COLORS)]
|
|
218
|
+
events.append(SweepEvent(t_elapsed=ts, label=label, color=color))
|
|
219
|
+
widget.push_events(events)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _run(
|
|
223
|
+
data_topic: str = typer.Argument(..., help="ezmsg topic for continuous data"),
|
|
224
|
+
mode: PlotMode = typer.Option(PlotMode.timeseries, help="Plot mode"),
|
|
225
|
+
event_topic: str | None = typer.Option(None, "--events", help="ezmsg topic for event markers"),
|
|
226
|
+
event_filter: str | None = typer.Option(
|
|
227
|
+
None, "--event-filter", help="Only show events whose label contains this string"
|
|
228
|
+
),
|
|
229
|
+
graph_addr: str = typer.Option(
|
|
230
|
+
":".join((GRAPH_IP, str(GRAPH_PORT))),
|
|
231
|
+
help="ezmsg graph address (ip:port)",
|
|
232
|
+
),
|
|
233
|
+
) -> None:
|
|
234
|
+
graph_ip, graph_port_str = graph_addr.split(":")
|
|
235
|
+
graph_address = (graph_ip, int(graph_port_str))
|
|
236
|
+
|
|
237
|
+
app = QApplication.instance() or QApplication(sys.argv)
|
|
238
|
+
session = EzSession(graph_address=graph_address)
|
|
239
|
+
window = ViewerWindow(
|
|
240
|
+
session=session,
|
|
241
|
+
mode=mode,
|
|
242
|
+
data_topic=data_topic,
|
|
243
|
+
event_topic=event_topic,
|
|
244
|
+
event_filter=event_filter,
|
|
245
|
+
)
|
|
246
|
+
window.showMaximized()
|
|
247
|
+
with session:
|
|
248
|
+
app.exec()
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def main() -> None:
|
|
252
|
+
typer.run(_run)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
if __name__ == "__main__":
|
|
256
|
+
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|