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