QuLab 2.10.10__cp313-cp313-win_amd64.whl
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.
- qulab/__init__.py +33 -0
- qulab/__main__.py +4 -0
- qulab/cli/__init__.py +0 -0
- qulab/cli/commands.py +30 -0
- qulab/cli/config.py +170 -0
- qulab/cli/decorators.py +28 -0
- qulab/dicttree.py +523 -0
- qulab/executor/__init__.py +5 -0
- qulab/executor/analyze.py +188 -0
- qulab/executor/cli.py +434 -0
- qulab/executor/load.py +563 -0
- qulab/executor/registry.py +185 -0
- qulab/executor/schedule.py +543 -0
- qulab/executor/storage.py +615 -0
- qulab/executor/template.py +259 -0
- qulab/executor/utils.py +194 -0
- qulab/expression.py +827 -0
- qulab/fun.cp313-win_amd64.pyd +0 -0
- qulab/monitor/__init__.py +1 -0
- qulab/monitor/__main__.py +8 -0
- qulab/monitor/config.py +41 -0
- qulab/monitor/dataset.py +77 -0
- qulab/monitor/event_queue.py +54 -0
- qulab/monitor/mainwindow.py +234 -0
- qulab/monitor/monitor.py +115 -0
- qulab/monitor/ploter.py +123 -0
- qulab/monitor/qt_compat.py +16 -0
- qulab/monitor/toolbar.py +265 -0
- qulab/scan/__init__.py +2 -0
- qulab/scan/curd.py +221 -0
- qulab/scan/models.py +554 -0
- qulab/scan/optimize.py +76 -0
- qulab/scan/query.py +387 -0
- qulab/scan/record.py +603 -0
- qulab/scan/scan.py +1166 -0
- qulab/scan/server.py +450 -0
- qulab/scan/space.py +213 -0
- qulab/scan/utils.py +234 -0
- qulab/storage/__init__.py +0 -0
- qulab/storage/__main__.py +51 -0
- qulab/storage/backend/__init__.py +0 -0
- qulab/storage/backend/redis.py +204 -0
- qulab/storage/base_dataset.py +352 -0
- qulab/storage/chunk.py +60 -0
- qulab/storage/dataset.py +127 -0
- qulab/storage/file.py +273 -0
- qulab/storage/models/__init__.py +22 -0
- qulab/storage/models/base.py +4 -0
- qulab/storage/models/config.py +28 -0
- qulab/storage/models/file.py +89 -0
- qulab/storage/models/ipy.py +58 -0
- qulab/storage/models/models.py +88 -0
- qulab/storage/models/record.py +161 -0
- qulab/storage/models/report.py +22 -0
- qulab/storage/models/tag.py +93 -0
- qulab/storage/storage.py +95 -0
- qulab/sys/__init__.py +2 -0
- qulab/sys/chat.py +688 -0
- qulab/sys/device/__init__.py +3 -0
- qulab/sys/device/basedevice.py +255 -0
- qulab/sys/device/loader.py +86 -0
- qulab/sys/device/utils.py +79 -0
- qulab/sys/drivers/FakeInstrument.py +68 -0
- qulab/sys/drivers/__init__.py +0 -0
- qulab/sys/ipy_events.py +125 -0
- qulab/sys/net/__init__.py +0 -0
- qulab/sys/net/bencoder.py +205 -0
- qulab/sys/net/cli.py +169 -0
- qulab/sys/net/dhcp.py +543 -0
- qulab/sys/net/dhcpd.py +176 -0
- qulab/sys/net/kad.py +1142 -0
- qulab/sys/net/kcp.py +192 -0
- qulab/sys/net/nginx.py +194 -0
- qulab/sys/progress.py +190 -0
- qulab/sys/rpc/__init__.py +0 -0
- qulab/sys/rpc/client.py +0 -0
- qulab/sys/rpc/exceptions.py +96 -0
- qulab/sys/rpc/msgpack.py +1052 -0
- qulab/sys/rpc/msgpack.pyi +41 -0
- qulab/sys/rpc/router.py +35 -0
- qulab/sys/rpc/rpc.py +412 -0
- qulab/sys/rpc/serialize.py +139 -0
- qulab/sys/rpc/server.py +29 -0
- qulab/sys/rpc/socket.py +29 -0
- qulab/sys/rpc/utils.py +25 -0
- qulab/sys/rpc/worker.py +0 -0
- qulab/sys/rpc/zmq_socket.py +227 -0
- qulab/tools/__init__.py +0 -0
- qulab/tools/connection_helper.py +39 -0
- qulab/typing.py +2 -0
- qulab/utils.py +95 -0
- qulab/version.py +1 -0
- qulab/visualization/__init__.py +188 -0
- qulab/visualization/__main__.py +71 -0
- qulab/visualization/_autoplot.py +464 -0
- qulab/visualization/plot_circ.py +319 -0
- qulab/visualization/plot_layout.py +408 -0
- qulab/visualization/plot_seq.py +242 -0
- qulab/visualization/qdat.py +152 -0
- qulab/visualization/rot3d.py +23 -0
- qulab/visualization/widgets.py +86 -0
- qulab-2.10.10.dist-info/METADATA +110 -0
- qulab-2.10.10.dist-info/RECORD +107 -0
- qulab-2.10.10.dist-info/WHEEL +5 -0
- qulab-2.10.10.dist-info/entry_points.txt +2 -0
- qulab-2.10.10.dist-info/licenses/LICENSE +21 -0
- qulab-2.10.10.dist-info/top_level.txt +1 -0
Binary file
|
@@ -0,0 +1 @@
|
|
1
|
+
from .monitor import Monitor, get_monitor
|
qulab/monitor/config.py
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
import numpy as np
|
2
|
+
|
3
|
+
style = '''
|
4
|
+
QWidget {
|
5
|
+
font: medium Ubuntu;
|
6
|
+
background-color: #011F2F;
|
7
|
+
font-size: 16px;
|
8
|
+
font-size: 16px;
|
9
|
+
color:#FFFFFF;
|
10
|
+
}
|
11
|
+
'''
|
12
|
+
#
|
13
|
+
Nroll = 6
|
14
|
+
|
15
|
+
# Format of the data.
|
16
|
+
forms = {
|
17
|
+
"mag": lambda w, ang: np.abs(w),
|
18
|
+
"phase": lambda w, ang: np.angle(w),
|
19
|
+
"real": lambda w, ang: np.real(w),
|
20
|
+
"imag": lambda w, ang: np.imag(w),
|
21
|
+
"rot": lambda w, ang: np.real(np.exp(1j * ang) * np.array(w))
|
22
|
+
}
|
23
|
+
form_keys = list(forms.keys())
|
24
|
+
#
|
25
|
+
COL_SEL = (0, 0, 0)
|
26
|
+
COL_UNSEL = (6, 6, 8)
|
27
|
+
#
|
28
|
+
|
29
|
+
defualt_colors = [
|
30
|
+
(200, 0, 0),
|
31
|
+
(55, 100, 180),
|
32
|
+
(40, 80, 150),
|
33
|
+
(30, 50, 110),
|
34
|
+
(25, 40, 70),
|
35
|
+
(25, 30, 50),
|
36
|
+
]
|
37
|
+
|
38
|
+
widths = [3, 2, 2, 2, 1, 1]
|
39
|
+
SymSize = [5, 0, 0, 0, 0, 0]
|
40
|
+
ridx = list(range(Nroll))
|
41
|
+
ridx.reverse()
|
qulab/monitor/dataset.py
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
from collections import defaultdict, deque
|
2
|
+
|
3
|
+
from .config import Nroll
|
4
|
+
|
5
|
+
PAUSE_TIME = 1
|
6
|
+
|
7
|
+
Number = int | float | complex
|
8
|
+
|
9
|
+
|
10
|
+
def remove_duplicates(input_list: list[str]) -> list[str]:
|
11
|
+
"""
|
12
|
+
Remove duplicates from a list of strings, keeping the order of the elements.
|
13
|
+
"""
|
14
|
+
return list(dict.fromkeys(input_list))
|
15
|
+
|
16
|
+
|
17
|
+
class Dataset():
|
18
|
+
|
19
|
+
def __init__(self):
|
20
|
+
self.column_names = []
|
21
|
+
self.box = deque(maxlen=Nroll)
|
22
|
+
self.dirty = True
|
23
|
+
|
24
|
+
def clear(self):
|
25
|
+
self.box.clear()
|
26
|
+
|
27
|
+
def clear_history(self):
|
28
|
+
o = self.box.popleft()
|
29
|
+
self.box.clear()
|
30
|
+
self.box.appendleft(o)
|
31
|
+
|
32
|
+
def set_column_names(self, column_names: list[str]):
|
33
|
+
column_names = remove_duplicates(column_names)
|
34
|
+
if column_names != self.column_names:
|
35
|
+
self.clear()
|
36
|
+
self.column_names = column_names
|
37
|
+
|
38
|
+
def get_data(self, step: int, xname: str,
|
39
|
+
yname: str) -> tuple[list[Number], list[Number]]:
|
40
|
+
try:
|
41
|
+
b = self.box[step]
|
42
|
+
except IndexError:
|
43
|
+
return [], []
|
44
|
+
return b[xname], b[yname]
|
45
|
+
|
46
|
+
def append(self, dataframe: list[Number] | list[list[Number]]):
|
47
|
+
if not dataframe:
|
48
|
+
return
|
49
|
+
try:
|
50
|
+
iter(dataframe[0]) # test if dataframe is a list of list
|
51
|
+
self._append_traces(dataframe)
|
52
|
+
except TypeError:
|
53
|
+
self._append_points(dataframe)
|
54
|
+
|
55
|
+
def roll(self):
|
56
|
+
self.box.appendleft(defaultdict(list))
|
57
|
+
|
58
|
+
def _append_points(self, points: list[Number]):
|
59
|
+
self.dirty = True
|
60
|
+
assert (len(points) == len(
|
61
|
+
self.column_names)), (f"-PointDataBox\n"
|
62
|
+
f"-ap\n"
|
63
|
+
f"-Length Must be same\n"
|
64
|
+
f"the column_names : {self.column_names}\n"
|
65
|
+
f"given data : {points}")
|
66
|
+
for name, p in zip(self.column_names, points):
|
67
|
+
self.box[0][name].append(p)
|
68
|
+
|
69
|
+
def _append_traces(self, traces: list[list[Number]]):
|
70
|
+
self.dirty = True
|
71
|
+
assert (len(traces) == len(
|
72
|
+
self.column_names)), (f"-TraceDataBox\n"
|
73
|
+
f"-at\n"
|
74
|
+
f"-Length Must be same\n"
|
75
|
+
f"the column_names : {self.column_names}\n"
|
76
|
+
f"given data : {traces}")
|
77
|
+
self.box.appendleft(dict(zip(self.column_names, traces)))
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import warnings
|
2
|
+
from multiprocessing import Queue
|
3
|
+
|
4
|
+
from .dataset import Dataset
|
5
|
+
from .toolbar import ToolBar
|
6
|
+
|
7
|
+
|
8
|
+
class EventQueue():
|
9
|
+
|
10
|
+
def __init__(self, queue: Queue, toolbar: ToolBar, point_dataset: Dataset,
|
11
|
+
trace_dataset: Dataset):
|
12
|
+
self.toolbar = toolbar
|
13
|
+
self.queue = queue
|
14
|
+
self.point_dataset = point_dataset
|
15
|
+
self.trace_dataset = trace_dataset
|
16
|
+
|
17
|
+
def flush(self):
|
18
|
+
while (not self.queue.empty()):
|
19
|
+
words = self.queue.get()
|
20
|
+
try:
|
21
|
+
assert (isinstance(
|
22
|
+
words, tuple)), "QueueHandler - fifo Content must be tuple"
|
23
|
+
assert (
|
24
|
+
len(words) == 2
|
25
|
+
), "QueueHandler -the tuple must be like (\"command_type\" , \"DATA\")"
|
26
|
+
cmd, data = words
|
27
|
+
assert (isinstance(
|
28
|
+
cmd, str)), "QueueHandler - the command should be a string"
|
29
|
+
except AssertionError as e:
|
30
|
+
warnings.warn(e.args[0])
|
31
|
+
continue
|
32
|
+
|
33
|
+
self.handle(cmd, data)
|
34
|
+
|
35
|
+
def handle(self, cmd, data):
|
36
|
+
match cmd:
|
37
|
+
case "PN":
|
38
|
+
self.point_dataset.set_column_names(data)
|
39
|
+
self.toolbar.refresh_comb()
|
40
|
+
case "TN":
|
41
|
+
self.trace_dataset.set_column_names(data)
|
42
|
+
self.toolbar.refresh_comb()
|
43
|
+
case "PD":
|
44
|
+
self.point_dataset.append(data)
|
45
|
+
case "TD":
|
46
|
+
self.trace_dataset.append(data)
|
47
|
+
case "ROLL":
|
48
|
+
self.point_dataset.roll()
|
49
|
+
case "PXY":
|
50
|
+
self.toolbar.set_point_text(data)
|
51
|
+
case "TXY":
|
52
|
+
self.toolbar.set_trace_text(data)
|
53
|
+
case _:
|
54
|
+
warnings.warn(f"QueueHandler - unknown command : {cmd}")
|
@@ -0,0 +1,234 @@
|
|
1
|
+
from multiprocessing import Queue
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
from .config import forms, ridx, style
|
5
|
+
from .dataset import Dataset
|
6
|
+
from .event_queue import EventQueue
|
7
|
+
from .ploter import PlotWidget
|
8
|
+
from .qt_compat import (BottomDockWidgetArea, QtCore, QtWidgets,
|
9
|
+
ScrollBarAlwaysOff, ScrollBarAlwaysOn,
|
10
|
+
TopDockWidgetArea)
|
11
|
+
from .toolbar import ToolBar
|
12
|
+
|
13
|
+
|
14
|
+
class MainWindow(QtWidgets.QMainWindow):
|
15
|
+
|
16
|
+
def __init__(self,
|
17
|
+
queue: Queue,
|
18
|
+
ncols=3,
|
19
|
+
plot_minimum_height=350,
|
20
|
+
plot_colors: list[tuple[int, int, int]] | None = None):
|
21
|
+
super().__init__()
|
22
|
+
self.ncols = ncols
|
23
|
+
self.need_reshuffled = False
|
24
|
+
self.plot_minimum_height = plot_minimum_height
|
25
|
+
self.plot_widgets: list[PlotWidget] = []
|
26
|
+
self.plot_colors = plot_colors
|
27
|
+
self.toolbar = ToolBar()
|
28
|
+
self.trace_data_box = Dataset()
|
29
|
+
self.point_data_box = Dataset()
|
30
|
+
self.queue = EventQueue(queue, self.toolbar, self.point_data_box,
|
31
|
+
self.trace_data_box)
|
32
|
+
|
33
|
+
self.init_ui()
|
34
|
+
|
35
|
+
self.timer = QtCore.QTimer()
|
36
|
+
self.timer.timeout.connect(self.update)
|
37
|
+
self.timer.start(250)
|
38
|
+
|
39
|
+
def init_ui(self):
|
40
|
+
self.setStyleSheet(style)
|
41
|
+
self.setMinimumHeight(500)
|
42
|
+
self.setMinimumWidth(700)
|
43
|
+
self.scroll = QtWidgets.QScrollArea(
|
44
|
+
) # Scroll Area which contains the widgets, set as the centralWidget
|
45
|
+
self.widget = QtWidgets.QWidget(
|
46
|
+
) # Widget that contains the collection of Vertical Box
|
47
|
+
self.layout = QtWidgets.QGridLayout()
|
48
|
+
self.widget.setLayout(self.layout)
|
49
|
+
|
50
|
+
#Scroll Area Properties
|
51
|
+
#self.setCorner(Qt.TopSection, Qt.TopDockWidgetArea);
|
52
|
+
self.scroll.setVerticalScrollBarPolicy(ScrollBarAlwaysOn)
|
53
|
+
self.scroll.setHorizontalScrollBarPolicy(ScrollBarAlwaysOff)
|
54
|
+
self.scroll.setWidgetResizable(True)
|
55
|
+
self.scroll.setWidget(self.widget)
|
56
|
+
self.setCentralWidget(self.scroll)
|
57
|
+
|
58
|
+
self.dock = QtWidgets.QDockWidget(self)
|
59
|
+
self.dock.setAllowedAreas(TopDockWidgetArea | BottomDockWidgetArea)
|
60
|
+
self.addDockWidget(TopDockWidgetArea, self.dock)
|
61
|
+
self.dock.setFloating(False)
|
62
|
+
self.dock.setWidget(self.toolbar)
|
63
|
+
self.dock.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable)
|
64
|
+
#self.tabifyDockWidget(self.dock,None);
|
65
|
+
#self.addDockWidget(self.dock);
|
66
|
+
#self.setStatusBar(self.toolbar);
|
67
|
+
#self.layout.addWidget(self.toolbar , 0 , 0 , 1, self.ncol);
|
68
|
+
|
69
|
+
self.setWindowTitle('Scroll multi view')
|
70
|
+
self.show()
|
71
|
+
self.toolbar.set_mainwindow(self)
|
72
|
+
self.toolbar.pb.setChecked(True)
|
73
|
+
|
74
|
+
@property
|
75
|
+
def mode(self) -> Literal["P", "T"]:
|
76
|
+
return self.toolbar.mode
|
77
|
+
|
78
|
+
@property
|
79
|
+
def dataset(self) -> Dataset:
|
80
|
+
return {"P": self.point_data_box, "T": self.trace_data_box}[self.mode]
|
81
|
+
|
82
|
+
def set_ncols(self, x: int):
|
83
|
+
x = max(1, min(10, int(x)))
|
84
|
+
if (x != self.ncols):
|
85
|
+
self.need_reshuffled = True
|
86
|
+
self.ncols = x
|
87
|
+
|
88
|
+
def add_subplot(self):
|
89
|
+
n = len(self.plot_widgets)
|
90
|
+
pw = PlotWidget(self.plot_minimum_height, self.plot_colors)
|
91
|
+
self.plot_widgets.append(pw)
|
92
|
+
grid_row = n // self.ncols
|
93
|
+
grid_col = n % self.ncols
|
94
|
+
self.layout.addWidget(pw, grid_row + 1, grid_col)
|
95
|
+
return pw
|
96
|
+
|
97
|
+
def create_subplots(self, xy_pairs):
|
98
|
+
for xn, yn in xy_pairs:
|
99
|
+
pw = self.add_subplot()
|
100
|
+
pw.set_X_label(xn)
|
101
|
+
pw.set_Y_label(yn)
|
102
|
+
self.do_link()
|
103
|
+
self.all_enable_auto_range()
|
104
|
+
|
105
|
+
def clear_subplots(self):
|
106
|
+
for i in range(len(self.plot_widgets)):
|
107
|
+
self.layout.removeWidget(self.plot_widgets[i])
|
108
|
+
self.plot_widgets[i].setParent(None)
|
109
|
+
self.plot_widgets.clear()
|
110
|
+
|
111
|
+
def remove_plot(self, w: PlotWidget):
|
112
|
+
w.setParent(None)
|
113
|
+
self.plot_widgets.remove(w)
|
114
|
+
self.reshuffle()
|
115
|
+
|
116
|
+
def drop_last_plot(self, i_=-1):
|
117
|
+
# delete the one
|
118
|
+
i = int(i_)
|
119
|
+
if (i < len(self.plot_widgets)):
|
120
|
+
w = self.plot_widgets[i]
|
121
|
+
w.setParent(None)
|
122
|
+
del w
|
123
|
+
del self.plot_widgets[i]
|
124
|
+
self.reshuffle()
|
125
|
+
|
126
|
+
def reshuffle(self):
|
127
|
+
for idx, widget in enumerate(self.plot_widgets):
|
128
|
+
widget.setParent(None)
|
129
|
+
grid_row = idx // self.ncols
|
130
|
+
grid_col = idx % self.ncols
|
131
|
+
self.layout.addWidget(widget, grid_row + 1, grid_col)
|
132
|
+
|
133
|
+
def keyPressEvent(self, ev):
|
134
|
+
#print(ev.text());
|
135
|
+
tx = ev.text()
|
136
|
+
if (tx in ['_', '-']):
|
137
|
+
self.set_ncols(self.ncols - 1)
|
138
|
+
elif (tx in ['=', '+']):
|
139
|
+
self.set_ncols(self.ncols + 1)
|
140
|
+
|
141
|
+
def mouse_click(self):
|
142
|
+
pass
|
143
|
+
|
144
|
+
def do_link(self):
|
145
|
+
"""
|
146
|
+
link the plot
|
147
|
+
|
148
|
+
share the same x or y axis
|
149
|
+
"""
|
150
|
+
same_X = {}
|
151
|
+
xy_pairs = self.toolbar.xypairs
|
152
|
+
for idx, xyn in enumerate(xy_pairs):
|
153
|
+
xn, yn = xyn
|
154
|
+
if xn not in same_X:
|
155
|
+
same_X[xn] = []
|
156
|
+
same_X[xn].append(idx)
|
157
|
+
|
158
|
+
sharex, sharey = self.toolbar.sharexy()
|
159
|
+
|
160
|
+
s_A = not (sharex and sharey)
|
161
|
+
for x, yidxs in same_X.items():
|
162
|
+
pre_yidx = -1
|
163
|
+
for yidx in yidxs:
|
164
|
+
if (-1 != pre_yidx):
|
165
|
+
if (s_A):
|
166
|
+
self.plot_widgets[pre_yidx].plotItem.vb.setXLink(None)
|
167
|
+
self.plot_widgets[pre_yidx].plotItem.vb.setYLink(None)
|
168
|
+
|
169
|
+
if sharex:
|
170
|
+
self.plot_widgets[pre_yidx].plotItem.vb.setXLink(
|
171
|
+
self.plot_widgets[yidx].plotItem.vb)
|
172
|
+
|
173
|
+
if sharey:
|
174
|
+
self.plot_widgets[pre_yidx].plotItem.vb.setYLink(
|
175
|
+
self.plot_widgets[yidx].plotItem.vb)
|
176
|
+
pre_yidx = yidx
|
177
|
+
|
178
|
+
def all_auto_range(self):
|
179
|
+
for pw in self.plot_widgets:
|
180
|
+
pw.auto_range()
|
181
|
+
|
182
|
+
def all_enable_auto_range(self):
|
183
|
+
for pw in self.plot_widgets:
|
184
|
+
pw.enable_auto_range()
|
185
|
+
|
186
|
+
def update(self):
|
187
|
+
# update the queue
|
188
|
+
self.queue.flush()
|
189
|
+
|
190
|
+
rescale = False
|
191
|
+
|
192
|
+
# setup the xyfm
|
193
|
+
if (self.toolbar.xypairs_dirty):
|
194
|
+
self.clear_subplots()
|
195
|
+
self.create_subplots(self.toolbar.xypairs)
|
196
|
+
self.toolbar.xypairs_dirty = False
|
197
|
+
rescale = True
|
198
|
+
|
199
|
+
if (self.toolbar.link_dirty):
|
200
|
+
self.do_link()
|
201
|
+
self.toolbar.link_dirty = False
|
202
|
+
|
203
|
+
if (self.need_reshuffled):
|
204
|
+
self.need_reshuffled = False
|
205
|
+
self.reshuffle()
|
206
|
+
|
207
|
+
# checking the log space
|
208
|
+
if (self.toolbar.xyfm_dirty):
|
209
|
+
for pw in self.plot_widgets:
|
210
|
+
pw.plotItem.ctrl.logXCheck.setChecked(self.toolbar.lx)
|
211
|
+
pw.plotItem.ctrl.logYCheck.setChecked(self.toolbar.ly)
|
212
|
+
|
213
|
+
#update the plot
|
214
|
+
# if clear is set then do the clear :
|
215
|
+
if (self.toolbar.CR_flag):
|
216
|
+
self.toolbar.CR_flag = False
|
217
|
+
self.dataset.clear_history()
|
218
|
+
self.dataset.dirty = True
|
219
|
+
|
220
|
+
if (self.dataset.dirty or self.toolbar.xyfm_dirty or rescale):
|
221
|
+
self.dataset.dirty = False
|
222
|
+
self.xyfm_dirty = False
|
223
|
+
for pw in self.plot_widgets:
|
224
|
+
|
225
|
+
fx = forms[self.toolbar.fx]
|
226
|
+
fy = forms[self.toolbar.fy]
|
227
|
+
for i in ridx:
|
228
|
+
x, y = self.dataset.get_data(i, pw.xname, pw.yname)
|
229
|
+
l = min(len(x), len(y))
|
230
|
+
x, y = fx(x[:l], 0), fy(y[:l], 0)
|
231
|
+
pw.set_data(i, x, y)
|
232
|
+
pw.update()
|
233
|
+
if rescale:
|
234
|
+
pw.auto_range()
|
qulab/monitor/monitor.py
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
import multiprocessing as mp
|
2
|
+
import sys
|
3
|
+
|
4
|
+
# try:
|
5
|
+
# mp.set_start_method("spawn")
|
6
|
+
# except:
|
7
|
+
# pass
|
8
|
+
|
9
|
+
|
10
|
+
def main(queue: mp.Queue,
|
11
|
+
ncols: int = 4,
|
12
|
+
minimum_height: int = 400,
|
13
|
+
colors: list[tuple[int, int, int]] = []):
|
14
|
+
from .mainwindow import MainWindow
|
15
|
+
from .qt_compat import QtWidgets
|
16
|
+
|
17
|
+
app = QtWidgets.QApplication(sys.argv)
|
18
|
+
main = MainWindow(queue, ncols, minimum_height, colors)
|
19
|
+
sys.exit(app.exec())
|
20
|
+
|
21
|
+
|
22
|
+
class Monitor():
|
23
|
+
|
24
|
+
def __init__(self,
|
25
|
+
number_of_columns: int = 4,
|
26
|
+
minimum_height: int = 400,
|
27
|
+
colors: list[tuple[int, int, int]] = []):
|
28
|
+
self.colors = [tuple(color) for color in colors]
|
29
|
+
self.number_of_columns = number_of_columns
|
30
|
+
self.minimum_height = minimum_height
|
31
|
+
self.queue = mp.Queue(20)
|
32
|
+
self.process = None
|
33
|
+
self.start()
|
34
|
+
|
35
|
+
def start(self):
|
36
|
+
if self.process is not None and self.process.is_alive():
|
37
|
+
return
|
38
|
+
self.queue = mp.Queue(20)
|
39
|
+
self.process = mp.Process(target=main,
|
40
|
+
args=(self.queue, self.number_of_columns,
|
41
|
+
self.minimum_height, self.colors))
|
42
|
+
self.process.start()
|
43
|
+
|
44
|
+
def _put(self, w: tuple):
|
45
|
+
self.queue.put(w)
|
46
|
+
|
47
|
+
def roll(self):
|
48
|
+
self._put(("ROLL", None))
|
49
|
+
|
50
|
+
def set_column_names(self, *arg):
|
51
|
+
self._put(('PN', list(arg)))
|
52
|
+
|
53
|
+
def add_point(self, *arg):
|
54
|
+
self._put(('PD', list(arg)))
|
55
|
+
|
56
|
+
def set_plots(self, arg):
|
57
|
+
"""
|
58
|
+
arg: str, like "(x,y)" or "(x1,y1);(x2,y2);"
|
59
|
+
"""
|
60
|
+
self._put(('PXY', str(arg)))
|
61
|
+
|
62
|
+
def set_trace_column_names(self, *arg):
|
63
|
+
self._put(('TN', list(arg)))
|
64
|
+
|
65
|
+
def add_trace(self, *arg):
|
66
|
+
self._put(('TD', list(arg)))
|
67
|
+
|
68
|
+
def set_trace_plots(self, arg):
|
69
|
+
"""
|
70
|
+
arg: str, like "(x,y)" or "(x1,y1);(x2,y2);"
|
71
|
+
"""
|
72
|
+
self._put(('TXY', str(arg)))
|
73
|
+
|
74
|
+
def __del__(self):
|
75
|
+
try:
|
76
|
+
self.process.kill()
|
77
|
+
except:
|
78
|
+
pass
|
79
|
+
|
80
|
+
def is_alive(self):
|
81
|
+
return self.process.is_alive()
|
82
|
+
|
83
|
+
|
84
|
+
_monitor = None
|
85
|
+
|
86
|
+
|
87
|
+
def get_monitor(auto_open=True):
|
88
|
+
global _monitor
|
89
|
+
|
90
|
+
if auto_open and (_monitor is None or not _monitor.is_alive()):
|
91
|
+
_monitor = Monitor()
|
92
|
+
|
93
|
+
return _monitor
|
94
|
+
|
95
|
+
|
96
|
+
if __name__ == "__main__":
|
97
|
+
import time
|
98
|
+
|
99
|
+
import numpy as np
|
100
|
+
|
101
|
+
for i in range(3):
|
102
|
+
index = 0
|
103
|
+
while True:
|
104
|
+
if index >= 100:
|
105
|
+
break
|
106
|
+
|
107
|
+
m = get_monitor()
|
108
|
+
|
109
|
+
if index == 0:
|
110
|
+
m.set_column_names("index", "H", "S")
|
111
|
+
m.set_plots("(index,H);(index,S)")
|
112
|
+
m.roll()
|
113
|
+
m.add_point(index, np.random.randn(), np.sin(index / 20))
|
114
|
+
index += 1
|
115
|
+
time.sleep(0.2)
|
qulab/monitor/ploter.py
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
from .config import COL_SEL, COL_UNSEL, SymSize, defualt_colors, ridx, widths
|
2
|
+
from .qt_compat import QtWidgets
|
3
|
+
|
4
|
+
# the plotting widget
|
5
|
+
try:
|
6
|
+
import pyqtgraph as pg
|
7
|
+
except:
|
8
|
+
raise ImportError("Please install pyqtgraph first")
|
9
|
+
|
10
|
+
try:
|
11
|
+
import pyperclip as pc
|
12
|
+
hasCliper = True
|
13
|
+
except:
|
14
|
+
hasCliper = False
|
15
|
+
|
16
|
+
|
17
|
+
class PlotWidget(pg.PlotWidget):
|
18
|
+
|
19
|
+
def __init__(self, minimum_height=300, colors=None):
|
20
|
+
self.XAxisLinked = False
|
21
|
+
self.YAxisLinked = False
|
22
|
+
if colors is None:
|
23
|
+
colors = defualt_colors
|
24
|
+
elif len(colors) < len(defualt_colors):
|
25
|
+
colors.extend(defualt_colors[len(colors):])
|
26
|
+
self.colors = colors
|
27
|
+
self.xname = ""
|
28
|
+
self.yname = ""
|
29
|
+
super().__init__()
|
30
|
+
|
31
|
+
self.setMinimumHeight(minimum_height)
|
32
|
+
self.showGrid(x=True, y=True)
|
33
|
+
self.setBackground(COL_UNSEL)
|
34
|
+
|
35
|
+
self.plotItem.vb.autoRange()
|
36
|
+
|
37
|
+
## Labeling
|
38
|
+
self.XLabel = QtWidgets.QLabel(self)
|
39
|
+
self.XLabel.setText("X:")
|
40
|
+
self.XLabel.move(0, 5)
|
41
|
+
self.YLabel = QtWidgets.QLabel(self)
|
42
|
+
self.YLabel.setText("Y:")
|
43
|
+
self.YLabel.move(0, 35)
|
44
|
+
|
45
|
+
self.plots = {}
|
46
|
+
self.clippos1 = 0
|
47
|
+
self.clippos2 = 0
|
48
|
+
self.range_select = False
|
49
|
+
for i in ridx:
|
50
|
+
self.plots[i] = \
|
51
|
+
self.plot([],[] ,pen={"color":self.colors[i] ,"width":widths[i]} ,
|
52
|
+
symbolBrush = self.colors[i],
|
53
|
+
symbolPen = { "width":0 ,"color":self.colors[i] } ,
|
54
|
+
symbolSize =SymSize[i] ,
|
55
|
+
)
|
56
|
+
self.update()
|
57
|
+
|
58
|
+
def set_X_label(self, w):
|
59
|
+
self.xname = w
|
60
|
+
self.XLabel.setText(f"X:{w}")
|
61
|
+
|
62
|
+
def set_Y_label(self, w):
|
63
|
+
self.yname = w
|
64
|
+
self.YLabel.setText(f"Y:{w}")
|
65
|
+
|
66
|
+
def auto_range(self):
|
67
|
+
self.plotItem.vb.autoRange()
|
68
|
+
|
69
|
+
def enable_auto_range(self):
|
70
|
+
self.plotItem.vb.enableAutoRange()
|
71
|
+
|
72
|
+
def keyPressEvent(self, ev):
|
73
|
+
#print(ev.text());
|
74
|
+
tx = ev.text()
|
75
|
+
if ('f' == tx or 'F' == tx):
|
76
|
+
self.plotItem.vb.autoRange()
|
77
|
+
if ('a' == tx or 'A' == tx):
|
78
|
+
self.plotItem.vb.setAutoPan()
|
79
|
+
if ('r' == tx or 'R' == tx):
|
80
|
+
self.range_select = True
|
81
|
+
super().keyPressEvent(ev)
|
82
|
+
|
83
|
+
def keyReleaseEvent(self, ev):
|
84
|
+
#print(ev.text());
|
85
|
+
tx = ev.text()
|
86
|
+
if ('f' == tx or 'F' == tx):
|
87
|
+
self.plotItem.vb.autoRange()
|
88
|
+
if ('a' == tx or 'A' == tx):
|
89
|
+
self.plotItem.vb.setAutoPan()
|
90
|
+
if ('r' == tx or 'R' == tx):
|
91
|
+
self.range_select = False
|
92
|
+
super().keyReleaseEvent(ev)
|
93
|
+
|
94
|
+
def mousePressEvent(self, ev):
|
95
|
+
if (4 == ev.button()):
|
96
|
+
# print(ev.flags())
|
97
|
+
if (hasCliper):
|
98
|
+
# print("Mouse is pressed")
|
99
|
+
self.clippos1 = self.plotItem.vb.mapSceneToView(ev.pos()).x()
|
100
|
+
else:
|
101
|
+
super().mousePressEvent(ev)
|
102
|
+
|
103
|
+
def mouseReleaseEvent(self, ev):
|
104
|
+
if (4 == ev.button()):
|
105
|
+
p = ev.pos()
|
106
|
+
if (hasCliper):
|
107
|
+
# print("Mouse is released")
|
108
|
+
self.clippos2 = self.plotItem.vb.mapSceneToView(ev.pos()).x()
|
109
|
+
if (self.range_select):
|
110
|
+
pc.copy(f"{self.clippos1},{self.clippos2}")
|
111
|
+
else:
|
112
|
+
pc.copy(self.clippos2)
|
113
|
+
else:
|
114
|
+
super().mouseReleaseEvent(ev)
|
115
|
+
|
116
|
+
def update(self):
|
117
|
+
super().update()
|
118
|
+
|
119
|
+
def set_data(self, i, x, y):
|
120
|
+
self.plots[i].setData(x, y)
|
121
|
+
|
122
|
+
# def mouseDoubleClickEvent(self, ev):
|
123
|
+
# super().mouseDoubleClickEvent(ev)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from matplotlib.backends.qt_compat import QT_API, QtCore, QtWidgets
|
2
|
+
|
3
|
+
if QT_API in ['PySide6', 'PyQt6']:
|
4
|
+
AlignRight = QtCore.Qt.AlignmentFlag.AlignRight
|
5
|
+
BottomDockWidgetArea = QtCore.Qt.DockWidgetArea.BottomDockWidgetArea
|
6
|
+
ScrollBarAlwaysOn = QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOn
|
7
|
+
ScrollBarAlwaysOff = QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff
|
8
|
+
TopDockWidgetArea = QtCore.Qt.DockWidgetArea.TopDockWidgetArea
|
9
|
+
elif QT_API in ['PyQt5', 'PySide2']:
|
10
|
+
AlignRight = QtCore.Qt.AlignRight
|
11
|
+
BottomDockWidgetArea = QtCore.Qt.BottomDockWidgetArea
|
12
|
+
ScrollBarAlwaysOn = QtCore.Qt.ScrollBarAlwaysOn
|
13
|
+
ScrollBarAlwaysOff = QtCore.Qt.ScrollBarAlwaysOff
|
14
|
+
TopDockWidgetArea = QtCore.Qt.TopDockWidgetArea
|
15
|
+
else:
|
16
|
+
raise AssertionError(f"Unexpected QT_API: {QT_API}")
|