syncmoss 0.4.2__tar.gz → 0.5.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 (226) hide show
  1. {syncmoss-0.4.2 → syncmoss-0.5.0}/PKG-INFO +1 -1
  2. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/constants.py +4 -0
  3. syncmoss-0.5.0/syncmoss/exclusion_bar.py +252 -0
  4. syncmoss-0.5.0/syncmoss/exclusion_regions.py +137 -0
  5. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/fitting_io.py +202 -60
  6. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/instrumental_io.py +439 -83
  7. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/main.py +1 -1
  8. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/minimi_lib.py +12 -5
  9. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/model_io.py +57 -14
  10. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/models.py +30 -11
  11. syncmoss-0.5.0/syncmoss/one_model.py +442 -0
  12. syncmoss-0.5.0/syncmoss/one_spectrum.py +109 -0
  13. syncmoss-0.5.0/syncmoss/parameters/Calibration.dat +513 -0
  14. syncmoss-0.5.0/syncmoss/parameters/GCMS.txt +1 -0
  15. syncmoss-0.5.0/syncmoss/parameters/INSexp.txt +1 -0
  16. syncmoss-0.5.0/syncmoss/parameters/INSint.txt +1 -0
  17. syncmoss-0.5.0/syncmoss/parameters/calibr.png +0 -0
  18. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/parameters/help.md +175 -24
  19. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/parameters_table.py +101 -21
  20. syncmoss-0.5.0/syncmoss/result_window.py +408 -0
  21. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/results_table.py +82 -92
  22. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/spectrum_io.py +279 -30
  23. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/spectrum_parameters.py +2 -2
  24. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/spectrum_plotter.py +226 -107
  25. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/support_math.py +17 -5
  26. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/syncmoss_main.py +1139 -177
  27. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss.egg-info/PKG-INFO +1 -1
  28. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss.egg-info/SOURCES.txt +25 -1
  29. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss.egg-info/scm_file_list.json +24 -0
  30. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/conftest.py +38 -17
  31. syncmoss-0.5.0/tests/data/parameters/INSth.txt +1 -0
  32. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/syncmoss_test.py +11 -3
  33. syncmoss-0.5.0/tests/test_create_spectrum_from_model.py +253 -0
  34. syncmoss-0.5.0/tests/test_exclusion_fit.py +264 -0
  35. syncmoss-0.5.0/tests/test_exclusion_gui.py +412 -0
  36. syncmoss-0.5.0/tests/test_exclusion_regions.py +148 -0
  37. syncmoss-0.5.0/tests/test_fit_core.py +286 -0
  38. syncmoss-0.5.0/tests/test_fit_one_spectrum.py +243 -0
  39. syncmoss-0.5.0/tests/test_instrumental_joint.py +305 -0
  40. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_instrumental_window.py +2 -7
  41. syncmoss-0.5.0/tests/test_minimi_bounds.py +68 -0
  42. syncmoss-0.5.0/tests/test_model_pointwise.py +120 -0
  43. syncmoss-0.5.0/tests/test_one_model_expand.py +262 -0
  44. syncmoss-0.5.0/tests/test_one_model_gui.py +516 -0
  45. syncmoss-0.5.0/tests/test_overwrite_dat_instrumental.py +282 -0
  46. syncmoss-0.5.0/tests/test_result_window.py +185 -0
  47. syncmoss-0.5.0/tests/test_results_table_rows.py +383 -0
  48. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_save_result_model.py +63 -10
  49. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_search_guards.py +4 -2
  50. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_spectrum_parameters.py +5 -5
  51. syncmoss-0.5.0/tests/test_support_math.py +166 -0
  52. syncmoss-0.4.2/syncmoss/parameters/calibr.png +0 -0
  53. syncmoss-0.4.2/tests/test_support_math.py +0 -87
  54. {syncmoss-0.4.2 → syncmoss-0.5.0}/.gitattributes +0 -0
  55. {syncmoss-0.4.2 → syncmoss-0.5.0}/.github/workflows/Tests.yml +0 -0
  56. {syncmoss-0.4.2 → syncmoss-0.5.0}/.github/workflows/_MacOS.yml +0 -0
  57. {syncmoss-0.4.2 → syncmoss-0.5.0}/.github/workflows/_PyPI.yml +0 -0
  58. {syncmoss-0.4.2 → syncmoss-0.5.0}/.github/workflows/_TagRelease.yml +0 -0
  59. {syncmoss-0.4.2 → syncmoss-0.5.0}/.github/workflows/_Tests.yml +0 -0
  60. {syncmoss-0.4.2 → syncmoss-0.5.0}/.github/workflows/_UploadRelease.yml +0 -0
  61. {syncmoss-0.4.2 → syncmoss-0.5.0}/.github/workflows/_Version.yml +0 -0
  62. {syncmoss-0.4.2 → syncmoss-0.5.0}/.github/workflows/_Wheels.yml +0 -0
  63. {syncmoss-0.4.2 → syncmoss-0.5.0}/.github/workflows/_WindowsExe.yml +0 -0
  64. {syncmoss-0.4.2 → syncmoss-0.5.0}/.github/workflows/release.yml +0 -0
  65. {syncmoss-0.4.2 → syncmoss-0.5.0}/.gitignore +0 -0
  66. {syncmoss-0.4.2 → syncmoss-0.5.0}/.vscode/settings.json +0 -0
  67. {syncmoss-0.4.2 → syncmoss-0.5.0}/COPYING.txt +0 -0
  68. {syncmoss-0.4.2 → syncmoss-0.5.0}/LICENSE +0 -0
  69. {syncmoss-0.4.2 → syncmoss-0.5.0}/Lib_test/test.mdl +0 -0
  70. {syncmoss-0.4.2 → syncmoss-0.5.0}/NOTICE.txt +0 -0
  71. {syncmoss-0.4.2 → syncmoss-0.5.0}/README.md +0 -0
  72. {syncmoss-0.4.2 → syncmoss-0.5.0}/bundle/MacOS.spec +0 -0
  73. {syncmoss-0.4.2 → syncmoss-0.5.0}/bundle/SYNCmoss.png +0 -0
  74. {syncmoss-0.4.2 → syncmoss-0.5.0}/bundle/Windows.spec +0 -0
  75. {syncmoss-0.4.2 → syncmoss-0.5.0}/bundle/bundle.py +0 -0
  76. {syncmoss-0.4.2 → syncmoss-0.5.0}/bundle/dmg_background.pdf +0 -0
  77. {syncmoss-0.4.2 → syncmoss-0.5.0}/bundle/licenses/GPL-3.0.txt +0 -0
  78. {syncmoss-0.4.2 → syncmoss-0.5.0}/bundle/licenses/LGPL-3.0.txt +0 -0
  79. {syncmoss-0.4.2 → syncmoss-0.5.0}/bundle/macos_bundle.py +0 -0
  80. {syncmoss-0.4.2 → syncmoss-0.5.0}/bundle/third_party_licenses.py +0 -0
  81. {syncmoss-0.4.2 → syncmoss-0.5.0}/docs/thick_models.md +0 -0
  82. {syncmoss-0.4.2 → syncmoss-0.5.0}/docs/thick_vs_thin.md +0 -0
  83. {syncmoss-0.4.2 → syncmoss-0.5.0}/pyproject.toml +0 -0
  84. {syncmoss-0.4.2 → syncmoss-0.5.0}/requirements.txt +0 -0
  85. {syncmoss-0.4.2 → syncmoss-0.5.0}/setup.cfg +0 -0
  86. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Calibration.py +0 -0
  87. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Hamiltonian_helper.py +0 -0
  88. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Aegirine.mdl +0 -0
  89. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Akaganeite.mdl +0 -0
  90. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Almandine.mdl +0 -0
  91. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Antitaenite.mdl +0 -0
  92. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Arsenopyrite.mdl +0 -0
  93. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Bornite.mdl +0 -0
  94. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Bridgmanite (Fe,Al-bearing) @100 GPa.mdl +0 -0
  95. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Bridgmanite (Fe,Al-bearing).mdl +0 -0
  96. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Celadonite.mdl +0 -0
  97. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Cementite.mdl +0 -0
  98. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Chalcopyrite.mdl +0 -0
  99. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Clinopyroxene (augite).mdl +0 -0
  100. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Copiapite.mdl +0 -0
  101. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Cubanite.mdl +0 -0
  102. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Epidote.mdl +0 -0
  103. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Fayalite (Fe-olivine).mdl +0 -0
  104. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Ferrihydrite (2-line).mdl +0 -0
  105. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Ferropericlase.mdl +0 -0
  106. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Goethite.mdl +0 -0
  107. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Green rust (fougerite, carbonate).mdl +0 -0
  108. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Haegg carbide.mdl +0 -0
  109. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Hedenbergite.mdl +0 -0
  110. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Hematite.mdl +0 -0
  111. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Heterosite (FePO4).mdl +0 -0
  112. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Ilmenite.mdl +0 -0
  113. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Iron phosphide.mdl +0 -0
  114. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Isocubanite.mdl +0 -0
  115. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Jarosite.mdl +0 -0
  116. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Kamacite.mdl +0 -0
  117. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Lepidocrocite.mdl +0 -0
  118. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Maghemite.mdl +0 -0
  119. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Magnetite.mdl +0 -0
  120. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Marcasite.mdl +0 -0
  121. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Melanterite.mdl +0 -0
  122. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Orthopyroxene (enstatite-ferrosilite).mdl +0 -0
  123. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Prussian blue (ferric ferrocyanide).mdl +0 -0
  124. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Pyrite.mdl +0 -0
  125. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Pyrrhotite.mdl +0 -0
  126. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Rhomboclase.mdl +0 -0
  127. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Roemerite.mdl +0 -0
  128. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Siderite.mdl +0 -0
  129. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Szomolnokite.mdl +0 -0
  130. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Taenite.mdl +0 -0
  131. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Talnakhite.mdl +0 -0
  132. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Tetrataenite.mdl +0 -0
  133. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Triphylite (LiFePO4).mdl +0 -0
  134. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Troilite.mdl +0 -0
  135. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Vivianite.mdl +0 -0
  136. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/Voltaite.mdl +0 -0
  137. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library/alpha-Fe (metallic iron, bcc).mdl +0 -0
  138. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library_io.py +0 -0
  139. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/Library_window.py +0 -0
  140. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/bliss_channel.py +0 -0
  141. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/error_reporter.py +0 -0
  142. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/icons/CheckBox.png +0 -0
  143. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/icons/CheckBox_.png +0 -0
  144. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/icons/CheckBox_L.png +0 -0
  145. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/icons/CheckBox_L2.png +0 -0
  146. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/icons/DU.png +0 -0
  147. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/icons/Switch.png +0 -0
  148. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/icons/UD.png +0 -0
  149. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/icons/icon_r.ico +0 -0
  150. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/instrumental_window.py +0 -0
  151. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/legacy.py +0 -0
  152. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/license_window.py +0 -0
  153. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/models_description_window.py +0 -0
  154. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/models_positions.py +0 -0
  155. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/parameters/ABSorber3.txt +0 -0
  156. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/parameters/Be.txt +0 -0
  157. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/parameters/INS_APS.txt +0 -0
  158. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/parameters/INSth.txt +0 -0
  159. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/parameters/KB.txt +0 -0
  160. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/parameters/NFS.txt +0 -0
  161. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/parameters/models_description.md +0 -0
  162. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/sms_theory.py +0 -0
  163. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/supp_menu.py +0 -0
  164. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/test_graf.txt +0 -0
  165. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/test_param.txt +0 -0
  166. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/theme_dark.json +0 -0
  167. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss/theme_light.json +0 -0
  168. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss.egg-info/dependency_links.txt +0 -0
  169. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss.egg-info/entry_points.txt +0 -0
  170. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss.egg-info/requires.txt +0 -0
  171. {syncmoss-0.4.2 → syncmoss-0.5.0}/syncmoss.egg-info/top_level.txt +0 -0
  172. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/data/Distr_corr.mdl +0 -0
  173. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/data/Distr_distr.mdl +0 -0
  174. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/data/alpha_fe_sms_000.mca +0 -0
  175. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/data/calibration_000_golden.json +0 -0
  176. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/data/generate_cal_fixtures.py +0 -0
  177. {syncmoss-0.4.2/syncmoss → syncmoss-0.5.0/tests/data}/parameters/Calibration.dat +0 -0
  178. {syncmoss-0.4.2/syncmoss → syncmoss-0.5.0/tests/data}/parameters/GCMS.txt +0 -0
  179. {syncmoss-0.4.2/syncmoss → syncmoss-0.5.0/tests/data}/parameters/INSexp.txt +0 -0
  180. {syncmoss-0.4.2/syncmoss → syncmoss-0.5.0/tests/data}/parameters/INSint.txt +0 -0
  181. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/data/synthetic_alpha_fe_cms_linear.mca +0 -0
  182. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/golden_models.json +0 -0
  183. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_bliss_channel.py +0 -0
  184. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_calibration.py +0 -0
  185. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_calibration_helpers.py +0 -0
  186. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_calibration_path.py +0 -0
  187. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_chi2_spread.py +0 -0
  188. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_contact_author.py +0 -0
  189. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_distr_composition.py +0 -0
  190. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_distr_expansion.py +0 -0
  191. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_distr_mdl.py +0 -0
  192. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_distr_targets.py +0 -0
  193. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_error_reporter.py +0 -0
  194. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_expression_fit_start.py +0 -0
  195. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_expression_validation.py +0 -0
  196. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_golden_models.py +0 -0
  197. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_gui_smoke.py +0 -0
  198. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_hamiltonian_texture.py +0 -0
  199. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_impurity_presets.py +0 -0
  200. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_instrumental_metadata.py +0 -0
  201. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_instrumental_method_choice.py +0 -0
  202. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_instrumental_recentre.py +0 -0
  203. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_integral_points.py +0 -0
  204. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_interrupt.py +0 -0
  205. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_legacy.py +0 -0
  206. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_library_dir.py +0 -0
  207. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_library_io.py +0 -0
  208. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_license_window.py +0 -0
  209. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_model_append.py +0 -0
  210. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_model_io.py +0 -0
  211. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_model_only_mode.py +0 -0
  212. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_nbaseline_results.py +0 -0
  213. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_parameter_links.py +0 -0
  214. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_polarization.py +0 -0
  215. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_recon_composition.py +0 -0
  216. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_recon_mdl.py +0 -0
  217. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_recon_results.py +0 -0
  218. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_reference_shift.py +0 -0
  219. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_results_table_image.py +0 -0
  220. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_sdw_grid.py +0 -0
  221. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_sms_theory.py +0 -0
  222. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_table_marks.py +0 -0
  223. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_take_result.py +0 -0
  224. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_texture_s1.py +0 -0
  225. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_theory_search.py +0 -0
  226. {syncmoss-0.4.2 → syncmoss-0.5.0}/tests/test_thick_gui.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syncmoss
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: SYNCmoss — Mössbauer spectroscopy analysis application
5
5
  Author: Sergey Yaroslavtsev
