QuLab 2.4.0__cp312-cp312-macosx_10_13_universal2.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.
Files changed (97) hide show
  1. QuLab-2.4.0.dist-info/LICENSE +21 -0
  2. QuLab-2.4.0.dist-info/METADATA +105 -0
  3. QuLab-2.4.0.dist-info/RECORD +97 -0
  4. QuLab-2.4.0.dist-info/WHEEL +5 -0
  5. QuLab-2.4.0.dist-info/entry_points.txt +2 -0
  6. QuLab-2.4.0.dist-info/top_level.txt +1 -0
  7. qulab/__init__.py +3 -0
  8. qulab/__main__.py +30 -0
  9. qulab/dicttree.py +511 -0
  10. qulab/executor/__init__.py +5 -0
  11. qulab/executor/__main__.py +89 -0
  12. qulab/executor/load.py +202 -0
  13. qulab/executor/schedule.py +223 -0
  14. qulab/executor/storage.py +143 -0
  15. qulab/executor/transform.py +90 -0
  16. qulab/executor/utils.py +107 -0
  17. qulab/fun.cpython-312-darwin.so +0 -0
  18. qulab/monitor/__init__.py +1 -0
  19. qulab/monitor/__main__.py +8 -0
  20. qulab/monitor/config.py +41 -0
  21. qulab/monitor/dataset.py +77 -0
  22. qulab/monitor/event_queue.py +54 -0
  23. qulab/monitor/mainwindow.py +234 -0
  24. qulab/monitor/monitor.py +93 -0
  25. qulab/monitor/ploter.py +123 -0
  26. qulab/monitor/qt_compat.py +16 -0
  27. qulab/monitor/toolbar.py +265 -0
  28. qulab/scan/__init__.py +3 -0
  29. qulab/scan/curd.py +221 -0
  30. qulab/scan/expression.py +646 -0
  31. qulab/scan/models.py +554 -0
  32. qulab/scan/optimize.py +76 -0
  33. qulab/scan/query.py +374 -0
  34. qulab/scan/record.py +603 -0
  35. qulab/scan/scan.py +1166 -0
  36. qulab/scan/server.py +533 -0
  37. qulab/scan/space.py +213 -0
  38. qulab/scan/utils.py +229 -0
  39. qulab/storage/__init__.py +0 -0
  40. qulab/storage/__main__.py +51 -0
  41. qulab/storage/backend/__init__.py +0 -0
  42. qulab/storage/backend/redis.py +204 -0
  43. qulab/storage/base_dataset.py +352 -0
  44. qulab/storage/chunk.py +60 -0
  45. qulab/storage/dataset.py +127 -0
  46. qulab/storage/file.py +273 -0
  47. qulab/storage/models/__init__.py +22 -0
  48. qulab/storage/models/base.py +4 -0
  49. qulab/storage/models/config.py +28 -0
  50. qulab/storage/models/file.py +89 -0
  51. qulab/storage/models/ipy.py +58 -0
  52. qulab/storage/models/models.py +88 -0
  53. qulab/storage/models/record.py +161 -0
  54. qulab/storage/models/report.py +22 -0
  55. qulab/storage/models/tag.py +93 -0
  56. qulab/storage/storage.py +95 -0
  57. qulab/sys/__init__.py +0 -0
  58. qulab/sys/chat.py +688 -0
  59. qulab/sys/device/__init__.py +3 -0
  60. qulab/sys/device/basedevice.py +229 -0
  61. qulab/sys/device/loader.py +86 -0
  62. qulab/sys/device/utils.py +79 -0
  63. qulab/sys/drivers/FakeInstrument.py +52 -0
  64. qulab/sys/drivers/__init__.py +0 -0
  65. qulab/sys/ipy_events.py +125 -0
  66. qulab/sys/net/__init__.py +0 -0
  67. qulab/sys/net/bencoder.py +205 -0
  68. qulab/sys/net/cli.py +169 -0
  69. qulab/sys/net/dhcp.py +543 -0
  70. qulab/sys/net/dhcpd.py +176 -0
  71. qulab/sys/net/kad.py +1142 -0
  72. qulab/sys/net/kcp.py +192 -0
  73. qulab/sys/net/nginx.py +192 -0
  74. qulab/sys/progress.py +190 -0
  75. qulab/sys/rpc/__init__.py +0 -0
  76. qulab/sys/rpc/client.py +0 -0
  77. qulab/sys/rpc/exceptions.py +96 -0
  78. qulab/sys/rpc/msgpack.py +1052 -0
  79. qulab/sys/rpc/msgpack.pyi +41 -0
  80. qulab/sys/rpc/router.py +35 -0
  81. qulab/sys/rpc/rpc.py +412 -0
  82. qulab/sys/rpc/serialize.py +139 -0
  83. qulab/sys/rpc/server.py +29 -0
  84. qulab/sys/rpc/socket.py +29 -0
  85. qulab/sys/rpc/utils.py +25 -0
  86. qulab/sys/rpc/worker.py +0 -0
  87. qulab/sys/rpc/zmq_socket.py +220 -0
  88. qulab/version.py +1 -0
  89. qulab/visualization/__init__.py +188 -0
  90. qulab/visualization/__main__.py +71 -0
  91. qulab/visualization/_autoplot.py +464 -0
  92. qulab/visualization/plot_circ.py +319 -0
  93. qulab/visualization/plot_layout.py +408 -0
  94. qulab/visualization/plot_seq.py +242 -0
  95. qulab/visualization/qdat.py +152 -0
  96. qulab/visualization/rot3d.py +23 -0
  97. qulab/visualization/widgets.py +86 -0
