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,92 @@
|
|
|
1
|
+
# This configuration file specifies the default values if they are not filled in the
|
|
2
|
+
# per-experiment config.toml file.
|
|
3
|
+
|
|
4
|
+
expid = ""
|
|
5
|
+
data_directory = "." # path or "."
|
|
6
|
+
|
|
7
|
+
[files]
|
|
8
|
+
[files.pickup]
|
|
9
|
+
# Pickup coil binary file
|
|
10
|
+
ext = "-pickup.bin"
|
|
11
|
+
header = 0
|
|
12
|
+
precision = 8 # number of bytes/element
|
|
13
|
+
endian = "<" # endian, ">" for big-endian, "<" for little-endian
|
|
14
|
+
order = "F" # order style in the binary file, "F" for Fortran, "C" for C
|
|
15
|
+
[files.oscillo]
|
|
16
|
+
# Oscilloscope binary file
|
|
17
|
+
ext = ".bin"
|
|
18
|
+
header = 0
|
|
19
|
+
precision = 4 # number of bytes/element
|
|
20
|
+
endian = ">" # endian, ">" for big-endian, "<" for little-endian
|
|
21
|
+
order = "F" # order style in the binary file, "F" for Fortran, "C" for C
|
|
22
|
+
[files.reference_time]
|
|
23
|
+
# Reference time text file
|
|
24
|
+
ext = ".txt"
|
|
25
|
+
header = 8 # number of lines composing the header (data should start at header + 1)
|
|
26
|
+
delimiter = "\t"
|
|
27
|
+
|
|
28
|
+
[measurements]
|
|
29
|
+
# Measurements settings
|
|
30
|
+
# Link a physical measurement to a channel of the oscilloscope
|
|
31
|
+
# For oscillo data saved with LabVIEW, they correspond to the columns in the file (0-based)
|
|
32
|
+
# For WFM files they correspond to the file number (..._chX.wfm)
|
|
33
|
+
amplitude = 0
|
|
34
|
+
in_phase = 1
|
|
35
|
+
out_phase = 2
|
|
36
|
+
|
|
37
|
+
# Parameters are values that should be fixed once and for all for a given experiment
|
|
38
|
+
[parameters]
|
|
39
|
+
pickup_surface = 1 # surface of the pickup coil (m²)
|
|
40
|
+
pickup_samplerate = 1 # pickup coil acquisition rate (Hz)
|
|
41
|
+
sample_length = 1 # length of the sample (m)
|
|
42
|
+
sample_speed = 1 # (estimated) speed in the measured mode (m/s)
|
|
43
|
+
detection_mode = "reflection" # "reflection" or "transmission"
|
|
44
|
+
|
|
45
|
+
pickup_number = 1 # number of pickup(s) used and save in the pickup bin file
|
|
46
|
+
pickup_index = 0 # index of the pickup to use for analysis, 0-based
|
|
47
|
+
logamp_slope = 24.75e-3 # logarithmic amplificator slope
|
|
48
|
+
|
|
49
|
+
# Settings are analysis-specific and can be changed afterwards
|
|
50
|
+
[settings]
|
|
51
|
+
echo_index = 1 # number of the analyzed echo (1-based)
|
|
52
|
+
frame_indices = [0, 1] # frame index to plot
|
|
53
|
+
rolling_mean_wlen = 3 # rolling-average time window size (in indices)
|
|
54
|
+
rolling_mean_subsample = false # sub-sample resulting time trace
|
|
55
|
+
range_baseline = [0.01, 0.02] # range in which the baseline of amplitude and phase are estimated in seconds
|
|
56
|
+
analysis_window = [] # frame time-range in which to perform attenuation and phase-shift computation
|
|
57
|
+
max_phase_jump = 0.5 # maximum allowed phase-jump (multiplied by pi)
|
|
58
|
+
|
|
59
|
+
[metadata]
|
|
60
|
+
# Parameters on how to handle time reference text file (only for bin files)
|
|
61
|
+
[metadata.index_map]
|
|
62
|
+
# map a metadata to its line index in the header (0-based)
|
|
63
|
+
timestamp = 0
|
|
64
|
+
sample_name = 1
|
|
65
|
+
sample_angle = 2
|
|
66
|
+
polarisation = 3
|
|
67
|
+
rf_frequency = 4
|
|
68
|
+
dt_acq = 5
|
|
69
|
+
temperature = 6
|
|
70
|
+
[metadata.conversion_map]
|
|
71
|
+
# determine whether conversion to numeric is required
|
|
72
|
+
timestamp = false
|
|
73
|
+
sample_name = false
|
|
74
|
+
sample_angle = false
|
|
75
|
+
polarisation = false
|
|
76
|
+
rf_frequency = true
|
|
77
|
+
dt_acq = true
|
|
78
|
+
temperature = true
|
|
79
|
+
|
|
80
|
+
[nx]
|
|
81
|
+
# NeXus file consolidation
|
|
82
|
+
|
|
83
|
+
[nexus]
|
|
84
|
+
[nexus.groups]
|
|
85
|
+
# NeXus groups attributes
|
|
86
|
+
root = {name = ""}
|
|
87
|
+
# processed/analysis
|
|
88
|
+
analysis = {}
|
|
89
|
+
# data
|
|
90
|
+
data = {}
|
|
91
|
+
[nexus.datasets]
|
|
92
|
+
# NeXus datasets attributes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Application for ultra-sound echoes experiments during high-magnetic field."""
|
|
2
|
+
|
|
3
|
+
from PyQt6 import QtWidgets
|
|
4
|
+
|
|
5
|
+
from .main import MainWindow
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def run(exec: bool = True) -> None | MainWindow:
|
|
9
|
+
"""
|
|
10
|
+
Build and run the app.
|
|
11
|
+
|
|
12
|
+
Set `exec=False` in an interactive prompt to interact with the GUI from the shell.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
exec : bool, optional
|
|
17
|
+
Execute the process thread. Set to False in an interactive prompt. Default is
|
|
18
|
+
True.
|
|
19
|
+
"""
|
|
20
|
+
app = QtWidgets.QApplication([])
|
|
21
|
+
win = MainWindow()
|
|
22
|
+
win.show()
|
|
23
|
+
if exec:
|
|
24
|
+
app.exec()
|
|
25
|
+
else:
|
|
26
|
+
return win
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Worker that connects to an EchoProcessor object."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot
|
|
6
|
+
|
|
7
|
+
from pymagnetos.pyuson import EchoProcessor
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ProcessorWorker(QObject):
|
|
11
|
+
"""
|
|
12
|
+
Wrapper around the EchoProcessor class.
|
|
13
|
+
|
|
14
|
+
It exposes the EchoProcessor methods as slots and emits a signal when the task is
|
|
15
|
+
finished.
|
|
16
|
+
|
|
17
|
+
pyqtSignals
|
|
18
|
+
-------
|
|
19
|
+
sig_load_finished : emits when loading data is finished.
|
|
20
|
+
sig_align_finished : emits when aligning magnetic field is finished.
|
|
21
|
+
sig_rolling_finished : emits when rolling average is finished.
|
|
22
|
+
sig_find_f0_finished : emits f0 when finding center frequency is finished.
|
|
23
|
+
sig_demodulate_finished : emits when demodulation is finished.
|
|
24
|
+
sig_average_finished : emits when averaging frames and computing is finished.
|
|
25
|
+
sig_batch_finished : emits when batch-processing is finished.
|
|
26
|
+
|
|
27
|
+
sig_export_csv_finished : emits when exporting as CSV is finished.
|
|
28
|
+
sig_save_nexus_finished : emits when saving as NeXus file is finished.
|
|
29
|
+
|
|
30
|
+
sig_demodulation_progress : emits step index during demodulation.
|
|
31
|
+
sig_batch_process_progress : emits step index during batch-processing.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
sig_load_finished = pyqtSignal()
|
|
35
|
+
sig_align_finished = pyqtSignal()
|
|
36
|
+
sig_rolling_finished = pyqtSignal()
|
|
37
|
+
sig_find_f0_finished = pyqtSignal(float)
|
|
38
|
+
sig_demodulate_finished = pyqtSignal()
|
|
39
|
+
sig_average_finished = pyqtSignal(bool)
|
|
40
|
+
sig_batch_finished = pyqtSignal()
|
|
41
|
+
|
|
42
|
+
sig_export_csv_finished = pyqtSignal()
|
|
43
|
+
sig_save_nexus_finished = pyqtSignal()
|
|
44
|
+
|
|
45
|
+
sig_demodulation_progress = pyqtSignal(int)
|
|
46
|
+
sig_batch_progress = pyqtSignal(int)
|
|
47
|
+
|
|
48
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
49
|
+
super().__init__()
|
|
50
|
+
self.proc = EchoProcessor(*args, **kwargs)
|
|
51
|
+
if self.proc.is_nexus_file:
|
|
52
|
+
# From file : data should already be loaded and ready to use
|
|
53
|
+
self.is_dataloaded = True
|
|
54
|
+
else:
|
|
55
|
+
self.is_dataloaded = False
|
|
56
|
+
|
|
57
|
+
@pyqtSlot()
|
|
58
|
+
def load_data(self) -> None:
|
|
59
|
+
"""Load data."""
|
|
60
|
+
self.proc.load_oscillo(scale=True)
|
|
61
|
+
self.is_dataloaded = True
|
|
62
|
+
self.proc.align_field() # use the Processor method directly to not emit signal
|
|
63
|
+
self.sig_load_finished.emit()
|
|
64
|
+
|
|
65
|
+
@pyqtSlot()
|
|
66
|
+
def rolling_average(self) -> None:
|
|
67
|
+
"""Apply moving average."""
|
|
68
|
+
self.proc.rolling_average()
|
|
69
|
+
self.sig_rolling_finished.emit()
|
|
70
|
+
|
|
71
|
+
def align_field(self) -> None:
|
|
72
|
+
"""Align magnetic field on data."""
|
|
73
|
+
self.proc.align_field()
|
|
74
|
+
self.sig_align_finished.emit()
|
|
75
|
+
|
|
76
|
+
@pyqtSlot()
|
|
77
|
+
def find_f0(self) -> None:
|
|
78
|
+
"""Find F0."""
|
|
79
|
+
self.proc.find_f0()
|
|
80
|
+
self.sig_find_f0_finished.emit(self.proc.metadata["rf_frequency"])
|
|
81
|
+
|
|
82
|
+
@pyqtSlot()
|
|
83
|
+
def demodulate(self) -> None:
|
|
84
|
+
"""Apply digital demodulation."""
|
|
85
|
+
self.proc.demodulate(progress_emitter=self.sig_demodulation_progress)
|
|
86
|
+
self.sig_demodulate_finished.emit()
|
|
87
|
+
|
|
88
|
+
@pyqtSlot()
|
|
89
|
+
def average_frame(self) -> None:
|
|
90
|
+
"""Average in the time window, comptue attenuation and phase-shift."""
|
|
91
|
+
self.proc.average_frame_range().compute_attenuation().compute_phase_shift()
|
|
92
|
+
# Check it went fine
|
|
93
|
+
if self.proc.is_averaged:
|
|
94
|
+
self.sig_average_finished.emit(True)
|
|
95
|
+
else:
|
|
96
|
+
self.sig_average_finished.emit(False)
|
|
97
|
+
|
|
98
|
+
@pyqtSlot(list, bool, bool, bool, bool)
|
|
99
|
+
def batch_process(
|
|
100
|
+
self,
|
|
101
|
+
expids: list,
|
|
102
|
+
rolling_average: bool,
|
|
103
|
+
save_csv: bool,
|
|
104
|
+
to_cm: bool,
|
|
105
|
+
find_f0: bool,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Batch-process several datasets."""
|
|
108
|
+
self.proc.batch_process(
|
|
109
|
+
expids,
|
|
110
|
+
rolling_average=rolling_average,
|
|
111
|
+
save_csv=save_csv,
|
|
112
|
+
save_csv_kwargs=dict(to_cm=to_cm),
|
|
113
|
+
find_f0=find_f0,
|
|
114
|
+
batch_progress_emitter=self.sig_batch_progress,
|
|
115
|
+
demodulation_progress_emitter=self.sig_demodulation_progress,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Emit signals
|
|
119
|
+
self.sig_load_finished.emit()
|
|
120
|
+
if self.proc.is_digital:
|
|
121
|
+
self.sig_demodulate_finished.emit()
|
|
122
|
+
self.sig_average_finished.emit(True)
|
|
123
|
+
self.sig_batch_finished.emit()
|
|
124
|
+
|
|
125
|
+
@pyqtSlot(str, bool)
|
|
126
|
+
def export_as_csv(self, fname: str, to_cm: bool) -> None:
|
|
127
|
+
"""Export results as CSV."""
|
|
128
|
+
self.proc.to_csv(fname=fname, to_cm=to_cm)
|
|
129
|
+
self.sig_export_csv_finished.emit()
|
|
130
|
+
|
|
131
|
+
@pyqtSlot(str)
|
|
132
|
+
def save_as_nexus(self, fname: str | Path) -> None:
|
|
133
|
+
"""Save data as NeXus."""
|
|
134
|
+
self.proc.save(filename=fname)
|
|
135
|
+
self.sig_save_nexus_finished.emit()
|