6
6
  License: MIT
@@ -92,6 +92,10 @@ number_of_baseline_parameters = 8
92
92
  # Default color sequence for plotting models
93
93
  model_colors = ['red', 'blue', 'cyan', 'yellow', 'fuchsia', 'lime', 'darkorange', 'blueviolet', 'green', 'tomato', 'white', 'silver', 'lightgreen', 'pink']
94
94
 
95
+ # An Nbaseline row is shown light grey: it opens the next spectrum's section,
96
+ # it is not a component. 'silver' is a colour every .mdl reader already knows.
97
+ NBASELINE_COLOR = 'silver'
98
+
95
99
  # Every color name a .mdl color row may contain (used to recognise the color
96
100
  # line when loading model files — shared by model_io and Library_io so both
97
101
  # accept the same files).
@@ -0,0 +1,252 @@
1
+ """The exclusion-regions bar of the main window, between the plot toolbar and
2
+ the plot: the regions as text, "Pick on plot", Save and Load.
3
+
4
+ It is shown while "Apply exclusion" (the plot toolbar) is on. ``regions`` are
5
+ the applied regions; they change only through commit(), which emits
6
+ ``regions_changed``. What a change does to the plot and to a fit result is the
7
+ main window's business (PhysicsApp._on_exclusion_changed).
8
+ """
9
+ import os
10
+
11
+ import numpy as np
12
+ from PySide6.QtCore import Qt, QEvent, Signal
13
+ from PySide6.QtGui import QFont, QKeySequence, QShortcut
14
+ from PySide6.QtWidgets import (
15
+ QWidget, QHBoxLayout, QVBoxLayout, QLabel, QLineEdit, QPushButton, QFileDialog, QSizePolicy,
16
+ )
17
+
18
+ from syncmoss import exclusion_regions as er
19
+ from syncmoss.spectrum_plotter import (
20
+ DATA_GID, SPECTRUM_AXES_GID, EXCLUSION_COLOR, spectrum_axes_of,
21
+ )
22
+
23
+ _FILE_FILTER = "Exclusion regions (*_exclusion.txt);;Text files (*.txt);;All files (*)"
24
+
25
+
26
+ class ExclusionBar(QWidget):
27
+ """The text box of the exclusion regions and its buttons."""
28
+
29
+ regions_changed = Signal()
30
+
31
+ def __init__(self, main_window):
32
+ super().__init__(main_window)
33
+ self.main_window = main_window
34
+ self.regions = () # the applied regions
35
+ self._first = None # the first end of a region being picked
36
+ self._marks = [] # its dashed lines on the plot
37
+
38
+ # Its own height only: the plot takes every spare pixel
39
+ self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
40
+ font = QFont('Arial', 14)
41
+
42
+ label = QLabel("Exclusion regions, mm/s:")
43
+ label.setFont(font)
44
+ self.text = QLineEdit()
45
+ self.text.setFont(font)
46
+ self.text.setPlaceholderText("-3:-2; 1:2")
47
+ self.text.setToolTip("Velocity regions whose points are not fitted: lo:hi; lo:hi\n"
48
+ "Enter or leaving the box applies them.")
49
+ self.text.installEventFilter(self)
50
+ self.text.editingFinished.connect(self.commit)
51
+ self.text.textEdited.connect(lambda _text: self._mark_invalid(False))
52
+
53
+ self.pick_btn = QPushButton("Pick on plot")
54
+ self.pick_btn.setFont(font)
55
+ self.pick_btn.setCheckable(True)
56
+ self.pick_btn.setToolTip("Click the two ends of a region on the plot (Esc cancels)")
57
+ self.pick_btn.toggled.connect(self._pick_toggled)
58
+ self.clean_btn = QPushButton("Clean")
59
+ self.clean_btn.setFont(font)
60
+ self.clean_btn.setToolTip("Remove every exclusion region")
61
+ self.clean_btn.clicked.connect(self.clean)
62
+ self.save_btn = QPushButton("Save")
63
+ self.save_btn.setFont(font)
64
+ self.save_btn.clicked.connect(self.save)
65
+ self.load_btn = QPushButton("Load")
66
+ self.load_btn.setFont(font)
67
+ self.load_btn.setToolTip("Replace the regions with those of a file")
68
+ self.load_btn.clicked.connect(self.load)
69
+
70
+ # The label and the buttons on one line, the regions under them
71
+ buttons = QHBoxLayout()
72
+ buttons.addWidget(label)
73
+ buttons.addStretch(1)
74
+ buttons.addWidget(self.pick_btn)
75
+ buttons.addWidget(self.clean_btn)
76
+ buttons.addWidget(self.save_btn)
77
+ buttons.addWidget(self.load_btn)
78
+ layout = QVBoxLayout(self)
79
+ layout.setContentsMargins(0, 0, 0, 0)
80
+ layout.addLayout(buttons)
81
+ layout.addWidget(self.text)
82
+
83
+ # Esc cancels a pick; enabled only while picking
84
+ self.escape = QShortcut(QKeySequence(Qt.Key.Key_Escape), main_window)
85
+ self.escape.setEnabled(False)
86
+ self.escape.activated.connect(self.cancel_pick)
87
+
88
+ # --- the text ---------------------------------------------------------------
89
+
90
+ def eventFilter(self, obj, event):
91
+ # Enter in the box applies the regions: it must not reach the window's
92
+ # Return shortcut (Show model)
93
+ if (obj is self.text and event.type() == QEvent.Type.ShortcutOverride
94
+ and event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter)):
95
+ event.accept()
96
+ return True
97
+ return super().eventFilter(obj, event)
98
+
99
+ def _mark_invalid(self, invalid):
100
+ self.text.setStyleSheet("background-color: red; color: white;" if invalid else "")
101
+
102
+ def commit(self, action_label=None):
103
+ """Apply the text. True when it holds regions -- written back cleaned,
104
+ and ``regions_changed`` emitted when they differ from the applied ones.
105
+ False (and the reason in the status) when it does not, or when they
106
+ cannot change now; then the applied regions stay. *action_label* names
107
+ what is not started because of it."""
108
+ try:
109
+ regions = er.parse(self.text.text())
110
+ except ValueError as e:
111
+ self._mark_invalid(True)
112
+ if action_label:
113
+ self.main_window.set_status(
114
+ f"{action_label} was not started — exclusion regions: {e}", "red")
115
+ else:
116
+ self.main_window.set_status(f"Exclusion regions: {e}", "red")
117
+ return False
118
+ self._mark_invalid(False)
119
+ if regions != self.regions and self.main_window._reject_if_busy('Changing exclusion regions'):
120
+ self.text.setText(er.format_regions(self.regions))
121
+ return False
122
+ self.text.setText(er.format_regions(regions))
123
+ if regions != self.regions:
124
+ self.regions = regions
125
+ self.regions_changed.emit()
126
+ return True
127
+
128
+ def clean(self):
129
+ """Remove every region ("Apply exclusion" stays as it is); refused,
130
+ like any change, while a calculation runs."""
131
+ self.pick_btn.setChecked(False)
132
+ self.text.setText("")
133
+ self.commit()
134
+
135
+ # --- Pick on plot -----------------------------------------------------------
136
+
137
+ def _pick_toggled(self, on):
138
+ if not on:
139
+ self._forget_first()
140
+ self.escape.setEnabled(False)
141
+ return
142
+ if self.main_window._reject_if_busy('Picking an exclusion region'):
143
+ self.pick_btn.setChecked(False)
144
+ return
145
+ self.escape.setEnabled(True)
146
+ self.main_window.set_status("Pick on plot: click the two ends of the region "
147
+ "(Esc cancels)", "blue")
148
+
149
+ def cancel_pick(self):
150
+ if self.pick_btn.isChecked():
151
+ self.pick_btn.setChecked(False)
152
+ self.main_window.set_status("Picking canceled", "orange")
153
+
154
+ def _forget_first(self):
155
+ self._first = None
156
+ for mark in self._marks:
157
+ mark.remove()
158
+ if self._marks:
159
+ self.main_window.canvas.draw_idle()
160
+ self._marks = []
161
+
162
+ def on_canvas_click(self, event):
163
+ """A click on the main plot (connected to the canvas, so it survives
164
+ every redraw): one end of the region being picked."""
165
+ if not self.pick_btn.isChecked() or event.button != 1:
166
+ return
167
+ if str(self.main_window.toolbar.mode): # Pan or Zoom is on
168
+ return
169
+ ax = event.inaxes
170
+ if ax is None or ax.get_gid() != SPECTRUM_AXES_GID or event.xdata is None:
171
+ return
172
+ end = self._boundary(ax, float(event.xdata))
173
+ if self._first is None:
174
+ self._first = end
175
+ for spectrum_ax in spectrum_axes_of(ax.figure):
176
+ xlim = spectrum_ax.get_xlim()
177
+ self._marks.append(spectrum_ax.axvline(end, color=EXCLUSION_COLOR,
178
+ linestyle='--', linewidth=1))
179
+ spectrum_ax.set_xlim(xlim)
180
+ self.main_window.canvas.draw_idle()
181
+ self.main_window.set_status(f"One end at {er.format_velocity(end)} mm/s: "
182
+ f"click the other end (Esc cancels)", "blue")
183
+ return
184
+ first = self._first
185
+ self.pick_btn.setChecked(False) # forgets the first end
186
+ if first == end:
187
+ self.main_window.set_status("Both ends are between the same two points: "
188
+ "no point would be excluded", "orange")
189
+ return
190
+ try:
191
+ current = er.parse(self.text.text())
192
+ except ValueError:
193
+ current = self.regions
194
+ self.text.setText(er.format_regions(er.normalize(current + ((first, end),))))
195
+ self.commit()
196
+
197
+ def _boundary(self, ax, x):
198
+ """Where an end clicked at *x* goes: between the data points around it,
199
+ or, on a plot without data points, within half a pixel."""
200
+ grid = [np.asarray(line.get_xdata(), dtype=float)
201
+ for line in ax.lines if line.get_gid() == DATA_GID]
202
+ if grid:
203
+ end = er.boundary_between_points(np.concatenate(grid), x)
204
+ if end is not None:
205
+ return end
206
+ x0, x1 = ax.get_xlim()
207
+ return er.boundary_at_pixel(x, abs(x1 - x0) / max(ax.bbox.width, 1.0) / 2)
208
+
209
+ # --- Save / Load ------------------------------------------------------------
210
+
211
+ def save(self):
212
+ if not self.commit():
213
+ return
214
+ if not self.regions:
215
+ self.main_window.set_status("No exclusion regions to save", "orange")
216
+ return
217
+ base = self.main_window._result_base_path()
218
+ default = (base + er.FILE_SUFFIX if base
219
+ else os.path.join(self.main_window.workfolder or "", "regions" + er.FILE_SUFFIX))
220
+ path, _ = QFileDialog.getSaveFileName(self, "Save exclusion regions", default, _FILE_FILTER)
221
+ if not path:
222
+ self.main_window.set_status("Saving canceled", "orange")
223
+ return
224
+ try:
225
+ er.write_file(path, self.regions)
226
+ except OSError as e:
227
+ self.main_window.set_status(f"Exclusion regions not saved: {e}", "red")
228
+ return
229
+ self.main_window.set_status(f"Exclusion regions saved to {os.path.basename(path)}", "green")
230
+
231
+ def load(self):
232
+ """Replace the regions with those of a file."""
233
+ path, _ = QFileDialog.getOpenFileName(self, "Load exclusion regions",
234
+ self.main_window.workfolder or "", _FILE_FILTER)
235
+ if not path:
236
+ self.main_window.set_status("Loading canceled", "orange")
237
+ return
238
+ self.load_file(path)
239
+
240
+ def load_file(self, path):
241
+ try:
242
+ regions = er.read_file(path)
243
+ except (OSError, ValueError) as e:
244
+ self.main_window.set_status(f"Exclusion regions not loaded from "
245
+ f"{os.path.basename(path)}: {e}", "red")
246
+ return False
247
+ self.text.setText(er.format_regions(regions))
248
+ if not self.commit():
249
+ return False
250
+ self.main_window.set_status(f"Exclusion regions loaded from {os.path.basename(path)}: "
251
+ f"{er.format_regions(regions) or 'none'}", "green")
252
+ return True
@@ -0,0 +1,137 @@
1
+ """Exclusion regions: velocity ranges whose points are left out of a fit.
2
+
3
+ A region is a pair ``(lo, hi)`` in mm/s. The regions of a fit are a tuple of
4
+ such pairs, sorted, each with ``lo < hi``, none overlapping or touching; ``()``
5
+ means none. A point is excluded when ``lo <= v <= hi``.
6
+
7
+ The model at one velocity does not depend on the other velocities computed --
8
+ the instrumental function is integrated over the source energy for every
9
+ velocity on its own (tests/test_model_pointwise.py) -- so a fit with exclusion
10
+ regions is simply a fit of the points that are left.
11
+
12
+ The user types the regions as ``lo:hi; lo:hi``; only ';' separates them.
13
+ """
14
+ import sys
15
+
16
+ import numpy as np
17
+
18
+ FILE_SUFFIX = '_exclusion.txt'
19
+ FILE_HEADER = '# SYNCmoss exclusion regions, velocity in mm/s'
20
+
21
+
22
+ def normalize(pairs):
23
+ """The regions of *pairs* (each in either order): sorted, overlapping or
24
+ touching ones merged."""
25
+ ordered = sorted((min(float(a), float(b)), max(float(a), float(b))) for a, b in pairs)
26
+ merged = []
27
+ for lo, hi in ordered:
28
+ if merged and lo <= merged[-1][1]:
29
+ merged[-1] = (merged[-1][0], max(merged[-1][1], hi))
30
+ else:
31
+ merged.append((lo, hi))
32
+ return tuple(merged)
33
+
34
+
35
+ def parse(text):
36
+ """The regions written in *text* (``lo:hi; lo:hi``), normalized.
37
+
38
+ Raises ValueError with a message for the user. A ',' anywhere is refused,
39
+ so a decimal comma can never be read as something else.
40
+ """
41
+ text = str(text).strip()
42
+ if ',' in text:
43
+ raise ValueError("use ';' between regions and '.' for decimals")
44
+ pairs = []
45
+ for piece in text.split(';'):
46
+ piece = piece.strip()
47
+ if not piece:
48
+ continue
49
+ ends = piece.split(':')
50
+ if len(ends) != 2:
51
+ raise ValueError(f"'{piece}' is not a region: write it as lo:hi")
52
+ try:
53
+ lo, hi = float(ends[0]), float(ends[1])
54
+ except ValueError:
55
+ raise ValueError(f"'{piece}': both ends must be numbers") from None
56
+ if not (np.isfinite(lo) and np.isfinite(hi)):
57
+ raise ValueError(f"'{piece}': both ends must be finite numbers")
58
+ if lo == hi:
59
+ raise ValueError(f"'{piece}' is empty: its two ends are the same")
60
+ pairs.append((lo, hi))
61
+ return normalize(pairs)
62
+
63
+
64
+ def format_velocity(value):
65
+ """*value* written in the shortest form that reads back exactly."""
66
+ return np.format_float_positional(float(value), trim='-')
67
+
68
+
69
+ def format_regions(regions):
70
+ """``-3.05:-1.95; 0.95:2.05`` -- the text parse() reads back."""
71
+ return '; '.join(f'{format_velocity(lo)}:{format_velocity(hi)}' for lo, hi in regions)
72
+
73
+
74
+ def kept(velocities, regions):
75
+ """Boolean mask over *velocities*: True for the points that are fitted."""
76
+ v = np.asarray(velocities, dtype=float)
77
+ keep = np.ones(v.shape, dtype=bool)
78
+ for lo, hi in regions:
79
+ keep &= ~((v >= lo) & (v <= hi))
80
+ return keep
81
+
82
+
83
+ def shortest_decimal_in(lo, hi, target):
84
+ """The number with the fewest decimals strictly between *lo* and *hi*;
85
+ of those, the one closest to *target*."""
86
+ lo, hi = min(lo, hi), max(lo, hi)
87
+ for decimals in range(sys.float_info.dig + 3):
88
+ scale = 10.0 ** decimals
89
+ first, last = int(np.floor(lo * scale)), int(np.ceil(hi * scale))
90
+ nearest = int(min(max(round(target * scale), first), last))
91
+ # the products above are rounded, so the candidates next to the
92
+ # nearest one are checked as well
93
+ for k in sorted((nearest - 1, nearest, nearest + 1),
94
+ key=lambda k: abs(k - target * scale)):
95
+ if lo < k / scale < hi:
96
+ return k / scale
97
+ return float(target)
98
+
99
+
100
+ def boundary_between_points(grid, x):
101
+ """Where a region boundary clicked at *x* goes: between the two points of
102
+ *grid* around *x* (beyond an end, between the end point and one step
103
+ further), written with the fewest decimals that tell those points apart.
104
+ None when the grid has fewer than two points."""
105
+ points = np.unique(np.asarray(grid, dtype=float))
106
+ if points.size < 2:
107
+ return None
108
+ i = int(np.searchsorted(points, x))
109
+ if i == 0:
110
+ left, right = 2 * points[0] - points[1], points[0]
111
+ elif i == points.size:
112
+ left, right = points[-1], 2 * points[-1] - points[-2]
113
+ else:
114
+ left, right = points[i - 1], points[i]
115
+ return shortest_decimal_in(left, right, x)
116
+
117
+
118
+ def boundary_at_pixel(x, half_pixel):
119
+ """A boundary clicked at *x* on a plot without data points: the fewest
120
+ decimals within half a pixel (*half_pixel*, in mm/s) of the click."""
121
+ return shortest_decimal_in(x - half_pixel, x + half_pixel, x)
122
+
123
+
124
+ def write_file(path, regions):
125
+ """Write *regions* to an exclusion-regions file (read_file reads it)."""
126
+ with open(path, 'w', encoding='utf-8') as f:
127
+ f.write(FILE_HEADER + '\n')
128
+ f.write(format_regions(regions) + '\n')
129
+
130
+
131
+ def read_file(path):
132
+ """The regions of an exclusion-regions file: its lines that are not
133
+ comments, joined with ';'. Raises ValueError for a bad file (OSError when
134
+ it cannot be read)."""
135
+ with open(path, 'r', encoding='utf-8') as f:
136
+ lines = [line.strip() for line in f]
137
+ return parse('; '.join(line for line in lines if line and not line.startswith('#')))