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.
Files changed (51) hide show
  1. pymagnetos/__init__.py +15 -0
  2. pymagnetos/cli.py +40 -0
  3. pymagnetos/core/__init__.py +19 -0
  4. pymagnetos/core/_config.py +340 -0
  5. pymagnetos/core/_data.py +132 -0
  6. pymagnetos/core/_processor.py +905 -0
  7. pymagnetos/core/config_models.py +57 -0
  8. pymagnetos/core/gui/__init__.py +6 -0
  9. pymagnetos/core/gui/_base_mainwindow.py +819 -0
  10. pymagnetos/core/gui/widgets/__init__.py +19 -0
  11. pymagnetos/core/gui/widgets/_batch_processing.py +319 -0
  12. pymagnetos/core/gui/widgets/_configuration.py +167 -0
  13. pymagnetos/core/gui/widgets/_files.py +129 -0
  14. pymagnetos/core/gui/widgets/_graphs.py +93 -0
  15. pymagnetos/core/gui/widgets/_param_content.py +20 -0
  16. pymagnetos/core/gui/widgets/_popup_progressbar.py +29 -0
  17. pymagnetos/core/gui/widgets/_text_logger.py +32 -0
  18. pymagnetos/core/signal_processing.py +1004 -0
  19. pymagnetos/core/utils.py +85 -0
  20. pymagnetos/log.py +126 -0
  21. pymagnetos/py.typed +0 -0
  22. pymagnetos/pytdo/__init__.py +6 -0
  23. pymagnetos/pytdo/_config.py +24 -0
  24. pymagnetos/pytdo/_config_models.py +59 -0
  25. pymagnetos/pytdo/_tdoprocessor.py +1052 -0
  26. pymagnetos/pytdo/assets/config_default.toml +84 -0
  27. pymagnetos/pytdo/gui/__init__.py +26 -0
  28. pymagnetos/pytdo/gui/_worker.py +106 -0
  29. pymagnetos/pytdo/gui/main.py +617 -0
  30. pymagnetos/pytdo/gui/widgets/__init__.py +8 -0
  31. pymagnetos/pytdo/gui/widgets/_buttons.py +66 -0
  32. pymagnetos/pytdo/gui/widgets/_configuration.py +78 -0
  33. pymagnetos/pytdo/gui/widgets/_graphs.py +280 -0
  34. pymagnetos/pytdo/gui/widgets/_param_content.py +137 -0
  35. pymagnetos/pyuson/__init__.py +7 -0
  36. pymagnetos/pyuson/_config.py +26 -0
  37. pymagnetos/pyuson/_config_models.py +71 -0
  38. pymagnetos/pyuson/_echoprocessor.py +1901 -0
  39. pymagnetos/pyuson/assets/config_default.toml +92 -0
  40. pymagnetos/pyuson/gui/__init__.py +26 -0
  41. pymagnetos/pyuson/gui/_worker.py +135 -0
  42. pymagnetos/pyuson/gui/main.py +767 -0
  43. pymagnetos/pyuson/gui/widgets/__init__.py +7 -0
  44. pymagnetos/pyuson/gui/widgets/_buttons.py +95 -0
  45. pymagnetos/pyuson/gui/widgets/_configuration.py +85 -0
  46. pymagnetos/pyuson/gui/widgets/_graphs.py +248 -0
  47. pymagnetos/pyuson/gui/widgets/_param_content.py +193 -0
  48. pymagnetos-0.1.0.dist-info/METADATA +23 -0
  49. pymagnetos-0.1.0.dist-info/RECORD +51 -0
  50. pymagnetos-0.1.0.dist-info/WHEEL +4 -0
  51. 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,7 @@
1
+ """The `pyuson` module, an app for ultra-sound echoes experiments."""
2
+
3
+ from .. import core as core
4
+ from ._config import EchoConfig
5
+ from ._echoprocessor import EchoProcessor
6
+
7
+ __all__ = ["EchoConfig", "EchoProcessor"]
@@ -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)