ZaksPhysicsLibrary 1.2.2__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.
- PhysicsLibrary/__init__.py +59 -0
- PhysicsLibrary/analysis.py +251 -0
- PhysicsLibrary/dataset.py +125 -0
- PhysicsLibrary/file_parser.py +59 -0
- PhysicsLibrary/file_parser_generic.py +287 -0
- PhysicsLibrary/loaders/__init__.py +0 -0
- PhysicsLibrary/loaders/oxysoft_loader.py +235 -0
- PhysicsLibrary/loaders/pt2_loader.py +43 -0
- PhysicsLibrary/loaders/tdt_loader.py +49 -0
- PhysicsLibrary/models.py +62 -0
- PhysicsLibrary/processing_TDT.py +273 -0
- zaksphysicslibrary-1.2.2.dist-info/METADATA +142 -0
- zaksphysicslibrary-1.2.2.dist-info/RECORD +16 -0
- zaksphysicslibrary-1.2.2.dist-info/WHEEL +5 -0
- zaksphysicslibrary-1.2.2.dist-info/licenses/LICENSE +21 -0
- zaksphysicslibrary-1.2.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PhysicsLibrary
|
|
3
|
+
--------------
|
|
4
|
+
Data processing and analysis library for Physics Analysis GUI.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from importlib.metadata import version as _version, PackageNotFoundError
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
__version__ = _version("PhysicsLibrary")
|
|
11
|
+
except PackageNotFoundError:
|
|
12
|
+
__version__ = "unknown"
|
|
13
|
+
|
|
14
|
+
from .file_parser_generic import load_any_file
|
|
15
|
+
|
|
16
|
+
from .dataset import (
|
|
17
|
+
choose_file,
|
|
18
|
+
detect_format,
|
|
19
|
+
detect_format_file,
|
|
20
|
+
DataFormat,
|
|
21
|
+
Dataset,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from .file_parser import (
|
|
25
|
+
load_dataset,
|
|
26
|
+
load_dataset_file,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from .loaders.pt2_loader import load_pt2
|
|
30
|
+
|
|
31
|
+
from .processing_TDT import (
|
|
32
|
+
process_tdt_folder,
|
|
33
|
+
validate_tdt_folder,
|
|
34
|
+
get_tdt_struct,
|
|
35
|
+
get_plot_data,
|
|
36
|
+
correct_bleaching,
|
|
37
|
+
denoise_signal,
|
|
38
|
+
get_event_markers,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
from .analysis import (
|
|
42
|
+
get_zscore_slice,
|
|
43
|
+
smooth_signal,
|
|
44
|
+
bin_for_heatmap,
|
|
45
|
+
compute_fft_slice,
|
|
46
|
+
annotate_fft_peaks,
|
|
47
|
+
compute_slope_segment,
|
|
48
|
+
fit_model_to_segment,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
from .models import (
|
|
52
|
+
double_exponential_model,
|
|
53
|
+
visibility_model,
|
|
54
|
+
linear_model,
|
|
55
|
+
single_exponential_model,
|
|
56
|
+
exponential_rise_model,
|
|
57
|
+
gaussian_model,
|
|
58
|
+
sinusoidal_model,
|
|
59
|
+
)
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""
|
|
2
|
+
analysis.py
|
|
3
|
+
-----------
|
|
4
|
+
Format-agnostic analysis routines for Physics Analysis GUI.
|
|
5
|
+
|
|
6
|
+
Currently:
|
|
7
|
+
- Z-Score PETH (get_zscore_slice, smooth_signal, bin_for_heatmap)
|
|
8
|
+
- FFT (compute_fft_slice, annotate_fft_peaks)
|
|
9
|
+
- Curve fitting (compute_slope_segment, fit_model_to_segment)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from scipy.signal import detrend, find_peaks
|
|
14
|
+
from scipy.optimize import curve_fit
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_zscore_slice(time_array, signal, center_t, window=30):
|
|
18
|
+
"""
|
|
19
|
+
Extract and z-score a time window around an event.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
time_array : array
|
|
24
|
+
signal : array
|
|
25
|
+
center_t : float
|
|
26
|
+
Event time in seconds
|
|
27
|
+
window : float
|
|
28
|
+
Total window size in seconds
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
(time segment, z-scored signal)
|
|
33
|
+
"""
|
|
34
|
+
half_win = window / 2
|
|
35
|
+
start_idx = np.searchsorted(time_array, center_t - half_win)
|
|
36
|
+
end_idx = np.searchsorted(time_array, center_t + half_win)
|
|
37
|
+
|
|
38
|
+
seg_y = signal[start_idx:end_idx]
|
|
39
|
+
seg_x = time_array[start_idx:end_idx]
|
|
40
|
+
|
|
41
|
+
# Clip extreme artefacts before z-scoring so outliers don't dominate the baseline std.
|
|
42
|
+
seg_y = np.clip(seg_y, -5, 5)
|
|
43
|
+
|
|
44
|
+
baseline_end = len(seg_y) // 2
|
|
45
|
+
baseline_period = seg_y[:baseline_end]
|
|
46
|
+
mu = np.mean(baseline_period)
|
|
47
|
+
std = np.std(baseline_period)
|
|
48
|
+
|
|
49
|
+
if std < 1e-6:
|
|
50
|
+
return seg_x, np.zeros_like(seg_y)
|
|
51
|
+
|
|
52
|
+
return seg_x, (seg_y - mu) / std
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def smooth_signal(data, fs, window_sec=0.5):
|
|
56
|
+
"""
|
|
57
|
+
Moving average smoothing filter.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
data : array
|
|
62
|
+
fs : float
|
|
63
|
+
Sampling frequency in Hz
|
|
64
|
+
window_sec : float
|
|
65
|
+
Smoothing window in seconds
|
|
66
|
+
|
|
67
|
+
Returns
|
|
68
|
+
-------
|
|
69
|
+
array
|
|
70
|
+
Smoothed signal
|
|
71
|
+
"""
|
|
72
|
+
window_size = int(fs * window_sec)
|
|
73
|
+
if window_size % 2 == 0:
|
|
74
|
+
window_size += 1
|
|
75
|
+
return np.convolve(data, np.ones(window_size) / window_size, mode='same')
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def bin_for_heatmap(z_seg, num_bins=300):
|
|
79
|
+
"""
|
|
80
|
+
Bin a signal into equal segments for heatmap plotting.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
z_seg : array
|
|
85
|
+
num_bins : int
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
array
|
|
90
|
+
Binned signal
|
|
91
|
+
"""
|
|
92
|
+
if z_seg is None or len(z_seg) == 0:
|
|
93
|
+
return np.zeros(num_bins)
|
|
94
|
+
bin_edges = np.linspace(0, len(z_seg), num_bins + 1).astype(int)
|
|
95
|
+
return np.array([np.mean(z_seg[bin_edges[i]:bin_edges[i+1]]) for i in range(num_bins)])
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def compute_fft_slice(time_array, signal, center_t, fs, window=30):
|
|
99
|
+
"""
|
|
100
|
+
Extract a time window around center_t and compute its FFT.
|
|
101
|
+
|
|
102
|
+
Applies mean removal and linear detrending before FFT to eliminate
|
|
103
|
+
the DC spike and slow drift, making physiological frequencies
|
|
104
|
+
(breathing ~0.3 Hz, heart rate ~1 Hz) visible.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
time_array : array
|
|
109
|
+
signal : array
|
|
110
|
+
center_t : float
|
|
111
|
+
Center time in seconds
|
|
112
|
+
fs : float
|
|
113
|
+
Sampling frequency in Hz
|
|
114
|
+
window : float
|
|
115
|
+
Total window size in seconds
|
|
116
|
+
|
|
117
|
+
Returns
|
|
118
|
+
-------
|
|
119
|
+
freqs : array
|
|
120
|
+
power : array
|
|
121
|
+
seg_x : array
|
|
122
|
+
seg_y : array
|
|
123
|
+
"""
|
|
124
|
+
half_win = window / 2
|
|
125
|
+
start_idx = np.searchsorted(time_array, center_t - half_win)
|
|
126
|
+
end_idx = np.searchsorted(time_array, center_t + half_win)
|
|
127
|
+
|
|
128
|
+
seg_y = signal[start_idx:end_idx]
|
|
129
|
+
seg_x = time_array[start_idx:end_idx]
|
|
130
|
+
|
|
131
|
+
if len(seg_y) < 4:
|
|
132
|
+
return np.array([]), np.array([]), seg_x, seg_y
|
|
133
|
+
|
|
134
|
+
seg_y = detrend(seg_y, type='linear') # removes mean and linear trend
|
|
135
|
+
windowed = seg_y * np.hanning(len(seg_y))
|
|
136
|
+
|
|
137
|
+
n = len(windowed)
|
|
138
|
+
fft_y = np.fft.rfft(windowed)
|
|
139
|
+
freqs = np.fft.rfftfreq(n, d=1.0 / fs)
|
|
140
|
+
power = (np.abs(fft_y) ** 2) / n
|
|
141
|
+
|
|
142
|
+
return freqs, power, seg_x, seg_y
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def annotate_fft_peaks(ax_f, freqs, power, color, n_peaks=3):
|
|
146
|
+
"""
|
|
147
|
+
Find top N peaks in a power spectrum and annotate them with
|
|
148
|
+
frequency and BPM labels directly on the axes.
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
ax_f : matplotlib Axes
|
|
153
|
+
freqs : array
|
|
154
|
+
power : array
|
|
155
|
+
color : str
|
|
156
|
+
n_peaks: int
|
|
157
|
+
"""
|
|
158
|
+
mask = freqs >= 0.05
|
|
159
|
+
f_m = freqs[mask]
|
|
160
|
+
p_m = power[mask]
|
|
161
|
+
if len(p_m) < 3:
|
|
162
|
+
return
|
|
163
|
+
min_prom = 0.05 * p_m.max()
|
|
164
|
+
peaks, _ = find_peaks(p_m, prominence=min_prom)
|
|
165
|
+
if len(peaks) == 0:
|
|
166
|
+
return
|
|
167
|
+
top = sorted(peaks, key=lambda i: p_m[i], reverse=True)[:n_peaks]
|
|
168
|
+
for idx in top:
|
|
169
|
+
freq = f_m[idx]
|
|
170
|
+
pwr = p_m[idx]
|
|
171
|
+
bpm = freq * 60
|
|
172
|
+
ax_f.annotate(
|
|
173
|
+
f"{freq:.2f} Hz\n({bpm:.0f} bpm)",
|
|
174
|
+
xy=(freq, pwr),
|
|
175
|
+
xytext=(freq + 0.05, pwr * 0.92),
|
|
176
|
+
fontsize=7, color=color, fontweight='bold',
|
|
177
|
+
arrowprops=dict(arrowstyle='->', color=color, lw=0.8),
|
|
178
|
+
)
|
|
179
|
+
ax_f.axvline(freq, color=color, lw=0.7, linestyle=':', alpha=0.5)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def compute_slope_segment(x_data, y_data, p1_idx, p2_idx, padding_pct=0.05):
|
|
183
|
+
"""
|
|
184
|
+
Least-squares linear regression slope between two index boundaries.
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
x_data : array
|
|
189
|
+
y_data : array
|
|
190
|
+
p1_idx : int
|
|
191
|
+
p2_idx : int
|
|
192
|
+
padding_pct : float visual context padding
|
|
193
|
+
|
|
194
|
+
Returns
|
|
195
|
+
-------
|
|
196
|
+
dict with slope, intercept, crop_x, crop_y, x1, y1, x2, y2
|
|
197
|
+
"""
|
|
198
|
+
idx1, idx2 = sorted([p1_idx, p2_idx])
|
|
199
|
+
|
|
200
|
+
fit_x = x_data[idx1:idx2 + 1]
|
|
201
|
+
fit_y = y_data[idx1:idx2 + 1]
|
|
202
|
+
|
|
203
|
+
if len(fit_x) < 2:
|
|
204
|
+
slope, intercept = 0.0, 0.0
|
|
205
|
+
else:
|
|
206
|
+
slope, intercept = np.polyfit(fit_x, fit_y, 1)
|
|
207
|
+
|
|
208
|
+
x1, y1 = fit_x[0], fit_y[0]
|
|
209
|
+
x2, y2 = fit_x[-1], fit_y[-1]
|
|
210
|
+
|
|
211
|
+
pad = max(5, int(len(x_data) * padding_pct))
|
|
212
|
+
start_idx = max(0, idx1 - pad)
|
|
213
|
+
end_idx = min(len(x_data) - 1, idx2 + pad)
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
'slope': slope,
|
|
217
|
+
'intercept': intercept,
|
|
218
|
+
'crop_x': x_data[start_idx:end_idx + 1],
|
|
219
|
+
'crop_y': y_data[start_idx:end_idx + 1],
|
|
220
|
+
'x1': x1, 'y1': y1,
|
|
221
|
+
'x2': x2, 'y2': y2,
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def fit_model_to_segment(x_seg, y_seg, model_fn, p0_fn):
|
|
226
|
+
"""
|
|
227
|
+
Fit a model function to a data segment using scipy curve_fit.
|
|
228
|
+
|
|
229
|
+
Parameters
|
|
230
|
+
----------
|
|
231
|
+
x_seg : array
|
|
232
|
+
y_seg : array
|
|
233
|
+
model_fn : callable f(x, *params) -> y
|
|
234
|
+
p0_fn : callable f(x_seg, y_seg) -> list of initial guesses
|
|
235
|
+
|
|
236
|
+
Returns
|
|
237
|
+
-------
|
|
238
|
+
dict with popt, y_fit, r2, success, error
|
|
239
|
+
"""
|
|
240
|
+
try:
|
|
241
|
+
p0 = p0_fn(x_seg, y_seg)
|
|
242
|
+
popt, _ = curve_fit(model_fn, x_seg, y_seg, p0=p0, maxfev=10000)
|
|
243
|
+
y_fit = model_fn(x_seg, *popt)
|
|
244
|
+
ss_res = np.sum((y_seg - y_fit) ** 2)
|
|
245
|
+
ss_tot = np.sum((y_seg - y_seg.mean()) ** 2)
|
|
246
|
+
r2 = 1 - ss_res / ss_tot if ss_tot > 0 else 0.0
|
|
247
|
+
return {"popt": popt, "y_fit": y_fit, "r2": r2,
|
|
248
|
+
"success": True, "error": None}
|
|
249
|
+
except Exception as e:
|
|
250
|
+
return {"popt": None, "y_fit": np.zeros_like(y_seg),
|
|
251
|
+
"r2": 0.0, "success": False, "error": str(e)}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dataset.py
|
|
3
|
+
----------
|
|
4
|
+
The universal Dataset container returned by every loader, plus format
|
|
5
|
+
detection and folder selection. No parsing logic lives here — that's in
|
|
6
|
+
loaders/.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from enum import Enum, auto
|
|
14
|
+
from tkinter import filedialog
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# Folder selection
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
def choose_file(parent_window=None) -> tuple[Optional[str], Optional[str]]:
|
|
25
|
+
"""
|
|
26
|
+
Opens a native folder selection dialog.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
(folder_path, folder_name) or (None, None) if cancelled.
|
|
31
|
+
"""
|
|
32
|
+
folder_path = filedialog.askdirectory(
|
|
33
|
+
parent=parent_window,
|
|
34
|
+
title="Open Lab Data Folder",
|
|
35
|
+
)
|
|
36
|
+
if not folder_path:
|
|
37
|
+
return None, None
|
|
38
|
+
return folder_path, os.path.basename(folder_path)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
# Universal output struct
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class Dataset:
|
|
47
|
+
"""Universal container returned by every loader."""
|
|
48
|
+
|
|
49
|
+
source_format: str # 'TDT' | 'Oxysoft'
|
|
50
|
+
folder_path: str
|
|
51
|
+
folder_name: str
|
|
52
|
+
|
|
53
|
+
# timing
|
|
54
|
+
sample_rate: float = 0.0 # Hz
|
|
55
|
+
num_samples: int = 0
|
|
56
|
+
duration_s: float = 0.0 # seconds
|
|
57
|
+
|
|
58
|
+
# signals – shape (num_channels, num_samples)
|
|
59
|
+
signals: Optional[np.ndarray] = None
|
|
60
|
+
|
|
61
|
+
# channel metadata
|
|
62
|
+
channel_names: list[str] = field(default_factory=list)
|
|
63
|
+
num_channels: int = 0
|
|
64
|
+
|
|
65
|
+
# events – list of dicts with at least {'label': str, 'sample': int}
|
|
66
|
+
events: list[dict] = field(default_factory=list)
|
|
67
|
+
|
|
68
|
+
# raw header / metadata blob (format-specific)
|
|
69
|
+
metadata: dict = field(default_factory=dict)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Format detection
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
class DataFormat(Enum):
|
|
77
|
+
TDT = auto()
|
|
78
|
+
OXYSOFT = auto()
|
|
79
|
+
UNKNOWN = auto()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
_TDT_EXTENSIONS = {'.tbk', '.tdx', '.tev', '.tsq', '.sev'}
|
|
83
|
+
_OXYSOFT_HEADER_MARKER = 'Datafile sample rate'
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def detect_format(folder_path: str) -> DataFormat:
|
|
87
|
+
"""
|
|
88
|
+
Inspect the contents of *folder_path* and return the matching DataFormat.
|
|
89
|
+
Priority: TDT first (proprietary extensions), then Oxysoft (.txt marker).
|
|
90
|
+
"""
|
|
91
|
+
entries = os.listdir(folder_path)
|
|
92
|
+
extensions = {os.path.splitext(e)[1].lower() for e in entries}
|
|
93
|
+
|
|
94
|
+
if extensions & _TDT_EXTENSIONS:
|
|
95
|
+
return DataFormat.TDT
|
|
96
|
+
|
|
97
|
+
txt_files = [e for e in entries if e.lower().endswith('.txt')]
|
|
98
|
+
for fname in txt_files:
|
|
99
|
+
fpath = os.path.join(folder_path, fname)
|
|
100
|
+
try:
|
|
101
|
+
with open(fpath, 'r', encoding='utf-8', errors='replace') as fh:
|
|
102
|
+
for _ in range(30):
|
|
103
|
+
if _OXYSOFT_HEADER_MARKER in fh.readline():
|
|
104
|
+
return DataFormat.OXYSOFT
|
|
105
|
+
except OSError:
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
return DataFormat.UNKNOWN
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def detect_format_file(file_path: str) -> DataFormat:
|
|
112
|
+
"""
|
|
113
|
+
Detect the format of a single file (as opposed to a folder).
|
|
114
|
+
Currently supports Oxysoft .txt exports.
|
|
115
|
+
"""
|
|
116
|
+
if not file_path.lower().endswith('.txt'):
|
|
117
|
+
return DataFormat.UNKNOWN
|
|
118
|
+
try:
|
|
119
|
+
with open(file_path, 'r', encoding='utf-8', errors='replace') as fh:
|
|
120
|
+
for _ in range(30):
|
|
121
|
+
if _OXYSOFT_HEADER_MARKER in fh.readline():
|
|
122
|
+
return DataFormat.OXYSOFT
|
|
123
|
+
except OSError:
|
|
124
|
+
pass
|
|
125
|
+
return DataFormat.UNKNOWN
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
file_parser.py
|
|
3
|
+
--------------
|
|
4
|
+
Top-level dispatcher: detects the recording format inside a selected
|
|
5
|
+
folder/file and routes to the matching loader, returning a universal
|
|
6
|
+
Dataset struct.
|
|
7
|
+
|
|
8
|
+
- TDT (Tucker-Davis Technologies) – loaders/tdt_loader.py
|
|
9
|
+
- Oxysoft / Artinis (Oxymon, OctaMon, PortaMon …) – loaders/oxysoft_loader.py
|
|
10
|
+
|
|
11
|
+
Format detection, the Dataset struct, and folder selection live in
|
|
12
|
+
dataset.py. The .pt2 EFNMR/MRI image format is a standalone parser in
|
|
13
|
+
loaders/pt2_loader.py (it returns a raw image array, not a Dataset).
|
|
14
|
+
|
|
15
|
+
Usage
|
|
16
|
+
-----
|
|
17
|
+
from file_parser import detect_format, load_dataset, DataFormat
|
|
18
|
+
|
|
19
|
+
fmt = detect_format(folder_path)
|
|
20
|
+
dataset = load_dataset(folder_path, fmt)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import os
|
|
26
|
+
from typing import Optional
|
|
27
|
+
|
|
28
|
+
from .dataset import Dataset, DataFormat, detect_format, detect_format_file
|
|
29
|
+
from .loaders.tdt_loader import load_tdt
|
|
30
|
+
from .loaders.oxysoft_loader import load_oxysoft, load_oxysoft_file
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load_dataset_file(file_path: str) -> Dataset:
|
|
34
|
+
"""Load a single file and return a Dataset."""
|
|
35
|
+
fmt = detect_format_file(file_path)
|
|
36
|
+
if fmt == DataFormat.OXYSOFT:
|
|
37
|
+
return load_oxysoft_file(file_path)
|
|
38
|
+
raise ValueError(f"Unrecognised file format: {file_path}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def load_dataset(folder_path: str, fmt: Optional[DataFormat] = None) -> Dataset:
|
|
42
|
+
"""
|
|
43
|
+
Load a recording folder and return a Dataset.
|
|
44
|
+
If *fmt* is None, detect_format() is called automatically.
|
|
45
|
+
"""
|
|
46
|
+
folder_name = os.path.basename(folder_path.rstrip('/\\'))
|
|
47
|
+
|
|
48
|
+
if fmt is None:
|
|
49
|
+
fmt = detect_format(folder_path)
|
|
50
|
+
|
|
51
|
+
if fmt is DataFormat.TDT:
|
|
52
|
+
return load_tdt(folder_path, folder_name)
|
|
53
|
+
elif fmt is DataFormat.OXYSOFT:
|
|
54
|
+
return load_oxysoft(folder_path, folder_name)
|
|
55
|
+
else:
|
|
56
|
+
raise ValueError(
|
|
57
|
+
f"Could not identify a supported data format in: {folder_path}\n"
|
|
58
|
+
"Expected TDT proprietary files (.Tbk/.tev/…) or an Oxysoft .txt export."
|
|
59
|
+
)
|