tomwer 1.0.3__py3-none-any.whl → 1.1.0__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.
Files changed (269) hide show
  1. orangecontrib/tomwer/tutorials/EBS_tomo_listener.ows +39 -0
  2. orangecontrib/tomwer/tutorials/cast_volume.ows +34 -0
  3. orangecontrib/tomwer/tutorials/simple_slice_reconstruction.ows +39 -0
  4. orangecontrib/tomwer/tutorials/simple_volume_local_reconstruction.ows +49 -0
  5. orangecontrib/tomwer/tutorials/simple_volume_to_slurm_reconstruction.ows +59 -0
  6. orangecontrib/tomwer/tutorials/using_saaxis_to_find_cor.ows +44 -0
  7. orangecontrib/tomwer/widgets/cluster/FutureSupervisorOW.py +1 -1
  8. orangecontrib/tomwer/widgets/cluster/SlurmClusterOW.py +14 -4
  9. orangecontrib/tomwer/widgets/cluster/__init__.py +1 -1
  10. orangecontrib/tomwer/widgets/control/DataListOW.py +12 -5
  11. orangecontrib/tomwer/widgets/control/DataListenerOW.py +18 -9
  12. orangecontrib/tomwer/widgets/control/DataSelectorOW.py +13 -6
  13. orangecontrib/tomwer/widgets/control/DataTransfertOW.py +3 -5
  14. orangecontrib/tomwer/widgets/control/DataValidatorOW.py +8 -4
  15. orangecontrib/tomwer/widgets/control/DataWatcherOW.py +4 -6
  16. orangecontrib/tomwer/widgets/control/EDF2NXTomomillOW.py +49 -62
  17. orangecontrib/tomwer/widgets/control/FilterOW.py +2 -4
  18. orangecontrib/tomwer/widgets/control/NXTomomillMixIn.py +93 -0
  19. orangecontrib/tomwer/widgets/control/NXTomomillOW.py +135 -129
  20. orangecontrib/tomwer/widgets/control/NotifierOW.py +34 -9
  21. orangecontrib/tomwer/widgets/control/SingleTomoObjOW.py +3 -5
  22. orangecontrib/tomwer/widgets/control/TomoObjSerieOW.py +19 -13
  23. orangecontrib/tomwer/widgets/control/VolumeSelector.py +12 -4
  24. orangecontrib/tomwer/widgets/control/VolumeSymLinkOW.py +11 -7
  25. orangecontrib/tomwer/widgets/control/icons/notification.svg +4 -4
  26. orangecontrib/tomwer/widgets/control/icons/nxtomomill.png +0 -0
  27. orangecontrib/tomwer/widgets/control/icons/nxtomomill.svg +8 -5
  28. orangecontrib/tomwer/widgets/control/icons/tomoobjserie.png +0 -0
  29. orangecontrib/tomwer/widgets/control/icons/tomoobjserie.svg +73 -78
  30. orangecontrib/tomwer/widgets/edit/DarkFlatPatchOW.py +16 -4
  31. orangecontrib/tomwer/widgets/edit/NXtomoEditorOW.py +100 -0
  32. orangecontrib/tomwer/widgets/edit/icons/image_key_editor.png +0 -0
  33. orangecontrib/tomwer/widgets/edit/icons/image_key_upgrader.png +0 -0
  34. orangecontrib/tomwer/widgets/edit/icons/nx_tomo_editor.png +0 -0
  35. orangecontrib/tomwer/widgets/edit/icons/nx_tomo_editor.svg +123 -0
  36. orangecontrib/tomwer/widgets/edit/test/test_dark_flat_patch.py +21 -1
  37. orangecontrib/tomwer/widgets/edit/test/test_image_key_editor.py +1 -1
  38. orangecontrib/tomwer/widgets/edit/test/test_image_key_upgrader.py +1 -1
  39. orangecontrib/tomwer/widgets/edit/test/test_nxtomo_editor.py +25 -0
  40. orangecontrib/tomwer/widgets/other/PythonScriptOW.py +19 -11
  41. orangecontrib/tomwer/widgets/reconstruction/AxisOW.py +20 -14
  42. orangecontrib/tomwer/widgets/reconstruction/CastNabuVolumeOW.py +24 -10
  43. orangecontrib/tomwer/widgets/reconstruction/DarkRefAndCopyOW.py +26 -21
  44. orangecontrib/tomwer/widgets/reconstruction/NabuOW.py +29 -12
  45. orangecontrib/tomwer/widgets/reconstruction/NabuVolumeOW.py +44 -17
  46. orangecontrib/tomwer/widgets/reconstruction/SAAxisOW.py +28 -20
  47. orangecontrib/tomwer/widgets/reconstruction/SADeltaBetaOW.py +24 -18
  48. orangecontrib/tomwer/widgets/reconstruction/SinoNormOW.py +6 -6
  49. orangecontrib/tomwer/widgets/reconstruction/TofuOW.py +4 -2
  50. orangecontrib/tomwer/widgets/reconstruction/icons/nabu_2d.png +0 -0
  51. orangecontrib/tomwer/widgets/reconstruction/icons/nabu_2d.svg +11 -8
  52. orangecontrib/tomwer/widgets/visualization/DataViewerOW.py +10 -4
  53. orangecontrib/tomwer/widgets/visualization/DiffViewerOW.py +1 -1
  54. orangecontrib/tomwer/widgets/visualization/NXtomoMetadataViewerOW.py +69 -0
  55. orangecontrib/tomwer/widgets/visualization/SampleMovedOW.py +2 -4
  56. orangecontrib/tomwer/widgets/visualization/icons/nx_tomo_metadata_viewer.png +0 -0
  57. orangecontrib/tomwer/widgets/visualization/icons/nx_tomo_metadata_viewer.svg +105 -0
  58. tomwer/__main__.py +10 -5
  59. tomwer/app/canvas_launcher/config.py +10 -10
  60. tomwer/app/canvas_launcher/mainwindow.py +68 -6
  61. tomwer/app/canvas_launcher/widgetsscheme.py +1 -3
  62. tomwer/app/darkref.py +16 -12
  63. tomwer/app/imagekeyeditor.py +2 -2
  64. tomwer/app/imagekeyupgrader.py +104 -0
  65. tomwer/app/intensitynormalization.py +0 -1
  66. tomwer/app/nxtomoeditor.py +103 -0
  67. tomwer/app/rsync.py +1 -1
  68. tomwer/core/cluster/cluster.py +1 -1
  69. tomwer/core/futureobject.py +1 -0
  70. tomwer/core/process/control/datalistener/datalistener.py +7 -1
  71. tomwer/core/process/control/datalistener/rpcserver.py +3 -4
  72. tomwer/core/process/control/datawatcher/datawatcher.py +18 -18
  73. tomwer/core/process/control/datawatcher/datawatcherobserver.py +5 -8
  74. tomwer/core/process/control/datawatcher/datawatcherprocess.py +2 -3
  75. tomwer/core/process/control/datawatcher/edfdwprocess.py +2 -2
  76. tomwer/core/process/control/nxtomomill.py +33 -58
  77. tomwer/core/process/control/scanlist.py +2 -1
  78. tomwer/core/process/control/scanselector.py +7 -0
  79. tomwer/core/process/control/scantransfer.py +2 -2
  80. tomwer/core/process/control/scanvalidator.py +6 -5
  81. tomwer/core/process/control/singletomoobj.py +2 -1
  82. tomwer/core/process/control/timer.py +2 -1
  83. tomwer/core/process/control/tomoobjserie.py +8 -2
  84. tomwer/core/process/control/volumeselector.py +2 -1
  85. tomwer/core/process/control/volumesymlink.py +2 -1
  86. tomwer/core/process/edit/darkflatpatch.py +2 -1
  87. tomwer/core/process/edit/imagekeyeditor.py +4 -3
  88. tomwer/core/process/reconstruction/axis/axis.py +29 -32
  89. tomwer/core/process/reconstruction/axis/mode.py +3 -2
  90. tomwer/core/process/reconstruction/axis/params.py +35 -16
  91. tomwer/core/process/reconstruction/darkref/darkrefs.py +90 -707
  92. tomwer/core/process/reconstruction/darkref/darkrefscopy.py +44 -16
  93. tomwer/core/process/reconstruction/darkref/params.py +62 -67
  94. tomwer/core/process/reconstruction/lamino/tofu.py +1 -2
  95. tomwer/core/process/reconstruction/nabu/castvolume.py +21 -26
  96. tomwer/core/process/reconstruction/nabu/nabucommon.py +36 -38
  97. tomwer/core/process/reconstruction/nabu/nabuscores.py +28 -13
  98. tomwer/core/process/reconstruction/nabu/nabuslices.py +41 -14
  99. tomwer/core/process/reconstruction/nabu/nabuvolume.py +21 -12
  100. tomwer/core/process/reconstruction/nabu/utils.py +32 -3
  101. tomwer/core/process/reconstruction/normalization/normalization.py +9 -8
  102. tomwer/core/process/reconstruction/saaxis/saaxis.py +46 -20
  103. tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +38 -12
  104. tomwer/core/process/reconstruction/test/__init__.py +0 -39
  105. tomwer/core/process/reconstruction/test/test_axis_params.py +25 -3
  106. tomwer/core/process/reconstruction/test/test_darkref_copy.py +117 -1
  107. tomwer/core/process/script/python.py +16 -12
  108. tomwer/core/process/task.py +3 -7
  109. tomwer/core/process/test/test_axis.py +1 -1
  110. tomwer/core/process/test/test_dark_and_flat.py +41 -111
  111. tomwer/core/process/test/test_data_listener.py +0 -29
  112. tomwer/core/process/test/test_data_transfer.py +10 -14
  113. tomwer/core/process/test/test_nabu.py +1 -1
  114. tomwer/core/process/test/test_normalization.py +1 -1
  115. tomwer/core/process/visualization/liveslice.py +6 -0
  116. tomwer/core/scan/blissscan.py +37 -2
  117. tomwer/core/scan/edfscan.py +19 -8
  118. tomwer/core/scan/hdf5scan.py +10 -4
  119. tomwer/core/scan/scanbase.py +35 -29
  120. tomwer/core/scan/scanfactory.py +3 -17
  121. tomwer/core/scan/test/test_h5.py +1 -1
  122. tomwer/core/scan/test/test_process_registration.py +0 -11
  123. tomwer/core/scan/test/test_scan.py +32 -30
  124. tomwer/core/settings.py +2 -2
  125. tomwer/core/test/test_utils.py +1 -1
  126. tomwer/core/tomwer_object.py +19 -0
  127. tomwer/core/utils/__init__.py +0 -45
  128. tomwer/core/utils/char.py +2 -0
  129. tomwer/core/utils/gpu.py +5 -5
  130. tomwer/core/utils/nxtomoutils.py +2 -2
  131. tomwer/core/utils/scanutils.py +50 -0
  132. tomwer/core/utils/volumeutils.py +13 -0
  133. tomwer/core/volume/edfvolume.py +4 -0
  134. tomwer/core/volume/hdf5volume.py +4 -0
  135. tomwer/core/volume/jp2kvolume.py +4 -0
  136. tomwer/core/volume/rawvolume.py +22 -5
  137. tomwer/core/volume/tiffvolume.py +4 -0
  138. tomwer/core/volume/volumebase.py +19 -12
  139. tomwer/core/volume/volumefactory.py +20 -1
  140. tomwer/gui/cluster/slurm.py +1 -1
  141. tomwer/gui/cluster/supervisor.py +0 -2
  142. tomwer/gui/cluster/test/test_cluster.py +2 -2
  143. tomwer/gui/control/datalist.py +109 -36
  144. tomwer/gui/control/datatransfert.py +1 -1
  145. tomwer/gui/control/datawatcher/configuration.py +0 -2
  146. tomwer/gui/control/datawatcher/datawatcher.py +23 -13
  147. tomwer/gui/control/datawatcher/datawatcherobserver.py +1 -1
  148. tomwer/gui/control/observations.py +0 -3
  149. tomwer/gui/control/selectorwidgetbase.py +42 -12
  150. tomwer/gui/control/serie/seriecreator.py +967 -0
  151. tomwer/{web/__init__.py → gui/control/serie/seriewaiter.py} +5 -7
  152. tomwer/gui/control/singletomoobj.py +15 -4
  153. tomwer/gui/control/test/test_datalist.py +1 -1
  154. tomwer/gui/control/test/test_datalistener.py +1 -1
  155. tomwer/gui/control/test/test_inputwidget.py +1 -1
  156. tomwer/gui/control/test/test_process_manager.py +1 -13
  157. tomwer/gui/control/test/test_scanselector.py +1 -1
  158. tomwer/gui/control/test/test_scanvalidator.py +1 -1
  159. tomwer/gui/control/test/test_single_tomo_obj.py +1 -1
  160. tomwer/gui/control/test/test_volume_dialog.py +19 -7
  161. tomwer/gui/control/test/test_volumeselector.py +4 -4
  162. tomwer/gui/debugtools/datasetgenerator.py +1 -9
  163. tomwer/gui/edit/dkrfpatch.py +2 -3
  164. tomwer/gui/edit/imagekeyeditor.py +12 -11
  165. tomwer/gui/edit/nxtomoeditor.py +475 -0
  166. tomwer/gui/edit/test/test_dkrf_patch.py +2 -14
  167. tomwer/gui/edit/test/test_image_key_editor.py +2 -2
  168. tomwer/gui/edit/test/test_nx_editor.py +155 -0
  169. tomwer/gui/icons.py +0 -1
  170. tomwer/gui/qfolderdialog.py +11 -0
  171. tomwer/gui/reconstruction/axis/CompareImages.py +27 -29
  172. tomwer/gui/reconstruction/axis/axis.py +2 -0
  173. tomwer/gui/reconstruction/axis/radioaxis.py +70 -14
  174. tomwer/gui/reconstruction/darkref/darkrefcopywidget.py +7 -9
  175. tomwer/gui/reconstruction/darkref/darkrefwidget.py +22 -24
  176. tomwer/gui/reconstruction/lamino/tofu/projections.py +1 -1
  177. tomwer/gui/reconstruction/lamino/tofu/tofu.py +3 -3
  178. tomwer/gui/reconstruction/lamino/tofu/tofuexpert.py +4 -4
  179. tomwer/gui/reconstruction/lamino/tofu/tofuoutput.py +10 -5
  180. tomwer/gui/reconstruction/nabu/castvolume.py +103 -24
  181. tomwer/gui/reconstruction/nabu/check.py +1 -1
  182. tomwer/gui/reconstruction/nabu/nabuconfig/ctf.py +352 -0
  183. tomwer/gui/reconstruction/nabu/nabuconfig/nabuconfig.py +0 -9
  184. tomwer/gui/reconstruction/nabu/nabuconfig/output.py +1 -1
  185. tomwer/gui/reconstruction/nabu/nabuconfig/phase.py +18 -19
  186. tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +30 -7
  187. tomwer/gui/reconstruction/nabu/nabuconfig/reconstruction.py +26 -15
  188. tomwer/gui/reconstruction/nabu/slices.py +10 -4
  189. tomwer/gui/reconstruction/nabu/slurm.py +1 -1
  190. tomwer/gui/reconstruction/nabu/volume.py +13 -7
  191. tomwer/gui/reconstruction/normalization/intensity.py +1 -5
  192. tomwer/gui/reconstruction/saaxis/corrangeselector.py +10 -37
  193. tomwer/gui/reconstruction/saaxis/saaxis.py +11 -7
  194. tomwer/gui/reconstruction/saaxis/sliceselector.py +11 -26
  195. tomwer/gui/reconstruction/sadeltabeta/saadeltabeta.py +13 -8
  196. tomwer/gui/reconstruction/scores/scoreplot.py +67 -62
  197. tomwer/gui/reconstruction/test/test_axis.py +2 -2
  198. tomwer/gui/reconstruction/test/test_lamino.py +2 -2
  199. tomwer/gui/reconstruction/test/test_nabu.py +14 -1
  200. tomwer/gui/reconstruction/test/test_saaxis.py +8 -17
  201. tomwer/gui/reconstruction/test/test_sadeltabeta.py +7 -13
  202. tomwer/gui/stackplot.py +11 -28
  203. tomwer/gui/test/test_axis_gui.py +4 -4
  204. tomwer/gui/test/test_qfolder_dialog.py +12 -0
  205. tomwer/gui/utils/inputwidget.py +42 -22
  206. tomwer/gui/utils/lineselector/lineselector.py +13 -21
  207. tomwer/gui/utils/scandescription.py +2 -4
  208. tomwer/gui/utils/slider.py +1 -102
  209. tomwer/gui/utils/unitsystem.py +48 -11
  210. tomwer/gui/visualization/dataviewer.py +24 -17
  211. tomwer/gui/visualization/diffviewer/diffviewer.py +2 -11
  212. tomwer/gui/visualization/nxtomometadata.py +21 -0
  213. tomwer/gui/visualization/scanoverview.py +0 -1
  214. tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py +72 -0
  215. tomwer/gui/visualization/test/test_stacks.py +1 -1
  216. tomwer/gui/visualization/tomoobjoverview.py +49 -0
  217. tomwer/gui/visualization/volumeoverview.py +64 -0
  218. tomwer/gui/visualization/volumeviewer.py +1 -1
  219. tomwer/io/utils/utils.py +2 -2
  220. tomwer/resources/gui/icons/multi-document-save.png +0 -0
  221. tomwer/resources/gui/icons/multi-document-save.svg +101 -0
  222. tomwer/resources/gui/illustrations/ctf_z1.png +0 -0
  223. tomwer/resources/gui/illustrations/ctf_z1.svg +471 -0
  224. tomwer/synctools/axis.py +0 -1
  225. tomwer/synctools/darkref.py +0 -1
  226. tomwer/synctools/datalistener.py +5 -1
  227. tomwer/synctools/imageloaderthread.py +2 -2
  228. tomwer/synctools/saaxis.py +0 -1
  229. tomwer/synctools/sadeltabeta.py +0 -1
  230. tomwer/synctools/stacks/edit/imagekeyeditor.py +1 -1
  231. tomwer/synctools/stacks/processingstack.py +2 -2
  232. tomwer/synctools/stacks/reconstruction/castvolume.py +1 -0
  233. tomwer/synctools/stacks/reconstruction/dkrefcopy.py +1 -1
  234. tomwer/synctools/stacks/reconstruction/lamino.py +1 -3
  235. tomwer/synctools/stacks/reconstruction/sadeltabeta.py +0 -2
  236. tomwer/synctools/test/test_darkRefs.py +32 -149
  237. tomwer/synctools/test/test_foldertransfer.py +1 -1
  238. tomwer/synctools/test/test_scanstages.py +2 -2
  239. tomwer/tests/conftest.py +51 -0
  240. tomwer/{test → tests}/test_scripts.py +1 -1
  241. tomwer/tests/test_utils.py +10 -0
  242. tomwer/{test → tests}/utils/utilstest.py +0 -11
  243. tomwer/version.py +3 -3
  244. {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/METADATA +14 -16
  245. {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/RECORD +255 -235
  246. {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/WHEEL +1 -1
  247. {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/entry_points.txt +6 -0
  248. orangecontrib/tomwer/setup.py +0 -45
  249. orangecontrib/tomwer/widgets/setup.py +0 -49
  250. tomwer/app/process.py +0 -153
  251. tomwer/core/process/reconstruction/nabu/slurm.py +0 -36
  252. tomwer/core/process/reconstruction/utils/nabu_slice_exec.py +0 -10
  253. tomwer/core/utils/laminoutils.py +0 -80
  254. tomwer/gui/utils/lineselector/lineselection.py +0 -76
  255. tomwer/setup.py +0 -52
  256. tomwer/slurm/executor.py +0 -36
  257. tomwer/slurm/job.py +0 -349
  258. tomwer/slurm/utils.py +0 -44
  259. tomwer/web/client.py +0 -43
  260. tomwer/web/config.py +0 -36
  261. tomwer/web/test/test_graylog_connection.py +0 -59
  262. {tomwer/slurm → orangecontrib/tomwer/tutorials}/__init__.py +0 -0
  263. /tomwer/{test → gui/control/serie}/__init__.py +0 -0
  264. /tomwer/{web/test → tests}/__init__.py +0 -0
  265. /tomwer/{test → tests}/utils/__init__.py +0 -0
  266. /tomwer-1.0.3-py3.8-nspkg.pth → /tomwer-1.1.0-py3.9-nspkg.pth +0 -0
  267. {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/LICENSE +0 -0
  268. {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/namespace_packages.txt +0 -0
  269. {tomwer-1.0.3.dist-info → tomwer-1.1.0.dist-info}/top_level.txt +0 -0
@@ -16,6 +16,7 @@ from orangecanvas.application.outputview import (
16
16
  ExceptHook,
17
17
  TerminalTextDocument,
18
18
  )
19
+ from xml.sax.saxutils import escape
19
20
  import logging
20
21
  import os
21
22
  import shutil
@@ -26,6 +27,19 @@ from .config import TomwerConfig, TomwerSplashScreen
26
27
  import tomwer.version
27
28
  from urllib.request import urlopen
28
29
 
30
+ try:
31
+ import nabu
32
+ except ImportError:
33
+ has_nabu = False
34
+ else:
35
+ has_nabu = True
36
+ try:
37
+ import nxtomomill.version
38
+ except ImportError:
39
+ has_nxtomomill = False
40
+ else:
41
+ has_nxtomomill = True
42
+
29
43
  _logger = logging.getLogger(__file__)
30
44
 
31
45
  MAX_LOG_FILE = 10
@@ -68,20 +82,27 @@ class MainWindow(_MainWindow):
68
82
  super().setup_menu()
69
83
  self.view_menu.addAction(self.show_processes_manager_action)
70
84
 
85
+ def open_about(self):
86
+ # type: () -> None
87
+ """Open the about dialog."""
88
+ dlg = AboutDialog(self)
89
+ dlg.setAttribute(qt.Qt.WA_DeleteOnClose)
90
+ dlg.exec()
91
+
71
92
 
72
93
  log = logging.getLogger(__name__)
73
94
 
74
95
 
75
- def check_for_updates():
76
- pass
96
+ def check_for_updates() -> bool:
97
+ return False
77
98
 
78
99
 
79
- def send_usage_statistics():
80
- pass
100
+ def send_usage_statistics() -> bool:
101
+ return False
81
102
 
82
103
 
83
- def pull_notifications():
84
- pass
104
+ def pull_notifications() -> bool:
105
+ return False
85
106
 
86
107
 
87
108
  def make_sql_logger(level=logging.INFO):
@@ -175,6 +196,47 @@ def widget_settings_dir():
175
196
  return os.path.join(data_dir(), "widgets")
176
197
 
177
198
 
199
+ ABOUT_TEMPLATE = """\
200
+ <center>
201
+ <h4>{name}</h4>
202
+ <p>tomwer version: {tomwer_version}</p>
203
+ <p>nabu version: {nabu_version}</p>
204
+ <p>nxtomomill version: {nxtomomill_version}</p>
205
+ </center>
206
+ """
207
+
208
+
209
+ class AboutDialog(qt.QDialog):
210
+ def __init__(self, parent=None, **kwargs) -> None:
211
+ super().__init__(parent, **kwargs)
212
+ layout = qt.QVBoxLayout()
213
+ label = qt.QLabel(self)
214
+
215
+ pixmap, _ = TomwerConfig.splash_screen()
216
+ pixmap = pixmap.scaled(150, 150)
217
+
218
+ label.setPixmap(pixmap)
219
+
220
+ layout.addWidget(label, qt.Qt.AlignCenter)
221
+
222
+ text = ABOUT_TEMPLATE.format(
223
+ name=escape("tomwer"),
224
+ tomwer_version=escape(tomwer.version.version),
225
+ nabu_version=escape(nabu.version if has_nabu else "not installed"),
226
+ nxtomomill_version=escape(
227
+ nxtomomill.version.version if has_nxtomomill else "not installed"
228
+ ),
229
+ )
230
+ text_label = qt.QLabel(text)
231
+ layout.addWidget(text_label, qt.Qt.AlignCenter)
232
+
233
+ buttons = qt.QDialogButtonBox(qt.QDialogButtonBox.Close, qt.Qt.Horizontal, self)
234
+ layout.addWidget(buttons)
235
+ buttons.rejected.connect(self.accept)
236
+ layout.setSizeConstraint(qt.QVBoxLayout.SetFixedSize)
237
+ self.setLayout(layout)
238
+
239
+
178
240
  class OMain(_OMain):
179
241
  config: TomwerConfig
180
242
  DefaultConfig = "tomwer.app.canvas_launcher.config.TomwerConfig"
@@ -50,9 +50,7 @@ class WidgetsScheme(Scheme):
50
50
  return changed
51
51
 
52
52
  def show_report_view(self):
53
- inst = self.report_view()
54
- inst.show()
55
- inst.raise_()
53
+ return
56
54
 
57
55
  def has_report(self):
58
56
  """
tomwer/app/darkref.py CHANGED
@@ -6,7 +6,7 @@ import logging
6
6
  import signal
7
7
  import sys
8
8
  from tomwer.core.scan.scanfactory import ScanFactory
9
- from tomwer.core.process.reconstruction.darkref.params import Method
9
+ from tomwer.core.process.reconstruction.darkref.params import ReduceMethod
10
10
  from tomwer.core.process.reconstruction.darkref.params import DKRFRP
11
11
  from tomwer.core.process.reconstruction.darkref.darkrefs import DarkRefs
12
12
  from tomwer.core.utils.resource import increase_max_number_file
@@ -24,11 +24,15 @@ def _exec_without_interaction(scan, dark_method, flat_method, overwrite):
24
24
  recons_params.overwrite_dark = overwrite
25
25
  recons_params.overwrite_ref = overwrite
26
26
  recons_params.dark_calc_method = dark_method
27
- recons_params.ref_calc_method = flat_method
28
- dark_ref = DarkRefs()
29
- dark_ref.set_recons_params(recons_params)
27
+ recons_params.flat_calc_method = flat_method
28
+ dark_ref = DarkRefs(
29
+ inputs={
30
+ "data": scan,
31
+ "dark_ref_params": recons_params,
32
+ }
33
+ )
30
34
  _logger.info("Start processing of {}".format(str(scan)))
31
- dark_ref.process(scan)
35
+ dark_ref.run()
32
36
  _logger.info("End processing of {}".format(str(scan)))
33
37
  return 0
34
38
 
@@ -37,7 +41,7 @@ def _exec_with_interaction(scan, dark_method, flat_method, overwrite):
37
41
  from silx.gui import qt
38
42
  from tomwer.gui import icons
39
43
  from tomwer.gui.reconstruction.darkref.darkrefwidget import DarkRefWidget
40
- from tomwer.synctools.ftseries import _QDKRFRP
44
+ from tomwer.synctools.darkref import QDKRFRP
41
45
  from tomwer.gui.utils.splashscreen import getMainSplashScreen
42
46
 
43
47
  class _DarkRefWidgetRunnable(DarkRefWidget):
@@ -45,7 +49,7 @@ def _exec_with_interaction(scan, dark_method, flat_method, overwrite):
45
49
 
46
50
  def __init__(self, scan, parent=None):
47
51
  self.__scan = scan
48
- self.__darkref_rp = _QDKRFRP()
52
+ self.__darkref_rp = QDKRFRP()
49
53
  DarkRefWidget.__init__(self, parent=parent, reconsparams=self.__darkref_rp)
50
54
  buttonExec = qt.QPushButton("execute", parent=self)
51
55
  buttonExec.setAutoDefault(True)
@@ -91,7 +95,7 @@ def _exec_with_interaction(scan, dark_method, flat_method, overwrite):
91
95
  widget.recons_params.overwrite_dark = overwrite
92
96
  widget.recons_params.overwrite_ref = overwrite
93
97
  widget.recons_params.dark_calc_method = dark_method
94
- widget.recons_params.ref_calc_method = flat_method
98
+ widget.recons_params.flat_calc_method = flat_method
95
99
  splash.finish(widget)
96
100
  widget.show()
97
101
  return app.exec_()
@@ -110,12 +114,12 @@ def main(argv):
110
114
  parser.add_argument(
111
115
  "--dark-method",
112
116
  help="Define the method to be used for computing dark",
113
- default=Method.average,
117
+ default=ReduceMethod.MEAN,
114
118
  )
115
119
  parser.add_argument(
116
120
  "--flat-method",
117
121
  help="Define the method to be used for computing flat",
118
- default=Method.median,
122
+ default=ReduceMethod.MEDIAN,
119
123
  )
120
124
  parser.add_argument(
121
125
  "--no-gui",
@@ -149,8 +153,8 @@ def main(argv):
149
153
  scan = ScanFactory.create_scan_object(options.scan_path, entry=options.entry)
150
154
  scan.set_process_index_frm_tomwer_process_file()
151
155
 
152
- dark_method = Method.from_value(options.dark_method)
153
- flat_method = Method.from_value(options.flat_method)
156
+ dark_method = ReduceMethod.from_value(options.dark_method)
157
+ flat_method = ReduceMethod.from_value(options.flat_method)
154
158
 
155
159
  if options.run:
156
160
  exit(
@@ -8,7 +8,7 @@ import argparse
8
8
  from tomwer.gui import icons
9
9
  from tomwer.gui.utils.splashscreen import getMainSplashScreen
10
10
  from tomwer.core.scan.hdf5scan import HDF5TomoScan
11
- from tomoscan.esrf.hdf5scan import ImageKey
11
+ from tomoscan.esrf.scan.hdf5scan import ImageKey
12
12
  from tomwer.gui.edit.imagekeyeditor import ImageKeyDialog as _ImageKeyDialog
13
13
  from nxtomomill.utils import change_image_key_control
14
14
  from tomwer.core.utils.resource import increase_max_number_file
@@ -56,7 +56,7 @@ class ImageKeyDialog(_ImageKeyDialog):
56
56
 
57
57
 
58
58
  def getinputinfo():
59
- return "tomwer nabu [scan_path]"
59
+ return "tomwer image-key-editor [scan_path] [entry]"
60
60
 
61
61
 
62
62
  def sigintHandler(*args):
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import logging
5
+ import sys
6
+ from silx.gui import qt
7
+ import argparse
8
+ from tomwer.gui import icons
9
+ from tomwer.gui.utils.splashscreen import getMainSplashScreen
10
+ from tomwer.core.scan.hdf5scan import HDF5TomoScan
11
+ from tomwer.gui.edit.imagekeyeditor import (
12
+ ImageKeyUpgraderWidget as _ImageKeyUpgraderWidget,
13
+ )
14
+ from tomwer.core.process.edit.imagekeyeditor import ImageKeyUpgraderTask
15
+ from tomwer.core.utils.resource import increase_max_number_file
16
+ import signal
17
+
18
+ logging.basicConfig(level=logging.WARNING)
19
+ _logger = logging.getLogger(__name__)
20
+
21
+
22
+ class ImageKeyUpgraderDialog(qt.QDialog):
23
+ def __init__(self, *args, **kwargs):
24
+ super().__init__(*args, **kwargs)
25
+ self._scan = None
26
+
27
+ self.setLayout(qt.QVBoxLayout())
28
+ self._widget = _ImageKeyUpgraderWidget(self)
29
+ self.layout().addWidget(self._widget)
30
+
31
+ types = qt.QDialogButtonBox.Ok | qt.QDialogButtonBox.Cancel
32
+ self._buttons = qt.QDialogButtonBox(self)
33
+ self._buttons.setStandardButtons(types)
34
+ self.layout().addWidget(self._buttons)
35
+
36
+ # signal / slot connection
37
+ self._buttons.button(qt.QDialogButtonBox.Ok).released.connect(self.validate)
38
+ self._buttons.button(qt.QDialogButtonBox.Cancel).released.connect(self.close)
39
+
40
+ def setScan(self, scan):
41
+ if not isinstance(scan, HDF5TomoScan):
42
+ raise TypeError("This only manage HDF5TomoScan")
43
+ self._scan = scan
44
+
45
+ def validate(self):
46
+ operations = self._widget.getOperations()
47
+ task = ImageKeyUpgraderTask(
48
+ inputs={
49
+ "data": self._scan,
50
+ "operations": operations,
51
+ },
52
+ )
53
+ task.run()
54
+ print("edition finished")
55
+ self.close()
56
+
57
+
58
+ def getinputinfo():
59
+ return "tomwer image-key-upgrader [scan_path] [entry]"
60
+
61
+
62
+ def sigintHandler(*args):
63
+ """Handler for the SIGINT signal."""
64
+ qt.QApplication.quit()
65
+
66
+
67
+ def main(argv):
68
+ import os
69
+
70
+ parser = argparse.ArgumentParser(description=__doc__)
71
+ parser.add_argument("scan_path", help="NXtomo to edit")
72
+ parser.add_argument("entry", default=None, help="Entry to treat")
73
+ options = parser.parse_args(argv[1:])
74
+
75
+ # image key can only handle HDF5Tomoscan for now
76
+ scan = HDF5TomoScan(scan=options.scan_path, entry=options.entry)
77
+ increase_max_number_file()
78
+
79
+ app = qt.QApplication.instance() or qt.QApplication([])
80
+
81
+ qt.QLocale.setDefault(qt.QLocale(qt.QLocale.English))
82
+ qt.QLocale.setDefault(qt.QLocale.c())
83
+ signal.signal(signal.SIGINT, sigintHandler)
84
+ sys.excepthook = qt.exceptionHandler
85
+ timer = qt.QTimer()
86
+ timer.start(500)
87
+ # Application have to wake up Python interpreter, else SIGINT is not
88
+ # catch
89
+ timer.timeout.connect(lambda: None)
90
+
91
+ splash = getMainSplashScreen()
92
+ options.scan_path = options.scan_path.rstrip(os.path.sep)
93
+
94
+ widget = ImageKeyUpgraderDialog(parent=None)
95
+ widget.setScan(scan)
96
+ widget.setWindowTitle("Image key upgrader")
97
+ widget.setWindowIcon(icons.getQIcon("tomwer"))
98
+ splash.finish(widget)
99
+ widget.show()
100
+ app.exec_()
101
+
102
+
103
+ if __name__ == "__main__":
104
+ main(sys.argv)
@@ -46,7 +46,6 @@ class IntensityNormalizationThread(qt.QThread):
46
46
  self._result = None
47
47
 
48
48
  def run(self) -> None:
49
-
50
49
  process = SinoNormalizationTask(
51
50
  process_id=None,
52
51
  inputs={
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import sys
5
+ import argparse
6
+ from silx.gui import qt
7
+ import signal
8
+ from tomwer.gui.utils.splashscreen import getMainSplashScreen
9
+ from tomwer.core.scan.scanfactory import ScanFactory
10
+ from tomwer.gui.edit.nxtomoeditor import NXtomoEditorDialog as _NXtomoEditorDialog
11
+ import logging
12
+
13
+
14
+ logging.basicConfig(level=logging.WARNING)
15
+ _logger = logging.getLogger(__name__)
16
+
17
+
18
+ class NXtomoEditorDialog(_NXtomoEditorDialog):
19
+ def __init__(self) -> None:
20
+ super().__init__()
21
+
22
+ # connect signal / slot
23
+ self._buttons.button(qt.QDialogButtonBox.Ok).released.connect(
24
+ self._overwriteNXtomo
25
+ )
26
+
27
+ def _overwriteNXtomo(self, *args, **kwargs):
28
+ print("start overwrite")
29
+ try:
30
+ self.overwriteNXtomo()
31
+ except Exception as e:
32
+ print(f"write failed with error {e}")
33
+ else:
34
+ print("overwrite succeded")
35
+
36
+
37
+ def main(argv):
38
+ parser = argparse.ArgumentParser(description=__doc__)
39
+ parser.add_argument("scan_path", help="nexus file (HDF5 file) to edit.")
40
+ parser.add_argument(
41
+ "entry",
42
+ help="path to the file where starts the NXtomo. Usually named 'entry', or 'entryXXXX'",
43
+ )
44
+ parser.add_argument(
45
+ "--debug",
46
+ dest="debug",
47
+ action="store_true",
48
+ default=False,
49
+ help="Set logging system in debug mode",
50
+ )
51
+
52
+ options = parser.parse_args(argv[1:])
53
+
54
+ if options.debug:
55
+ logging.root.setLevel(logging.DEBUG)
56
+
57
+ global app # QApplication must be global to avoid seg fault on quit
58
+ app = qt.QApplication.instance() or qt.QApplication([])
59
+ splash = getMainSplashScreen()
60
+ qt.QApplication.setOverrideCursor(qt.Qt.WaitCursor)
61
+ app.processEvents()
62
+
63
+ qt.QLocale.setDefault(qt.QLocale(qt.QLocale.English))
64
+ qt.QLocale.setDefault(qt.QLocale.c())
65
+ signal.signal(signal.SIGINT, sigintHandler)
66
+ sys.excepthook = qt.exceptionHandler
67
+
68
+ timer = qt.QTimer()
69
+ timer.start(500)
70
+ # Application have to wake up Python interpreter, else SIGINT is not
71
+ # catch
72
+ timer.timeout.connect(lambda: None)
73
+
74
+ try:
75
+ scan = ScanFactory.create_scan_object(
76
+ scan_path=options.scan_path, entry=options.entry
77
+ )
78
+ except Exception as e:
79
+ _logger.error(
80
+ f"Fail to find an NXtomo from {options.scan_path} at {options.entry}. Error is {e}"
81
+ )
82
+ return
83
+
84
+ dialog = NXtomoEditorDialog()
85
+ dialog.setScan(scan)
86
+ dialog.show()
87
+ splash.finish(dialog)
88
+
89
+ qt.QApplication.restoreOverrideCursor()
90
+ app.exec_()
91
+
92
+
93
+ def getinputinfo():
94
+ return "tomwer nxtomo-editor [scanDir]"
95
+
96
+
97
+ def sigintHandler(*args):
98
+ """Handler for the SIGINT signal."""
99
+ qt.QApplication.quit()
100
+
101
+
102
+ if __name__ == "__main__":
103
+ main(sys.argv)
tomwer/app/rsync.py CHANGED
@@ -92,7 +92,7 @@ def main(argv):
92
92
 
93
93
  # fresh option treatment
94
94
  def refresh_dir(dir_):
95
- logger.info("recreate", dir_)
95
+ logger.info(f"recreate {dir_}")
96
96
  shutil.rmtree(dir_)
97
97
  os.makedirs(dir_)
98
98
 
@@ -42,7 +42,7 @@ class SlurmClusterConfiguration:
42
42
  n_cpu_per_task=_SlurmSettings.N_CORES_PER_TASK,
43
43
  n_tasks=_SlurmSettings.N_TASKS,
44
44
  memory=_SlurmSettings.MEMORY_PER_WORKER,
45
- queue=_SlurmSettings.PARTIION,
45
+ queue=_SlurmSettings.PARTITION,
46
46
  n_gpus=_SlurmSettings.N_GPUS_PER_WORKER,
47
47
  project_name=_SlurmSettings.PROJECT_NAME,
48
48
  walltime=_SlurmSettings.DEFAULT_WALLTIME,
@@ -67,6 +67,7 @@ class FutureTomwerObject:
67
67
  raise TypeError(
68
68
  f"scan is expected to be an instance of {TomwerObject} not {type(tomo_obj)}"
69
69
  )
70
+ self._job_id = None
70
71
  self._tomo_obj = tomo_obj
71
72
  self._start_time = (
72
73
  start_time if start_time is not None else datetime.datetime.now()
@@ -40,6 +40,7 @@ from tomwer.core.scan.hdf5scan import HDF5TomoScan
40
40
  from tomoscan.io import HDF5File
41
41
  from silx.io.utils import h5py_read_dataset
42
42
  from ewokscore.task import Task as EwoksTask
43
+ from tomwer.core.scan.blissscan import BlissScan
43
44
  import typing
44
45
  import time
45
46
 
@@ -469,7 +470,12 @@ class DataListener(BaseProcessInfo):
469
470
  configuration = HDF5Config.from_dict(conf)
470
471
  if configuration.output_file is None:
471
472
  output_file_path = NxTomomillProcess.deduce_output_file_path(
472
- bliss_file, entry=entry, outputdir=None
473
+ bliss_file,
474
+ entry=entry,
475
+ outputdir=None,
476
+ scan=BlissScan(
477
+ master_file=bliss_file, entry=entry, proposal_file=bliss_file
478
+ ),
473
479
  )
474
480
  else:
475
481
  output_file_path = configuration.output_file
@@ -68,7 +68,7 @@ class BlissAcquisition:
68
68
  self.proposal_file = proposal_file
69
69
  self.sample_file = sample_file
70
70
  self.scan_numbers = []
71
- self.status = BlissAcquisition
71
+ self.status = TangoAcquisitionStatus.STARTED
72
72
  self._start_time = start_time
73
73
  self._end_time = None
74
74
  self._error = None
@@ -151,7 +151,6 @@ class _BaseDataListenerThread:
151
151
  def _rpc_sequence_started(
152
152
  self, saving_file, scan_title, sequence_scan_number, proposal_file, sample_file
153
153
  ):
154
-
155
154
  self._current_sequence = BlissAcquisition(
156
155
  file_path=saving_file,
157
156
  entry_name=sequence_scan_number,
@@ -187,7 +186,7 @@ class _BaseDataListenerThread:
187
186
  def scan_ended(self, acquisition: BlissAcquisition, scan_number):
188
187
  print("scan {} ended".format(scan_number))
189
188
 
190
- def _rpc_sequence_ended(self, saving_file, sequence_scan_number, success):
189
+ def _rpc_sequence_ended(self, saving_file, sequence_scan_number, success: bool):
191
190
  if success is False:
192
191
  _logger.warning(
193
192
  "sequence {}@{} failed".format(sequence_scan_number, saving_file)
@@ -196,7 +195,7 @@ class _BaseDataListenerThread:
196
195
  try:
197
196
  self.sequence_failed(self._current_sequence)
198
197
  except Exception as e:
199
- _logger.warning("Fail to catch cancel scan. error is", e)
198
+ _logger.warning(f"Fail to catch cancel scan. error is {e}")
200
199
 
201
200
  return
202
201
 
@@ -53,8 +53,16 @@ from ewokscore.task import Task as EwoksTask
53
53
  logger = logging.getLogger(__name__)
54
54
 
55
55
 
56
- class _DataWatchertaskPlaceHolder(EwoksTask, output_names=("data",)):
57
- pass
56
+ class DataWatcherEwoksTask(EwoksTask, input_names=["data"], output_names=["data"]):
57
+ """
58
+ For now the data watcher is a 'special case'. Because it will trigger downstream workflows / prcessing each time it discover
59
+ a new scan.
60
+ To move to 'default' ewoks workflows. Like launching it from the command line the simpler for now is to
61
+ simply pass the `data` input that user can fill manually.
62
+ """
63
+
64
+ def run(self):
65
+ self.outputs.data = self.inputs.data
58
66
 
59
67
 
60
68
  class _DataWatcher(BaseProcessInfo):
@@ -93,6 +101,10 @@ class _DataWatcher(BaseProcessInfo):
93
101
  self._launchObservation()
94
102
  self._checkThread = None
95
103
 
104
+ def _launchObservation(self):
105
+ """Main function of the widget"""
106
+ raise NotImplementedError("Base class")
107
+
96
108
  def _initClass(self):
97
109
  self.srcPattern = get_lbsram_path()
98
110
  self.destPattern = get_dest_path()
@@ -188,15 +200,6 @@ class _DataWatcher(BaseProcessInfo):
188
200
 
189
201
  return True
190
202
 
191
- def isObserving(self):
192
- """
193
-
194
- :return bool: True if the widget is actually observing the root
195
- directory
196
-
197
- """
198
- return self.isObserving
199
-
200
203
  def _setIsObserving(self, b):
201
204
  self.isObserving = b
202
205
 
@@ -316,10 +319,7 @@ class _DataWatcher(BaseProcessInfo):
316
319
  _scan = ScanFactory.create_scan_object(scan_path=scanID)
317
320
  except Exception as e:
318
321
  logger.error(
319
- "Fail to create TomoBase from",
320
- scanID,
321
- "Error is",
322
- e,
322
+ f"Fail to create TomoBase from {scanID}. Error is {e}"
323
323
  )
324
324
  else:
325
325
  self._signalScanReady(scan=_scan)
@@ -369,14 +369,14 @@ class _DataWatcher(BaseProcessInfo):
369
369
  try:
370
370
  scan = ScanFactory.create_scan_object(scan_path=info)
371
371
  except Exception as e:
372
- logger.warning("Fail to create scan object from", info, "Reason is", e)
372
+ logger.warning(f"Fail to create scan object from {info}. Reason is {e}")
373
373
  else:
374
374
  self._signalScanReady(scan=scan)
375
375
 
376
376
  def _signalScanReady(self, scan):
377
377
  assert isinstance(scan, (TomwerScanBase, BlissScan))
378
378
  self.lastFoundScans.add(scan)
379
- self.sigScanReady.emit(scan)
379
+ self.sigScanReady.emit(scan) # pylint: disable=E1101
380
380
 
381
381
  def mockObservation(self, folder):
382
382
  # simple mocking emitting a signal to say that the given folder is valid
@@ -446,7 +446,7 @@ class DataWatcher(_DataWatcher):
446
446
  # manage data watcher observation
447
447
  if self.observationThread is None or not self._observation_thread_running():
448
448
  if self._initObservation() is False:
449
- self.currentStatus = self._setCurrentStatus("failure")
449
+ self._setCurrentStatus("failure")
450
450
  logger.info(
451
451
  "failed on observation",
452
452
  extra={logconfig.DOC_TITLE: self._scheme_title},
@@ -39,7 +39,6 @@ import threading
39
39
  from collections import OrderedDict
40
40
  from typing import Optional
41
41
  from tomwer.core.process.control.datawatcher import status as datawatcherstatus
42
- from tomwer.web.client import OWClient
43
42
  from .edfdwprocess import (
44
43
  _DataWatcherProcessParseInfo,
45
44
  _DataWatcherProcessUserFilePattern,
@@ -56,7 +55,7 @@ import h5py
56
55
  logger = logging.getLogger(__name__)
57
56
 
58
57
 
59
- class _DataWatcherStaticObserverMixIn(OWClient):
58
+ class _DataWatcherStaticObserverMixIn:
60
59
  def __init__(
61
60
  self,
62
61
  scanID,
@@ -75,7 +74,6 @@ class _DataWatcherStaticObserverMixIn(OWClient):
75
74
  :param Union[str,None] patternObs:
76
75
  :param observationRegistry: what registry the observation should update
77
76
  """
78
- OWClient.__init__(self)
79
77
  self.obsMethod = obsMethod
80
78
  self.srcPattern = srcPattern
81
79
  self.destPattern = destPattern
@@ -271,7 +269,7 @@ class _DataWatcherStaticObserver(_DataWatcherStaticObserverMixIn, threading.Thre
271
269
  )
272
270
 
273
271
 
274
- class _DataWatcherObserver_MixIn(OWClient):
272
+ class _DataWatcherObserver_MixIn:
275
273
  """Thread launching the data watcher process (observation of acquisition)
276
274
 
277
275
  :param str headDir: the root dir to make to fin dacquisition
@@ -302,7 +300,6 @@ class _DataWatcherObserver_MixIn(OWClient):
302
300
  ignoredFolders=None,
303
301
  file_name_pattern=None,
304
302
  ):
305
- OWClient.__init__(self)
306
303
  self.observations = observationClass()
307
304
  self.observations.ignoredFolders = (
308
305
  [] if ignoredFolders is None else ignoredFolders
@@ -385,11 +382,11 @@ class _DataWatcherObserver_MixIn(OWClient):
385
382
  if not os.path.isdir(self.headDir):
386
383
  logger.warning("can't observe %s, not a directory" % self.headDir)
387
384
  return
388
- self._check_scans_ready()
385
+ self._check_scans_ready() # pylint: disable=E1101
389
386
  self.dataWatcherProcess = self._getDataWatcherProcess()
390
387
  process_observations(self.headDir)
391
388
  self._processObservation()
392
- self._check_scans_ready()
389
+ self._check_scans_ready() # pylint: disable=E1101
393
390
  else:
394
391
  time.sleep(0.5)
395
392
 
@@ -473,7 +470,7 @@ class _DataWatcherObserver_MixIn(OWClient):
473
470
  thread.setSourceToDestinationPatterns(self.srcPattern, self.destPattern)
474
471
 
475
472
  def observe(self, scanID):
476
- self.observations.add(self._getObserver(scanID))
473
+ self.observations.add(self._getObserver(scanID)) # pylint: disable=E1101
477
474
 
478
475
  def cancelObservation(self, scanID):
479
476
  if self.observations.isObserving(scanID) is False: