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,7 @@
1
+ """Custom widgets for pyuson, the GUI for ultra-sound experiments."""
2
+
3
+ from ._buttons import ButtonsWidget
4
+ from ._configuration import ConfigurationWidget, ParamContent
5
+ from ._graphs import GraphsWidget
6
+
7
+ __all__ = ["ButtonsWidget", "ConfigurationWidget", "GraphsWidget", "ParamContent"]
@@ -0,0 +1,95 @@
1
+ """The widget that hosts the grid with the main action buttons for pyuson."""
2
+
3
+ from PyQt6 import QtWidgets
4
+ from PyQt6.QtCore import Qt, pyqtSignal
5
+
6
+
7
+ class ButtonsWidget(QtWidgets.QWidget):
8
+ """
9
+ The main action buttons.
10
+
11
+ pyqtSignals
12
+ -------
13
+ sig_load : emits when the "Load data" button is clicked.
14
+ sig_show : emits when the "Show frames" button is clicked.
15
+ sig_rolling : emits when the "Rolling average" button is clicked.
16
+ sig_refresh : emits when the "Refresh" button is clicked.
17
+ sig_save_csv : emits when the "Export as CSV" button is clicked.
18
+ sig_save_nexus : emits when the "Save as NeXus" button is clicked.
19
+ """
20
+
21
+ sig_load = pyqtSignal()
22
+ sig_show = pyqtSignal()
23
+ sig_rolling = pyqtSignal()
24
+ sig_refresh = pyqtSignal()
25
+ sig_save_csv = pyqtSignal()
26
+ sig_save_nexus = pyqtSignal()
27
+
28
+ def __init__(self) -> None:
29
+ super().__init__()
30
+ """Initialize buttons."""
31
+ # Create grid
32
+ grid = QtWidgets.QGridLayout()
33
+
34
+ # Create buttons
35
+ # Load data
36
+ self.button_load = QtWidgets.QPushButton("Load data", self)
37
+ # Show some frames
38
+ self.button_show = QtWidgets.QPushButton("Show frames", self)
39
+ # Apply moving mean filter
40
+ self.button_rolling = QtWidgets.QPushButton("Rolling average", self)
41
+ # Trigger computation of attenuation and phase shift
42
+ self.button_refresh = QtWidgets.QPushButton("Refresh", self)
43
+ # Export as CSV
44
+ self.button_save_csv = QtWidgets.QPushButton("Export as CSV", self)
45
+ # Checkbox to convert to dB/cm
46
+ self.checkbox_to_cm = QtWidgets.QCheckBox("Convert to dB/cm in CSV", self)
47
+ self.checkbox_to_cm.setChecked(True)
48
+
49
+ # Save as NeXus
50
+ self.button_save_nexus = QtWidgets.QPushButton("Save as NeXus", self)
51
+
52
+ # Disable all buttons
53
+ self.disable_buttons()
54
+
55
+ # Connect them
56
+ self.connect_buttons()
57
+
58
+ # Add buttons to layout
59
+ grid.addWidget(self.button_load, 0, 0)
60
+ grid.addWidget(self.button_show, 0, 1)
61
+ grid.addWidget(self.button_rolling, 1, 0)
62
+ grid.addWidget(self.button_refresh, 1, 1)
63
+ grid.addWidget(self.button_save_csv, 2, 0)
64
+ grid.addWidget(self.button_save_nexus, 2, 1)
65
+ grid.addWidget(self.checkbox_to_cm, 3, 0, Qt.AlignmentFlag.AlignHCenter)
66
+
67
+ # Set widget layout
68
+ self.setLayout(grid)
69
+
70
+ def connect_buttons(self) -> None:
71
+ """Connect the buttons to their signals."""
72
+ self.button_load.clicked.connect(self.sig_load.emit)
73
+ self.button_show.clicked.connect(self.sig_show.emit)
74
+ self.button_rolling.clicked.connect(self.sig_rolling.emit)
75
+ self.button_refresh.clicked.connect(self.sig_refresh.emit)
76
+ self.button_save_csv.clicked.connect(self.sig_save_csv.emit)
77
+ self.button_save_nexus.clicked.connect(self.sig_save_nexus.emit)
78
+
79
+ def disable_buttons(self) -> None:
80
+ """Disable all buttons."""
81
+ self.button_load.setEnabled(False)
82
+ self.button_rolling.setEnabled(False)
83
+ self.button_show.setEnabled(False)
84
+ self.button_refresh.setEnabled(False)
85
+ self.button_save_csv.setEnabled(False)
86
+ self.button_save_nexus.setEnabled(False)
87
+
88
+ def enable_buttons(self) -> None:
89
+ """Enable all buttons (except Load data)."""
90
+ self.button_load.setEnabled(False)
91
+ self.button_rolling.setEnabled(True)
92
+ self.button_show.setEnabled(True)
93
+ self.button_refresh.setEnabled(True)
94
+ self.button_save_csv.setEnabled(True)
95
+ self.button_save_nexus.setEnabled(True)
@@ -0,0 +1,85 @@
1
+ """The Configuration widget for pyuson."""
2
+
3
+ from functools import partial
4
+
5
+ from PyQt6.QtCore import pyqtSignal
6
+ from pyqtgraph.parametertree import Parameter
7
+
8
+ from pymagnetos.core.gui.widgets import BaseConfigurationWidget
9
+
10
+ from ._param_content import ParamContent
11
+
12
+
13
+ class ConfigurationWidget(BaseConfigurationWidget):
14
+ """
15
+ Initialize a PyQtGraph ParameterTree.
16
+
17
+ pyqtSignals
18
+ -------
19
+ sig_echo_index_changed : emits when the echo index (in the Settings section ) is
20
+ changed.
21
+ sig_find_f0 : emits when the "Find f0" button is clicked.
22
+ sig_demodulate : emits when the "Demodulate" button is clicked.
23
+ """
24
+
25
+ sig_echo_index_changed = pyqtSignal()
26
+ sig_find_f0 = pyqtSignal()
27
+ sig_demodulate = pyqtSignal()
28
+
29
+ def __init__(self, param_content: type[ParamContent]) -> None:
30
+ super().__init__(param_content)
31
+
32
+ def init_demodulation_tree(self) -> None:
33
+ """Create ParameterTree for demodulation parameters."""
34
+ if not hasattr(self._param_content, "children_demodulation"):
35
+ raise ValueError(
36
+ "Could not add the demodulation parameter tree, not in digital mode"
37
+ )
38
+ self.demodulation_parameters = Parameter.create(
39
+ name="Demodulation",
40
+ type="group",
41
+ children=self._param_content.children_demodulation,
42
+ )
43
+ self.host_parameters.addChild(self.demodulation_parameters)
44
+
45
+ # Store buttons as the others
46
+ self.button_findf0 = self.demodulation_parameters.child("find_f0")
47
+ self.button_findf0.setOpts(enabled=False)
48
+ self.button_demodulate = self.demodulation_parameters.child("demodulate")
49
+ self.button_demodulate.setOpts(enabled=False)
50
+
51
+ self.connect_to_signals_demodulation()
52
+
53
+ def connect_to_signals(self) -> None:
54
+ """Connect changes to signals."""
55
+ super().connect_to_signals()
56
+
57
+ # Special case for echo_index to sync it with another spinbox in the Batch
58
+ # processing tab
59
+ self.settings_parameters.child("echo_index").sigValueChanged.connect(
60
+ self.sig_echo_index_changed.emit
61
+ )
62
+
63
+ def connect_to_signals_demodulation(self) -> None:
64
+ """Additionnal connections for demodulation."""
65
+ self.button_findf0.sigActivated.connect(self.sig_find_f0.emit)
66
+ self.button_demodulate.sigActivated.connect(self.sig_demodulate.emit)
67
+
68
+ for p in self.demodulation_parameters:
69
+ self.demodulation_parameters.child(p.name()).sigValueChanged.connect(
70
+ partial(self.sig_parameter_changed.emit, p.name(), "demodulation")
71
+ )
72
+
73
+ def enable_buttons(self) -> None:
74
+ super().enable_buttons()
75
+ if hasattr(self, "button_findf0"):
76
+ self.button_findf0.setOpts(enabled=True)
77
+ if hasattr(self, "button_demodulate"):
78
+ self.button_demodulate.setOpts(enabled=True)
79
+
80
+ def disable_buttons(self) -> None:
81
+ super().disable_buttons()
82
+ if hasattr(self, "button_findf0"):
83
+ self.button_findf0.setOpts(enabled=False)
84
+ if hasattr(self, "button_demodulate"):
85
+ self.button_demodulate.setOpts(enabled=False)
@@ -0,0 +1,248 @@
1
+ """The graph area widget for pyuson."""
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
+ pyqtSignals
15
+ -------
16
+ sig_roi_changed : emits when the draggable time-window moved.
17
+ """
18
+
19
+ sig_roi_changed = pyqtSignal()
20
+
21
+ def __init__(self) -> None:
22
+ super().__init__()
23
+
24
+ # Internal conversion factor from time to frames
25
+ self._time2frame_scale = 1.0
26
+
27
+ # Create empty canvases in tabs
28
+ field_tab = self.create_field_plot()
29
+ frame_tab = self.create_frame_plot()
30
+ amplitude_tab = self.create_amplitude_plot()
31
+ phase_tab = self.create_phase_plot()
32
+
33
+ self.init_coordinates_on_hover()
34
+
35
+ # Create a main splitter for rows (vertical)
36
+ main_splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Vertical)
37
+
38
+ # Create a top splitter for the first row (horizontal)
39
+ top_splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal)
40
+ top_splitter.addWidget(field_tab)
41
+ top_splitter.addWidget(frame_tab)
42
+ top_splitter.setStretchFactor(0, 1) # field_tab stretch factor
43
+ # frame_tab spans 3 columns
44
+ top_splitter.setStretchFactor(1, 3)
45
+
46
+ # Create a bottom splitter for the second row (horizontal)
47
+ bottom_splitter = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal)
48
+ bottom_splitter.addWidget(amplitude_tab)
49
+ bottom_splitter.addWidget(phase_tab)
50
+ # amplitude_tab spans 2 columns
51
+ bottom_splitter.setStretchFactor(0, 2)
52
+ # phase_tab spans 2 columns
53
+ bottom_splitter.setStretchFactor(1, 2)
54
+
55
+ # Add the top and bottom splitters to the main splitter
56
+ main_splitter.addWidget(top_splitter)
57
+ main_splitter.addWidget(bottom_splitter)
58
+ main_splitter.setStretchFactor(0, 1) # top row stretch factor
59
+ main_splitter.setStretchFactor(1, 1) # bottom row stretch factor
60
+
61
+ # Set the main splitter as the central widget
62
+ layout = QtWidgets.QVBoxLayout()
63
+ layout.addWidget(main_splitter)
64
+ self.setLayout(layout)
65
+
66
+ @property
67
+ def time2frame_scale(self) -> float:
68
+ return self._time2frame_scale
69
+
70
+ @time2frame_scale.setter
71
+ def time2frame_scale(self, value: float) -> None:
72
+ self._time2frame_scale = value
73
+ self.field.getAxis("top").setScale(self.time2frame_scale)
74
+ self.dfield.getAxis("top").setScale(self.time2frame_scale)
75
+
76
+ def create_field_plot(self) -> QtWidgets.QTabWidget:
77
+ tab = QtWidgets.QTabWidget(self)
78
+
79
+ # magnetic field (integrated)
80
+ self.field = pg.PlotWidget(title="Magnetic field")
81
+ self.field.setLabel("bottom", "time (s)")
82
+ self.field.setLabel("top", "frame (#) - field not aligned")
83
+ self.field.setLabel("left", "field (T)")
84
+ self.field.showGrid(y=True)
85
+ self._plots_list.add(self.field)
86
+
87
+ # pickup coil voltage (measured)
88
+ self.dfield = pg.PlotWidget(title="Pickup")
89
+ self.dfield.setLabel("bottom", "time (s)")
90
+ self.dfield.setLabel("top", "frame (#)")
91
+ self.dfield.setLabel("left", "pickup (V)")
92
+ self.dfield.showGrid(y=True)
93
+ self._plots_list.add(self.dfield)
94
+
95
+ tab.addTab(self.field, "B(t)")
96
+ tab.addTab(self.dfield, "Pickup")
97
+
98
+ return tab
99
+
100
+ def create_frame_plot(self) -> QtWidgets.QTabWidget:
101
+ tab = QtWidgets.QTabWidget(self)
102
+
103
+ # amplitude
104
+ self.amp_frame = pg.PlotWidget(title="Frames amplitude")
105
+ self.amp_frame.setLabel("bottom", "time (µs)")
106
+ self.amp_frame.setLabel("left", "signal (V)")
107
+ self.amp_frame.showGrid(y=True)
108
+ self.roi = pg.LinearRegionItem() # time range selector
109
+ self.roi.sigRegionChangeFinished.connect(self.roi1_changed)
110
+ self.roi.sigRegionChangeFinished.connect(self.sig_roi_changed.emit)
111
+ self.amp_frame.addItem(self.roi, ignoreBounds=True) # ty:ignore[unknown-argument]
112
+ self._plots_list.add(self.amp_frame)
113
+
114
+ # phases
115
+ self.phase_frame = pg.PlotWidget(title="Frames phase")
116
+ self.phase_frame.setLabel("bottom", "time (µs)")
117
+ self.phase_frame.setLabel("left", "signal (V)")
118
+ self.phase_frame.showGrid(y=True)
119
+ self.roi2 = pg.LinearRegionItem() # twin in phase plot
120
+ self.phase_frame.addItem(self.roi2, ignoreBounds=True) # ty:ignore[unknown-argument]
121
+ self.roi2.sigRegionChangeFinished.connect(self.roi2_changed)
122
+ self.phase_frame.getPlotItem().addLegend()
123
+ self._plots_list.add(self.phase_frame)
124
+
125
+ tab.addTab(self.amp_frame, "Frame amplitude")
126
+ tab.addTab(self.phase_frame, "Frame phase")
127
+
128
+ # Keep a reference to it to add more plots if needed
129
+ self.tab_frame_plot = tab
130
+
131
+ return tab
132
+
133
+ def add_reference_in_frame_tab(self) -> None:
134
+ """Add a plot for the reference signal for digital demodulation."""
135
+ if hasattr(self, "reference_frame") and isinstance(
136
+ self.reference_frame, pg.PlotWidget
137
+ ):
138
+ # Already has a reference frame plot
139
+ return
140
+ self.reference_frame = pg.PlotWidget(title="Frames reference")
141
+ self.reference_frame.setLabel("bottom", "time (µs)")
142
+ self.reference_frame.setLabel("left", "signal (V)")
143
+ self.reference_frame.showGrid(y=True)
144
+ self._plots_list.add(self.reference_frame)
145
+ self.coordinates_on_hover(self.reference_frame)
146
+
147
+ self.tab_frame_plot.addTab(self.reference_frame, "Frame reference")
148
+
149
+ def create_amplitude_plot(self) -> QtWidgets.QTabWidget:
150
+ tab = QtWidgets.QTabWidget(self)
151
+
152
+ # vs field
153
+ self.amp_field = pg.PlotWidget(title="Amplitude (B)")
154
+ self.amp_field.setLabel("bottom", "field (T)")
155
+ self.amp_field.setLabel("left", "attenuation (dB/cm)")
156
+ self.amp_field.showGrid(y=True)
157
+ self.amp_field.getPlotItem().addLegend()
158
+ self._plots_list.add(self.amp_field)
159
+
160
+ # vs amplitude
161
+ self.amp_time = pg.PlotWidget(title="Amplitude (t)")
162
+ self.amp_time.setLabel("bottom", "time (s)")
163
+ self.amp_time.setLabel("left", "attenuation (dB/cm)")
164
+ self.amp_time.showGrid(y=True)
165
+ self._plots_list.add(self.amp_time)
166
+
167
+ tab.addTab(self.amp_field, "Amplitude (B)")
168
+ tab.addTab(self.amp_time, "Amplitude (t)")
169
+
170
+ return tab
171
+
172
+ def create_phase_plot(self) -> QtWidgets.QTabWidget:
173
+ tab = QtWidgets.QTabWidget(self)
174
+
175
+ # vs field
176
+ self.phase_field = pg.PlotWidget(title="Phase (B)")
177
+ self.phase_field.setLabel("bottom", "field (T)")
178
+ self.phase_field.setLabel("left", "dphi/phi")
179
+ self.phase_field.showGrid(y=True)
180
+ self.phase_field.getPlotItem().addLegend()
181
+ self._plots_list.add(self.phase_field)
182
+
183
+ # vs plot
184
+ self.phase_time = pg.PlotWidget(title="Phase (t)")
185
+ self.phase_time.setLabel("bottom", "time (s)")
186
+ self.phase_time.setLabel("left", "dphi/phi")
187
+ self.phase_time.showGrid(y=True)
188
+ self._plots_list.add(self.phase_time)
189
+
190
+ tab.addTab(self.phase_field, "Phase (B)")
191
+ tab.addTab(self.phase_time, "Phase (t)")
192
+
193
+ return tab
194
+
195
+ def init_plot_style(self) -> None:
196
+ """Set up PyQtGraph line styles."""
197
+ w0 = 1
198
+ w1 = 1
199
+ self.pen_field = pg.mkPen("#c7c7c7", width=w0)
200
+ self.pen_amp = pg.mkPen("#1f77b480", width=w0)
201
+ self.pen_amp_demod = pg.mkPen("#d6272880", width=w0)
202
+ self.pen_in_phase = pg.mkPen("#ff7f0e80", width=w1)
203
+ self.pen_out_phase = pg.mkPen("#17becf80", width=w1)
204
+ self.pen_phase_demod = pg.mkPen("#9467bd80", width=w0)
205
+ self.pen_bup = pg.mkPen("#2ca02cbf", width=w0)
206
+ self.pen_bdown = pg.mkPen("#d62728bf", width=w0)
207
+
208
+ def init_field_crosshair(self) -> None:
209
+ """Create line cursor on field plot that tracks frame number."""
210
+ self.field_vline = pg.InfiniteLine(angle=90, movable=False)
211
+ self.field.addItem(self.field_vline, ignoreBounds=True) # ty:ignore[unknown-argument]
212
+ self.field.scene().sigMouseMoved.connect(self.field_crosshair_moved) # ty:ignore[possibly-missing-attribute]
213
+
214
+ def field_crosshair_moved(self, evt) -> None:
215
+ """Define the callback function when the cursor is moved."""
216
+ pos = evt
217
+ vb = self.field.getViewBox()
218
+ if self.field.sceneBoundingRect().contains(pos):
219
+ mouse_point = vb.mapSceneToView(pos)
220
+ index = int(mouse_point.x() * self.time2frame_scale)
221
+ self.field.setLabel("top", f"frame (#), current : {index}")
222
+ self.field_vline.setPos(mouse_point.x())
223
+
224
+ @pyqtSlot()
225
+ def roi1_changed(self) -> None:
226
+ """Update ROI in the phase panel when the ROI in the amplitude panel moved."""
227
+ roi1 = self.roi.getRegion() # amplitude panel
228
+ roi2 = self.roi2.getRegion() # phase panel
229
+ if roi1 != roi2:
230
+ self.roi2.setRegion(self.roi.getRegion())
231
+
232
+ @pyqtSlot()
233
+ def roi2_changed(self) -> None:
234
+ """Update ROI in the amplitude panel when the ROI in the phase panel moved."""
235
+ roi1 = self.roi.getRegion() # amplitude panel
236
+ roi2 = self.roi2.getRegion() # phase panel
237
+ if roi1 != roi2:
238
+ self.roi.setRegion(self.roi2.getRegion())
239
+
240
+ def enable_rois(self) -> None:
241
+ """Enable moving ROIs."""
242
+ self.roi.setMovable(True)
243
+ self.roi2.setMovable(True)
244
+
245
+ def disable_rois(self) -> None:
246
+ """Disable moving ROIs."""
247
+ self.roi.setMovable(False)
248
+ self.roi2.setMovable(False)
@@ -0,0 +1,193 @@
1
+ """ParameterTree content for pyuson."""
2
+
3
+ import numpy as np
4
+
5
+ from pymagnetos.core.gui.widgets import BaseParamContent
6
+
7
+
8
+ class ParamContent(BaseParamContent):
9
+ # List of parameters that will need to be parsed from string to list
10
+ PARAMS_TO_PARSE = ["frame_indices", "range_baseline", "analysis_window"]
11
+
12
+ # Define parameters in the "Parameters" section
13
+ children_parameters = [
14
+ dict(
15
+ name="pickup_surface",
16
+ type="float",
17
+ limits=[0, np.inf],
18
+ value=None,
19
+ suffix="m²",
20
+ siPrefix=False,
21
+ readonly=True,
22
+ title="Pickup surface",
23
+ ),
24
+ dict(
25
+ name="pickup_samplerate",
26
+ type="float",
27
+ limits=[0, np.inf],
28
+ value=250e3,
29
+ step=1e3,
30
+ suffix="Hz",
31
+ siPrefix=True,
32
+ readonly=True,
33
+ title="Pickup sample rate",
34
+ ),
35
+ dict(
36
+ name="sample_length",
37
+ type="float",
38
+ limits=[0, np.inf],
39
+ value=None,
40
+ step=0.5e-3,
41
+ suffix="m",
42
+ siPrefix=True,
43
+ readonly=True,
44
+ title="Sample length",
45
+ ),
46
+ dict(
47
+ name="sample_speed",
48
+ type="float",
49
+ limits=[0, np.inf],
50
+ value=None,
51
+ step=0.5e3,
52
+ suffix="m/s",
53
+ siPrefix=True,
54
+ readonly=True,
55
+ title="Sample speed",
56
+ ),
57
+ dict(
58
+ name="detection_mode",
59
+ type="list",
60
+ limits=["reflection", "transmission"],
61
+ value="reflection",
62
+ readonly=True,
63
+ title="Detection mode",
64
+ ),
65
+ ]
66
+
67
+ # Define parameters in the "Settings" section (editable)
68
+ children_settings = [
69
+ dict(
70
+ name="echo_index",
71
+ type="int",
72
+ limits=[1, np.inf],
73
+ step=1,
74
+ value=1,
75
+ title="Echo index",
76
+ ),
77
+ dict(name="frame_indices", type="str", title="Displayed frames"),
78
+ dict(
79
+ name="rolling_mean_wlen",
80
+ type="int",
81
+ limits=[0, np.inf],
82
+ step=1,
83
+ value=0,
84
+ title="Rolling mean window length",
85
+ ),
86
+ dict(
87
+ name="rolling_mean_subsample",
88
+ type="bool",
89
+ limits=[True, False],
90
+ value=False,
91
+ title="Rolling mean subsampling",
92
+ ),
93
+ dict(name="range_baseline", type="str", value="", title="Range baseline"),
94
+ dict(
95
+ name="analysis_window", type="str", value="", title="Analysis time window"
96
+ ),
97
+ dict(
98
+ name="max_phase_jump",
99
+ type="float",
100
+ value=0.5,
101
+ step=0.1,
102
+ limits=[0, np.inf],
103
+ title="Max. phase jump (* π)",
104
+ ),
105
+ ]
106
+
107
+ children_demodulation = [
108
+ dict(
109
+ name="f0",
110
+ type="float",
111
+ limits=[0, np.inf],
112
+ step=10e6,
113
+ value=0,
114
+ suffix="Hz",
115
+ siPrefix=True,
116
+ title="f0",
117
+ ),
118
+ dict(
119
+ name="fft_nframes",
120
+ type="int",
121
+ limits=[0, np.inf],
122
+ step=10,
123
+ value=0,
124
+ title="FFT : nframes",
125
+ ),
126
+ dict(
127
+ name="detrend",
128
+ type="bool",
129
+ limits=[False, True],
130
+ value=False,
131
+ title="FFT : detrend",
132
+ ),
133
+ dict(
134
+ name="findsig_nframes",
135
+ type="int",
136
+ limit=[0, np.inf],
137
+ step=10,
138
+ value=0,
139
+ title="Ref. finder : nframes",
140
+ ),
141
+ dict(
142
+ name="findsig_nstd",
143
+ type="float",
144
+ limit=[0, np.inf],
145
+ step=0.1,
146
+ value=1.5,
147
+ title="Ref. finder : nstd",
148
+ ),
149
+ dict(
150
+ name="findsig_extend",
151
+ type="float",
152
+ limit=[-np.inf, np.inf],
153
+ step=0.01,
154
+ value=-0.10,
155
+ title="Ref. finder : ext. frac.",
156
+ ),
157
+ dict(
158
+ name="chunksize",
159
+ type="int",
160
+ limits=[-1, np.inf],
161
+ step=100,
162
+ value=0,
163
+ title="Chunk size",
164
+ ),
165
+ dict(
166
+ name="decimate_factor",
167
+ type="int",
168
+ limits=[0, np.inf],
169
+ step=1,
170
+ value=0,
171
+ title="Decimation factor",
172
+ ),
173
+ dict(
174
+ name="filter_order",
175
+ type="int",
176
+ limits=[1, np.inf],
177
+ step=1,
178
+ value=10,
179
+ title="Filter order",
180
+ ),
181
+ dict(
182
+ name="filter_fc",
183
+ type="float",
184
+ limits=[0, np.inf],
185
+ step=10e6,
186
+ value=300e6,
187
+ suffix="Hz",
188
+ siPrefix=True,
189
+ title="Filter cut-off frequency",
190
+ ),
191
+ dict(name="find_f0", type="action", title="Find f0"),
192
+ dict(name="demodulate", type="action", title="Demodulate"),
193
+ ]
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.3
2
+ Name: pymagnetos
3
+ Version: 0.1.0
4
+ Summary: Applications for high magnetic field analysis
5
+ Author: Guillaume Le Goc
6
+ Author-email: Guillaume Le Goc <guillaume.le-goc@lncmi.cnrs.fr>
7
+ License: MIT
8
+ Classifier: Programming Language :: Python :: 3.13
9
+ Classifier: Programming Language :: Python :: 3.14
10
+ Requires-Dist: fftekwfm>=0.3.0.1
11
+ Requires-Dist: matplotlib>=3.10.8
12
+ Requires-Dist: nexusformat>=2.0.0
13
+ Requires-Dist: numpy>=2.4.1
14
+ Requires-Dist: pydantic>=2.12.5
15
+ Requires-Dist: pyqt6>=6.10.2
16
+ Requires-Dist: pyqtgraph>=0.14.0
17
+ Requires-Dist: rich>=14.3.1
18
+ Requires-Dist: scipy>=1.17.0
19
+ Requires-Dist: tomlkit>=0.14.0
20
+ Requires-Python: >=3.13
21
+ Project-URL: Source Code, https://gitlab.in2p3.fr/himagnetos/pymagnetos
22
+ Description-Content-Type: text/markdown
23
+