pytxs 0.0.1__py3-none-any.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.
@@ -0,0 +1,32 @@
1
+ License
2
+ ===============================================================================
3
+
4
+ txs
5
+ -------------------------------------------------------------------------------
6
+
7
+ txs is available under the `MIT License`_::
8
+
9
+ txs: time-resolved x-ray scattering/spectroscopy data analysis tools
10
+
11
+ Copyright (C) 2021-2022 ESRF - The European Synchrotron, Grenoble, France
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in
21
+ all copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
+ THE SOFTWARE.
30
+
31
+ .. _MIT License: http://opensource.org/licenses/MIT
32
+
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.1
2
+ Name: pytxs
3
+ Version: 0.0.1
4
+ Summary: time-resolved x-ray scattering
5
+ Author-email: Matteo Levantino <matteo.levantino@esrf.fr>
6
+ License: MIT
7
+ Project-URL: Homepage, https://gitlab.esrf.fr/levantin/txs
8
+ Classifier: Intended Audience :: Science/Research
9
+ Classifier: Topic :: Scientific/Engineering :: Physics
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/x-rst
14
+ License-File: LICENSE.rst
15
+ Requires-Dist: numpy >=1.10
16
+ Requires-Dist: h5py >3.0
17
+ Requires-Dist: tqdm
18
+ Requires-Dist: fabio
19
+ Requires-Dist: pyFAI
20
+ Requires-Dist: xraydb >=4.3.4
21
+ Requires-Dist: datastorage
22
+ Provides-Extra: gui
23
+ Requires-Dist: PyQt5 ; extra == 'gui'
24
+ Requires-Dist: matplotlib ; extra == 'gui'
25
+ Requires-Dist: silx ; extra == 'gui'
26
+
27
+ ====
28
+ txs
29
+ ====
30
+
31
+ **txs** is a free and open-source python package for the analysis
32
+ of time-resolved x-ray scattering/spectroscopy data.
33
+
34
+
35
+ GETTING txs
36
+ ------------
37
+
38
+ You can run txs on all major platforms. For download and installation:
39
+
40
+ .. code-block:: bash
41
+
42
+ pip install git+https://gitlab.esrf.fr/levantin/txs.git
43
+
44
+ For installing a specific version (e.g. version 0.1.0):
45
+
46
+ .. code-block:: bash
47
+
48
+ pip install git+https://gitlab.esrf.fr/levantin/txs.git@0.1.0
49
+
50
+ For upgrading txs to the last version/commit:
51
+
52
+ .. code-block:: bash
53
+
54
+ pip install --upgrade git+https://gitlab.esrf.fr/levantin/txs.git
55
+
56
+ For forcing the reinstallation of all packages (including dependencies):
57
+
58
+ .. code-block:: bash
59
+
60
+ pip install --force-reinstall git+https://gitlab.esrf.fr/levantin/txs.git
61
+
62
+
63
+ DOCUMENTATION
64
+ -------------
65
+
66
+ * Homepage: https://levantin.gitlab-pages.esrf.fr/txs
67
+ * Quick guide: https://confluence.esrf.fr/display/ID09KB/txs+Python+package
68
+
69
+
70
+ SOURCE CODE
71
+ -----------
72
+
73
+ * Source code: https://gitlab.esrf.fr/levantin/txs
74
+
75
+
76
+ LICENSE
77
+ -------
78
+
79
+ txs is available under MIT License. See LICENSE.rst for more details.
80
+
@@ -0,0 +1,42 @@
1
+ txs/__init__.py,sha256=aRMc_eJxijiNFiy0u7VplghfAUncgUsMVPTXDhAryCk,1457
2
+ txs/_version.py,sha256=13MjurOXftVhyioV1vByi3NsQEgvDtYwQRlv4qTa3nY,497
3
+ txs/azav.py,sha256=8z_yCgwQdFaok_wcVokVPN468ArLS8sSanvcMpDT7-I,37884
4
+ txs/common.py,sha256=59Zlxnzpk8CoqtwTQZY1ImTN8DIpGQD5I8PyzS6hwwc,1153
5
+ txs/corr.py,sha256=z488Ju8eew8vkVczv9zGzmy3t1sUttmHkHbMJZ_-pMw,3723
6
+ txs/datared.py,sha256=FHmuAAp8SpXU5Dlhy9jFc2UrtbMraWN1WGNfWRemHtE,29478
7
+ txs/datasets.py,sha256=UhIifqBfiXIVM4T6Tjl8mW1rBdN7NAcc8TQvzrh3KMA,36195
8
+ txs/heating.py,sha256=Km49Bb8zamY6W-UbO7l0S_d35UMbB1soPwq6sIMSF3g,8697
9
+ txs/live.py,sha256=NbVKXXY9rF8B4eAVo70SceLa5IjVTFtobILAbwNm_bM,14246
10
+ txs/plot.py,sha256=uqLnYUJZAkf0jvQJFDl0GzgggzbWVL9HZkb7AhQs2sk,38276
11
+ txs/utils.py,sha256=wAL49Gn4kD78Kj1LxZlCGurD0TyKQaCNPr--edk66PA,24921
12
+ txs/xes.py,sha256=xmRmPNC3KpOojoKfSvHJctIEv5GHxdd3zKwD0enZ5NA,435
13
+ txs/analysis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ txs/analysis/plot.py,sha256=3TAjUDg1H2ElTgkB4Efcq1acjnHtkbhZwe5unLP0-y0,9185
15
+ txs/analysis/svd.py,sha256=zqNjfnwWoQ2ZDco6v1lSFEiOP9UHD9IM8k3Stf3UcAI,11185
16
+ txs/analysis/utils.py,sha256=sGv8V1Nr4phjWMAg3gKiBK20oyVoLckJWIIpMlO9uq4,16120
17
+ txs/app/MainWindow.py,sha256=CNJRBVDhLk5xsmEsmFjKi00e0oY2mH10PCsqGve2u-Q,17566
18
+ txs/app/PlotBase.py,sha256=KGEdZwUekcOAsduNUnouDI3Fe5pHKEOqEMGH5dF_OME,2975
19
+ txs/app/PlotHeatmap.py,sha256=ynPgfpjcxXCzmaH_p1QGtWffe4OuaZGTC6j0YkIfsLo,4955
20
+ txs/app/ProcessWorker.py,sha256=TUvqVjCPoZEyENdflriSvmiZr5oL1els6im8aRxZn3s,4338
21
+ txs/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ txs/app/__main__.py,sha256=59vhADpUuAWIHBWcZQyBnAIyl1P-h5MhxlsQ361H5WE,2797
23
+ txs/app/actions.py,sha256=1kpE10zlGN4l5qXkwi0KkkJYSTHYTVXw8aNvtLyjzcI,2484
24
+ txs/app/mainwindow.ui,sha256=TmFp5rFMjG_X1Qe2SzUfOGMFjVtcS_aFSqxk4sTzhLQ,77252
25
+ txs/app/plot.py,sha256=waxkfpAujFrsO0lr9oH2ytm5u28l_C9gz5V1ELnCKzo,15242
26
+ txs/app/utils.py,sha256=unsaBA1kokkHQqK3Pp12tFuAvQ8kAuZfR_cvm5ivaSc,1334
27
+ txs/app/widgets.py,sha256=mQ9J9gtfu5sUfZo6clw3e6WxoqsubmXZ_tsCavgK4CM,1972
28
+ txs/app/tests/test_ProcessWorker.py,sha256=BGMUQm5XX4xNpxvS56yEMjpo986yx7Yqyorbg_3FyGQ,1350
29
+ txs/detectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
+ txs/detectors/jungfrau.py,sha256=IqCK_-mFc-ft7Csv9K96OYI1Cf97nnRVAwEnVoISkAA,21651
31
+ txs/tests/conftest.py,sha256=piMAb-m9pMnCEYF8nDDBBQRqHt5K76HnXIa4S3yRT-U,699
32
+ txs/tests/test_azav.py,sha256=3WyTIXzHONZe2mpDkUAK4paVhrYbKOWTIbNqDqEDDRE,2709
33
+ txs/tests/test_corr.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
+ txs/tests/test_datared.py,sha256=pP7Ktqeb8KvEECmPJTdKctMJ2aYny_0jEPuU_YrCvJY,675
35
+ txs/tests/test_datasets.py,sha256=2pIzrVttaG05CvFO3eYVN5nxLtQ1g8KWrhgcXDL1VM8,1500
36
+ txs/tests/test_utils.py,sha256=GNz3exN8HNwgYiI8SC2aCkb3fly6QSu3gJ6K2Jm21Vk,266
37
+ pytxs-0.0.1.dist-info/LICENSE.rst,sha256=TjMCSzUOzPc2_eHVGsz5iLgLwdVF0HIbIxR8NIEAmmM,1475
38
+ pytxs-0.0.1.dist-info/METADATA,sha256=5pbh1wYVhNM_pKFAuyA1l5qBSkhMptKDGfMGDV9kwE4,1927
39
+ pytxs-0.0.1.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
40
+ pytxs-0.0.1.dist-info/entry_points.txt,sha256=ZwQuA3S9N1-Tt_gxbYQrQmYSjKTlyw3SDdOrDIIPyzM,76
41
+ pytxs-0.0.1.dist-info/top_level.txt,sha256=Wg1zaJITs9A4ZjNCgE5PhTSrNAtfjIcL3uZwCvgWWKA,4
42
+ pytxs-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [gui_scripts]
2
+ txs = txs.app.__main__:main
3
+ txs2 = txs.app.__main__:main_txs2
@@ -0,0 +1 @@
1
+ txs
txs/__init__.py ADDED
@@ -0,0 +1,53 @@
1
+ # -*- coding: utf-8 -*-
2
+ """..."""
3
+
4
+ __author__ = "Matteo Levantino"
5
+ __contact__ = "matteo.levantino@esrf.fr"
6
+ __licence__ = "MIT"
7
+ __copyright__ = "ESRF - The European Synchrotron, Grenoble, France"
8
+ __date__ = "01/09/2021"
9
+
10
+ import xraydb as _xraydb
11
+
12
+
13
+ def _init_xraydb():
14
+ """Add extra materials to xraydb user config file"""
15
+ extra_materials = ( # name, formula, density, category
16
+ ("gallium nitride", "GaN", 6.15, "semiconductor"),
17
+ ("epoxy", "C21H25ClO5", 1.2, "polymer"),
18
+ ("zirconium", "Zr", 6.52, "metal"),
19
+ ("gadox", "Gd2O2S", 4.3, "scintllator"),
20
+ )
21
+
22
+ for name, formula, density, category in extra_materials:
23
+ material = _xraydb.find_material(name)
24
+ if (
25
+ material is None
26
+ or material.formula != formula
27
+ or material.density != density
28
+ or category not in material.categories
29
+ ):
30
+ _xraydb.add_material(name, formula, density, [category])
31
+
32
+
33
+ _init_xraydb()
34
+
35
+
36
+ from . import utils
37
+ from . import azav
38
+ from . import datared
39
+ from . import live
40
+ from . import corr
41
+ from . import plot
42
+ from . import heating
43
+ from .common import *
44
+ from .datasets import load_images, BlissDataset
45
+ from .azav import integrate1d_dataset as int1d_dset
46
+ from .datared import datared as dred
47
+ from .live import ana
48
+ from .corr import get_density, get_mu
49
+ from .plot import *
50
+ from .utils import get_ai
51
+
52
+ from . import _version
53
+ __version__ = _version.get_versions()['version']
txs/_version.py ADDED
@@ -0,0 +1,21 @@
1
+
2
+ # This file was generated by 'versioneer.py' (0.29) from
3
+ # revision-control system data, or from the parent directory name of an
4
+ # unpacked source archive. Distribution tarballs contain a pre-generated copy
5
+ # of this file.
6
+
7
+ import json
8
+
9
+ version_json = '''
10
+ {
11
+ "date": "2024-09-30T14:35:16+0200",
12
+ "dirty": false,
13
+ "error": null,
14
+ "full-revisionid": "5e98522f4c9f73d9c61d4e73ed903db1589ef9e7",
15
+ "version": "0.0.1"
16
+ }
17
+ ''' # END VERSION_JSON
18
+
19
+
20
+ def get_versions():
21
+ return json.loads(version_json)
File without changes
txs/analysis/plot.py ADDED
@@ -0,0 +1,299 @@
1
+ """Plotting functions to ease the analysis process.
2
+
3
+ .. warning::
4
+ These functions make use of ipywidgets and are meant to be used within a
5
+ Jupyter notebook with the 'magic' `%matplotlib widget`.
6
+
7
+ """
8
+
9
+ import numpy as np
10
+ import matplotlib.pyplot as plt
11
+ from matplotlib.cm import get_cmap
12
+ from ipywidgets import widgets
13
+
14
+ from txs.heating import remove_heating
15
+ from txs.utils import str2t
16
+ from txs.analysis.utils import sort_delays
17
+ from txs.analysis.svd import SVD
18
+
19
+
20
+ def plot_dataset(
21
+ data,
22
+ sample_names,
23
+ heating=None,
24
+ heating_qlim=(1.6, 2),
25
+ q_scaled=False
26
+ ):
27
+ """Plot the processed data for each time delays."""
28
+ fig, ax = plt.subplots(figsize=(10, 6))
29
+
30
+ incr = 0.001
31
+ ylabel = 'q' if q_scaled else ''
32
+
33
+ delays = {}
34
+ for s_idx, sample in enumerate(data):
35
+ for _, delay in enumerate(sample['t']):
36
+ if delay not in delays:
37
+ delays[delay] = []
38
+ delays[delay].append(s_idx)
39
+
40
+ delay_slider = widgets.IntSlider(
41
+ min=0, max=len(delays.keys()) - 1, value=0, step=1, description="delay"
42
+ )
43
+
44
+ cmap = get_cmap('tab10')
45
+
46
+ @widgets.interact(delay=delay_slider)
47
+ def update_plot(delay):
48
+ ax.clear()
49
+
50
+ t_key = list(delays.keys())[delay]
51
+
52
+ for idx, s_idx in enumerate(delays[t_key]):
53
+ q = data[s_idx]['q']
54
+ q_scale = q if q_scaled else np.ones_like(q)
55
+
56
+ t_idx = list(data[s_idx]['t']).index(t_key)
57
+ ax.errorbar(
58
+ q,
59
+ q_scale * data[s_idx]['diff_av'][:, t_idx] + idx * incr,
60
+ label=sample_names[s_idx],
61
+ color=cmap(idx),
62
+ alpha=0.4 if heating is not None else 1,
63
+ )
64
+
65
+ if heating is not None:
66
+ red2 = remove_heating(
67
+ data[s_idx], heating, qlim=heating_qlim, verbose=False
68
+ )
69
+ ax.errorbar(
70
+ q,
71
+ q_scale * red2['diff_av'][:, t_idx] + idx * incr,
72
+ color=cmap(idx),
73
+ )
74
+
75
+ ax.axhline(idx * incr, ls=":", alpha=0.2)
76
+
77
+ ax.set_xlabel("q [$\\rm \AA^{-1}$]")
78
+ ax.set_ylabel(f"{ylabel}S(q)")
79
+ ax.legend(loc=2, bbox_to_anchor=(1, 1))
80
+ ax.set_title(
81
+ f"$\\rm \Delta t$ = {t_key}\n" +
82
+ "semi-transparent -> " if heating is not None else "" +
83
+ "no heating removal" if heating is not None else ""
84
+ )
85
+
86
+ ax.relim()
87
+ ax.autoscale_view()
88
+ fig.tight_layout()
89
+
90
+ update_plot(0)
91
+
92
+
93
+ def plot_svd_analysis(
94
+ data,
95
+ heating=None,
96
+ heating_qlim=(1.8, 2),
97
+ q_scaled=False,
98
+ include_ref=False,
99
+ figsize=(10, 10),
100
+ plot_grid=None,
101
+ ref_delays=None,
102
+ ):
103
+ """Performs and plot a SVD analysis on the provided data."""
104
+ if heating is not None:
105
+ data = remove_heating(data, heating, qlim=heating_qlim, verbose=False)
106
+
107
+ q = data['q']
108
+ q_scale = q if q_scaled else np.ones_like(q)
109
+ ylabel = "q" if q_scaled else ""
110
+
111
+ data = sort_delays(data, ref_delays=ref_delays, include_ref=include_ref)
112
+
113
+ t = str2t(data['t'])
114
+
115
+ svd = SVD(data['diff_av']).run()
116
+
117
+ fig = plt.figure(figsize=figsize)
118
+ ax = []
119
+ if plot_grid is None:
120
+ gs = plt.GridSpec(3, 2, height_ratios=(1, 1, 1))
121
+ else:
122
+ gs = plot_grid
123
+ ax.append(fig.add_subplot(gs[0, :]))
124
+ ax.append(fig.add_subplot(gs[1, :], sharex=ax[0]))
125
+ ax.append(fig.add_subplot(gs[2, 0]))
126
+ ax.append(fig.add_subplot(gs[2, 1]))
127
+ cursor = Cursor(ax[:2])
128
+ cmap = get_cmap('gnuplot')
129
+
130
+ svd_rank = widgets.IntSlider(
131
+ min=0,
132
+ max=10,
133
+ step=1,
134
+ value=0,
135
+ description="svd rank",
136
+ )
137
+
138
+ incr_slider = widgets.FloatSlider(
139
+ min=0, max=0.01, value=0.0005, step=0.0002, description="increment"
140
+ )
141
+
142
+ leg_lines = {}
143
+ def on_pick(event):
144
+ legline = event.artist
145
+ for line in leg_lines[legline]:
146
+ visible = not line.get_visible()
147
+ line.set_visible(visible)
148
+ legline.set_alpha(1.0 if visible else 0.2)
149
+
150
+ @widgets.interact(rank=svd_rank, incr=incr_slider)
151
+ def update_plot(rank, incr, keep_lims=True):
152
+ ax0_lims = (ax[0].get_xlim(), ax[0].get_ylim())
153
+ ax1_lims = (ax[1].get_xlim(), ax[1].get_ylim())
154
+ ax[0].clear()
155
+
156
+ correlations = svd.autocorr()
157
+ if rank == 0:
158
+ rank = np.arange(correlations[0].size)[correlations[0] > 0.5]
159
+
160
+ lines = []
161
+ leg_lines.clear()
162
+ for idx, val in reversed(list(enumerate(data['diff_av'].T))):
163
+ line_group = []
164
+ line = ax[0].plot(
165
+ data['q'],
166
+ q_scale * val + incr * idx,
167
+ # savgol_filter(val, 15, 3) + incr * idx,
168
+ color=cmap(idx / data['diff_av'].shape[1]),
169
+ alpha=0.4
170
+ )
171
+ line_group.append(line[0])
172
+
173
+ line = ax[0].plot(
174
+ data['q'],
175
+ q_scale * svd.recompose(rank)[:, idx] + incr * idx,
176
+ # savgol_filter(svd.recompose(rank)[:, idx], 15, 3) + incr * idx,
177
+ color=cmap(idx / data['diff_av'].shape[1]),
178
+ label=data['t'][idx],
179
+ ls='--'
180
+ )
181
+ line_group.append(line[0])
182
+ line = ax[0].axhline(incr * idx, color='black', ls=':', alpha=0.2)
183
+ line_group.append(line)
184
+
185
+ lines.append(line_group)
186
+
187
+ ax[0].set_xlabel("q [$\\rm \AA^{-1}$]")
188
+ ax[0].set_ylabel(f'$\\rm {ylabel}\Delta S(q)$ ')
189
+ ax[0].grid(alpha=0.2)
190
+ if keep_lims:
191
+ ax[0].set_xlim(ax0_lims[0])
192
+ ax[0].set_ylim(ax0_lims[1])
193
+ leg = ax[0].legend(loc=2, bbox_to_anchor=(1, 1), fontsize=10)
194
+
195
+ for legline, origlines in zip(leg.get_lines(), lines):
196
+ legline.set_picker(True)
197
+ legline.set_pickradius(10)
198
+ leg_lines[legline] = origlines
199
+
200
+ patterns = svd.patterns(rank)
201
+ ax[1].clear()
202
+ for idx, val in enumerate(patterns[0].T):
203
+ ax[1].plot(
204
+ data['q'],
205
+ q_scale * val + incr * idx,
206
+ # savgol_filter(val, 15, 3) + incr * idx
207
+ )
208
+ ax[1].axhline(incr * idx, color='black', ls=':', alpha=0.2)
209
+ ax[1].set_xlabel("q [$\\rm \AA^{-1}$]")
210
+ ax[1].set_ylabel(f'{ylabel}U vectors\n[arb. units]')
211
+ ax[1].grid(alpha=0.2)
212
+ if keep_lims:
213
+ ax[1].set_xlim(ax1_lims[0])
214
+ ax[1].set_ylim(ax1_lims[1])
215
+ ax[1].autoscale_view()
216
+
217
+ ax[2].clear()
218
+
219
+ # patterns = patterns[1].T / np.max(np.abs(patterns[1])) + 0.5
220
+ patterns = patterns[1].T
221
+ ax[2].plot(
222
+ t,
223
+ patterns,
224
+ )
225
+ ax[2].set_xlabel('time')
226
+ ax[2].set_ylabel('V vectors\n[arb. units]')
227
+ # ax[2].set_xticklabels(delays, rotation=-45, ha='left')
228
+ ax[2].grid(alpha=0.2)
229
+ ax[2].axhline(0, color='black', ls=':')
230
+ ax[2].relim()
231
+ ax[2].autoscale_view()
232
+
233
+ ax[3].clear()
234
+ ax[3].plot(correlations[0], marker='o', label='U vectors')
235
+ ax[3].plot(correlations[1], marker='^', label='V vectors')
236
+ ax[3].axhline(0.5, color='black', ls=':')
237
+ ax[3].set_xlabel('rank')
238
+ ax[3].set_ylabel('autocorrelations\n[arb. units]')
239
+ ax[3].grid(alpha=0.2)
240
+ ax[3].relim()
241
+ ax[3].autoscale_view()
242
+ ax[3].legend(loc=2, bbox_to_anchor=(1, 1))
243
+
244
+ cursor.drawline()
245
+
246
+ update_plot(0, 0.0005, False)
247
+
248
+ fig.tight_layout()
249
+ fig.canvas.mpl_connect('motion_notify_event', cursor.on_mouse_move)
250
+ fig.canvas.mpl_connect('button_press_event', cursor.on_mouse_press)
251
+ fig.canvas.mpl_connect('pick_event', on_pick)
252
+
253
+ return data, svd
254
+
255
+
256
+ class Cursor:
257
+ """A vertical line cursor."""
258
+ def __init__(self, ax):
259
+ if not isinstance(ax, list):
260
+ ax = [ax]
261
+ self.ax = ax
262
+ self.vertical_line = []
263
+ self.persistent_lines = []
264
+
265
+ self.drawline()
266
+
267
+ def drawline(self):
268
+ self.vertical_line = [
269
+ val.axvline(color='k', lw=0.8, ls='--', alpha=0.6)
270
+ for val in self.ax
271
+ ]
272
+
273
+ def on_mouse_move(self, event):
274
+ if not event.inaxes:
275
+ for line in self.vertical_line:
276
+ line.set_visible(False)
277
+ pass
278
+ else:
279
+ for line in self.vertical_line:
280
+ line.set_visible(True)
281
+ x = event.xdata
282
+ # update the line positions
283
+ for idx, val in enumerate(self.ax):
284
+ self.vertical_line[idx].set_xdata([x])
285
+ # val.figure.canvas.draw()
286
+
287
+ def on_mouse_press(self, event):
288
+ if not event.inaxes:
289
+ pass
290
+ else:
291
+ x = event.xdata
292
+ side = event.button
293
+ if side == 1:
294
+ for val in self.ax:
295
+ line = val.axvline(x, color='k', lw=0.8, ls='--', alpha=0.6)
296
+ self.persistent_lines.append(line)
297
+ if side == 3:
298
+ for line in self.persistent_lines:
299
+ line.remove()