pyTRACTnmr 0.1.1b1__py3-none-any.whl → 0.1.2b2__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.
- pyTRACTnmr/main.py +4 -4
- pyTRACTnmr/processing.py +195 -33
- pyTRACTnmr/window.py +195 -43
- pytractnmr-0.1.2b2.dist-info/METADATA +120 -0
- pytractnmr-0.1.2b2.dist-info/RECORD +10 -0
- pytractnmr-0.1.2b2.dist-info/entry_points.txt +2 -0
- pytractnmr-0.1.2b2.dist-info/licenses/LICENSE +674 -0
- pytractnmr-0.1.1b1.dist-info/METADATA +0 -15
- pytractnmr-0.1.1b1.dist-info/RECORD +0 -9
- pytractnmr-0.1.1b1.dist-info/entry_points.txt +0 -2
- {pytractnmr-0.1.1b1.dist-info → pytractnmr-0.1.2b2.dist-info}/WHEEL +0 -0
pyTRACTnmr/window.py
CHANGED
|
@@ -21,17 +21,15 @@ from PySide6.QtWidgets import (
|
|
|
21
21
|
QHeaderView,
|
|
22
22
|
QMenu,
|
|
23
23
|
QSlider,
|
|
24
|
+
QComboBox,
|
|
24
25
|
)
|
|
25
26
|
from PySide6.QtGui import QAction
|
|
26
27
|
from PySide6.QtCore import Qt, QPoint
|
|
27
28
|
|
|
28
29
|
from matplotlib.widgets import SpanSelector
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
except ImportError:
|
|
33
|
-
from widgets import MplCanvas, CustomNavigationToolbar
|
|
34
|
-
import processing
|
|
30
|
+
|
|
31
|
+
from widgets import MplCanvas, CustomNavigationToolbar # type: ignore
|
|
32
|
+
import processing # type: ignore
|
|
35
33
|
|
|
36
34
|
|
|
37
35
|
class TractApp(QMainWindow):
|
|
@@ -41,13 +39,11 @@ class TractApp(QMainWindow):
|
|
|
41
39
|
self.resize(1200, 800)
|
|
42
40
|
|
|
43
41
|
# Data State
|
|
44
|
-
self.dic = None
|
|
45
|
-
self.data = None
|
|
46
|
-
self.proc_data = None
|
|
47
|
-
self.time_points = None
|
|
48
42
|
self.datasets: List[Dict[str, Any]] = []
|
|
49
43
|
self.current_idx: int = -1
|
|
50
44
|
self.selector: Optional[SpanSelector] = None
|
|
45
|
+
self.baseline_nodes: List[float] = []
|
|
46
|
+
self.picking_baseline: bool = False
|
|
51
47
|
|
|
52
48
|
self.init_ui()
|
|
53
49
|
|
|
@@ -70,7 +66,17 @@ class TractApp(QMainWindow):
|
|
|
70
66
|
self.table_data = QTableWidget()
|
|
71
67
|
self.table_data.setColumnCount(9)
|
|
72
68
|
self.table_data.setHorizontalHeaderLabels(
|
|
73
|
-
[
|
|
69
|
+
[
|
|
70
|
+
"Experiment",
|
|
71
|
+
"Temperature (K)",
|
|
72
|
+
"Delays",
|
|
73
|
+
"Ra (Hz)",
|
|
74
|
+
"Rb (Hz)",
|
|
75
|
+
"Tau_C (ns)",
|
|
76
|
+
"Err Ra",
|
|
77
|
+
"Err Rb",
|
|
78
|
+
"Err Tau_C",
|
|
79
|
+
]
|
|
74
80
|
)
|
|
75
81
|
self.table_data.horizontalHeader().setSectionResizeMode(
|
|
76
82
|
QHeaderView.ResizeMode.Stretch
|
|
@@ -101,6 +107,7 @@ class TractApp(QMainWindow):
|
|
|
101
107
|
# Top: Spectrum
|
|
102
108
|
self.canvas_spec = MplCanvas(self)
|
|
103
109
|
self.toolbar_spec = CustomNavigationToolbar(self.canvas_spec, self)
|
|
110
|
+
self.canvas_spec.mpl_connect("button_press_event", self.on_canvas_click)
|
|
104
111
|
widget_spec = QWidget()
|
|
105
112
|
layout_spec = QVBoxLayout()
|
|
106
113
|
lbl_spec = QLabel("<b>Processed Spectrum (Phase Check)</b>")
|
|
@@ -161,6 +168,14 @@ class TractApp(QMainWindow):
|
|
|
161
168
|
self.input_points = QLineEdit("2048")
|
|
162
169
|
self.input_points.editingFinished.connect(self.process_data)
|
|
163
170
|
|
|
171
|
+
self.combo_apod = QComboBox()
|
|
172
|
+
self.combo_apod.addItems(["Sine Bell (sp)", "Exponential (em)"])
|
|
173
|
+
self.combo_apod.currentIndexChanged.connect(self.update_apod_ui)
|
|
174
|
+
self.combo_apod.currentIndexChanged.connect(self.process_data)
|
|
175
|
+
|
|
176
|
+
self.input_lb = QLineEdit("5.0")
|
|
177
|
+
self.input_lb.editingFinished.connect(self.process_data)
|
|
178
|
+
|
|
164
179
|
self.input_off = QLineEdit("0.35")
|
|
165
180
|
self.input_off.editingFinished.connect(self.process_data)
|
|
166
181
|
|
|
@@ -186,15 +201,31 @@ class TractApp(QMainWindow):
|
|
|
186
201
|
self.create_slider_layout(self.slider_p1_fine, self.input_p1),
|
|
187
202
|
)
|
|
188
203
|
layout_t1.addRow(QLabel("<b>Apodization & ZF</b>"))
|
|
204
|
+
layout_t1.addRow("Function:", self.combo_apod)
|
|
189
205
|
layout_t1.addRow("Points (ZF):", self.input_points)
|
|
206
|
+
layout_t1.addRow("Line Broadening (Hz):", self.input_lb)
|
|
190
207
|
layout_t1.addRow("Sine Offset:", self.input_off)
|
|
191
208
|
layout_t1.addRow("Sine End:", self.input_end)
|
|
192
209
|
layout_t1.addRow("Sine Power:", self.input_pow)
|
|
193
210
|
layout_t1.addRow(QLabel("<b>Integration Range</b>"))
|
|
194
211
|
layout_t1.addRow("Start (ppm):", self.input_int_start)
|
|
195
212
|
layout_t1.addRow("End (ppm):", self.input_int_end)
|
|
213
|
+
|
|
214
|
+
layout_t1.addRow(QLabel("<b>Baseline Correction</b>"))
|
|
215
|
+
self.btn_pick_bl = QPushButton("Pick Nodes")
|
|
216
|
+
self.btn_pick_bl.setCheckable(True)
|
|
217
|
+
self.btn_pick_bl.clicked.connect(self.toggle_picking)
|
|
218
|
+
self.btn_clear_bl = QPushButton("Clear Nodes")
|
|
219
|
+
self.btn_clear_bl.clicked.connect(self.clear_baseline)
|
|
220
|
+
layout_bl = QHBoxLayout()
|
|
221
|
+
layout_bl.addWidget(self.btn_pick_bl)
|
|
222
|
+
layout_bl.addWidget(self.btn_clear_bl)
|
|
223
|
+
layout_t1.addRow(layout_bl)
|
|
224
|
+
|
|
196
225
|
tab1.setLayout(layout_t1)
|
|
197
226
|
|
|
227
|
+
self.update_apod_ui()
|
|
228
|
+
|
|
198
229
|
# Tab 2: Fitting
|
|
199
230
|
tab2 = QWidget()
|
|
200
231
|
layout_t2 = QFormLayout()
|
|
@@ -235,6 +266,30 @@ class TractApp(QMainWindow):
|
|
|
235
266
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
236
267
|
return widget
|
|
237
268
|
|
|
269
|
+
def update_apod_ui(self) -> None:
|
|
270
|
+
is_sp = self.combo_apod.currentText().startswith("Sine")
|
|
271
|
+
layout = self.input_off.parent().layout()
|
|
272
|
+
if isinstance(layout, QFormLayout):
|
|
273
|
+
layout.setRowVisible(self.input_lb, not is_sp)
|
|
274
|
+
layout.setRowVisible(self.input_off, is_sp)
|
|
275
|
+
layout.setRowVisible(self.input_end, is_sp)
|
|
276
|
+
layout.setRowVisible(self.input_pow, is_sp)
|
|
277
|
+
|
|
278
|
+
def toggle_picking(self, checked: bool) -> None:
|
|
279
|
+
self.picking_baseline = checked
|
|
280
|
+
self.process_data()
|
|
281
|
+
|
|
282
|
+
def clear_baseline(self) -> None:
|
|
283
|
+
self.baseline_nodes = []
|
|
284
|
+
self.process_data()
|
|
285
|
+
|
|
286
|
+
def on_canvas_click(self, event) -> None:
|
|
287
|
+
if not self.picking_baseline or event.inaxes != self.canvas_spec.axes:
|
|
288
|
+
return
|
|
289
|
+
if event.button == 1 and event.xdata is not None:
|
|
290
|
+
self.baseline_nodes.append(event.xdata)
|
|
291
|
+
self.process_data()
|
|
292
|
+
|
|
238
293
|
def update_phase_from_text(self) -> None:
|
|
239
294
|
try:
|
|
240
295
|
p0 = float(self.input_p0.text())
|
|
@@ -266,8 +321,13 @@ class TractApp(QMainWindow):
|
|
|
266
321
|
try:
|
|
267
322
|
delay_list = None
|
|
268
323
|
if not os.path.exists(os.path.join(folder, "vdlist")):
|
|
324
|
+
QMessageBox.information(
|
|
325
|
+
self,
|
|
326
|
+
"Delay List Missing",
|
|
327
|
+
"The standard 'vdlist' file was not found. Please select a delay list file manually.",
|
|
328
|
+
)
|
|
269
329
|
delay_list, _ = QFileDialog.getOpenFileName(
|
|
270
|
-
self, "
|
|
330
|
+
self, "Select delay list file", folder
|
|
271
331
|
)
|
|
272
332
|
|
|
273
333
|
tb = processing.TractBruker(folder, delay_list=delay_list)
|
|
@@ -289,6 +349,11 @@ class TractApp(QMainWindow):
|
|
|
289
349
|
if self.current_idx < 0:
|
|
290
350
|
return
|
|
291
351
|
try:
|
|
352
|
+
# Capture current zoom level
|
|
353
|
+
xlim = self.canvas_spec.axes.get_xlim()
|
|
354
|
+
ylim = self.canvas_spec.axes.get_ylim()
|
|
355
|
+
has_zoom = len(self.canvas_spec.axes.lines) > 0
|
|
356
|
+
|
|
292
357
|
p0 = self.slider_p0_coarse.value() + (self.slider_p0_fine.value() / 10.0)
|
|
293
358
|
p1 = self.slider_p1_coarse.value() + (self.slider_p1_fine.value() / 10.0)
|
|
294
359
|
|
|
@@ -296,6 +361,11 @@ class TractApp(QMainWindow):
|
|
|
296
361
|
self.input_p1.setText(f"{p1:.1f}")
|
|
297
362
|
|
|
298
363
|
points = int(self.input_points.text())
|
|
364
|
+
apod_func = (
|
|
365
|
+
"sp" if self.combo_apod.currentText().startswith("Sine") else "em"
|
|
366
|
+
)
|
|
367
|
+
lb = float(self.input_lb.text())
|
|
368
|
+
|
|
299
369
|
off = float(self.input_off.text())
|
|
300
370
|
end = float(self.input_end.text())
|
|
301
371
|
pow_val = float(self.input_pow.text())
|
|
@@ -305,8 +375,24 @@ class TractApp(QMainWindow):
|
|
|
305
375
|
self.datasets[self.current_idx]["p1"] = p1
|
|
306
376
|
|
|
307
377
|
tb = self.datasets[self.current_idx]["handler"]
|
|
378
|
+
|
|
379
|
+
nodes_idx = []
|
|
380
|
+
if self.baseline_nodes and tb.unit_converter:
|
|
381
|
+
for ppm in self.baseline_nodes:
|
|
382
|
+
idx = tb.unit_converter(ppm, "ppm")
|
|
383
|
+
nodes_idx.append(int(idx))
|
|
384
|
+
nodes_idx.sort()
|
|
385
|
+
|
|
308
386
|
trace = tb.process_first_trace(
|
|
309
|
-
p0,
|
|
387
|
+
p0,
|
|
388
|
+
p1,
|
|
389
|
+
points=points,
|
|
390
|
+
apod_func=apod_func,
|
|
391
|
+
lb=lb,
|
|
392
|
+
off=off,
|
|
393
|
+
end=end,
|
|
394
|
+
pow=pow_val,
|
|
395
|
+
nodes=nodes_idx,
|
|
310
396
|
)
|
|
311
397
|
|
|
312
398
|
self.canvas_spec.axes.clear()
|
|
@@ -320,21 +406,35 @@ class TractApp(QMainWindow):
|
|
|
320
406
|
self.canvas_spec.axes.plot(trace, label="First Plane")
|
|
321
407
|
self.canvas_spec.axes.legend()
|
|
322
408
|
|
|
323
|
-
self.
|
|
324
|
-
self.canvas_spec.axes
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
409
|
+
for node in self.baseline_nodes:
|
|
410
|
+
self.canvas_spec.axes.axvline(
|
|
411
|
+
x=node, color="r", linestyle="--", alpha=0.5
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
if has_zoom:
|
|
415
|
+
self.canvas_spec.axes.set_xlim(xlim)
|
|
416
|
+
self.canvas_spec.axes.set_ylim(ylim)
|
|
417
|
+
|
|
418
|
+
if not self.picking_baseline:
|
|
419
|
+
self.selector = SpanSelector(
|
|
420
|
+
self.canvas_spec.axes,
|
|
421
|
+
self.on_span_select,
|
|
422
|
+
"horizontal",
|
|
423
|
+
useblit=True,
|
|
424
|
+
props=dict(alpha=0.2, facecolor="green"),
|
|
425
|
+
interactive=True,
|
|
426
|
+
drag_from_anywhere=True,
|
|
427
|
+
)
|
|
428
|
+
else:
|
|
429
|
+
self.selector = None
|
|
430
|
+
|
|
431
|
+
if self.selector:
|
|
432
|
+
try:
|
|
433
|
+
s = float(self.input_int_start.text())
|
|
434
|
+
e = float(self.input_int_end.text())
|
|
435
|
+
self.selector.extents = (min(s, e), max(s, e))
|
|
436
|
+
except ValueError:
|
|
437
|
+
pass
|
|
338
438
|
|
|
339
439
|
self.canvas_spec.draw()
|
|
340
440
|
except Exception as e:
|
|
@@ -349,6 +449,11 @@ class TractApp(QMainWindow):
|
|
|
349
449
|
p1 = self.slider_p1_coarse.value() + (self.slider_p1_fine.value() / 10.0)
|
|
350
450
|
|
|
351
451
|
points = int(self.input_points.text())
|
|
452
|
+
apod_func = (
|
|
453
|
+
"sp" if self.combo_apod.currentText().startswith("Sine") else "em"
|
|
454
|
+
)
|
|
455
|
+
lb = float(self.input_lb.text())
|
|
456
|
+
|
|
352
457
|
off = float(self.input_off.text())
|
|
353
458
|
end_param = float(self.input_end.text())
|
|
354
459
|
pow_val = float(self.input_pow.text())
|
|
@@ -369,24 +474,52 @@ class TractApp(QMainWindow):
|
|
|
369
474
|
n_boot = 1000
|
|
370
475
|
self.input_bootstraps.setText("1000")
|
|
371
476
|
|
|
372
|
-
|
|
477
|
+
nodes_idx = []
|
|
478
|
+
if self.baseline_nodes and tb.unit_converter:
|
|
479
|
+
for ppm in self.baseline_nodes:
|
|
480
|
+
idx = tb.unit_converter(ppm, "ppm")
|
|
481
|
+
nodes_idx.append(int(idx))
|
|
482
|
+
nodes_idx.sort()
|
|
483
|
+
|
|
484
|
+
tb.split_process(
|
|
485
|
+
p0,
|
|
486
|
+
p1,
|
|
487
|
+
points=points,
|
|
488
|
+
apod_func=apod_func,
|
|
489
|
+
lb=lb,
|
|
490
|
+
off=off,
|
|
491
|
+
end=end_param,
|
|
492
|
+
pow=pow_val,
|
|
493
|
+
nodes=nodes_idx,
|
|
494
|
+
)
|
|
373
495
|
tb.integrate_ppm(start_ppm, end_ppm)
|
|
374
496
|
tb.calc_relaxation()
|
|
375
497
|
|
|
376
498
|
b0 = float(self.input_field.text()) if self.input_field.text() else None
|
|
377
499
|
tb.calc_tc(B0=b0, S2=s2_val, n_bootstrap=n_boot)
|
|
378
500
|
|
|
379
|
-
x, y_a, y_b, popt_a, popt_b = tb.get_fit_data()
|
|
501
|
+
x, y_a, y_b, popt_a, popt_b, pcov_a, pcov_b = tb.get_fit_data()
|
|
380
502
|
|
|
381
503
|
self.canvas_fit.axes.clear()
|
|
382
504
|
self.canvas_fit.axes.plot(x, y_a, "bo", label=r"$\alpha -spin\ state$")
|
|
383
505
|
self.canvas_fit.axes.plot(x, y_b, "ro", label=r"$\beta -spin\ state$")
|
|
384
|
-
|
|
385
|
-
|
|
506
|
+
|
|
507
|
+
# Smooth lines for fit and CI
|
|
508
|
+
x_smooth = np.linspace(0, np.max(x) * 1.1, 100)
|
|
509
|
+
fit_a = processing.TractBruker._relax(x_smooth, *popt_a)
|
|
510
|
+
ci_a = tb.calc_confidence_interval(x_smooth, popt_a, pcov_a)
|
|
511
|
+
fit_b = processing.TractBruker._relax(x_smooth, *popt_b)
|
|
512
|
+
ci_b = tb.calc_confidence_interval(x_smooth, popt_b, pcov_b)
|
|
513
|
+
|
|
514
|
+
self.canvas_fit.axes.plot(x_smooth, fit_a, "b-")
|
|
515
|
+
self.canvas_fit.axes.fill_between(
|
|
516
|
+
x_smooth, fit_a - ci_a, fit_a + ci_a, color="b", alpha=0.2
|
|
386
517
|
)
|
|
387
|
-
self.canvas_fit.axes.plot(
|
|
388
|
-
|
|
518
|
+
self.canvas_fit.axes.plot(x_smooth, fit_b, "r-")
|
|
519
|
+
self.canvas_fit.axes.fill_between(
|
|
520
|
+
x_smooth, fit_b - ci_b, fit_b + ci_b, color="r", alpha=0.2
|
|
389
521
|
)
|
|
522
|
+
|
|
390
523
|
self.canvas_fit.axes.set_xlabel("Delay (s)")
|
|
391
524
|
self.canvas_fit.axes.set_ylabel(r"$I/I_0$")
|
|
392
525
|
|
|
@@ -409,7 +542,7 @@ class TractApp(QMainWindow):
|
|
|
409
542
|
if not path:
|
|
410
543
|
return
|
|
411
544
|
try:
|
|
412
|
-
with open(path,
|
|
545
|
+
with open(path, "w", newline="") as f:
|
|
413
546
|
writer = csv.writer(f)
|
|
414
547
|
headers = []
|
|
415
548
|
for col in range(self.table_data.columnCount()):
|
|
@@ -417,7 +550,12 @@ class TractApp(QMainWindow):
|
|
|
417
550
|
headers.append(item.text() if item else "")
|
|
418
551
|
writer.writerow(headers)
|
|
419
552
|
for row in range(self.table_data.rowCount()):
|
|
420
|
-
row_data = [
|
|
553
|
+
row_data = [
|
|
554
|
+
self.table_data.item(row, col).text()
|
|
555
|
+
if self.table_data.item(row, col)
|
|
556
|
+
else ""
|
|
557
|
+
for col in range(self.table_data.columnCount())
|
|
558
|
+
]
|
|
421
559
|
writer.writerow(row_data)
|
|
422
560
|
except Exception as e:
|
|
423
561
|
QMessageBox.critical(self, "Export Error", str(e))
|
|
@@ -434,7 +572,7 @@ class TractApp(QMainWindow):
|
|
|
434
572
|
# Temperature
|
|
435
573
|
try:
|
|
436
574
|
temp = ds["handler"].attributes["acqus"]["TE"]
|
|
437
|
-
except
|
|
575
|
+
except KeyError, TypeError:
|
|
438
576
|
temp = "N/A"
|
|
439
577
|
item_temp = QTableWidgetItem(str(temp))
|
|
440
578
|
item_temp.setFlags(item_temp.flags() & ~Qt.ItemFlag.ItemIsEditable)
|
|
@@ -503,7 +641,7 @@ class TractApp(QMainWindow):
|
|
|
503
641
|
# Update Field Strength from parameters
|
|
504
642
|
try:
|
|
505
643
|
self.input_field.setText("{:.2f}".format(tb.attributes["acqus"]["SFO1"]))
|
|
506
|
-
except
|
|
644
|
+
except KeyError, AttributeError:
|
|
507
645
|
pass
|
|
508
646
|
|
|
509
647
|
self.slider_p0_coarse.blockSignals(True)
|
|
@@ -526,6 +664,11 @@ class TractApp(QMainWindow):
|
|
|
526
664
|
self.slider_p1_coarse.blockSignals(False)
|
|
527
665
|
self.slider_p1_fine.blockSignals(False)
|
|
528
666
|
|
|
667
|
+
self.baseline_nodes = []
|
|
668
|
+
self.picking_baseline = False
|
|
669
|
+
self.btn_pick_bl.setChecked(False)
|
|
670
|
+
|
|
671
|
+
self.canvas_spec.axes.clear()
|
|
529
672
|
self.process_data()
|
|
530
673
|
|
|
531
674
|
# Update fit display
|
|
@@ -534,14 +677,23 @@ class TractApp(QMainWindow):
|
|
|
534
677
|
|
|
535
678
|
if hasattr(tb, "Ra") and hasattr(tb, "popt_alpha"):
|
|
536
679
|
try:
|
|
537
|
-
x, y_a, y_b, popt_a, popt_b = tb.get_fit_data()
|
|
680
|
+
x, y_a, y_b, popt_a, popt_b, pcov_a, pcov_b = tb.get_fit_data()
|
|
538
681
|
self.canvas_fit.axes.plot(x, y_a, "bo", label="Alpha (Anti-TROSY)")
|
|
539
682
|
self.canvas_fit.axes.plot(x, y_b, "ro", label="Beta (TROSY)")
|
|
540
|
-
|
|
541
|
-
|
|
683
|
+
|
|
684
|
+
x_smooth = np.linspace(0, np.max(x) * 1.1, 100)
|
|
685
|
+
fit_a = processing.TractBruker._relax(x_smooth, *popt_a)
|
|
686
|
+
ci_a = tb.calc_confidence_interval(x_smooth, popt_a, pcov_a)
|
|
687
|
+
fit_b = processing.TractBruker._relax(x_smooth, *popt_b)
|
|
688
|
+
ci_b = tb.calc_confidence_interval(x_smooth, popt_b, pcov_b)
|
|
689
|
+
|
|
690
|
+
self.canvas_fit.axes.plot(x_smooth, fit_a, "b-")
|
|
691
|
+
self.canvas_fit.axes.fill_between(
|
|
692
|
+
x_smooth, fit_a - ci_a, fit_a + ci_a, color="b", alpha=0.2
|
|
542
693
|
)
|
|
543
|
-
self.canvas_fit.axes.plot(
|
|
544
|
-
|
|
694
|
+
self.canvas_fit.axes.plot(x_smooth, fit_b, "r-")
|
|
695
|
+
self.canvas_fit.axes.fill_between(
|
|
696
|
+
x_smooth, fit_b - ci_b, fit_b + ci_b, color="r", alpha=0.2
|
|
545
697
|
)
|
|
546
698
|
|
|
547
699
|
tau_c_val = getattr(tb, "tau_c", 0.0)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyTRACTnmr
|
|
3
|
+
Version: 0.1.2b2
|
|
4
|
+
Summary: A simple gui based application to process and analyse TRACT data from NMR spectroscopy.
|
|
5
|
+
Project-URL: Homepage, https://github.com/debadutta-patra/pyTRACTnmr
|
|
6
|
+
Project-URL: Repository, https://github.com/debadutta-patra/pyTRACTnmr
|
|
7
|
+
Project-URL: Issues, https://github.com/debadutta-patra/pyTRACTnmr/issues
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.14
|
|
10
|
+
Requires-Dist: matplotlib>=3.10.8
|
|
11
|
+
Requires-Dist: nmrglue>=0.11
|
|
12
|
+
Requires-Dist: numpy>=2.4.2
|
|
13
|
+
Requires-Dist: pyside6-stubs>=6.7.3.0
|
|
14
|
+
Requires-Dist: pyside6>=6.10.2
|
|
15
|
+
Requires-Dist: scipy-stubs>=1.17.0.2
|
|
16
|
+
Requires-Dist: scipy>=1.17.0
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# pyTRACTnmr
|
|
20
|
+
|
|
21
|
+
**pyTRACTnmr** is a graphical user interface (GUI) application designed for the processing and analysis of TRACT (TROSY for Rotational Correlation Times) experiments in NMR spectroscopy. It provides a streamlined workflow to go from raw Bruker data to calculated rotational correlation times ($\tau_c$) with robust error estimation. Currently this only supports data collected with Bruker spectrometers using pulseprogram `tractf3gpphwg`.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
- **User-Friendly Interface**: Built with PySide6 for a responsive and native experience.
|
|
26
|
+
- **Bruker Data Import**: Directly load Bruker experiment directories.
|
|
27
|
+
- **Interactive Spectral Processing**:
|
|
28
|
+
- Real-time 0th and 1st order phase correction.
|
|
29
|
+
- Adjustable Apodization (Sine-bell squared) and Zero Filling.
|
|
30
|
+
- **Interactive Region Selection**: Drag directly on the spectrum to define integration limits.
|
|
31
|
+
- **Advanced Analysis**:
|
|
32
|
+
- Automatic calculation of relaxation rates ($R_\alpha$ and $R_\beta$).
|
|
33
|
+
- Determination of Rotational Correlation Time ($\tau_c$).
|
|
34
|
+
- **Bootstrap Error Analysis**: rigorous uncertainty estimation for $\tau_c$.
|
|
35
|
+
- **Data Management**:
|
|
36
|
+
- Tabular view of multiple loaded experiments.
|
|
37
|
+
- Context menu to export results to CSV.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
### Prerequisites
|
|
42
|
+
|
|
43
|
+
* Python 3.14 or higher recommended (Though it is not tested it should run with earlier version of Python 3).
|
|
44
|
+
* [uv](https://github.com/astral-sh/uv) (optional, but recommended for building).
|
|
45
|
+
|
|
46
|
+
### Installation
|
|
47
|
+
#### Quick Start with uv
|
|
48
|
+
The fastest way to try pyTRACTnmr without installation is:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
uvx pytractnmr
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
#### Using pip
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install pyTRACTnmr
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
#### From Source
|
|
61
|
+
|
|
62
|
+
1. **Clone the repository:**
|
|
63
|
+
```bash
|
|
64
|
+
git clone https://github.com/debadutta-patra/pyTRACTnmr.git
|
|
65
|
+
cd pyTRACTnmr
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
2. **Install the package:**
|
|
69
|
+
|
|
70
|
+
Using `uv` (fastest):
|
|
71
|
+
```bash
|
|
72
|
+
uv pip install .
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Using standard `pip`:
|
|
76
|
+
```bash
|
|
77
|
+
pip install .
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Usage
|
|
81
|
+
|
|
82
|
+
### Launching the App
|
|
83
|
+
|
|
84
|
+
After installation, you can start the application from the terminal:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pytractnmr
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Or run it as a python module:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
python -m pyTRACTnmr.main
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Analysis Workflow
|
|
97
|
+
|
|
98
|
+
1. **Load Data**: Click **"Load Bruker Directory"** and select your experiment folder.
|
|
99
|
+
2. **Process**:
|
|
100
|
+
* Use the **Processing** tab to adjust phase correction sliders.
|
|
101
|
+
* Drag the **green selection box** on the top spectrum plot to define the integration region (Start/End ppm).
|
|
102
|
+
3. **Fit**:
|
|
103
|
+
* Switch to the **Fitting** tab.
|
|
104
|
+
* Input experimental parameters (Field Strength, CSA, etc.).
|
|
105
|
+
* Set the number of **Bootstraps** (e.g., 1000).
|
|
106
|
+
* Click **"Calculate Tau_c"**.
|
|
107
|
+
4. **Export**:
|
|
108
|
+
* Right-click the results table to **Export Table to CSV**.
|
|
109
|
+
|
|
110
|
+
## Dependencies
|
|
111
|
+
|
|
112
|
+
* `PySide6`
|
|
113
|
+
* `numpy`
|
|
114
|
+
* `scipy`
|
|
115
|
+
* `matplotlib`
|
|
116
|
+
* `nmrglue`
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
This project is licensed under the GNU General Public License v3.0 License - see the LICENSE file for details.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
pyTRACTnmr/__init__.py,sha256=5rxO0E3s6JQ3X_bBwWIR3tOVEZrS4ZIC-_LZPemSnmQ,24
|
|
2
|
+
pyTRACTnmr/main.py,sha256=YPoQdIN0VIOknnAgWAHBk1ZdefID4NFa8jFY8piOZEM,253
|
|
3
|
+
pyTRACTnmr/processing.py,sha256=kH9tW5JhtdIYdoADGhLACX8BNGv4M5H_QX1O73z1I-Q,15466
|
|
4
|
+
pyTRACTnmr/widgets.py,sha256=DlSSWUZ_soVfeXvkm5s26zPKoxREUW-4x5__vvXtpj4,2818
|
|
5
|
+
pyTRACTnmr/window.py,sha256=9pu6LNJ4mskJYnHWmlk_NdHAAavK84jlqm0WGchNYe8,30061
|
|
6
|
+
pytractnmr-0.1.2b2.dist-info/METADATA,sha256=XTtlQlPSvPzDQpeTwVH-zkVSt5SMLmtmzWMsRomb9YI,3661
|
|
7
|
+
pytractnmr-0.1.2b2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
8
|
+
pytractnmr-0.1.2b2.dist-info/entry_points.txt,sha256=nub_92b4LtA_hmf3wD1ZQv6rbjBX24fHI0DtbZg5A1M,52
|
|
9
|
+
pytractnmr-0.1.2b2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
10
|
+
pytractnmr-0.1.2b2.dist-info/RECORD,,
|