opennmr 0.1.0__tar.gz

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 (406) hide show
  1. opennmr-0.1.0/LICENSE +21 -0
  2. opennmr-0.1.0/PKG-INFO +449 -0
  3. opennmr-0.1.0/README.md +418 -0
  4. opennmr-0.1.0/pyproject.toml +60 -0
  5. opennmr-0.1.0/setup.cfg +4 -0
  6. opennmr-0.1.0/src/opennmr/__init__.py +25 -0
  7. opennmr-0.1.0/src/opennmr/_version.py +22 -0
  8. opennmr-0.1.0/src/opennmr/core/__init__.py +4 -0
  9. opennmr-0.1.0/src/opennmr/core/acquisition.py +120 -0
  10. opennmr-0.1.0/src/opennmr/core/fit_progress.py +504 -0
  11. opennmr-0.1.0/src/opennmr/core/registry.py +300 -0
  12. opennmr-0.1.0/src/opennmr/core/spectrum.py +172 -0
  13. opennmr-0.1.0/src/opennmr/gui/__init__.py +0 -0
  14. opennmr-0.1.0/src/opennmr/gui/app.py +50 -0
  15. opennmr-0.1.0/src/opennmr/gui/arithmetic_panel.py +320 -0
  16. opennmr-0.1.0/src/opennmr/gui/batch_relaxation_panel.py +313 -0
  17. opennmr-0.1.0/src/opennmr/gui/combined_panels.py +2671 -0
  18. opennmr-0.1.0/src/opennmr/gui/combined_section.py +69 -0
  19. opennmr-0.1.0/src/opennmr/gui/compare_2d.py +320 -0
  20. opennmr-0.1.0/src/opennmr/gui/cp_buildup_panel.py +810 -0
  21. opennmr-0.1.0/src/opennmr/gui/cpmg_panel.py +367 -0
  22. opennmr-0.1.0/src/opennmr/gui/curve_fit_plot_box.py +161 -0
  23. opennmr-0.1.0/src/opennmr/gui/czjzek_panels.py +1009 -0
  24. opennmr-0.1.0/src/opennmr/gui/data_directory.py +56 -0
  25. opennmr-0.1.0/src/opennmr/gui/dft_panel.py +1022 -0
  26. opennmr-0.1.0/src/opennmr/gui/dnp_panel.py +1080 -0
  27. opennmr-0.1.0/src/opennmr/gui/drift_panel.py +443 -0
  28. opennmr-0.1.0/src/opennmr/gui/dynamics_panels.py +1520 -0
  29. opennmr-0.1.0/src/opennmr/gui/fact_box.py +124 -0
  30. opennmr-0.1.0/src/opennmr/gui/fit_display_panel.py +268 -0
  31. opennmr-0.1.0/src/opennmr/gui/fit_models_panel.py +388 -0
  32. opennmr-0.1.0/src/opennmr/gui/fit_progress_dialog.py +126 -0
  33. opennmr-0.1.0/src/opennmr/gui/fit_view_dialog.py +107 -0
  34. opennmr-0.1.0/src/opennmr/gui/inset_dialog.py +263 -0
  35. opennmr-0.1.0/src/opennmr/gui/jcoupling_panels.py +2244 -0
  36. opennmr-0.1.0/src/opennmr/gui/loader.py +146 -0
  37. opennmr-0.1.0/src/opennmr/gui/main_window.py +29527 -0
  38. opennmr-0.1.0/src/opennmr/gui/manual_baseline_box.py +434 -0
  39. opennmr-0.1.0/src/opennmr/gui/multi_fit_panel.py +1516 -0
  40. opennmr-0.1.0/src/opennmr/gui/offset_tracker_panel.py +841 -0
  41. opennmr-0.1.0/src/opennmr/gui/panel_state.py +314 -0
  42. opennmr-0.1.0/src/opennmr/gui/periodic_table_panel.py +946 -0
  43. opennmr-0.1.0/src/opennmr/gui/plot_options_dialog.py +36 -0
  44. opennmr-0.1.0/src/opennmr/gui/pnmr_export_row.py +67 -0
  45. opennmr-0.1.0/src/opennmr/gui/pnmr_fit_panel.py +1178 -0
  46. opennmr-0.1.0/src/opennmr/gui/pnmr_panel.py +1559 -0
  47. opennmr-0.1.0/src/opennmr/gui/procno_dialog.py +105 -0
  48. opennmr-0.1.0/src/opennmr/gui/progress.py +253 -0
  49. opennmr-0.1.0/src/opennmr/gui/quadrupolar_panels.py +839 -0
  50. opennmr-0.1.0/src/opennmr/gui/regularisation_panel.py +630 -0
  51. opennmr-0.1.0/src/opennmr/gui/report_format.py +314 -0
  52. opennmr-0.1.0/src/opennmr/gui/resources/facts.dat +0 -0
  53. opennmr-0.1.0/src/opennmr/gui/resources/launcher_advanced_processing.png +0 -0
  54. opennmr-0.1.0/src/opennmr/gui/resources/launcher_applications.png +0 -0
  55. opennmr-0.1.0/src/opennmr/gui/resources/launcher_comparison.png +0 -0
  56. opennmr-0.1.0/src/opennmr/gui/resources/launcher_fitting.png +0 -0
  57. opennmr-0.1.0/src/opennmr/gui/resources/launcher_integrate.png +0 -0
  58. opennmr-0.1.0/src/opennmr/gui/resources/launcher_load_save_compare.png +0 -0
  59. opennmr-0.1.0/src/opennmr/gui/resources/launcher_periodictable.png +0 -0
  60. opennmr-0.1.0/src/opennmr/gui/resources/launcher_processing.png +0 -0
  61. opennmr-0.1.0/src/opennmr/gui/resources/launcher_relaxation.png +0 -0
  62. opennmr-0.1.0/src/opennmr/gui/resources/launcher_settings.png +0 -0
  63. opennmr-0.1.0/src/opennmr/gui/resources/launcher_simulations.png +0 -0
  64. opennmr-0.1.0/src/opennmr/gui/resources/logo.png +0 -0
  65. opennmr-0.1.0/src/opennmr/gui/resources/toolbar/compare.png +0 -0
  66. opennmr-0.1.0/src/opennmr/gui/resources/toolbar/curve_fitting.png +0 -0
  67. opennmr-0.1.0/src/opennmr/gui/resources/toolbar/load.png +0 -0
  68. opennmr-0.1.0/src/opennmr/gui/resources/toolbar/processing.png +0 -0
  69. opennmr-0.1.0/src/opennmr/gui/resources/toolbar/settings.png +0 -0
  70. opennmr-0.1.0/src/opennmr/gui/resources/toolbar/simulate.png +0 -0
  71. opennmr-0.1.0/src/opennmr/gui/resources/toolbar/spectrum_fitting.png +0 -0
  72. opennmr-0.1.0/src/opennmr/gui/session.py +356 -0
  73. opennmr-0.1.0/src/opennmr/gui/shear_panel.py +542 -0
  74. opennmr-0.1.0/src/opennmr/gui/snr_panel.py +1106 -0
  75. opennmr-0.1.0/src/opennmr/gui/state_dialog.py +184 -0
  76. opennmr-0.1.0/src/opennmr/gui/stored_integrals_dialog.py +116 -0
  77. opennmr-0.1.0/src/opennmr/gui/style.py +348 -0
  78. opennmr-0.1.0/src/opennmr/gui/tensor_style_dialog.py +227 -0
  79. opennmr-0.1.0/src/opennmr/gui/tensor_view_panel.py +117 -0
  80. opennmr-0.1.0/src/opennmr/gui/welcome_wizard.py +285 -0
  81. opennmr-0.1.0/src/opennmr/gui/widgets/__init__.py +0 -0
  82. opennmr-0.1.0/src/opennmr/gui/widgets/advice_box.py +110 -0
  83. opennmr-0.1.0/src/opennmr/gui/widgets/canvas.py +9010 -0
  84. opennmr-0.1.0/src/opennmr/gui/widgets/collapsible_section.py +239 -0
  85. opennmr-0.1.0/src/opennmr/gui/widgets/data_browser.py +374 -0
  86. opennmr-0.1.0/src/opennmr/gui/widgets/drag_control.py +123 -0
  87. opennmr-0.1.0/src/opennmr/gui/widgets/field_input.py +234 -0
  88. opennmr-0.1.0/src/opennmr/gui/widgets/mode_button.py +75 -0
  89. opennmr-0.1.0/src/opennmr/gui/widgets/notes_editor.py +160 -0
  90. opennmr-0.1.0/src/opennmr/gui/widgets/ranges_edit.py +43 -0
  91. opennmr-0.1.0/src/opennmr/gui/widgets/reflow.py +460 -0
  92. opennmr-0.1.0/src/opennmr/gui/widgets/relative_spinbox.py +57 -0
  93. opennmr-0.1.0/src/opennmr/gui/widgets/report_label.py +75 -0
  94. opennmr-0.1.0/src/opennmr/gui/widgets/side_drawer.py +138 -0
  95. opennmr-0.1.0/src/opennmr/gui/widgets/sideband_hint.py +32 -0
  96. opennmr-0.1.0/src/opennmr/gui/widgets/sim_sites.py +317 -0
  97. opennmr-0.1.0/src/opennmr/gui/widgets/simulation_canvas.py +64 -0
  98. opennmr-0.1.0/src/opennmr/gui/widgets/typed_spinbox.py +182 -0
  99. opennmr-0.1.0/src/opennmr/gui/widgets/wheel_guard.py +79 -0
  100. opennmr-0.1.0/src/opennmr/gui/widgets/wrapping_tabs.py +360 -0
  101. opennmr-0.1.0/src/opennmr/io/__init__.py +20 -0
  102. opennmr-0.1.0/src/opennmr/io/base.py +122 -0
  103. opennmr-0.1.0/src/opennmr/io/bruker.py +2444 -0
  104. opennmr-0.1.0/src/opennmr/io/bruker_pfg.py +583 -0
  105. opennmr-0.1.0/src/opennmr/io/cloud.py +133 -0
  106. opennmr-0.1.0/src/opennmr/io/digital_filter.py +173 -0
  107. opennmr-0.1.0/src/opennmr/io/history.py +1267 -0
  108. opennmr-0.1.0/src/opennmr/io/jcampdx.py +154 -0
  109. opennmr-0.1.0/src/opennmr/io/jeol.py +567 -0
  110. opennmr-0.1.0/src/opennmr/io/lists.py +288 -0
  111. opennmr-0.1.0/src/opennmr/io/persistence.py +330 -0
  112. opennmr-0.1.0/src/opennmr/io/serialize.py +102 -0
  113. opennmr-0.1.0/src/opennmr/io/spinsolve.py +1250 -0
  114. opennmr-0.1.0/src/opennmr/io/state.py +263 -0
  115. opennmr-0.1.0/src/opennmr/io/varian.py +405 -0
  116. opennmr-0.1.0/src/opennmr/io/varian_binary.py +156 -0
  117. opennmr-0.1.0/src/opennmr/io/xpulse.py +1625 -0
  118. opennmr-0.1.0/src/opennmr/notebook/__init__.py +522 -0
  119. opennmr-0.1.0/src/opennmr/notebook/arithmetic.py +198 -0
  120. opennmr-0.1.0/src/opennmr/notebook/batch_relaxation.py +190 -0
  121. opennmr-0.1.0/src/opennmr/notebook/bms/__init__.py +17 -0
  122. opennmr-0.1.0/src/opennmr/notebook/bms/demag.py +90 -0
  123. opennmr-0.1.0/src/opennmr/notebook/bms/geometry.py +140 -0
  124. opennmr-0.1.0/src/opennmr/notebook/bms/io_utils.py +207 -0
  125. opennmr-0.1.0/src/opennmr/notebook/bms/main.py +251 -0
  126. opennmr-0.1.0/src/opennmr/notebook/bms/mas_sidebands.py +120 -0
  127. opennmr-0.1.0/src/opennmr/notebook/bms/packing.py +703 -0
  128. opennmr-0.1.0/src/opennmr/notebook/bms/shift.py +78 -0
  129. opennmr-0.1.0/src/opennmr/notebook/bms/simulate.py +90 -0
  130. opennmr-0.1.0/src/opennmr/notebook/bms/spectrum.py +132 -0
  131. opennmr-0.1.0/src/opennmr/notebook/bms_spectrum.py +224 -0
  132. opennmr-0.1.0/src/opennmr/notebook/bridge.py +128 -0
  133. opennmr-0.1.0/src/opennmr/notebook/combined_fitting.py +998 -0
  134. opennmr-0.1.0/src/opennmr/notebook/combined_hamiltonian.py +1135 -0
  135. opennmr-0.1.0/src/opennmr/notebook/cp_buildup.py +525 -0
  136. opennmr-0.1.0/src/opennmr/notebook/csa_fitting.py +1399 -0
  137. opennmr-0.1.0/src/opennmr/notebook/csa_relaxation.py +678 -0
  138. opennmr-0.1.0/src/opennmr/notebook/czjzek.py +1333 -0
  139. opennmr-0.1.0/src/opennmr/notebook/dft_calibration.py +668 -0
  140. opennmr-0.1.0/src/opennmr/notebook/dft_nmr.py +1189 -0
  141. opennmr-0.1.0/src/opennmr/notebook/diffusion.py +153 -0
  142. opennmr-0.1.0/src/opennmr/notebook/dnp.py +1199 -0
  143. opennmr-0.1.0/src/opennmr/notebook/dnp_workflow.py +955 -0
  144. opennmr-0.1.0/src/opennmr/notebook/drift_correction.py +796 -0
  145. opennmr-0.1.0/src/opennmr/notebook/exchange.py +854 -0
  146. opennmr-0.1.0/src/opennmr/notebook/fit_advice.py +1523 -0
  147. opennmr-0.1.0/src/opennmr/notebook/fit_correlation.py +341 -0
  148. opennmr-0.1.0/src/opennmr/notebook/fit_display.py +167 -0
  149. opennmr-0.1.0/src/opennmr/notebook/fit_models.py +436 -0
  150. opennmr-0.1.0/src/opennmr/notebook/fit_ranges.py +79 -0
  151. opennmr-0.1.0/src/opennmr/notebook/fit_values.py +237 -0
  152. opennmr-0.1.0/src/opennmr/notebook/fitting.py +1919 -0
  153. opennmr-0.1.0/src/opennmr/notebook/fonts.py +121 -0
  154. opennmr-0.1.0/src/opennmr/notebook/intensity_scaling.py +194 -0
  155. opennmr-0.1.0/src/opennmr/notebook/isotopes.py +173 -0
  156. opennmr-0.1.0/src/opennmr/notebook/jcoupling_fitting.py +1165 -0
  157. opennmr-0.1.0/src/opennmr/notebook/manual_baseline.py +394 -0
  158. opennmr-0.1.0/src/opennmr/notebook/multi_fitting.py +1348 -0
  159. opennmr-0.1.0/src/opennmr/notebook/notes.py +113 -0
  160. opennmr-0.1.0/src/opennmr/notebook/offset_tracker.py +742 -0
  161. opennmr-0.1.0/src/opennmr/notebook/one_d.py +1996 -0
  162. opennmr-0.1.0/src/opennmr/notebook/peak_width.py +367 -0
  163. opennmr-0.1.0/src/opennmr/notebook/periodic_table.py +715 -0
  164. opennmr-0.1.0/src/opennmr/notebook/plotting.py +721 -0
  165. opennmr-0.1.0/src/opennmr/notebook/pnmr_export.py +307 -0
  166. opennmr-0.1.0/src/opennmr/notebook/pnmr_fit.py +1881 -0
  167. opennmr-0.1.0/src/opennmr/notebook/pnmr_simulator.py +1135 -0
  168. opennmr-0.1.0/src/opennmr/notebook/progress.py +268 -0
  169. opennmr-0.1.0/src/opennmr/notebook/quadrupolar_fitting.py +787 -0
  170. opennmr-0.1.0/src/opennmr/notebook/referencing.py +204 -0
  171. opennmr-0.1.0/src/opennmr/notebook/regularisation.py +798 -0
  172. opennmr-0.1.0/src/opennmr/notebook/relaxation.py +1233 -0
  173. opennmr-0.1.0/src/opennmr/notebook/relaxometry.py +30 -0
  174. opennmr-0.1.0/src/opennmr/notebook/scripts/verify_rotation_convention.py +97 -0
  175. opennmr-0.1.0/src/opennmr/notebook/series_fitting.py +291 -0
  176. opennmr-0.1.0/src/opennmr/notebook/shearing.py +1261 -0
  177. opennmr-0.1.0/src/opennmr/notebook/simpson.py +1094 -0
  178. opennmr-0.1.0/src/opennmr/notebook/simulations.py +2254 -0
  179. opennmr-0.1.0/src/opennmr/notebook/snr.py +1538 -0
  180. opennmr-0.1.0/src/opennmr/notebook/spin_system.py +697 -0
  181. opennmr-0.1.0/src/opennmr/notebook/start_estimates.py +445 -0
  182. opennmr-0.1.0/src/opennmr/notebook/state.py +108 -0
  183. opennmr-0.1.0/src/opennmr/notebook/stored_integrals.py +264 -0
  184. opennmr-0.1.0/src/opennmr/notebook/tensor_views.py +688 -0
  185. opennmr-0.1.0/src/opennmr/notebook/two_d.py +3060 -0
  186. opennmr-0.1.0/src/opennmr/notebook/utils.py +672 -0
  187. opennmr-0.1.0/src/opennmr/notebook/vt_relaxation.py +1523 -0
  188. opennmr-0.1.0/src/opennmr/plotting/__init__.py +40 -0
  189. opennmr-0.1.0/src/opennmr/plotting/accessibility.py +455 -0
  190. opennmr-0.1.0/src/opennmr/plotting/fit.py +946 -0
  191. opennmr-0.1.0/src/opennmr/plotting/labels.py +307 -0
  192. opennmr-0.1.0/src/opennmr/plotting/lines.py +218 -0
  193. opennmr-0.1.0/src/opennmr/plotting/oned.py +276 -0
  194. opennmr-0.1.0/src/opennmr/plotting/style.py +1021 -0
  195. opennmr-0.1.0/src/opennmr/plotting/twod.py +1356 -0
  196. opennmr-0.1.0/src/opennmr/plotting/vector_export.py +159 -0
  197. opennmr-0.1.0/src/opennmr/processing/__init__.py +89 -0
  198. opennmr-0.1.0/src/opennmr/processing/arithmetic.py +136 -0
  199. opennmr-0.1.0/src/opennmr/processing/diffusion.py +767 -0
  200. opennmr-0.1.0/src/opennmr/processing/enmr.py +1477 -0
  201. opennmr-0.1.0/src/opennmr/processing/fitting.py +2123 -0
  202. opennmr-0.1.0/src/opennmr/processing/ft.py +1142 -0
  203. opennmr-0.1.0/src/opennmr/processing/ilt.py +343 -0
  204. opennmr-0.1.0/src/opennmr/processing/refdeconv.py +320 -0
  205. opennmr-0.1.0/src/opennmr/processing/relaxation.py +2350 -0
  206. opennmr-0.1.0/src/opennmr/processing/relaxometry.py +578 -0
  207. opennmr-0.1.0/src/opennmr/processing/report.py +355 -0
  208. opennmr-0.1.0/src/opennmr/processing/score.py +1007 -0
  209. opennmr-0.1.0/src/opennmr/processing/slicing.py +313 -0
  210. opennmr-0.1.0/src/opennmr/processing/vocs.py +342 -0
  211. opennmr-0.1.0/src/opennmr/processing/windows.py +67 -0
  212. opennmr-0.1.0/src/opennmr/simulate/__init__.py +6 -0
  213. opennmr-0.1.0/src/opennmr/simulate/bloch.py +914 -0
  214. opennmr-0.1.0/src/opennmr/simulate/diffusion.py +144 -0
  215. opennmr-0.1.0/src/opennmr/topspin/__init__.py +8 -0
  216. opennmr-0.1.0/src/opennmr/topspin/install.py +486 -0
  217. opennmr-0.1.0/src/opennmr/topspin/resources/opennmr.py +341 -0
  218. opennmr-0.1.0/src/opennmr/update.py +121 -0
  219. opennmr-0.1.0/src/opennmr.egg-info/PKG-INFO +449 -0
  220. opennmr-0.1.0/src/opennmr.egg-info/SOURCES.txt +404 -0
  221. opennmr-0.1.0/src/opennmr.egg-info/dependency_links.txt +1 -0
  222. opennmr-0.1.0/src/opennmr.egg-info/entry_points.txt +3 -0
  223. opennmr-0.1.0/src/opennmr.egg-info/requires.txt +17 -0
  224. opennmr-0.1.0/src/opennmr.egg-info/top_level.txt +1 -0
  225. opennmr-0.1.0/tests/test_accessibility.py +257 -0
  226. opennmr-0.1.0/tests/test_arithmetic.py +65 -0
  227. opennmr-0.1.0/tests/test_auto_spline_baseline.py +164 -0
  228. opennmr-0.1.0/tests/test_background_load.py +154 -0
  229. opennmr-0.1.0/tests/test_batch_relaxation.py +180 -0
  230. opennmr-0.1.0/tests/test_bloch_adiabatic.py +232 -0
  231. opennmr-0.1.0/tests/test_bloch_any_duration.py +111 -0
  232. opennmr-0.1.0/tests/test_bloch_tanh_tan_review.py +89 -0
  233. opennmr-0.1.0/tests/test_bruker.py +951 -0
  234. opennmr-0.1.0/tests/test_bruker_2d_default.py +101 -0
  235. opennmr-0.1.0/tests/test_bruker_pfg.py +242 -0
  236. opennmr-0.1.0/tests/test_bruker_raw_2d.py +871 -0
  237. opennmr-0.1.0/tests/test_bruker_raw_fid.py +364 -0
  238. opennmr-0.1.0/tests/test_cloud.py +136 -0
  239. opennmr-0.1.0/tests/test_collapsible_section.py +139 -0
  240. opennmr-0.1.0/tests/test_colorbar_gap_and_czjzek_labels.py +130 -0
  241. opennmr-0.1.0/tests/test_combined_fitting.py +138 -0
  242. opennmr-0.1.0/tests/test_combined_hamiltonian.py +256 -0
  243. opennmr-0.1.0/tests/test_compare_2d.py +199 -0
  244. opennmr-0.1.0/tests/test_compare_cross_measure.py +72 -0
  245. opennmr-0.1.0/tests/test_compare_width_ranges_cp.py +488 -0
  246. opennmr-0.1.0/tests/test_cpmg.py +227 -0
  247. opennmr-0.1.0/tests/test_csa_fitting.py +158 -0
  248. opennmr-0.1.0/tests/test_czjzek.py +288 -0
  249. opennmr-0.1.0/tests/test_data_browser.py +463 -0
  250. opennmr-0.1.0/tests/test_dft_calibration.py +228 -0
  251. opennmr-0.1.0/tests/test_dft_molecular.py +213 -0
  252. opennmr-0.1.0/tests/test_dft_nmr.py +407 -0
  253. opennmr-0.1.0/tests/test_diffusion.py +428 -0
  254. opennmr-0.1.0/tests/test_digital_filter.py +85 -0
  255. opennmr-0.1.0/tests/test_dnp.py +417 -0
  256. opennmr-0.1.0/tests/test_dnp_workflow.py +248 -0
  257. opennmr-0.1.0/tests/test_drift_correction.py +492 -0
  258. opennmr-0.1.0/tests/test_enmr.py +1161 -0
  259. opennmr-0.1.0/tests/test_exchange.py +273 -0
  260. opennmr-0.1.0/tests/test_experiment_notes.py +280 -0
  261. opennmr-0.1.0/tests/test_external_projection.py +398 -0
  262. opennmr-0.1.0/tests/test_fact_box.py +95 -0
  263. opennmr-0.1.0/tests/test_field_proton_typing_and_haeberlen_eta.py +206 -0
  264. opennmr-0.1.0/tests/test_fit_advice.py +244 -0
  265. opennmr-0.1.0/tests/test_fit_correlation.py +268 -0
  266. opennmr-0.1.0/tests/test_fit_mas_rate.py +219 -0
  267. opennmr-0.1.0/tests/test_fit_progress.py +222 -0
  268. opennmr-0.1.0/tests/test_fit_values_kept_fits_crosshair_state_folder.py +309 -0
  269. opennmr-0.1.0/tests/test_fitting.py +627 -0
  270. opennmr-0.1.0/tests/test_fonts_vector_export_headers.py +234 -0
  271. opennmr-0.1.0/tests/test_ft.py +1026 -0
  272. opennmr-0.1.0/tests/test_full_2d_autophase_warning.py +148 -0
  273. opennmr-0.1.0/tests/test_gui.py +16403 -0
  274. opennmr-0.1.0/tests/test_gui_adiabatic_tensors.py +178 -0
  275. opennmr-0.1.0/tests/test_gui_canvas.py +4511 -0
  276. opennmr-0.1.0/tests/test_gui_combined.py +141 -0
  277. opennmr-0.1.0/tests/test_gui_combined_consolidation.py +286 -0
  278. opennmr-0.1.0/tests/test_gui_combined_fit.py +247 -0
  279. opennmr-0.1.0/tests/test_gui_compare_tools.py +371 -0
  280. opennmr-0.1.0/tests/test_gui_copy_interactions.py +133 -0
  281. opennmr-0.1.0/tests/test_gui_csa.py +221 -0
  282. opennmr-0.1.0/tests/test_gui_curve_fit_plot.py +152 -0
  283. opennmr-0.1.0/tests/test_gui_czjzek_display.py +329 -0
  284. opennmr-0.1.0/tests/test_gui_dft.py +298 -0
  285. opennmr-0.1.0/tests/test_gui_dnp.py +226 -0
  286. opennmr-0.1.0/tests/test_gui_drift_and_sr_transfer.py +235 -0
  287. opennmr-0.1.0/tests/test_gui_dynamics.py +309 -0
  288. opennmr-0.1.0/tests/test_gui_export_scroll.py +160 -0
  289. opennmr-0.1.0/tests/test_gui_field_and_wheel.py +207 -0
  290. opennmr-0.1.0/tests/test_gui_fit_advice.py +179 -0
  291. opennmr-0.1.0/tests/test_gui_fit_sim_state.py +330 -0
  292. opennmr-0.1.0/tests/test_gui_integration_to_dynamics.py +110 -0
  293. opennmr-0.1.0/tests/test_gui_isotope_fitting.py +204 -0
  294. opennmr-0.1.0/tests/test_gui_jcoupling.py +481 -0
  295. opennmr-0.1.0/tests/test_gui_legend_names.py +192 -0
  296. opennmr-0.1.0/tests/test_gui_legend_style.py +304 -0
  297. opennmr-0.1.0/tests/test_gui_manual_baseline.py +310 -0
  298. opennmr-0.1.0/tests/test_gui_multi_fit.py +278 -0
  299. opennmr-0.1.0/tests/test_gui_multi_fit_keep_copy.py +165 -0
  300. opennmr-0.1.0/tests/test_gui_multisite_sim.py +290 -0
  301. opennmr-0.1.0/tests/test_gui_periodic_table.py +232 -0
  302. opennmr-0.1.0/tests/test_gui_pnmr.py +555 -0
  303. opennmr-0.1.0/tests/test_gui_pnmr_export.py +109 -0
  304. opennmr-0.1.0/tests/test_gui_pnmr_fit.py +413 -0
  305. opennmr-0.1.0/tests/test_gui_progress.py +425 -0
  306. opennmr-0.1.0/tests/test_gui_quadrupolar_fit.py +111 -0
  307. opennmr-0.1.0/tests/test_gui_readable_reports_zoom_site_table.py +297 -0
  308. opennmr-0.1.0/tests/test_gui_reflow.py +165 -0
  309. opennmr-0.1.0/tests/test_gui_relaxometry.py +180 -0
  310. opennmr-0.1.0/tests/test_gui_remove_and_plot_kinds.py +92 -0
  311. opennmr-0.1.0/tests/test_gui_round4_czjzek.py +120 -0
  312. opennmr-0.1.0/tests/test_gui_round4_fits.py +262 -0
  313. opennmr-0.1.0/tests/test_gui_round4_keep_sim.py +87 -0
  314. opennmr-0.1.0/tests/test_gui_round4_transfers.py +203 -0
  315. opennmr-0.1.0/tests/test_gui_shearing.py +155 -0
  316. opennmr-0.1.0/tests/test_gui_simulation_plots.py +363 -0
  317. opennmr-0.1.0/tests/test_gui_snr.py +189 -0
  318. opennmr-0.1.0/tests/test_gui_tensor_style.py +415 -0
  319. opennmr-0.1.0/tests/test_gui_usability.py +176 -0
  320. opennmr-0.1.0/tests/test_gui_view_and_modes.py +401 -0
  321. opennmr-0.1.0/tests/test_history.py +361 -0
  322. opennmr-0.1.0/tests/test_ilt.py +257 -0
  323. opennmr-0.1.0/tests/test_integrate_tile_plot_options_regularisation.py +349 -0
  324. opennmr-0.1.0/tests/test_io_base.py +55 -0
  325. opennmr-0.1.0/tests/test_isotope_fitting.py +283 -0
  326. opennmr-0.1.0/tests/test_jcampdx.py +105 -0
  327. opennmr-0.1.0/tests/test_jcoupling.py +193 -0
  328. opennmr-0.1.0/tests/test_jeol.py +344 -0
  329. opennmr-0.1.0/tests/test_lebedev_powder.py +166 -0
  330. opennmr-0.1.0/tests/test_line_styles.py +260 -0
  331. opennmr-0.1.0/tests/test_lists.py +207 -0
  332. opennmr-0.1.0/tests/test_magritek_1d_2d.py +248 -0
  333. opennmr-0.1.0/tests/test_manual_baseline.py +223 -0
  334. opennmr-0.1.0/tests/test_max_iterations_sidebands_contour_scroll.py +365 -0
  335. opennmr-0.1.0/tests/test_multi_fit_quantities.py +223 -0
  336. opennmr-0.1.0/tests/test_multi_fitting.py +235 -0
  337. opennmr-0.1.0/tests/test_multifit_sites_cp_regions_state_load.py +235 -0
  338. opennmr-0.1.0/tests/test_multisite_simulation.py +133 -0
  339. opennmr-0.1.0/tests/test_notebook_bridge.py +109 -0
  340. opennmr-0.1.0/tests/test_notebook_single_import.py +84 -0
  341. opennmr-0.1.0/tests/test_one_d.py +396 -0
  342. opennmr-0.1.0/tests/test_periodic_table.py +160 -0
  343. opennmr-0.1.0/tests/test_persistence.py +197 -0
  344. opennmr-0.1.0/tests/test_pfg_wiring.py +387 -0
  345. opennmr-0.1.0/tests/test_plotting_fit.py +497 -0
  346. opennmr-0.1.0/tests/test_plotting_oned.py +131 -0
  347. opennmr-0.1.0/tests/test_plotting_style.py +628 -0
  348. opennmr-0.1.0/tests/test_plotting_twod.py +701 -0
  349. opennmr-0.1.0/tests/test_pnmr_export.py +139 -0
  350. opennmr-0.1.0/tests/test_pnmr_fit.py +298 -0
  351. opennmr-0.1.0/tests/test_pnmr_simulator.py +530 -0
  352. opennmr-0.1.0/tests/test_procno_1d_of_2d_and_snr_view.py +154 -0
  353. opennmr-0.1.0/tests/test_procnos_snr_styles_units_sections.py +224 -0
  354. opennmr-0.1.0/tests/test_projection_style_and_labels.py +293 -0
  355. opennmr-0.1.0/tests/test_quadrupolar_fitting.py +163 -0
  356. opennmr-0.1.0/tests/test_quadrupolar_simulation.py +268 -0
  357. opennmr-0.1.0/tests/test_refdeconv.py +296 -0
  358. opennmr-0.1.0/tests/test_referencing.py +57 -0
  359. opennmr-0.1.0/tests/test_registry.py +104 -0
  360. opennmr-0.1.0/tests/test_regularisation.py +194 -0
  361. opennmr-0.1.0/tests/test_relaxation.py +927 -0
  362. opennmr-0.1.0/tests/test_relaxation_reporting.py +362 -0
  363. opennmr-0.1.0/tests/test_relaxometry.py +393 -0
  364. opennmr-0.1.0/tests/test_reopen_keeps_recorded_phase.py +102 -0
  365. opennmr-0.1.0/tests/test_report.py +130 -0
  366. opennmr-0.1.0/tests/test_round_2026_10_17.py +510 -0
  367. opennmr-0.1.0/tests/test_round_2026_10_18.py +303 -0
  368. opennmr-0.1.0/tests/test_round_2026_10_19.py +556 -0
  369. opennmr-0.1.0/tests/test_round_2026_10_20.py +254 -0
  370. opennmr-0.1.0/tests/test_scan_and_procno_limits.py +51 -0
  371. opennmr-0.1.0/tests/test_score.py +623 -0
  372. opennmr-0.1.0/tests/test_series_fitting.py +333 -0
  373. opennmr-0.1.0/tests/test_settings_data_sections_drag.py +255 -0
  374. opennmr-0.1.0/tests/test_shared_patterns.py +151 -0
  375. opennmr-0.1.0/tests/test_shearing.py +634 -0
  376. opennmr-0.1.0/tests/test_sim_csa_axis.py +65 -0
  377. opennmr-0.1.0/tests/test_simulate.py +152 -0
  378. opennmr-0.1.0/tests/test_slice_gaps_truncate_calibrate_undo.py +369 -0
  379. opennmr-0.1.0/tests/test_slice_panel_dblclick_clear_history_compare.py +246 -0
  380. opennmr-0.1.0/tests/test_slicing.py +152 -0
  381. opennmr-0.1.0/tests/test_snr.py +228 -0
  382. opennmr-0.1.0/tests/test_snr_conventions_2d.py +190 -0
  383. opennmr-0.1.0/tests/test_snr_display_and_drag_cleanup.py +218 -0
  384. opennmr-0.1.0/tests/test_snr_keeps_compare_colours.py +86 -0
  385. opennmr-0.1.0/tests/test_spin_system.py +321 -0
  386. opennmr-0.1.0/tests/test_spinsolve.py +161 -0
  387. opennmr-0.1.0/tests/test_start_estimates.py +150 -0
  388. opennmr-0.1.0/tests/test_startup_time.py +223 -0
  389. opennmr-0.1.0/tests/test_state_and_fit_models.py +348 -0
  390. opennmr-0.1.0/tests/test_tensor_views.py +165 -0
  391. opennmr-0.1.0/tests/test_text_files_are_utf8.py +36 -0
  392. opennmr-0.1.0/tests/test_topspin_install.py +105 -0
  393. opennmr-0.1.0/tests/test_topspin_processing_2d.py +212 -0
  394. opennmr-0.1.0/tests/test_tutorial_round_czjzek_series.py +198 -0
  395. opennmr-0.1.0/tests/test_two_d.py +282 -0
  396. opennmr-0.1.0/tests/test_update.py +99 -0
  397. opennmr-0.1.0/tests/test_varian.py +323 -0
  398. opennmr-0.1.0/tests/test_varian_binary.py +82 -0
  399. opennmr-0.1.0/tests/test_vocs.py +289 -0
  400. opennmr-0.1.0/tests/test_vocs_slices.py +101 -0
  401. opennmr-0.1.0/tests/test_vt_relaxation.py +331 -0
  402. opennmr-0.1.0/tests/test_welcome_wizard.py +175 -0
  403. opennmr-0.1.0/tests/test_window_cleanup.py +60 -0
  404. opennmr-0.1.0/tests/test_windows.py +75 -0
  405. opennmr-0.1.0/tests/test_wrapping_tabs.py +274 -0
  406. opennmr-0.1.0/tests/test_xpulse.py +1115 -0