File without changes
@@ -0,0 +1,220 @@
1
+ from typing import Optional
2
+
3
+ import zmq
4
+ import zmq.asyncio
5
+ import zmq.auth
6
+ from watchdog.events import FileSystemEventHandler
7
+ from watchdog.observers import Observer
8
+ from zmq.auth.asyncio import AsyncioAuthenticator
9
+ from zmq.auth.thread import ThreadAuthenticator
10
+
11
+
12
+ class ReloadCertificatesHandler(FileSystemEventHandler):
13
+
14
+ def __init__(self, zmq: 'ZMQContextManager'):
15
+ self.zmq = zmq
16
+
17
+ def on_modified(self, event):
18
+ self.zmq.reload_certificates()
19
+
20
+
21
+ class ZMQContextManager:
22
+ """
23
+ A context manager for managing ZeroMQ sockets with asynchronous support.
24
+ It handles the creation, connection, binding, and security configuration of
25
+ the sockets and ensures proper resource cleanup.
26
+
27
+ The context manager can be used as a synchronous context manager or an
28
+ asynchronous context manager. When used as an asynchronous context manager,
29
+ the socket is created with an asyncio context and can be used with the
30
+ `await` keyword.
31
+
32
+ The security settings for the socket can be configured using the secret key
33
+ and public key parameters. If the secret key is provided, the socket is
34
+ configured to use ZeroMQ curve encryption. The public key of the server can
35
+ also be provided for client sockets to connect to a server with curve
36
+ encryption. You can also provide the paths to the secret key and public key
37
+ files, and the public key of the server to load the keys from files. The
38
+ keys can be reloaded automatically when the files are modified by setting
39
+ the `public_keys_location` parameter.
40
+
41
+ To generate a secret key and public key pair, you can use the following
42
+ commands:
43
+
44
+ ```bash
45
+ # Generate a secret key and public key pair
46
+ python -c "import zmq.auth; zmq.auth.create_certificates('.', 'filename')"
47
+ ```
48
+
49
+ Attributes:
50
+ socket_type: zmq.SocketType
51
+ The type of the socket to create (e.g., zmq.REP).
52
+ bind: str, optional
53
+ The address to bind the socket to.
54
+ connect: str, optional
55
+ The address to connect the socket to.
56
+ secret_key_file: str, optional
57
+ The path to the secret key file for ZeroMQ curve encryption.
58
+ server_public_key_file: str, optional
59
+ The path to the public key file for the server in ZeroMQ curve
60
+ encryption.
61
+ public_keys_location: str, optional
62
+ The location to store the public keys for ZeroMQ curve encryption.
63
+ secret_key: bytes, optional
64
+ The secret key for ZeroMQ curve encryption.
65
+ public_key: bytes, optional
66
+ The public key for ZeroMQ curve encryption.
67
+ server_public_key: bytes, optional
68
+ The public key for the server in ZeroMQ curve encryption.
69
+
70
+ Methods:
71
+ _create_socket: zmq.Socket
72
+ Creates and configures a ZeroMQ socket.
73
+ _close_socket: None
74
+ Closes the ZeroMQ socket and the context, and stops the authenticator
75
+ if it was started.
76
+
77
+ Examples:
78
+ Create a REP socket and bind it to an address:
79
+
80
+ >>> async with ZMQContextManager(zmq.REP, bind='tcp://*:5555') as socket:
81
+ ... while True:
82
+ ... message = await socket.recv()
83
+ ... await socket.send(message)
84
+
85
+ Create a REQ socket and connect it to an address:
86
+
87
+ >>> with ZMQContextManager(zmq.REQ, connect='tcp://localhost:5555') as socket:
88
+ ... socket.send(b'Hello')
89
+ ... message = socket.recv()
90
+ """
91
+
92
+ def __init__(self,
93
+ socket_type: zmq.SocketType,
94
+ bind: Optional[str] = None,
95
+ connect: Optional[str] = None,
96
+ secret_key_file: Optional[str] = None,
97
+ server_public_key_file: Optional[str] = None,
98
+ public_keys_location: Optional[str] = None,
99
+ secret_key: Optional[bytes] = None,
100
+ public_key: Optional[bytes] = None,
101
+ server_public_key: Optional[bytes] = None,
102
+ socket: Optional[zmq.Socket] = None):
103
+ self.socket_type = socket_type
104
+ if bind is None and connect is None:
105
+ raise ValueError("Either 'bind' or 'connect' must be specified.")
106
+ if bind is not None and connect is not None:
107
+ raise ValueError("Both 'bind' and 'connect' cannot be specified.")
108
+ self.bind = bind
109
+ self.connect = connect
110
+ self.secret_key = secret_key
111
+ self.public_key = public_key
112
+ self.server_public_key = server_public_key
113
+
114
+ if secret_key_file:
115
+ self.public_key, self.secret_key = zmq.auth.load_certificate(
116
+ secret_key_file)
117
+
118
+ if (self.secret_key is not None and self.public_key is None
119
+ or self.secret_key is None and self.public_key is not None):
120
+ raise ValueError(
121
+ "Both secret key and public key must be specified.")
122
+
123
+ if server_public_key_file:
124
+ self.server_public_key = zmq.auth.load_certificate(
125
+ server_public_key_file)[0]
126
+
127
+ self.public_keys_location = public_keys_location
128
+
129
+ self.observer = None
130
+ self.auth = None
131
+ self.context = None
132
+ self.socket = None
133
+ self._external_socket = None
134
+ try:
135
+ if not socket.closed:
136
+ self._external_socket = socket
137
+ except:
138
+ pass
139
+
140
+ def _create_socket(self, asyncio=False) -> zmq.Socket:
141
+ """
142
+ Creates and configures a ZeroMQ socket. Sets up security if required,
143
+ and binds or connects the socket according to the specified settings.
144
+
145
+ Returns:
146
+ zmq.Socket: The configured ZeroMQ socket.
147
+ """
148
+ if self._external_socket:
149
+ return self._external_socket
150
+ if asyncio:
151
+ self.context = zmq.asyncio.Context()
152
+ else:
153
+ self.context = zmq.Context.instance()
154
+
155
+ self.socket = self.context.socket(self.socket_type)
156
+ self.auth = None
157
+
158
+ if self.bind and self.secret_key:
159
+ if asyncio:
160
+ self.auth = AsyncioAuthenticator(self.context)
161
+ else:
162
+ self.auth = ThreadAuthenticator(self.context)
163
+ self.auth.start()
164
+ self.reload_certificates()
165
+ self.auto_reload_certificates()
166
+ self.socket.curve_server = True # must come before bind
167
+
168
+ if self.secret_key:
169
+ self.socket.curve_secretkey = self.secret_key
170
+ self.socket.curve_publickey = self.public_key
171
+
172
+ if self.bind:
173
+ self.socket.bind(self.bind)
174
+ if self.connect:
175
+ if self.server_public_key:
176
+ self.socket.curve_serverkey = self.server_public_key
177
+ self.socket.connect(self.connect)
178
+ return self.socket
179
+
180
+ def reload_certificates(self):
181
+ if self.public_keys_location and self.auth:
182
+ self.auth.configure_curve(domain='*',
183
+ location=self.public_keys_location)
184
+
185
+ def auto_reload_certificates(self):
186
+ self.observer = Observer()
187
+ self.observer.schedule(ReloadCertificatesHandler(self),
188
+ self.public_keys_location,
189
+ recursive=False)
190
+ self.observer.start()
191
+
192
+ def _close_socket(self) -> None:
193
+ """
194
+ Closes the ZeroMQ socket and the context, and stops the authenticator
195
+ if it was started.
196
+ """
197
+ if self._external_socket:
198
+ return
199
+ if self.observer:
200
+ self.observer.stop()
201
+ self.observer.join()
202
+ self.socket.close()
203
+ if self.auth:
204
+ self.auth.stop()
205
+ self.context.term()
206
+
207
+ def __enter__(self) -> zmq.Socket:
208
+ return self._create_socket(asyncio=False)
209
+
210
+ def __exit__(self, exc_type: Optional[type], exc_val: Optional[Exception],
211
+ exc_tb: Optional[type]) -> None:
212
+ self._close_socket()
213
+
214
+ async def __aenter__(self) -> zmq.Socket:
215
+ return self._create_socket(asyncio=True)
216
+
217
+ async def __aexit__(self, exc_type: Optional[type],
218
+ exc_val: Optional[Exception],
219
+ exc_tb: Optional[type]) -> None:
220
+ self._close_socket()
qulab/version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "2.4.0"
@@ -0,0 +1,188 @@
1
+ import matplotlib.pyplot as plt
2
+ import numpy as np
3
+
4
+ from ._autoplot import autoplot
5
+
6
+
7
+ def plotLine(c0, c1, ax, **kwargs):
8
+ t = np.linspace(0, 1, 11)
9
+ c = (c1 - c0) * t + c0
10
+ ax.plot(c.real, c.imag, **kwargs)
11
+
12
+
13
+ def plotCircle(c0, r, ax, **kwargs):
14
+ t = np.linspace(0, 1, 1001) * 2 * np.pi
15
+ s = c0 + r * np.exp(1j * t)
16
+ ax.plot(s.real, s.imag, **kwargs)
17
+
18
+
19
+ def plotEllipse(c0, a, b, phi, ax, **kwargs):
20
+ t = np.linspace(0, 1, 1001) * 2 * np.pi
21
+ c = np.exp(1j * t)
22
+ s = c0 + (c.real * a + 1j * c.imag * b) * np.exp(1j * phi)
23
+ ax.plot(s.real, s.imag, **kwargs)
24
+
25
+
26
+ def plotDistribution(s0,
27
+ s1,
28
+ fig=None,
29
+ axes=None,
30
+ info=None,
31
+ hotThresh=10000,
32
+ logy=False):
33
+ from waveforms.math.fit import get_threshold_info, mult_gaussian_pdf
34
+
35
+ if info is None:
36
+ info = get_threshold_info(s0, s1)
37
+ else:
38
+ info = get_threshold_info(s0, s1, info['threshold'], info['phi'])
39
+ thr, phi = info['threshold'], info['phi']
40
+ # visibility, p0, p1 = info['visibility']
41
+ # print(
42
+ # f"thr={thr:.6f}, phi={phi:.6f}, visibility={visibility:.3f}, {p0}, {1-p1}"
43
+ # )
44
+
45
+ if axes is not None:
46
+ ax1, ax2 = axes
47
+ else:
48
+ if fig is None:
49
+ fig = plt.figure()
50
+ ax1 = fig.add_subplot(121)
51
+ ax2 = fig.add_subplot(122)
52
+
53
+ if (len(s0) + len(s1)) < hotThresh:
54
+ ax1.plot(np.real(s0), np.imag(s0), '.', alpha=0.2)
55
+ ax1.plot(np.real(s1), np.imag(s1), '.', alpha=0.2)
56
+ else:
57
+ _, *bins = np.histogram2d(np.real(np.hstack([s0, s1])),
58
+ np.imag(np.hstack([s0, s1])),
59
+ bins=50)
60
+
61
+ H0, *_ = np.histogram2d(np.real(s0),
62
+ np.imag(s0),
63
+ bins=bins,
64
+ density=True)
65
+ H1, *_ = np.histogram2d(np.real(s1),
66
+ np.imag(s1),
67
+ bins=bins,
68
+ density=True)
69
+ vlim = max(np.max(np.abs(H0)), np.max(np.abs(H1)))
70
+
71
+ ax1.imshow(H1.T - H0.T,
72
+ alpha=(np.fmax(H0.T, H1.T) / vlim).clip(0, 1),
73
+ interpolation='nearest',
74
+ origin='lower',
75
+ cmap='coolwarm',
76
+ vmin=-vlim,
77
+ vmax=vlim,
78
+ extent=(bins[0][0], bins[0][-1], bins[1][0], bins[1][-1]))
79
+
80
+ ax1.axis('equal')
81
+ ax1.set_xticks([])
82
+ ax1.set_yticks([])
83
+ for s in ax1.spines.values():
84
+ s.set_visible(False)
85
+
86
+ # c0, c1 = info['center']
87
+ # a0, b0, a1, b1 = info['std']
88
+ params = info['params']
89
+ r0, i0, r1, i1 = params[0][0], params[1][0], params[0][1], params[1][1]
90
+ a0, b0, a1, b1 = params[0][2], params[1][2], params[0][3], params[1][3]
91
+ c0 = (r0 + 1j * i0) * np.exp(1j * phi)
92
+ c1 = (r1 + 1j * i1) * np.exp(1j * phi)
93
+ phi0 = phi + params[0][6]
94
+ phi1 = phi + params[1][6]
95
+ plotEllipse(c0, 2 * a0, 2 * b0, phi0, ax1)
96
+ plotEllipse(c1, 2 * a1, 2 * b1, phi1, ax1)
97
+
98
+ im0, im1 = info['idle']
99
+ lim = min(im0.min(), im1.min()), max(im0.max(), im1.max())
100
+ t = (np.linspace(lim[0], lim[1], 3) + 1j * thr) * np.exp(-1j * phi)
101
+ ax1.plot(t.imag, t.real, 'k--')
102
+
103
+ ax1.plot(np.real(c0), np.imag(c0), 'o', color='C3')
104
+ ax1.plot(np.real(c1), np.imag(c1), 'o', color='C4')
105
+
106
+ re0, re1 = info['signal']
107
+ x, a, b, c = info['cdf']
108
+
109
+ xrange = (min(re0.min(), re1.min()), max(re0.max(), re1.max()))
110
+
111
+ n0, bins0, *_ = ax2.hist(re0, bins=80, range=xrange, alpha=0.5)
112
+ n1, bins1, *_ = ax2.hist(re1, bins=80, range=xrange, alpha=0.5)
113
+
114
+ x_range = np.linspace(x.min(), x.max(), 1001)
115
+ *_, cov0, cov1 = info['std']
116
+ ax2.plot(
117
+ x_range,
118
+ np.sum(n0) * (bins0[1] - bins0[0]) *
119
+ mult_gaussian_pdf(x_range, [r0, r1], [
120
+ np.sqrt(cov0[0, 0]), np.sqrt(cov1[0, 0])
121
+ ], [params[0][4], 1 - params[0][4]]))
122
+ ax2.plot(
123
+ x_range,
124
+ np.sum(n1) * (bins1[1] - bins1[0]) *
125
+ mult_gaussian_pdf(x_range, [r0, r1], [
126
+ np.sqrt(cov0[0, 0]), np.sqrt(cov1[0, 0])
127
+ ], [params[0][5], 1 - params[0][5]]))
128
+ ax2.set_ylabel('Count')
129
+ ax2.set_xlabel('Projection Axes')
130
+ if logy:
131
+ ax2.set_yscale('log')
132
+ ax2.set_ylim(0.1, max(np.sum(n0), np.sum(n1)))
133
+
134
+ ax3 = ax2.twinx()
135
+ ax3.plot(x, a, '--', lw=1, color='C0')
136
+ ax3.plot(x, b, '--', lw=1, color='C1')
137
+ ax3.plot(x, c, 'k--', alpha=0.5, lw=1)
138
+ ax3.set_ylim(0, 1.1)
139
+ ax3.vlines(thr, 0, 1.1, 'k', alpha=0.5)
140
+ ax3.set_ylabel('Integral Probability')
141
+
142
+ return info
143
+
144
+
145
+ ALLXYSeq = [('I', 'I'), ('X', 'X'), ('Y', 'Y'), ('X', 'Y'), ('Y', 'X'),
146
+ ('X/2', 'I'), ('Y/2', 'I'), ('X/2', 'Y/2'), ('Y/2', 'X/2'),
147
+ ('X/2', 'Y'), ('Y/2', 'X'), ('X', 'Y/2'), ('Y', 'X/2'),
148
+ ('X/2', 'X'), ('X', 'X/2'), ('Y/2', 'Y'), ('Y', 'Y/2'), ('X', 'I'),
149
+ ('Y', 'I'), ('X/2', 'X/2'), ('Y/2', 'Y/2')]
150
+
151
+
152
+ def plotALLXY(data, ax=None):
153
+ assert len(data) % len(ALLXYSeq) == 0
154
+
155
+ if ax is None:
156
+ ax = plt.gca()
157
+
158
+ ax.plot(np.array(data), 'o-')
159
+ repeat = len(data) // len(ALLXYSeq)
160
+ ax.set_xticks(np.arange(len(ALLXYSeq)) * repeat + 0.5 * (repeat - 1))
161
+ ax.set_xticklabels([','.join(seq) for seq in ALLXYSeq], rotation=60)
162
+ ax.grid(which='major')
163
+
164
+
165
+ def plot_mat(rho, title='$\\chi$', cmap='coolwarm'):
166
+ lim = np.abs(rho).max()
167
+ N = rho.shape[0]
168
+
169
+ fig = plt.figure(figsize=(6, 4))
170
+ fig.suptitle(title)
171
+
172
+ ax1 = plt.subplot(121)
173
+ cax1 = ax1.imshow(rho.real, vmin=-lim, vmax=lim, cmap=cmap)
174
+ ax1.set_title('Re')
175
+ ax1.set_xticks(np.arange(N))
176
+ ax1.set_yticks(np.arange(N))
177
+
178
+ ax2 = plt.subplot(122)
179
+ cax2 = ax2.imshow(rho.imag, vmin=-lim, vmax=lim, cmap=cmap)
180
+ ax2.set_title('Im')
181
+ ax2.set_xticks(np.arange(N))
182
+ ax2.set_yticks(np.arange(N))
183
+
184
+ plt.subplots_adjust(bottom=0.2, right=0.9, top=0.95)
185
+
186
+ cbar_ax = fig.add_axes([0.15, 0.15, 0.7, 0.05])
187
+ cb = fig.colorbar(cax1, cax=cbar_ax, orientation='horizontal')
188
+ plt.show()
@@ -0,0 +1,71 @@
1
+ import pathlib
2
+ import pickle
3
+
4
+ import click
5
+ import dill
6
+ import matplotlib.pyplot as plt
7
+ import numpy as np
8
+
9
+ from .qdat import draw as draw_qdat
10
+
11
+ default_draw_methods = {
12
+ '.qdat': draw_qdat,
13
+ }
14
+
15
+
16
+ def load_data(fname):
17
+ try:
18
+ from home.hkxu.tools import get_record_by_id
19
+ record_id = int(str(fname))
20
+ return get_record_by_id(record_id).data
21
+ except:
22
+ pass
23
+ with open(fname, 'rb') as f:
24
+ try:
25
+ data = pickle.load(f)
26
+ except:
27
+ f.seek(0)
28
+ data = dill.load(f)
29
+ return data
30
+
31
+
32
+ def draw_common(data):
33
+ try:
34
+ script = data['meta']['plot_script']
35
+ assert script.strip()
36
+ global_namespace = {'plt': plt, 'np': np, 'result': data}
37
+ exec(script, global_namespace)
38
+ except:
39
+ from home.hkxu.tools import plot_record
40
+ plot_record(data['meta']['id'])
41
+
42
+
43
+ def draw_error(data, text="No validate plot script found"):
44
+ fig = plt.figure()
45
+ ax = fig.add_subplot(111)
46
+ ax.text(0.5, 0.5, text, ha='center', va='center')
47
+ ax.set_axis_off()
48
+ return fig
49
+
50
+
51
+ @click.command()
52
+ @click.argument('fname', default='')
53
+ def plot(fname):
54
+ """Plot the data in the file."""
55
+ try:
56
+ fname = pathlib.Path(fname)
57
+ data = load_data(fname)
58
+ try:
59
+ draw_common(data)
60
+ except:
61
+ default_draw_methods.get(fname.suffix, draw_error)(data)
62
+ except FileNotFoundError:
63
+ draw_error(None, text=f"File {fname} not found.")
64
+ except pickle.UnpicklingError:
65
+ draw_error(None, text=f"File {fname} is not a pickle file.")
66
+
67
+ plt.show()
68
+
69
+
70
+ if __name__ == '__main__':
71
+ plot()