pymagnetos 0.1.0__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.
- pymagnetos/__init__.py +15 -0
- pymagnetos/cli.py +40 -0
- pymagnetos/core/__init__.py +19 -0
- pymagnetos/core/_config.py +340 -0
- pymagnetos/core/_data.py +132 -0
- pymagnetos/core/_processor.py +905 -0
- pymagnetos/core/config_models.py +57 -0
- pymagnetos/core/gui/__init__.py +6 -0
- pymagnetos/core/gui/_base_mainwindow.py +819 -0
- pymagnetos/core/gui/widgets/__init__.py +19 -0
- pymagnetos/core/gui/widgets/_batch_processing.py +319 -0
- pymagnetos/core/gui/widgets/_configuration.py +167 -0
- pymagnetos/core/gui/widgets/_files.py +129 -0
- pymagnetos/core/gui/widgets/_graphs.py +93 -0
- pymagnetos/core/gui/widgets/_param_content.py +20 -0
- pymagnetos/core/gui/widgets/_popup_progressbar.py +29 -0
- pymagnetos/core/gui/widgets/_text_logger.py +32 -0
- pymagnetos/core/signal_processing.py +1004 -0
- pymagnetos/core/utils.py +85 -0
- pymagnetos/log.py +126 -0
- pymagnetos/py.typed +0 -0
- pymagnetos/pytdo/__init__.py +6 -0
- pymagnetos/pytdo/_config.py +24 -0
- pymagnetos/pytdo/_config_models.py +59 -0
- pymagnetos/pytdo/_tdoprocessor.py +1052 -0
- pymagnetos/pytdo/assets/config_default.toml +84 -0
- pymagnetos/pytdo/gui/__init__.py +26 -0
- pymagnetos/pytdo/gui/_worker.py +106 -0
- pymagnetos/pytdo/gui/main.py +617 -0
- pymagnetos/pytdo/gui/widgets/__init__.py +8 -0
- pymagnetos/pytdo/gui/widgets/_buttons.py +66 -0
- pymagnetos/pytdo/gui/widgets/_configuration.py +78 -0
- pymagnetos/pytdo/gui/widgets/_graphs.py +280 -0
- pymagnetos/pytdo/gui/widgets/_param_content.py +137 -0
- pymagnetos/pyuson/__init__.py +7 -0
- pymagnetos/pyuson/_config.py +26 -0
- pymagnetos/pyuson/_config_models.py +71 -0
- pymagnetos/pyuson/_echoprocessor.py +1901 -0
- pymagnetos/pyuson/assets/config_default.toml +92 -0
- pymagnetos/pyuson/gui/__init__.py +26 -0
- pymagnetos/pyuson/gui/_worker.py +135 -0
- pymagnetos/pyuson/gui/main.py +767 -0
- pymagnetos/pyuson/gui/widgets/__init__.py +7 -0
- pymagnetos/pyuson/gui/widgets/_buttons.py +95 -0
- pymagnetos/pyuson/gui/widgets/_configuration.py +85 -0
- pymagnetos/pyuson/gui/widgets/_graphs.py +248 -0
- pymagnetos/pyuson/gui/widgets/_param_content.py +193 -0
- pymagnetos-0.1.0.dist-info/METADATA +23 -0
- pymagnetos-0.1.0.dist-info/RECORD +51 -0
- pymagnetos-0.1.0.dist-info/WHEEL +4 -0
- pymagnetos-0.1.0.dist-info/entry_points.txt +7 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""The Configuration section of the GUI."""
|
|
2
|
+
|
|
3
|
+
from PyQt6.QtCore import pyqtSignal
|
|
4
|
+
from pyqtgraph.parametertree import Parameter
|
|
5
|
+
|
|
6
|
+
from pymagnetos.core.gui.widgets import BaseConfigurationWidget
|
|
7
|
+
|
|
8
|
+
from ._param_content import ParamContent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ConfigurationWidget(BaseConfigurationWidget):
|
|
12
|
+
"""
|
|
13
|
+
Configuration Parameter Tree for TDO experiments.
|
|
14
|
+
|
|
15
|
+
Signals
|
|
16
|
+
-------
|
|
17
|
+
sig_syncroi_changed : emitted when the "Sync ROI" checkbox is changed.
|
|
18
|
+
sig_timeoffset_changed : emitted when the "Time offset" parameter is changed.
|
|
19
|
+
sig_spectro_nperseg_changed : emittend when the "n/segment" parameter is changed.
|
|
20
|
+
sig_fitdeg_changed : emitted when the fit degree parameter is changed.
|
|
21
|
+
sig_npoints_interp_changed : emitted when the npoints for interlation is changed.
|
|
22
|
+
sig_curveoffset_changed : emitted when the curve offset is changed.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
sig_syncroi_changed = pyqtSignal()
|
|
26
|
+
sig_timeoffset_changed = pyqtSignal()
|
|
27
|
+
sig_spectro_nperseg_changed = pyqtSignal()
|
|
28
|
+
sig_fitdeg_changed = pyqtSignal()
|
|
29
|
+
sig_npoints_interp_changed = pyqtSignal()
|
|
30
|
+
sig_curveoffset_changed = pyqtSignal()
|
|
31
|
+
|
|
32
|
+
def __init__(self, param_content: type[ParamContent]):
|
|
33
|
+
super().__init__(param_content)
|
|
34
|
+
|
|
35
|
+
self.add_extra_parameters()
|
|
36
|
+
self.connect_extras()
|
|
37
|
+
|
|
38
|
+
def add_extra_parameters(self):
|
|
39
|
+
"""Add extra parameters used only in the GUI."""
|
|
40
|
+
# Sync ROIs parameter
|
|
41
|
+
self.syncroi_parameter = Parameter.create(
|
|
42
|
+
name="syncroi",
|
|
43
|
+
type="bool",
|
|
44
|
+
value=False,
|
|
45
|
+
title="Sync. fit and FFT field-window",
|
|
46
|
+
)
|
|
47
|
+
self.host_parameters.addChild(self.syncroi_parameter)
|
|
48
|
+
|
|
49
|
+
# Show nperseg parameter in µs
|
|
50
|
+
nperseg = Parameter.create(
|
|
51
|
+
name="spectro_time_window",
|
|
52
|
+
type="float",
|
|
53
|
+
readonly=True,
|
|
54
|
+
value=1024 / 100e6,
|
|
55
|
+
suffix="s",
|
|
56
|
+
siPrefix=True,
|
|
57
|
+
title="Spectro: time window (n/persegment)",
|
|
58
|
+
)
|
|
59
|
+
self.host_parameters.addChild(nperseg)
|
|
60
|
+
|
|
61
|
+
def connect_extras(self):
|
|
62
|
+
"""Extra connections."""
|
|
63
|
+
self.syncroi_parameter.sigValueChanged.connect(self.sig_syncroi_changed.emit)
|
|
64
|
+
self.settings_parameters.child("time_offset").sigValueChanged.connect(
|
|
65
|
+
self.sig_timeoffset_changed.emit
|
|
66
|
+
)
|
|
67
|
+
self.settings_parameters.child("spectro_nperseg").sigValueChanged.connect(
|
|
68
|
+
self.sig_spectro_nperseg_changed.emit
|
|
69
|
+
)
|
|
70
|
+
self.settings_parameters.child("poly_deg").sigValueChanged.connect(
|
|
71
|
+
self.sig_fitdeg_changed.emit
|
|
72
|
+
)
|
|
73
|
+
self.settings_parameters.child(
|
|
74
|
+
"npoints_interp_inverse"
|
|
75
|
+
).sigValueChanged.connect(self.sig_npoints_interp_changed.emit)
|
|
76
|
+
self.settings_parameters.child("offset").sigValueChanged.connect(
|
|
77
|
+
self.sig_curveoffset_changed.emit
|
|
78
|
+
)
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""The graphs area that holds all the plots."""
|
|
2
|
+
|
|
3
|
+
import pyqtgraph as pg
|
|
4
|
+
from PyQt6 import QtCore, QtWidgets
|
|
5
|
+
from PyQt6.QtCore import pyqtSignal, pyqtSlot
|
|
6
|
+
|
|
7
|
+
from pymagnetos.core.gui.widgets import BaseGraphsWidget
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GraphsWidget(BaseGraphsWidget):
|
|
11
|
+
"""
|
|
12
|
+
The graphs area with all the plots.
|
|
13
|
+
|
|
14
|
+
There are two tabs, one with :
|
|
15
|
+
- dB/dt
|
|
16
|
+
- B(t)
|
|
17
|
+
- TDO(t)
|
|
18
|
+
- TDO(B)
|
|
19
|
+
The other with :
|
|
20
|
+
- B(t)
|
|
21
|
+
- TDO(B)
|
|
22
|
+
- TDO_detrended(B)
|
|
23
|
+
- TDO_detrended(1/B)
|
|
24
|
+
- FFT
|
|
25
|
+
|
|
26
|
+
Since B(t) and TDO(B) are re-used, placeholders plots are created. They are replaced
|
|
27
|
+
when the tab is selected.
|
|
28
|
+
|
|
29
|
+
Signals
|
|
30
|
+
-------
|
|
31
|
+
sig_roi1_changed : emits when the draggable field-window moved in tdo(B).
|
|
32
|
+
sig_roi2_changed : emits when the draggable field-window moved in tdo_detrend(B).
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
sig_roi1_changed = pyqtSignal()
|
|
36
|
+
sig_roi2_changed = pyqtSignal()
|
|
37
|
+
|
|
38
|
+
def __init__(self):
|
|
39
|
+
super().__init__()
|
|
40
|
+
# Flags
|
|
41
|
+
self._sync_roi = False
|
|
42
|
+
|
|
43
|
+
# Create layouts
|
|
44
|
+
widget1 = self.create_first_tab()
|
|
45
|
+
widget2 = self.create_second_tab()
|
|
46
|
+
|
|
47
|
+
# Create the tabs
|
|
48
|
+
tab = QtWidgets.QTabWidget(self)
|
|
49
|
+
tab.addTab(widget1, "TDO")
|
|
50
|
+
tab.addTab(widget2, "Oscillations")
|
|
51
|
+
tab.currentChanged.connect(self.move_plots) # replace placeholders
|
|
52
|
+
|
|
53
|
+
layout = QtWidgets.QVBoxLayout(self)
|
|
54
|
+
layout.addWidget(tab)
|
|
55
|
+
self.setLayout(layout)
|
|
56
|
+
|
|
57
|
+
# Apply axes style
|
|
58
|
+
self.customize_axis()
|
|
59
|
+
self.init_coordinates_on_hover()
|
|
60
|
+
|
|
61
|
+
def create_first_tab(self) -> QtWidgets.QSplitter:
|
|
62
|
+
"""Create the tab with the magnetic field and TDO signal."""
|
|
63
|
+
# Create the plots
|
|
64
|
+
self.create_field_plot()
|
|
65
|
+
self.create_signal_plot()
|
|
66
|
+
|
|
67
|
+
# Create splitter for the top row
|
|
68
|
+
self.tab1_top_splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal)
|
|
69
|
+
self.tab1_top_splitter.addWidget(self.dfield)
|
|
70
|
+
self.tab1_top_splitter.addWidget(self.field)
|
|
71
|
+
self.tab1_top_splitter.addWidget(self.sig_time)
|
|
72
|
+
|
|
73
|
+
# Create splitter for the bottom row
|
|
74
|
+
self.tab1_main_splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Vertical)
|
|
75
|
+
self.tab1_main_splitter.addWidget(self.tab1_top_splitter)
|
|
76
|
+
self.tab1_main_splitter.addWidget(self.sig_field)
|
|
77
|
+
|
|
78
|
+
# Adjust ratios
|
|
79
|
+
self.tab1_main_splitter.setStretchFactor(0, 1)
|
|
80
|
+
self.tab1_main_splitter.setStretchFactor(1, 2)
|
|
81
|
+
|
|
82
|
+
return self.tab1_main_splitter
|
|
83
|
+
|
|
84
|
+
def create_second_tab(self) -> QtWidgets.QSplitter:
|
|
85
|
+
"""Create the tab with the detrended TDO signals and the FFT."""
|
|
86
|
+
self.create_tdo_plot()
|
|
87
|
+
self.create_fft_plot()
|
|
88
|
+
|
|
89
|
+
# Create splitter for the top row
|
|
90
|
+
self.tab2_top_splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal)
|
|
91
|
+
# Add the placeholders plots, they will be replaced when the tab is shown
|
|
92
|
+
self.tab2_top_splitter.addWidget(self._v2field)
|
|
93
|
+
self.tab2_top_splitter.addWidget(self._v2sig_field)
|
|
94
|
+
|
|
95
|
+
# Create splitter for the bottom row
|
|
96
|
+
bottom_splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal)
|
|
97
|
+
bottom_splitter.addWidget(self.tdo_field)
|
|
98
|
+
bottom_splitter.addWidget(self.tdo_inverse_field)
|
|
99
|
+
bottom_splitter.addWidget(self.fft)
|
|
100
|
+
|
|
101
|
+
# Create main splitter
|
|
102
|
+
self.tab2_main_splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Vertical)
|
|
103
|
+
self.tab2_main_splitter.addWidget(self.tab2_top_splitter)
|
|
104
|
+
self.tab2_main_splitter.addWidget(bottom_splitter)
|
|
105
|
+
|
|
106
|
+
return self.tab2_main_splitter
|
|
107
|
+
|
|
108
|
+
def create_field_plot(self) -> None:
|
|
109
|
+
# magnetic field (integrated)
|
|
110
|
+
self.field = pg.PlotWidget(title="Magnetic field")
|
|
111
|
+
self.field.setLabel("bottom", "time (s)")
|
|
112
|
+
self.field.setLabel("left", "field (T)")
|
|
113
|
+
self.field.getAxis("left").enableAutoSIPrefix(False)
|
|
114
|
+
self._plots_list.add(self.field)
|
|
115
|
+
# magnetic field (placeholders)
|
|
116
|
+
self._v1field = pg.PlotWidget(title="Magnetic field")
|
|
117
|
+
self._v1field.setLabel("bottom", "time (s)")
|
|
118
|
+
self._v1field.setLabel("left", "field (T)")
|
|
119
|
+
self._v2field = pg.PlotWidget(title="Magnetic field")
|
|
120
|
+
self._v2field.setLabel("bottom", "time (s)")
|
|
121
|
+
self._v2field.setLabel("left", "field (T)")
|
|
122
|
+
|
|
123
|
+
# pickup coil voltage (measured)
|
|
124
|
+
self.dfield = pg.PlotWidget(title="Pickup")
|
|
125
|
+
self.dfield.setLabel("bottom", "time (s)")
|
|
126
|
+
self.dfield.setLabel("left", "pickup (V)")
|
|
127
|
+
self._plots_list.add(self.dfield)
|
|
128
|
+
|
|
129
|
+
def create_signal_plot(self):
|
|
130
|
+
# TDO signal versus field
|
|
131
|
+
self.sig_field = pg.PlotWidget(title="Signal versus field")
|
|
132
|
+
self.sig_field.setLabel("bottom", "field (T)")
|
|
133
|
+
self.sig_field.setLabel("left", "TDO frequency (Hz)")
|
|
134
|
+
# Field-window selector
|
|
135
|
+
self.roi = pg.LinearRegionItem(
|
|
136
|
+
brush=pg.mkBrush("#0000ff1a"), pen=self.pen_fitbounds
|
|
137
|
+
)
|
|
138
|
+
# Disable mouse drag
|
|
139
|
+
self.roi.hoverEvent = lambda *args, **kwargs: None # ty:ignore[invalid-assignment]
|
|
140
|
+
self.roi.mouseDragEvent = lambda *args, **kwargs: None # ty:ignore[invalid-assignment]
|
|
141
|
+
self.sig_field.addItem(self.roi, ignoreBounds=True) # ty:ignore[unknown-argument]
|
|
142
|
+
|
|
143
|
+
self.roi.sigRegionChangeFinished.connect(self.roi1_changed)
|
|
144
|
+
self.sig_field.getPlotItem().addLegend()
|
|
145
|
+
self._plots_list.add(self.sig_field)
|
|
146
|
+
|
|
147
|
+
# TDO signal versus field (placeholders)
|
|
148
|
+
self._v1sig_field = pg.PlotWidget(title="Signal versus field")
|
|
149
|
+
self._v1sig_field.setLabel("bottom", "field (T)")
|
|
150
|
+
self._v1sig_field.setLabel("left", "TDO frequency (Hz)")
|
|
151
|
+
self._v2sig_field = pg.PlotWidget(title="Signal versus field")
|
|
152
|
+
self._v2sig_field.setLabel("bottom", "field (T)")
|
|
153
|
+
self._v2sig_field.setLabel("left", "TDO frequency (Hz)")
|
|
154
|
+
|
|
155
|
+
# TDO signal versus time
|
|
156
|
+
self.sig_time = pg.PlotWidget(title="Signal versus time")
|
|
157
|
+
self.sig_time.setLabel("bottom", "time (s)")
|
|
158
|
+
self.sig_time.setLabel("left", "TDO frequency (Hz)")
|
|
159
|
+
self._plots_list.add(self.sig_time)
|
|
160
|
+
|
|
161
|
+
def create_tdo_plot(self):
|
|
162
|
+
# vs field
|
|
163
|
+
self.tdo_field = pg.PlotWidget(title="Oscillatory part versus field")
|
|
164
|
+
self.tdo_field.setLabel("bottom", "field (T)")
|
|
165
|
+
self.tdo_field.setLabel("left", "TDO detrended")
|
|
166
|
+
self.tdo_field.getPlotItem().addLegend()
|
|
167
|
+
# Field-window selector for FFT range
|
|
168
|
+
self.roi2 = pg.LinearRegionItem(
|
|
169
|
+
brush=pg.mkBrush("#0000ff1a"), pen=self.pen_fftbounds
|
|
170
|
+
)
|
|
171
|
+
# Disable mouse drag
|
|
172
|
+
self.roi2.hoverEvent = lambda *args, **kwargs: None # ty:ignore[invalid-assignment]
|
|
173
|
+
self.roi2.mouseDragEvent = lambda *args, **kwargs: None # ty:ignore[invalid-assignment]
|
|
174
|
+
|
|
175
|
+
self.tdo_field.addItem(self.roi2, ignoreBounds=True) # ty:ignore[unknown-argument]
|
|
176
|
+
self.roi2.sigRegionChangeFinished.connect(self.roi2_changed)
|
|
177
|
+
|
|
178
|
+
# Vertical lines to show polynomial fit range
|
|
179
|
+
self.fit_bounds1 = pg.InfiniteLine(pen=self.pen_fitbounds, movable=False)
|
|
180
|
+
self.fit_bounds2 = pg.InfiniteLine(pen=self.pen_fitbounds, movable=False)
|
|
181
|
+
self.tdo_field.addItem(self.fit_bounds1)
|
|
182
|
+
self.tdo_field.addItem(self.fit_bounds2)
|
|
183
|
+
self._plots_list.add(self.tdo_field)
|
|
184
|
+
|
|
185
|
+
# vs 1/B
|
|
186
|
+
self.tdo_inverse_field = pg.PlotWidget(title="Oscillatory part versus 1/B")
|
|
187
|
+
self.tdo_inverse_field.setLabel("bottom", "1/B (T^-1)")
|
|
188
|
+
self.tdo_inverse_field.setLabel("left", "TDO detrended")
|
|
189
|
+
# Vertical lines to show FFT range
|
|
190
|
+
self.fft_bounds1 = pg.InfiniteLine(pen=self.pen_fftbounds, movable=False)
|
|
191
|
+
self.fft_bounds2 = pg.InfiniteLine(pen=self.pen_fftbounds, movable=False)
|
|
192
|
+
self.tdo_inverse_field.addItem(self.fft_bounds1)
|
|
193
|
+
self.tdo_inverse_field.addItem(self.fft_bounds2)
|
|
194
|
+
self._plots_list.add(self.tdo_inverse_field)
|
|
195
|
+
|
|
196
|
+
def create_fft_plot(self):
|
|
197
|
+
# vs field
|
|
198
|
+
self.fft = pg.PlotWidget(title="Fourier transform")
|
|
199
|
+
self.fft.setLabel("bottom", "B-frequency (T)")
|
|
200
|
+
self.fft.setLabel("left", "magnitude")
|
|
201
|
+
self.fft.getPlotItem().addLegend()
|
|
202
|
+
self._plots_list.add(self.fft)
|
|
203
|
+
|
|
204
|
+
def customize_axis(self):
|
|
205
|
+
"""Set a right axis and show the grid, for all the plots."""
|
|
206
|
+
for plot in self._plots_list:
|
|
207
|
+
plot.showAxis("right")
|
|
208
|
+
plot.getAxis("right").setTicks([])
|
|
209
|
+
plot.showGrid(y=True)
|
|
210
|
+
|
|
211
|
+
def init_plot_style(self):
|
|
212
|
+
"""Set up PyQtGraph line styles."""
|
|
213
|
+
w0 = 1
|
|
214
|
+
w1 = 2
|
|
215
|
+
self.pen_field = pg.mkPen("#c7c7c7", width=w0)
|
|
216
|
+
self.pen_bup = pg.mkPen("#2ca02cff", width=w0)
|
|
217
|
+
self.pen_bdown = pg.mkPen("#d62728ff", width=w0)
|
|
218
|
+
self.pen_tdo = pg.mkPen("#fffc5c80", width=w0)
|
|
219
|
+
self.pen_fitbup = pg.mkPen("#2ca02cb1", width=w0)
|
|
220
|
+
self.pen_fitdown = pg.mkPen("#d62728b1", width=w0)
|
|
221
|
+
self.pen_fitbounds = pg.mkPen("#7477ed99", width=w1)
|
|
222
|
+
self.pen_fftbounds = pg.mkPen("#eddd86ff", width=w1)
|
|
223
|
+
|
|
224
|
+
@pyqtSlot()
|
|
225
|
+
def roi1_changed(self):
|
|
226
|
+
"""Update ROI in the TDO detrended panel when the ROI in the TDO panel moved."""
|
|
227
|
+
roi1 = self.roi.getRegion() # TDO panel
|
|
228
|
+
# Show the select fit range in the TDO detrended panel
|
|
229
|
+
self.fit_bounds1.setPos(roi1[0])
|
|
230
|
+
self.fit_bounds2.setPos(roi1[1])
|
|
231
|
+
self.sig_roi1_changed.emit()
|
|
232
|
+
if not self._sync_roi:
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
roi2 = self.roi2.getRegion() # TDO detrend panel
|
|
236
|
+
|
|
237
|
+
if roi1 != roi2:
|
|
238
|
+
self.roi2.setRegion(self.roi.getRegion())
|
|
239
|
+
|
|
240
|
+
@pyqtSlot()
|
|
241
|
+
def roi2_changed(self):
|
|
242
|
+
"""Update ROI in the TDO panel when the ROI in the TDO detrended panel moved."""
|
|
243
|
+
roi2 = self.roi2.getRegion() # TDO detrend panel
|
|
244
|
+
# Show the FFT range in the TDO 1/B panel
|
|
245
|
+
self.fft_bounds1.setPos(1 / roi2[0])
|
|
246
|
+
self.fft_bounds2.setPos(1 / roi2[1])
|
|
247
|
+
self.sig_roi2_changed.emit()
|
|
248
|
+
if not self._sync_roi:
|
|
249
|
+
return
|
|
250
|
+
|
|
251
|
+
roi1 = self.roi.getRegion() # TDO panel
|
|
252
|
+
|
|
253
|
+
if roi1 != roi2:
|
|
254
|
+
self.roi.setRegion(self.roi2.getRegion())
|
|
255
|
+
|
|
256
|
+
def enable_rois(self):
|
|
257
|
+
"""Enable moving ROIs."""
|
|
258
|
+
self.roi.setMovable(True)
|
|
259
|
+
self.roi2.setMovable(True)
|
|
260
|
+
|
|
261
|
+
def disable_rois(self):
|
|
262
|
+
"""Disable moving ROIs."""
|
|
263
|
+
self.roi.setMovable(False)
|
|
264
|
+
self.roi2.setMovable(False)
|
|
265
|
+
|
|
266
|
+
@pyqtSlot(int)
|
|
267
|
+
def move_plots(self, index: int):
|
|
268
|
+
"""Move plots from one tab to another, because a widget can't be copied."""
|
|
269
|
+
if index == 0:
|
|
270
|
+
# First tab
|
|
271
|
+
self.tab2_top_splitter.replaceWidget(0, self._v2field)
|
|
272
|
+
self.tab2_top_splitter.replaceWidget(1, self._v2sig_field)
|
|
273
|
+
self.tab1_top_splitter.replaceWidget(1, self.field)
|
|
274
|
+
self.tab1_main_splitter.replaceWidget(1, self.sig_field)
|
|
275
|
+
elif index == 1:
|
|
276
|
+
# Second tab
|
|
277
|
+
self.tab1_top_splitter.replaceWidget(1, self._v1field)
|
|
278
|
+
self.tab1_main_splitter.replaceWidget(1, self._v1sig_field)
|
|
279
|
+
self.tab2_top_splitter.replaceWidget(0, self.field)
|
|
280
|
+
self.tab2_top_splitter.replaceWidget(1, self.sig_field)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Parameters for the `pytdo` configuration."""
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from pymagnetos.core.gui.widgets import BaseParamContent
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ParamContent(BaseParamContent):
|
|
9
|
+
PARAMS_TO_PARSE = ["poly_window", "fft_window"]
|
|
10
|
+
children_parameters = [
|
|
11
|
+
dict(
|
|
12
|
+
name="pickup_surface",
|
|
13
|
+
type="float",
|
|
14
|
+
limits=[0, np.inf],
|
|
15
|
+
value=None,
|
|
16
|
+
suffix="m²",
|
|
17
|
+
siPrefix=False,
|
|
18
|
+
readonly=True,
|
|
19
|
+
title="Pickup surface",
|
|
20
|
+
tip="Surface of the pickup coil in m²",
|
|
21
|
+
)
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
children_settings = [
|
|
25
|
+
dict(
|
|
26
|
+
name="max_time",
|
|
27
|
+
type="float",
|
|
28
|
+
limits=[0, np.inf],
|
|
29
|
+
step=0.1,
|
|
30
|
+
value=1,
|
|
31
|
+
suffix="s",
|
|
32
|
+
siPrefix=True,
|
|
33
|
+
title="Max. time",
|
|
34
|
+
tip="Stop analysis after this duration (in seconds)",
|
|
35
|
+
),
|
|
36
|
+
dict(
|
|
37
|
+
name="spectro_nperseg",
|
|
38
|
+
type="int",
|
|
39
|
+
limits=[0, np.inf],
|
|
40
|
+
value=1024,
|
|
41
|
+
step=512,
|
|
42
|
+
title="Spectro: n/segment",
|
|
43
|
+
tip="Number of samples per segment for the spectrogram",
|
|
44
|
+
),
|
|
45
|
+
dict(
|
|
46
|
+
name="spectro_win_size",
|
|
47
|
+
type="int",
|
|
48
|
+
limits=[2, np.inf],
|
|
49
|
+
value=2048,
|
|
50
|
+
step=1024,
|
|
51
|
+
title="Spectro: FFT padding size",
|
|
52
|
+
tip="Padding size for the FFT in each segment for the spectrogram",
|
|
53
|
+
),
|
|
54
|
+
dict(
|
|
55
|
+
name="spectro_noverlap",
|
|
56
|
+
type="int",
|
|
57
|
+
limits=[-1, np.inf],
|
|
58
|
+
value=-1,
|
|
59
|
+
step=512,
|
|
60
|
+
title="Spectro: n overlap (-1 : n/2)",
|
|
61
|
+
),
|
|
62
|
+
dict(
|
|
63
|
+
name="barycenters_fwindow",
|
|
64
|
+
type="float",
|
|
65
|
+
limits=[0, np.inf],
|
|
66
|
+
step=1e3,
|
|
67
|
+
value=1,
|
|
68
|
+
suffix="Hz",
|
|
69
|
+
siPrefix=True,
|
|
70
|
+
title="Barycenters: frequency window",
|
|
71
|
+
),
|
|
72
|
+
dict(
|
|
73
|
+
name="barycenters_fast",
|
|
74
|
+
type="bool",
|
|
75
|
+
limits=[True, False],
|
|
76
|
+
value=True,
|
|
77
|
+
title="Barycenters: fast algo.",
|
|
78
|
+
),
|
|
79
|
+
dict(
|
|
80
|
+
name="time_offset",
|
|
81
|
+
type="float",
|
|
82
|
+
limits=[-np.inf, np.inf],
|
|
83
|
+
suffix="s",
|
|
84
|
+
siPrefix=True,
|
|
85
|
+
value=0e-6,
|
|
86
|
+
step=0.5e-6,
|
|
87
|
+
title="TDO/Pickup time offset",
|
|
88
|
+
),
|
|
89
|
+
dict(name="poly_window", type="str", value="", title="Fit: field window"),
|
|
90
|
+
dict(
|
|
91
|
+
name="poly_deg",
|
|
92
|
+
type="int",
|
|
93
|
+
value=3,
|
|
94
|
+
step=1,
|
|
95
|
+
limits=[1, 10],
|
|
96
|
+
title="Fit: degree",
|
|
97
|
+
),
|
|
98
|
+
dict(
|
|
99
|
+
name="npoints_interp_inverse",
|
|
100
|
+
type="int",
|
|
101
|
+
value=100000,
|
|
102
|
+
step=5e4,
|
|
103
|
+
limits=[1000, np.inf],
|
|
104
|
+
title="n points interp. 1/B",
|
|
105
|
+
),
|
|
106
|
+
dict(
|
|
107
|
+
name="fft_window",
|
|
108
|
+
type="str",
|
|
109
|
+
value="",
|
|
110
|
+
title="FFT: field window",
|
|
111
|
+
),
|
|
112
|
+
dict(
|
|
113
|
+
name="fft_pad_mult",
|
|
114
|
+
type="int",
|
|
115
|
+
value=100,
|
|
116
|
+
step=100,
|
|
117
|
+
title="FFT: padding size mult.",
|
|
118
|
+
),
|
|
119
|
+
dict(
|
|
120
|
+
name="max_bfreq",
|
|
121
|
+
type="float",
|
|
122
|
+
value=70000,
|
|
123
|
+
step=1e4,
|
|
124
|
+
limits=[1, np.inf],
|
|
125
|
+
suffix="T",
|
|
126
|
+
siPrefix=True,
|
|
127
|
+
title="FFT : Max. B-frequency",
|
|
128
|
+
),
|
|
129
|
+
dict(
|
|
130
|
+
name="offset",
|
|
131
|
+
type="float",
|
|
132
|
+
value=1000,
|
|
133
|
+
step=500,
|
|
134
|
+
limits=[0, np.inf],
|
|
135
|
+
title="Display: curve offset",
|
|
136
|
+
),
|
|
137
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Config class for the EchoProcessor object."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ..core import BaseConfig
|
|
7
|
+
from ._config_models import EchoConfigurationModel
|
|
8
|
+
|
|
9
|
+
CONFIG_DEFAULT = Path(__file__).parent / "assets" / "config_default.toml"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EchoConfig(BaseConfig):
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
user_file: str | Path | None = None,
|
|
16
|
+
default_file: str | Path | None = None,
|
|
17
|
+
**overrides: Any,
|
|
18
|
+
) -> None:
|
|
19
|
+
if not default_file:
|
|
20
|
+
default_file = CONFIG_DEFAULT
|
|
21
|
+
|
|
22
|
+
super().__init__(EchoConfigurationModel, user_file, default_file, **overrides)
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def loads(cls, json_data: str | bytes, **kwargs) -> "EchoConfig":
|
|
26
|
+
return super()._loads(EchoConfigurationModel, json_data, **kwargs)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Pydantic models for the EchoProcessor Config."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict
|
|
8
|
+
|
|
9
|
+
from ..core import config_models
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Parameters(config_models.Parameters):
|
|
13
|
+
"""Parameters section, things related to the experiment itself."""
|
|
14
|
+
|
|
15
|
+
pickup_samplerate: float
|
|
16
|
+
sample_length: float
|
|
17
|
+
sample_speed: float
|
|
18
|
+
detection_mode: str
|
|
19
|
+
logamp_slope: float
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Settings(BaseModel):
|
|
23
|
+
"""Settings section, things related to the analysis."""
|
|
24
|
+
|
|
25
|
+
echo_index: int
|
|
26
|
+
frame_indices: Sequence[int]
|
|
27
|
+
rolling_mean_wlen: int
|
|
28
|
+
rolling_mean_subsample: bool
|
|
29
|
+
range_baseline: Sequence[float]
|
|
30
|
+
analysis_window: Sequence[float]
|
|
31
|
+
max_phase_jump: float
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Demodulation(BaseModel):
|
|
35
|
+
"""Demodulation section, things related to the digital demodulation process."""
|
|
36
|
+
|
|
37
|
+
f0: float
|
|
38
|
+
fft_nframes: int
|
|
39
|
+
detrend: bool
|
|
40
|
+
findsig_nframes: int
|
|
41
|
+
findsig_nstd: float
|
|
42
|
+
findsig_extend: float
|
|
43
|
+
chunksize: int
|
|
44
|
+
decimate_factor: int
|
|
45
|
+
filter_order: int
|
|
46
|
+
filter_fc: float
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class EchoConfigurationModel(BaseModel):
|
|
50
|
+
"""A Model specialized for ultra-sound echoes experiment."""
|
|
51
|
+
|
|
52
|
+
expid: str
|
|
53
|
+
data_directory: Path
|
|
54
|
+
|
|
55
|
+
filenames: dict[str, str | Path] = dict()
|
|
56
|
+
|
|
57
|
+
files: dict[str, config_models.File]
|
|
58
|
+
|
|
59
|
+
measurements: dict[str, int]
|
|
60
|
+
|
|
61
|
+
parameters: Parameters
|
|
62
|
+
settings: Settings
|
|
63
|
+
|
|
64
|
+
metadata: config_models.Metadata
|
|
65
|
+
|
|
66
|
+
nx: dict[str, dict[str, Any]]
|
|
67
|
+
nexus: config_models.Nexus
|
|
68
|
+
|
|
69
|
+
demodulation: Demodulation | None = None
|
|
70
|
+
|
|
71
|
+
model_config = ConfigDict(validate_assignment=True)
|