opennmr-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 openNMR contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
opennmr-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,449 @@
1
+ Metadata-Version: 2.4
2
+ Name: opennmr
3
+ Version: 0.1.0
4
+ Summary: Vendor-agnostic NMR import, processing, plotting, and GUI
5
+ Author: Euan Bassey, Leo Gordon
6
+ License-Expression: MIT
7
+ Keywords: NMR,solid-state NMR,spectroscopy,fitting
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
10
+ Classifier: Topic :: Scientific/Engineering :: Physics
11
+ Classifier: Intended Audience :: Science/Research
12
+ Requires-Python: >=3.12
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: numpy
16
+ Requires-Dist: scipy>=1.15
17
+ Requires-Dist: pandas
18
+ Requires-Dist: matplotlib
19
+ Requires-Dist: PySide6-Essentials
20
+ Requires-Dist: sympy
21
+ Requires-Dist: pymatgen
22
+ Provides-Extra: test
23
+ Requires-Dist: pytest; extra == "test"
24
+ Requires-Dist: pytest-xdist; extra == "test"
25
+ Requires-Dist: pytest-timeout; extra == "test"
26
+ Requires-Dist: nmrglue; extra == "test"
27
+ Requires-Dist: pymatgen; extra == "test"
28
+ Provides-Extra: build
29
+ Requires-Dist: pyinstaller; extra == "build"
30
+ Dynamic: license-file
31
+
32
+ Vendor-agnostic NMR import, processing, plotting, and a desktop GUI.
33
+
34
+ Every vendor importer reads raw acquisition data (not a vendor's own
35
+ pre-processed output) and produces the same `NMRSpectrum` object, which
36
+ the same processing and plotting code then operates on. Currently
37
+ supported: Bruker (TopSpin `fid`/`ser`), X-Pulse (JCAMP-DX FID, including
38
+ arrayed/PFG experiments), Varian/Agilent (`fid`/`procpar`), JEOL Delta
39
+ (`.jdf`, 1D only so far).
40
+
41
+ ## Layout
42
+
43
+ - `opennmr.core` — vendor-agnostic data model (`NMRSpectrum`, `NMRAxis`,
44
+ `NMRMetadata`) and nucleus/gyromagnetic-ratio lookups.
45
+ - `opennmr.io` — per-vendor readers, all producing `NMRSpectrum`.
46
+ - `opennmr.processing` — FID processing (apodization, FFT, phasing),
47
+ VOCS, spectrum arithmetic, 2D slice extraction, peak fitting,
48
+ relaxation and diffusion fitting, PFG b-values and gradient
49
+ calibration, DOSY by inverse Laplace transform, and multivariate
50
+ component resolution (SCORE/OUTSCORE).
51
+ - `opennmr.plotting` — matplotlib plotting, `ax`-first so it embeds
52
+ cleanly in a GUI canvas as well as standalone notebook figures.
53
+ - `opennmr.gui` — PySide6 desktop shell.
54
+
55
+ ## Install (editable, into an existing environment)
56
+
57
+ ```
58
+ pip install -e ".[test]"
59
+ ```
60
+
61
+ ## Run the GUI
62
+
63
+ ```
64
+ opennmr-gui
65
+ ```
66
+
67
+ ## Add an OpenNMR button to TopSpin
68
+
69
+ ```
70
+ opennmr-topspin-install
71
+ ```
72
+
73
+ Finds the TopSpin installations on the local machine, offers to copy the
74
+ OpenNMR plugin into each installation's `exp/stan/nmr/py/user/` folder,
75
+ and to add the `opennmr` launch command to TopSpin's `autostart.mac`.
76
+ At the TopSpin command line, `opennmr` puts the button on the main
77
+ toolbar and `opennmr -kill` takes it off again.
78
+
79
+ **The plugin records the absolute path of the environment you run the
80
+ installer from**, rather than looking for `opennmr-gui` on PATH when the
81
+ button is clicked. Depending on how TopSpin is launched, shell startup
82
+ files may not be read, so a conda install is invisible to it, and on
83
+ Windows neither conda nor python.org's installer puts itself on PATH by
84
+ default. The cost is that moving or recreating the environment can break
85
+ the button; re-run the installer if it does.
86
+
87
+ `--topspin FOLDER` defines a TopSpin installation directory the search
88
+ missed; `--uninstall` removes the plugin and its autostart entry;
89
+ `--list` shows what was found. Writing into TopSpin's own folder may
90
+ need administrator rights.
91
+
92
+ ## Run tests
93
+
94
+ ```
95
+ pytest
96
+ ```
97
+
98
+ ## Getting good data for diffusion and relaxation analysis
99
+
100
+ The analysis methods in this package differ enormously in how much they
101
+ ask of the data, and **the demanding ones fail quietly**: they return a
102
+ confident-looking answer with an excellent fit statistic. This section
103
+ is what each one actually needs. The GUI repeats the essentials in the
104
+ relevant dialogs.
105
+
106
+ ### The single most important thing
107
+
108
+ **Fit quality does not tell you whether a multivariate resolution is
109
+ right.** Measured directly on synthetic mixtures in this repository's
110
+ own test suite: an OUTSCORE run whose diffusion coefficients were **69%
111
+ wrong** reported R² = 0.99940, against 0.99937 for the run that got them
112
+ right. The error surface really is that flat — wildly different answers
113
+ reproduce the data equally well. A good R² means "this many components
114
+ can fit", never "these are the right components".
115
+
116
+ This is why `processing.score` reports cross-talk alongside R², warns
117
+ when the two methods disagree while fitting equally well, and says in
118
+ its own report which number the run actually optimised.
119
+
120
+ ### Acquisition
121
+
122
+ | | Peak-by-peak fit | DOSY (ILT) | SCORE / OUTSCORE |
123
+ |---|---|---|---|
124
+ | Gradient levels / delays | 8+ | 12+ | 8–10 minimum, 15+ preferred |
125
+ | Attenuation of the fastest component | to ~20% | to ~5% | to ~10% |
126
+ | Phase / baseline | helps | required | required |
127
+ | Resolves overlapped peaks | no | no | yes |
128
+
129
+ * **Sample the decay, don't just start it.** A component that loses
130
+ only 20% of its intensity across the whole series is nearly a
131
+ constant, and its decay constant is poorly determined no matter how
132
+ good the fit looks. There is a test in `tests/test_score.py` pinning
133
+ this down: a component attenuating by 8% is recovered only to within
134
+ a factor, which is a property of the experiment and not of the
135
+ algorithm. The fix is a longer diffusion time or stronger gradients.
136
+ * **Space the levels sensibly.** Equal steps in *b* (equal steps in
137
+ gradient *squared*) sample a decay far better than equal steps in
138
+ gradient amplitude, which crowds points into the undecayed start.
139
+ * **Keep signal-to-noise up on the last levels too**, not just the
140
+ first. The most-attenuated points carry most of the information about
141
+ the slow components.
142
+ * **Don't let peaks move.** A bilinear model — the assumption behind
143
+ SCORE, OUTSCORE and DOSY alike — says each component has one fixed
144
+ spectrum. A peak that drifts across the series (temperature,
145
+ exchange, referencing) breaks that, and shows up as a spurious extra
146
+ component rather than as an error.
147
+
148
+ ### Processing, before any of these methods
149
+
150
+ 1. **Phase properly, in absorption mode.** Non-negativity is the single
151
+ most stabilising constraint available to SCORE, OUTSCORE and the
152
+ ILT, and it means nothing on a dispersive spectrum.
153
+ 2. **Baseline-correct.** A rolling baseline is not a non-negative
154
+ superposition of component spectra; it will be absorbed into
155
+ whichever component can best accommodate it.
156
+ 3. **Reference consistently across the series.**
157
+ 4. **Use real b-values.** The GUI refuses to run DOSY or SCORE when the
158
+ x-axis has fallen back to plain slice indices, because the resulting
159
+ "diffusion coefficients" would be in no physical unit while being
160
+ labelled m²/s. Either read them from a Bruker `diff.xml`, or supply
161
+ δ/Δ/gradient and let `processing.diffusion.b_value` compute them —
162
+ and get the **gradient shape factor** right: treating a stock Bruker
163
+ `SMSQ10` sequence as a perfect rectangle overestimates *b* by 23%,
164
+ which lands directly on D.
165
+
166
+ ### Where the x-values come from
167
+
168
+ The Curve Fitting sub-tab's **Values** source sets the x-axis for Fit
169
+ Curve, DOSY and SCORE alike — one setting, so the three cannot end up
170
+ fitting the same series against different axes. The line under it
171
+ always shows what the current source resolves to, against the selected
172
+ experiment.
173
+
174
+ | Source | What it reads |
175
+ |---|---|
176
+ | Auto | this experiment's own `vdlist`/`diff.xml`, else slice index |
177
+ | Manual list | values typed in, comma- or whitespace-separated (`10m` etc. accepted) |
178
+ | Start + step | an evenly-spaced series, one value per slice |
179
+ | **List file × factor** | **any one-column text file of numbers, scaled by a constant or an acquisition parameter** |
180
+ | Relative gradient | an X-Pulse arrayed PFG series' own gradient fractions |
181
+ | b from gradients | a true b-value computed from δ/Δ/G |
182
+
183
+ **List file × factor** is the vendor-neutral one. `opennmr.io.lists`
184
+ reads any one-column list — a Bruker `vdlist`/`vclist`/`difflist`, an
185
+ X-Pulse `parameters/…_valueslist.txt`, your own `delays.txt` — and
186
+ `find_value_lists` offers whatever is actually sitting with the data
187
+ (the experiment folder, Bruker's `lists/` tree, X-Pulse's `parameters/`,
188
+ and the parent folder), with Browse… for anything else. Nothing is
189
+ inferred from the filename: the Factor is what turns a loop counter or
190
+ a relative fraction into a physical quantity.
191
+
192
+ The **Factor accepts an expression in the experiment's own acquisition
193
+ parameters**, not only a number — the same names a custom fitting
194
+ equation uses, from `opennmr.core.acquisition.acquisition_constants`
195
+ (`TAU`, `P90`, `D70` for X-Pulse; `d1`, `p1`, `cnst2`, `l4` for
196
+ Bruker). So an X-Pulse CPMG echo-count list becomes a T2 delay series
197
+ with a factor of `TAU*2`, with no arithmetic done by hand. The panel
198
+ shows what the expression resolved to and which parameters went into
199
+ it. Parameters are used **exactly as the vendor recorded them** — an
200
+ X-Pulse `TAU` is in µs and stays in µs, so any conversion belongs in
201
+ the expression; only the person reading the pulse programme knows the
202
+ unit it was written in.
203
+
204
+ Everything in the panel is **per experiment**. Selecting a different
205
+ spectrum re-scans *its* folder for list files and restores *its* own
206
+ factor, unit and source — nothing is carried over from the one before.
207
+
208
+ **Unit.** The Unit combo says what the numbers in this panel *are*,
209
+ not only how they are displayed — a typed list of `10, 20, 40` and a
210
+ list *file* holding `10, 20, 40` both mean 10, 20, 40 of that unit.
211
+ It defaults to **ms**; fits are always done in seconds. For a list
212
+ file it applies to the whole product, list × Factor, which makes the
213
+ X-Pulse CPMG case simpler rather than harder: with the Unit on µs an
214
+ echo-count list takes a factor of plain `TAU*2`, since `TAU` is
215
+ already recorded in µs.
216
+
217
+ Two things are never rescaled by it, because they already carry a unit
218
+ of their own: a value that names one (`10m` is 10 ms whatever the combo
219
+ says — a `vdlist` written that way is taken at its word) and the **Auto**
220
+ source, which is the vendor's own values in the vendor's own unit. A
221
+ diffusion fit has no Unit row at all: a b-value has no comparable
222
+ prefix convention.
223
+
224
+ The unit reaches the **whole** panel, not just the conversion: the Fit
225
+ tab's x-axis and label, the guess table's time rows (in both
226
+ directions — a `T2` guess typed as `50` under a ms heading reaches the
227
+ model as 0.05 s), "adopt fit as guess", the bounds error messages, and
228
+ the CSV export. Only *time* parameters scale —
229
+ `relaxation._TIME_PARAMS`, the same set that decides which reported
230
+ values get a time unit printed after them — so an amplitude, a stretch
231
+ exponent and a diffusion coefficient are left alone. The stored fit is
232
+ never modified; the unit is a display transform, so changing it redraws
233
+ rather than refits.
234
+
235
+ Fitted values in the report keep `format_time_value`'s automatic
236
+ ms/µs/ns scaling rather than being forced to the chosen unit: it is
237
+ never wrong, and pinning it would print a 2 s result as "2000 ms".
238
+
239
+ **Slices.** The Curve Fitting panel's Slices box limits the analysis to
240
+ the first N slices of a series ("all" by default). One setting feeds
241
+ Fit Spectrum, Fit Curve, DOSY and SCORE, so they cannot disagree about
242
+ how much of a series is in use; DOSY and SCORE cut the data matrix with
243
+ it, not only the x-values.
244
+
245
+ A value list **longer** than the data is not an error — the surplus is
246
+ simply unused, the way TopSpin uses the first F1-TD entries of a longer
247
+ `vdlist`. A shorter one cannot be fitted, and says so with the slice
248
+ count to set. Pseudo-2D row counts are capped at `acqu2s`'s own `TD`
249
+ (the *status* parameter file): the floor-division fallback in
250
+ `_guess_raw_2d_shape` only exists to find a reshapable shape, and rows
251
+ past TD are not acquired data.
252
+
253
+ **Relative amplitudes.** Any model with more than one amplitude reports
254
+ each one as a fraction of the total — on the parameter's own row and
255
+ again in a section pairing it with the rate it belongs to
256
+ (`relaxation.amplitude_fractions`). That is the form a multi-component
257
+ result is quoted and compared in; a raw `A1 = 0.0532` is on whatever
258
+ arbitrary intensity scale the spectra were processed on and says
259
+ nothing on its own. The compact per-peak summary and the global fit's
260
+ per-curve block quote amplitudes *only* as shares, since comparing
261
+ curves is the whole reason those blocks exist.
262
+
263
+ Amplitudes are recognised by name — `M0`, `A`/`A1`/`A2`, `I0`/`I0a`/
264
+ `I0b`, `I1`…`In` — the same way `_TIME_PARAMS` decides what gets a time
265
+ unit. A **custom equation gets no ratios**: its parameters are whatever
266
+ you named them, and treating the wrong symbol as an amplitude would be
267
+ worse than showing nothing. A share outside 0–100% (components of
268
+ opposite sign, usually an over-parameterised model) is flagged rather
269
+ than hidden.
270
+
271
+ **Export curve fit (CSV)** writes the data behind the plot — the x-axis
272
+ in the displayed unit, then every peak's measured integral or
273
+ amplitude at each point with the fitted curve beside it
274
+ (`processing.relaxation.curve_fit_to_csv`). Peaks are not assumed to
275
+ share an x-axis: a point excluded from one peak's fit leaves an empty
276
+ cell rather than a number that was never measured.
277
+
278
+ ### SCORE fits the same model Fit Curve does
279
+
280
+ SCORE's per-component decay is not a private exponential: it is
281
+ whichever `processing.relaxation.RELAXATION_MODELS` entry the Model
282
+ combo names, evaluated through `processing.score.build_kernel`. So a
283
+ stretched or inversion-recovery model resolves components with a β or
284
+ an inversion efficiency of their own, and a Stejskal-Tanner fit and a
285
+ SCORE resolution of the same series are the same function asked two
286
+ different questions.
287
+
288
+ The model's **amplitude** parameter is pinned at 1 rather than fitted —
289
+ each component's size lives entirely in its resolved spectrum, which is
290
+ solved for linearly, and fitting both would be exactly redundant. Which
291
+ parameter that is gets measured (doubling it doubles the curve), not
292
+ matched against a list of names, so a custom equation works too. A
293
+ model that already describes several populations (a biexponential) has
294
+ no such parameter and is refused with that reason: SCORE's own
295
+ component count is doing that job.
296
+ 5. **Calibrate the gradient if you don't know it.** A maximum gradient
297
+ 10% wrong makes every D **21%** wrong, with nothing in the fit to
298
+ show for it. `Analysis ▸ Calibrate Gradient…` measures it from a
299
+ standard of known D.
300
+ 6. **Check the gradient axis.** A probe's maximum gradient is saved per
301
+ axis, because the three coils of a triple-axis probe are rarely the
302
+ same strength — one number shared between them quietly rescales
303
+ every D measured on the other two. Set the axis alongside the
304
+ maximum; "Save gradient for probe" then stores that axis's value
305
+ and leaves the others alone. It is the same setting the Imaging tab
306
+ shows, and the same number: one experiment, one coil, one maximum.
307
+
308
+ ### Reference deconvolution (`processing.refdeconv`)
309
+
310
+ > **Not wired into the GUI, and not usable on a processed spectrum.**
311
+ > It inverse-transforms the spectrum back to its FID, so the input must
312
+ > be the transform of a *causal* FID. A TopSpin `2rr`/`2ii` is not — it
313
+ > carries the digital-filter group delay and has apodisation and
314
+ > baseline correction folded in. Applied to one, it destroys the line
315
+ > (measured: a peak of 2397 spanning 0.0159 ppm became 16 spanning
316
+ > 0.536 ppm). It needs the raw FID path; that wiring does not exist yet.
317
+
318
+
319
+ Corrects a lineshape distortion that **every peak shares** — a bad
320
+ shim, a non-spinning sample, or the electrodes in an eNMR cell. Pick an
321
+ isolated resonance, assert the shape it should have had, and the ratio
322
+ of ideal to measured is the distortion; divide it out and every peak is
323
+ corrected at once.
324
+
325
+ ```python
326
+ from opennmr.processing import refdeconv
327
+ result = refdeconv.reference_deconvolve(
328
+ spectrum_row, ppm, region=(-0.2, 0.2), fwhm=0.01) # target width, ppm
329
+ ```
330
+
331
+ Validated against a constructed truth (build an FID, distort it,
332
+ transform): a pure phase distortion broadening a line from 0.147 to
333
+ 0.244 ppm is corrected back to 0.159, **and peaks outside the reference
334
+ region are corrected too** — the method's whole claim. Recovery degrades
335
+ as the distortion destroys more of the FID (0.031 s of surviving signal
336
+ recovers only 0.574 → 0.366); that limit is information, not
337
+ implementation.
338
+
339
+ `REGULARISATIONS` is the method, not a detail — the reference FID decays
340
+ into noise and an undamped reciprocal diverges. `wiener` (default)
341
+ rolls off smoothly; `truncate` is sharper but rings; `none` exists to
342
+ demonstrate the failure.
343
+
344
+ It does **not** fix convection or electro-osmotic flow (per-scan phase
345
+ modulation, not one fixed envelope), needs a genuinely isolated
346
+ reference, and gives you whatever linewidth you ask for — asking for one
347
+ the acquisition cannot support amplifies noise until the fit looks sharp
348
+ and means nothing.
349
+
350
+ ### eNMR (`processing.enmr`, and the Analysis ▸ eNMR tab)
351
+
352
+ The phase a resonance picks up under an applied field is linear in the
353
+ voltage: `Δφ = γ·g·δ·Δ·μ·V/d`.
354
+
355
+ **The slope is the result** — it is what the data determines, in rad/V,
356
+ and needs nothing but the data. Converting it to a mobility needs the
357
+ cell's electrode gap and the sequence's gradient and timings, which live
358
+ in a lab notebook rather than any vendor file, so that step is optional:
359
+
360
+ ```python
361
+ from opennmr.processing import enmr
362
+ phases = enmr.phase_series(rows, ppm, region=(3.18, 3.42))
363
+
364
+ enmr.fit_enmr(voltages, phases) # slope alone
365
+ enmr.fit_enmr(voltages, phases, electrode_gap=0.0385,
366
+ gamma=2 * np.pi * 42.577e6, gradient=0.3,
367
+ delta=1e-3, big_delta=0.1) # ... and a mobility
368
+ ```
369
+
370
+ **Two levels of pairing, and they are different things.**
371
+
372
+ *Within one experiment*, each voltage is acquired at **both
373
+ polarities**. Reversing the field reverses the drift, so
374
+ `(φ₊ − φ₋)/2` doubles the signal and cancels every phase error that is
375
+ not field-driven. `polarity_pairs` detects this rather than assuming
376
+ it.
377
+
378
+ *Across experiments*, the whole run is repeated with the **gradient
379
+ reversed**, which flips the sign of the fitted slope. `combine_experiments`
380
+ aligns the signs and averages — a plain average would cancel the
381
+ measurement away — and reports the `spread`, because two runs of one
382
+ sample agreeing is the evidence that either measured anything. On a real
383
+ reversed pair: +1.244e-2 and −1.396e-2 → **+1.320e-2 ± 1.5e-3 rad/V**,
384
+ spread 8%.
385
+
386
+ **Unwrap with `phase_ramp`, not `phase_series`.** Consecutive points of
387
+ a +V/−V series are opposite polarity and legitimately far apart, so
388
+ unwrapping across them makes `np.unwrap` read a polarity jump as a wrap
389
+ and insert a 2π step that was never there. On real data that turned a
390
+ clean ramp (R² 0.88) into no correlation at all (R² 0.009) — it does not
391
+ look like a broken calculation, it looks like a negative result.
392
+ `phase_ramp` takes the voltages and picks the stride itself.
393
+
394
+ **Validated against an accepted literature value**, the only check that
395
+ catches a wrong 2π, a wrong drift time or a unit slip, all of which
396
+ still produce a beautiful straight line. On a TMABr standard
397
+ (`test_data/eNMR/...2024-05-13/21`):
398
+
399
+ | species | slope | R² | μ |
400
+ |---|---|---|---|
401
+ | TMA⁺ | 7.90e-3 ± 2.2e-4 rad/V | 0.9954 | **3.79e-8 m²/Vs** (lit. 3.79e-8) |
402
+ | water | 3.3e-5 ± 8.8e-5 rad/V | 0.023 | zero to within error |
403
+
404
+ Water sits in the same tube, field and shim, so it controls for
405
+ everything except charge.
406
+
407
+ Phase comes from the **complex integral** over the peak region, which on
408
+ this data gives R² = 0.995 with no lineshape fit at all; a full complex
409
+ fit is for *overlapped* peaks, not for phase accuracy as such.
410
+
411
+ **The PFG constants read themselves.** `pfg_params(spectrum)` — or
412
+ `fit_enmr(..., spectrum=spectrum)` — fills in γ, g, δ and Δ, so only the
413
+ electrode gap needs typing. Which source each comes from was measured
414
+ across five real datasets, because the pulse programme's legend is not
415
+ enough: `Δ = d20` reproduces `diff.xml` exactly, but `δ` does not
416
+ (`d17+d18` is 67–83% out on four of five), and the gradient in T/m needs
417
+ the probe calibration that only `diff.xml`/`diffConf.xml` carries. So
418
+ `diff.xml` is the source of record, `d20` the cross-check, and each
419
+ value reports its provenance. γ is converted to rad/s/T once, on the way
420
+ out.
421
+
422
+ `electrode_gap` has no default, and a half-supplied set of constants is
423
+ rejected rather than silently skipped: μ scales directly with the gap,
424
+ no vendor file records it, and a plausible-looking default would quietly
425
+ rescale every result.
426
+
427
+ ### Choosing a method
428
+
429
+ * **One clean, well-separated peak per species** → ordinary curve
430
+ fitting (`Analysis ▸ Curve Fitting`). It is the only one of these
431
+ that gives a diffusion coefficient with a meaningful uncertainty.
432
+ * **Peaks belonging to the same molecule** → tick *Fit all peaks
433
+ together* and share D. Fitting separately and averaging understates
434
+ the uncertainty.
435
+ * **Overlapped peaks, species differing ≳30% in D** → **SCORE**.
436
+ * **Overlapped peaks, species differing by only a few percent in D** →
437
+ **OUTSCORE**, which minimises cross-talk rather than residual. It
438
+ separates down to ~3% where SCORE needs ~30% — but it assumes the
439
+ components have genuinely *different* peaks. For species that legitimately
440
+ share most of their resonances, minimising overlap pushes toward
441
+ something untrue; the report warns when that has happened.
442
+ * **Unknown number of components, or a continuous distribution** →
443
+ the **DOSY map** (`Analysis ▸ DOSY Map (ILT)`). Treat peak *positions*
444
+ as the result and peak *widths* as an artefact of the regularisation.
445
+
446
+ Both `SCORE`/`OUTSCORE` and the curve fits take the **number of
447
+ components as an input, not an output**. Run at one more and one fewer
448
+ and prefer the answer whose component spectra look like plausible,
449
+ separate molecules.