tomwer 1.3.4__py3-none-any.whl → 1.3.26__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 (59) hide show
  1. orangecontrib/tomwer/widgets/reconstruction/DarkRefAndCopyOW.py +1 -0
  2. orangecontrib/tomwer/widgets/reconstruction/NabuHelicalPrepareWeightsDoubleOW.py +184 -184
  3. tomwer/app/canvas_launcher/mainwindow.py +0 -1
  4. tomwer/app/zstitching.py +0 -1
  5. tomwer/core/cluster/cluster.py +0 -9
  6. tomwer/core/process/control/datalistener/datalistener.py +15 -12
  7. tomwer/core/process/control/datawatcher/edfdwprocess.py +0 -9
  8. tomwer/core/process/reconstruction/axis/axis.py +3 -3
  9. tomwer/core/process/reconstruction/axis/params.py +3 -3
  10. tomwer/core/process/reconstruction/darkref/darkrefscopy.py +37 -8
  11. tomwer/core/process/reconstruction/nabu/nabucommon.py +3 -4
  12. tomwer/core/process/reconstruction/nabu/nabuscores.py +1 -0
  13. tomwer/core/process/reconstruction/nabu/nabuslices.py +6 -52
  14. tomwer/core/process/reconstruction/nabu/nabuvolume.py +2 -5
  15. tomwer/core/process/reconstruction/nabu/utils.py +10 -2
  16. tomwer/core/process/reconstruction/saaxis/saaxis.py +2 -0
  17. tomwer/core/process/reconstruction/sadeltabeta/sadeltabeta.py +2 -0
  18. tomwer/core/process/task.py +4 -2
  19. tomwer/core/process/test/test_data_transfer.py +4 -3
  20. tomwer/core/scan/blissscan.py +3 -3
  21. tomwer/core/scan/nxtomoscan.py +2 -2
  22. tomwer/core/scan/scanbase.py +5 -6
  23. tomwer/core/utils/scanutils.py +5 -1
  24. tomwer/gui/cluster/slurm.py +1 -21
  25. tomwer/gui/cluster/test/test_cluster.py +0 -1
  26. tomwer/gui/control/datawatcher/datawatcher.py +1 -24
  27. tomwer/gui/control/reducedarkflatselector.py +2 -2
  28. tomwer/gui/control/selectorwidgetbase.py +3 -1
  29. tomwer/gui/edit/dkrfpatch.py +4 -4
  30. tomwer/gui/edit/nxtomoeditor.py +28 -20
  31. tomwer/gui/edit/nxtomowarmer.py +3 -2
  32. tomwer/gui/edit/test/test_nx_editor.py +58 -1
  33. tomwer/gui/imagefromfile.py +2 -2
  34. tomwer/gui/qfolderdialog.py +4 -0
  35. tomwer/gui/reconstruction/axis/axis.py +16 -13
  36. tomwer/gui/reconstruction/axis/radioaxis.py +3 -1
  37. tomwer/gui/reconstruction/darkref/darkrefcopywidget.py +11 -0
  38. tomwer/gui/reconstruction/nabu/nabuconfig/preprocessing.py +16 -14
  39. tomwer/gui/reconstruction/saaxis/saaxis.py +2 -2
  40. tomwer/gui/stitching/stitching.py +8 -3
  41. tomwer/gui/visualization/dataviewer.py +27 -15
  42. tomwer/gui/visualization/diffviewer/diffviewer.py +9 -8
  43. tomwer/gui/visualization/volumeviewer.py +10 -4
  44. tomwer/io/utils/h5pyutils.py +3 -7
  45. tomwer/io/utils/utils.py +3 -3
  46. tomwer/synctools/stacks/reconstruction/castvolume.py +20 -5
  47. tomwer/synctools/stacks/reconstruction/dkrefcopy.py +10 -0
  48. tomwer/tests/datasets.py +5 -1
  49. tomwer/utils.py +1 -4
  50. tomwer/version.py +1 -1
  51. tomwer-1.3.26-py3.11-nspkg.pth +1 -0
  52. {tomwer-1.3.4.dist-info → tomwer-1.3.26.dist-info}/METADATA +34 -48
  53. {tomwer-1.3.4.dist-info → tomwer-1.3.26.dist-info}/RECORD +58 -58
  54. {tomwer-1.3.4.dist-info → tomwer-1.3.26.dist-info}/WHEEL +1 -1
  55. tomwer-1.3.4-py3.11-nspkg.pth +0 -1
  56. {tomwer-1.3.4.dist-info → tomwer-1.3.26.dist-info}/LICENSE +0 -0
  57. {tomwer-1.3.4.dist-info → tomwer-1.3.26.dist-info}/entry_points.txt +0 -0
  58. {tomwer-1.3.4.dist-info → tomwer-1.3.26.dist-info}/namespace_packages.txt +0 -0
  59. {tomwer-1.3.4.dist-info → tomwer-1.3.26.dist-info}/top_level.txt +0 -0
@@ -8,11 +8,11 @@ from silx.gui import qt
8
8
  from silx.gui.plot import Plot2D
9
9
  from silx.io.dictdump import h5todict
10
10
  from silx.io.url import DataUrl
11
+ from silx.io.utils import open as open_hdf5
11
12
  from silx.gui.dialog.DataFileDialog import DataFileDialog
12
13
 
13
14
  from tomoscan.esrf.scan.utils import cwd_context
14
15
  from tomoscan.framereducer.target import REDUCER_TARGET
15
- from tomoscan.io import HDF5File, get_swmr_mode
16
16
 
17
17
  from tomwer.io.utils import get_default_directory
18
18
 
@@ -249,7 +249,7 @@ class ReduceDarkFlatSelectorTableWidget(qt.QWidget):
249
249
  if not os.path.exists(file_path):
250
250
  _logger.error(f"file doesn't exists ({file_path})")
251
251
 
252
- with HDF5File(file_path, mode="r", swmr=get_swmr_mode()) as h5f:
252
+ with open_hdf5(file_path) as h5f:
253
253
  entries = tuple(h5f.keys())
254
254
 
255
255
  res = []
@@ -156,7 +156,9 @@ class _SelectorWidget(qt.QMainWindow):
156
156
 
157
157
  def removeSelectedDatasets(self):
158
158
  sItem = self.dataList.selectedItems()
159
- selection = [_item.text().get_identifier().to_str() for _item in sItem]
159
+ selection = [
160
+ _item.data(qt.Qt.UserRole).get_identifier().to_str() for _item in sItem
161
+ ]
160
162
 
161
163
  with block_signals(self):
162
164
  # make sure sigUpdated is called only once.
@@ -36,8 +36,8 @@ from silx.gui import qt
36
36
  from silx.gui.dialog.DataFileDialog import DataFileDialog
37
37
  from silx.io.url import DataUrl
38
38
  from silx.io.utils import h5py_read_dataset
39
+ from silx.io.utils import open as open_hdf5
39
40
  from nxtomo.nxobject.nxdetector import ImageKey
40
- from tomoscan.io import HDF5File, get_swmr_mode
41
41
 
42
42
  import tomwer.core.utils.nxtomoutils as nxtomo_utils
43
43
  from tomwer.core.scan.nxtomoscan import NXtomoScan
@@ -83,7 +83,7 @@ class _DarkOrFlatUrl(qt.QWidget):
83
83
  url = self._redirectDataPath(url, logger=_logger)
84
84
 
85
85
  def dataset_invalid(url):
86
- with HDF5File(url.file_path(), mode="r", swmr=get_swmr_mode()) as h5s:
86
+ with open_hdf5(url.file_path()) as h5s:
87
87
  if not isinstance(h5s[url.data_path()], h5py.Dataset):
88
88
  return True
89
89
  return False
@@ -113,7 +113,7 @@ class _DarkOrFlatUrl(qt.QWidget):
113
113
 
114
114
  def _redirectDataPath(self, url, logger=None):
115
115
  try:
116
- with HDF5File(url.file_path(), mode="r", swmr=get_swmr_mode()) as h5s:
116
+ with open_hdf5(url.file_path()) as h5s:
117
117
  node = h5s[url.data_path()]
118
118
 
119
119
  if NXtomoScan.entry_is_nx_tomo(node):
@@ -188,7 +188,7 @@ class _DarkOrFlatUrl(qt.QWidget):
188
188
  def _getImageKey(self, url):
189
189
  # if we are on a 'detector / data dataset' then we can try to reach
190
190
  # image_key information
191
- with HDF5File(url.file_path(), mode="r", swmr=get_swmr_mode()) as h5s:
191
+ with open_hdf5(url.file_path()) as h5s:
192
192
  dataset = h5s[url.data_path()]
193
193
  grp_parent = dataset.parent
194
194
  if grp_parent is not None and NXtomoScan.is_nxdetector(grp_parent):
@@ -574,28 +574,36 @@ class NXtomoEditor(qt.QWidget):
574
574
  ),
575
575
  solve_empty_dependency=True,
576
576
  )
577
- detector_transformation_path = "/".join(
578
- (
579
- nexus_paths.INSTRUMENT_PATH,
580
- nexus_paths.nx_instrument_paths.DETECTOR_PATH,
581
- nexus_paths.nx_detector_paths.NX_TRANSFORMATIONS,
582
- ),
583
- )
584
- if detector_transformation_path in entry:
585
- del entry[detector_transformation_path]
586
-
587
- detector_transformation_path = "/".join(
588
- (scan.entry, detector_transformation_path)
577
+ if nexus_paths.nx_detector_paths.NX_TRANSFORMATIONS is not None:
578
+ # old NXtomo are not handling NX_TRANSFORMATIONS
579
+ detector_transformation_path = "/".join(
580
+ (
581
+ nexus_paths.INSTRUMENT_PATH,
582
+ nexus_paths.nx_instrument_paths.DETECTOR_PATH,
583
+ nexus_paths.nx_detector_paths.NX_TRANSFORMATIONS,
584
+ ),
585
+ )
586
+ if detector_transformation_path in entry:
587
+ del entry[detector_transformation_path]
588
+
589
+ detector_transformation_path = "/".join(
590
+ (scan.entry, detector_transformation_path)
591
+ )
592
+ else:
593
+ _logger.debug(
594
+ "Old version of NXtomo found. No information about transformation will be saved"
595
+ )
596
+ detector_transformation_path = None
597
+
598
+ if detector_transformation_path is not None:
599
+ dicttonx(
600
+ nx_dict,
601
+ h5file=scan.master_file,
602
+ h5path=detector_transformation_path,
603
+ update_mode="replace",
604
+ mode="a",
589
605
  )
590
606
 
591
- dicttonx(
592
- nx_dict,
593
- h5file=scan.master_file,
594
- h5path=detector_transformation_path,
595
- update_mode="replace",
596
- mode="a",
597
- )
598
-
599
607
  # clear caches to make sure all modifications will be considered
600
608
  scan.clear_caches()
601
609
  scan.clear_frames_caches()
@@ -1,9 +1,10 @@
1
1
  from typing import Optional
2
2
  import h5py
3
3
  from silx.gui import qt
4
+ from silx.io.utils import open as open_hdf5
5
+
4
6
  from tomwer.core.scan.nxtomoscan import NXtomoScan
5
7
  from tomwer.gui import icons
6
- from tomoscan.io import HDF5File, get_swmr_mode
7
8
 
8
9
 
9
10
  class NXtomoProxyWarmer(qt.QWidget):
@@ -44,7 +45,7 @@ class NXtomoProxyWarmer(qt.QWidget):
44
45
  if scan is None:
45
46
  self._activateWarning(False)
46
47
  elif isinstance(scan, NXtomoScan):
47
- with HDF5File(scan.master_file, mode="r", swmr=get_swmr_mode()) as h5f:
48
+ with open_hdf5(scan.master_file) as h5f:
48
49
  entry = h5f.get(
49
50
  name=scan.entry, getclass=True, getlink=True, default=None
50
51
  )
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import h5py
2
3
 
3
4
  import numpy
4
5
  import pytest
@@ -252,7 +253,7 @@ def test_nx_editor_lock(
252
253
  # 3.0 save the nxtomo
253
254
  widget.overwriteNXtomo()
254
255
 
255
- # 4.0 check save went twell
256
+ # 4.0 check save went well
256
257
  overwrite_nx_tomo = NXtomo().load(
257
258
  file_path=file_path,
258
259
  data_path=entry,
@@ -305,3 +306,59 @@ def test_nx_editor_lock(
305
306
  "instrument.detector.x_flipped": (False, False),
306
307
  "instrument.detector.y_flipped": (True, False),
307
308
  }
309
+
310
+
311
+ def test_nxtomo_editor_with_missing_paths(
312
+ tmp_path,
313
+ qtapp, # noqa F811
314
+ ):
315
+ """
316
+ test widget behavior in the case some nxtomo path don't exist
317
+ """
318
+
319
+ # create nx tomo with raw data
320
+ nx_tomo = NXtomo()
321
+ nx_tomo.instrument.detector.image_key_control = [ImageKey.PROJECTION.value] * 12
322
+ nx_tomo.instrument.detector.data = numpy.empty(shape=(12, 10, 10))
323
+ nx_tomo.sample.rotation_angle = numpy.linspace(0, 20, num=12)
324
+
325
+ file_path = os.path.join(tmp_path, "nxtomo.nx")
326
+ entry = "entry0000"
327
+ nx_tomo.save(
328
+ file_path=file_path,
329
+ data_path=entry,
330
+ )
331
+ # delete some path that can be missing in some case
332
+ with h5py.File(file_path, mode="a") as h5f:
333
+ assert "entry0000" in h5f
334
+ assert "entry0000/beam" not in h5f
335
+ assert "entry0000/instrument/beam" not in h5f
336
+ assert "entry0000/instrument/detector/distance" not in h5f
337
+ assert "entry0000/instrument/detector/x_pixel_size" not in h5f
338
+ assert "entry0000/instrument/detector/y_pixel_size" not in h5f
339
+ assert "entry0000/instrument/detector/transformations" not in h5f
340
+
341
+ scan = NXtomoScan(file_path, entry)
342
+
343
+ # create the widget and do the edition
344
+ widget = NXtomoEditor()
345
+
346
+ widget.setScan(scan=scan)
347
+
348
+ widget._distanceMetricEntry.setValue(0.05)
349
+ widget._energyEntry.setValue(50)
350
+ widget._xPixelSizeMetricEntry.setValue(0.02)
351
+ widget._yPixelSizeMetricEntry.setValue(0.03)
352
+
353
+ # overwrite the NXtomo
354
+ widget.overwriteNXtomo()
355
+
356
+ # check save went well
357
+ overwrite_nx_tomo = NXtomo().load(
358
+ file_path=file_path,
359
+ data_path=entry,
360
+ )
361
+ assert overwrite_nx_tomo.instrument.detector.x_pixel_size.value == 0.02
362
+ assert overwrite_nx_tomo.instrument.detector.y_pixel_size.value == 0.03
363
+ assert overwrite_nx_tomo.energy.value == 50
364
+ assert overwrite_nx_tomo.instrument.detector.distance.value == 0.05
@@ -34,7 +34,7 @@ import logging
34
34
 
35
35
  from silx.gui import qt
36
36
  from silx.io.url import DataUrl
37
- from tomoscan.io import HDF5File, get_swmr_mode
37
+ from silx.io.utils import open as open_hdf5
38
38
 
39
39
  from tomwer.core.scan.scanbase import TomwerScanBase
40
40
  from tomwer.synctools.imageloaderthread import ImageLoaderThread
@@ -71,7 +71,7 @@ class ImageFromFile(_Image):
71
71
 
72
72
  def get_nabu_entry():
73
73
  try:
74
- with HDF5File(_file, mode="r", swmr=get_swmr_mode()) as h5s:
74
+ with open_hdf5(_file) as h5s:
75
75
  for node in h5s:
76
76
  if "reconstruction" in h5s[node]:
77
77
  return "/".join(
@@ -544,6 +544,10 @@ class QVolumeDialog(qt.QDialog):
544
544
  constructor = TIFFVolume
545
545
  elif file_extension in self._JP2K_EXTENSIONS:
546
546
  constructor = JP2KVolume
547
+ else:
548
+ raise NotImplementedError(
549
+ f"unhandled file extension ({file_extension})"
550
+ )
547
551
  volume = constructor(
548
552
  folder=file_path,
549
553
  volume_basename=None if basename in ("", None) else basename,
@@ -31,6 +31,7 @@ __license__ = "MIT"
31
31
  __date__ = "14/10/2019"
32
32
 
33
33
 
34
+ import numpy
34
35
  import logging
35
36
  from typing import Optional
36
37
 
@@ -272,6 +273,7 @@ class _AxisWidget(qt.QMainWindow):
272
273
  self._axis_params.relative_cor_value,
273
274
  self._axis_params.absolute_cor_value,
274
275
  )
276
+ self._controlWidget._positionInfo.setAxis(self._axis_params)
275
277
 
276
278
  # connect signal / slots
277
279
  self._controlWidget.sigComputationRequest.connect(self.sigComputationRequested)
@@ -663,7 +665,13 @@ class _PositionInfoWidget(qt.QWidget):
663
665
  if self._relativePositionQLE.text().startswith((".", "?")):
664
666
  return
665
667
  else:
666
- self.sigRelativeValueSet.emit(float(self._relativePositionQLE.text()))
668
+ value = float(self._relativePositionQLE.text())
669
+ # make sure we only emit the signal if the value changed (and when the Qline has been edited).
670
+ if self._axis.relative_cor_value is None or (
671
+ self._axis is not None
672
+ and not numpy.isclose(value, self._axis.relative_cor_value, atol=1e-3)
673
+ ):
674
+ self.sigRelativeValueSet.emit(value)
667
675
 
668
676
  def _userUpdatedAbsolutePosition(self, *args, **kwargs):
669
677
  palette = self.palette()
@@ -675,24 +683,19 @@ class _PositionInfoWidget(qt.QWidget):
675
683
  if self._absolutePositionQLE.text().startswith((".", "?")):
676
684
  return
677
685
  else:
678
- self.sigAbsolueValueSet.emit(float(self._absolutePositionQLE.text()))
686
+ value = float(self._absolutePositionQLE.text())
687
+ # make sure we only emit the signal if the value changed (and when the Qline has been edited).
688
+ if self._axis.absolute_cor_value is None or (
689
+ self._axis is not None
690
+ and not numpy.isclose(value, self._axis.absolute_cor_value, atol=1e-3)
691
+ ):
692
+ self.sigAbsolueValueSet.emit(value)
679
693
 
680
694
  def setAxis(self, axis):
681
695
  assert isinstance(axis, QAxisRP)
682
696
  if axis == self._axis:
683
697
  return
684
- if self._axis is not None:
685
- self._axis.sigChanged.disconnect(self._updatePosition)
686
698
  self._axis = axis
687
- self._axis.sigChanged.connect(self._updatePosition)
688
- self._updatePosition()
689
-
690
- def _updatePosition(self):
691
- if self._axis:
692
- self.setPosition(
693
- relative_cor=self._axis.relative_cor_value,
694
- abs_cor=self._axis.absolute_cor_value,
695
- )
696
699
 
697
700
  def getPosition(self):
698
701
  return float(self._relativePositionQLE.text())
@@ -1148,7 +1148,6 @@ class _ShiftInformation(qt.QWidget):
1148
1148
  self.setPalette(palette)
1149
1149
 
1150
1150
  def _userEndEditing(self, *args, **kwargs):
1151
- print("user end editing")
1152
1151
  palette = self.palette()
1153
1152
  palette.setColor(
1154
1153
  self.backgroundRole(),
@@ -1647,6 +1646,9 @@ class _CalculationWidget(qt.QWidget):
1647
1646
  def setEstimatedCorValue(self, value):
1648
1647
  if value is not None:
1649
1648
  self._qleNearPosQLE.setText(str(value))
1649
+ # note: keep self._axis_params up to date.
1650
+ if self._axis_params:
1651
+ self._axis_params.estimated_cor = value
1650
1652
 
1651
1653
  def getEstimatedCor(self):
1652
1654
  try:
@@ -54,6 +54,7 @@ class DarkRefAndCopyWidget(DarkRefWidget):
54
54
  """Signal emitted when the mode auto change"""
55
55
  sigCopyActivationChanged = qt.Signal()
56
56
  """Signal emitted when the copy is activated or deactivated"""
57
+ sigClearCache = qt.Signal()
57
58
 
58
59
  def __init__(self, save_dir: str, parent=None, reconsparams=None, process_id=None):
59
60
  DarkRefWidget.__init__(
@@ -79,6 +80,7 @@ class DarkRefAndCopyWidget(DarkRefWidget):
79
80
  self._refCopyWidget.sigCopyActivationChanged.connect(
80
81
  self._triggerCopyActivation
81
82
  )
83
+ self._refCopyWidget.sigClearCache.connect(self.sigClearCache)
82
84
 
83
85
  def setRefSetBy(self, scan_id: str):
84
86
  self._refCopyWidget._statusBar.showMessage(f"ref set from {scan_id}")
@@ -155,6 +157,8 @@ class RefCopyWidget(qt.QGroupBox):
155
157
  """Signal emitted when the mode auto change"""
156
158
  sigCopyActivationChanged = qt.Signal()
157
159
  """Signal emitted when the copy is activated or deactivated"""
160
+ sigClearCache = qt.Signal()
161
+ """Signal when the cache needs to be cleared"""
158
162
 
159
163
  _DEFAULT_DIRECTORY = "/lbsram/data/visitor"
160
164
  """Default directory used when the user need to set path to references"""
@@ -190,6 +194,12 @@ class RefCopyWidget(qt.QGroupBox):
190
194
  spacer = qt.QWidget(self)
191
195
  spacer.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding)
192
196
  self.layout().addWidget(spacer)
197
+ self._removeCacheFile = qt.QPushButton("clear cache")
198
+ self._removeCacheFile.setToolTip(
199
+ "Remove the file used for cacheing reduce dark / flat."
200
+ )
201
+ self.layout().addWidget(self._removeCacheFile)
202
+
193
203
  self.layout().addWidget(self.__createStatusBarGUI())
194
204
 
195
205
  self.setModeAuto(True)
@@ -201,6 +211,7 @@ class RefCopyWidget(qt.QGroupBox):
201
211
  # connect signal / slot
202
212
  self.toggled.connect(self._triggerCopyActivated)
203
213
  self._qcbAutoMode.toggled.connect(self._triggerModeAutoChanged)
214
+ self._removeCacheFile.released.connect(self.sigClearCache)
204
215
 
205
216
  def _triggerCopyActivated(self, *args, **kwargs):
206
217
  self.sigCopyActivationChanged.emit()
@@ -40,6 +40,7 @@ from tomwer.core.process.reconstruction.nabu.utils import (
40
40
  from tomwer.gui.reconstruction.nabu.nabuconfig.base import _NabuStageConfigBase
41
41
  from tomwer.gui.utils.scrollarea import QComboBoxIgnoreWheel as QComboBox
42
42
  from tomwer.gui.utils.scrollarea import QDoubleSpinBoxIgnoreWheel as QDoubleSpinBox
43
+ from tomwer.gui.utils.scrollarea import QSpinBoxIgnoreWheel as QSpinBox
43
44
  from tomwer.utils import docstring
44
45
 
45
46
  _logger = logging.getLogger(__name__)
@@ -104,7 +105,7 @@ class _NabuPreProcessingConfig(_NabuStageConfigBase, qt.QWidget):
104
105
  self.layout().addWidget(self._sinoRingCorrectionMthd, 2, 2, 1, 1)
105
106
  self.registerWidget(self._sinoRingCorrectionMthd, "required")
106
107
 
107
- self._sinoRingsOpts = SinoRingsOptions(parent=self)
108
+ self._sinoRingsOpts = SinoRingsOptions(parent=self, scrollArea=scrollArea)
108
109
  self.layout().addWidget(self._sinoRingsOpts, 3, 1, 1, 3)
109
110
 
110
111
  ## ccd filter
@@ -187,13 +188,14 @@ class _NabuPreProcessingConfig(_NabuStageConfigBase, qt.QWidget):
187
188
 
188
189
  # option dedicated to Helical
189
190
  ## process file
190
- self._processFileQLE = qt.QLabel("file containing weights maps", self)
191
+ self._processFileLabel = qt.QLabel("file containing weights maps", self)
192
+ self.registerWidget(self._processFileLabel, "advanced")
193
+ self.layout().addWidget(self._processFileLabel, 20, 0, 1, 1)
194
+ self._processFileQLE = qt.QLineEdit("", self)
195
+ self.registerWidget(self._processFileQLE, "advanced")
191
196
  self._processFileQLE.setToolTip(
192
197
  "also know as 'process_file'. If you don't have this file it can be created from the 'helical-prepare-weights' widget"
193
198
  )
194
- self.layout().addWidget(self._processFileQLE, 20, 0, 1, 1)
195
- self._processFileQLE = qt.QLineEdit("", self)
196
- self.registerWidget(self._processFileQLE, "advanced")
197
199
  self.layout().addWidget(self._processFileQLE, 20, 1, 1, 3)
198
200
 
199
201
  # style
@@ -443,7 +445,7 @@ class _NabuPreProcessingConfig(_NabuStageConfigBase, qt.QWidget):
443
445
  class SinoRingsOptions(qt.QWidget):
444
446
  _VO_DIMS = ("horizontaly", "horizontaly and vertically")
445
447
 
446
- def __init__(self, parent=None, *args, **kwargs):
448
+ def __init__(self, parent=None, scrollArea=None, *args, **kwargs):
447
449
  super().__init__(parent, *args, **kwargs)
448
450
  self._method = None
449
451
  self.setLayout(qt.QFormLayout())
@@ -451,12 +453,12 @@ class SinoRingsOptions(qt.QWidget):
451
453
  self.layout().setSpacing(0)
452
454
  # munch parameters
453
455
  self._sigmaMunchLabel = qt.QLabel("sigma", self)
454
- self._sigmaMunch = qt.QDoubleSpinBox(self)
456
+ self._sigmaMunch = QDoubleSpinBox(self, scrollArea=scrollArea)
455
457
  self._sigmaMunch.setRange(0.0, 2147483647)
456
458
  self.layout().addRow(self._sigmaMunchLabel, self._sigmaMunch)
457
459
 
458
460
  self._levelsMunchLabel = qt.QLabel("levels", self)
459
- self._levelsMunch = qt.QSpinBox(self)
461
+ self._levelsMunch = QSpinBox(self, scrollArea=scrollArea)
460
462
  self._levelsMunch.setRange(0, 2147483647)
461
463
  self.layout().addRow(self._levelsMunchLabel, self._levelsMunch)
462
464
 
@@ -465,7 +467,7 @@ class SinoRingsOptions(qt.QWidget):
465
467
 
466
468
  # vo parameters
467
469
  self._snrVOLabel = qt.QLabel("snr", self)
468
- self._snrVO = qt.QDoubleSpinBox(self)
470
+ self._snrVO = QDoubleSpinBox(self, scrollArea=scrollArea)
469
471
  self._snrVO.setMinimum(0.0)
470
472
  tooltip = "Ratio used to locate large stripes. Greater is less sensitive."
471
473
  self._snrVO.setToolTip(tooltip)
@@ -473,7 +475,7 @@ class SinoRingsOptions(qt.QWidget):
473
475
  self.layout().addRow(self._snrVOLabel, self._snrVO)
474
476
 
475
477
  self._laSizeVOLabel = qt.QLabel("la_size", self)
476
- self._laSizeVO = qt.QSpinBox(self)
478
+ self._laSizeVO = QSpinBox(self, scrollArea=scrollArea)
477
479
  self._laSizeVO.setMinimum(0)
478
480
  tooltip = "Window size of the median filter to remove large stripes."
479
481
  self._laSizeVO.setToolTip(tooltip)
@@ -481,7 +483,7 @@ class SinoRingsOptions(qt.QWidget):
481
483
  self.layout().addRow(self._laSizeVOLabel, self._laSizeVO)
482
484
 
483
485
  self._smSizeVOLabel = qt.QLabel("sm_size", self)
484
- self._smSizeVO = qt.QSpinBox(self)
486
+ self._smSizeVO = QSpinBox(self, scrollArea=scrollArea)
485
487
  self._smSizeVO.setMinimum(0)
486
488
  tooltip = "Window size of the median filter to remove small-to-medium stripes."
487
489
  self._laSizeVO.setToolTip(tooltip)
@@ -489,18 +491,18 @@ class SinoRingsOptions(qt.QWidget):
489
491
  self.layout().addRow(self._smSizeVOLabel, self._smSizeVO)
490
492
 
491
493
  self._dimVOLabel = qt.QLabel("dimension", self)
492
- self._dimVO = qt.QComboBox(self)
494
+ self._dimVO = QComboBox(self, scrollArea=scrollArea)
493
495
  self._dimVO.addItems(self._VO_DIMS)
494
496
  self.layout().addRow(self._dimVOLabel, self._dimVO)
495
497
 
496
498
  # sino mean deringer
497
499
  self._sigmaLowLabel = qt.QLabel("signal low", self)
498
- self._sigmaLow = qt.QDoubleSpinBox(self)
500
+ self._sigmaLow = QDoubleSpinBox(self, scrollArea=scrollArea)
499
501
  self._sigmaLow.setMinimum(0.0)
500
502
  self._sigmaHighLabel = qt.QLabel("signal high", self)
501
503
  self.layout().addRow(self._sigmaLowLabel, self._sigmaLow)
502
504
 
503
- self._sigmaHigh = qt.QDoubleSpinBox(self)
505
+ self._sigmaHigh = QDoubleSpinBox(self, scrollArea=scrollArea)
504
506
  self._sigmaHigh.setMinimum(0.0)
505
507
  tooltip = (
506
508
  "sigma low and sigma high values are defining the standard deviation of "
@@ -66,14 +66,14 @@ class ScorePlot(_ScorePlot, constructor=CorSelection):
66
66
 
67
67
  def _updateScores(self):
68
68
  scan = self.__scan() if self.__scan else None
69
+ img_width = None
69
70
  if scan is not None:
70
71
  if scan.saaxis_params:
71
72
  scan.saaxis_params.score_method = self.getScoreMethod()
72
73
  img_width = scan.dim_1
73
74
  # update autofocus
74
75
  SAAxisTask.autofocus(scan)
75
- else:
76
- img_width = None
76
+
77
77
  self.setVarScores(
78
78
  scores=self._scores,
79
79
  score_method=self.getScoreMethod(),
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  import os
3
+ import shutil
3
4
  import tempfile
4
5
  import functools
5
6
 
@@ -399,7 +400,7 @@ class ZStitchingWindow(qt.QMainWindow):
399
400
  # separator
400
401
  toolbar.addSeparator()
401
402
 
402
- # configuraiton level / mode
403
+ # configuration level / mode
403
404
  self.__configurationModesAction = qt.QAction(self)
404
405
  self.__configurationModesAction.setCheckable(False)
405
406
  menu = qt.QMenu(self)
@@ -602,6 +603,8 @@ class ZStitchingWindow(qt.QMainWindow):
602
603
  self._callbackToSetSlurmConfig = callback
603
604
 
604
605
  def close(self):
606
+ # remove folder used for preview
607
+ shutil.rmtree(self._previewFolder, ignore_errors=True)
605
608
  self._widget.close()
606
609
  # requested for the waiting plot update
607
610
  super().close()
@@ -618,7 +621,6 @@ class ZStitchingWindow(qt.QMainWindow):
618
621
 
619
622
  def getPreviewFolder(self):
620
623
  if self._previewFolder is None:
621
- # TODO: improve management of file: must be removed when the widget is closed...
622
624
  self._previewFolder = tempfile.mkdtemp(prefix="tomwer_stitcher_preview")
623
625
  return self._previewFolder
624
626
 
@@ -782,7 +784,6 @@ class ZStitchingWindow(qt.QMainWindow):
782
784
  tomo_obj = existing_tomo_obj.get(tomo_obj_id, None)
783
785
  if tomo_obj is None:
784
786
  continue
785
- # TODO: recuperer le tomo obj a partir de l'identifiant
786
787
  if update_requested[0]:
787
788
  tomo_obj.stitching_metadata.setPxPos(int(new_axis_0_pos), 0)
788
789
  if update_requested[2]:
@@ -859,6 +860,10 @@ class ZStitchingWindow(qt.QMainWindow):
859
860
  level >= ConfigurationLevel.ADVANCED
860
861
  )
861
862
 
863
+ def close(self):
864
+ shutil.rmtree(self._previewFolder, ignore_errors=True)
865
+ super().close()
866
+
862
867
 
863
868
  def concatenate_dict(dict_1, dict_2) -> dict:
864
869
  """update dict which has dict as values. And we want concatenate those values to"""
@@ -34,7 +34,6 @@ from silx.gui import qt
34
34
  from silx.gui.dialog.ColormapDialog import DisplayMode
35
35
  from silx.gui.plot.ImageStack import ImageStack as _ImageStack
36
36
  from silx.gui.plot.ImageStack import UrlLoader
37
- from silx.gui.utils.signal import SignalProxy
38
37
  from silx.io.url import DataUrl
39
38
  from silx.utils.enum import Enum as _Enum
40
39
 
@@ -86,17 +85,6 @@ class DataViewer(qt.QMainWindow):
86
85
  self._viewer.getPlotWidget().setKeepDataAspectRatio(True)
87
86
  self.setCentralWidget(self._viewer)
88
87
 
89
- # signal / slot
90
- # add a signal proxy on the QSlider
91
- self._viewer._slider.sigCurrentUrlIndexChanged.disconnect(
92
- self._viewer.setCurrentUrlIndex
93
- )
94
- self._proxySig = SignalProxy(
95
- self._viewer._slider.sigCurrentUrlIndexChanged,
96
- delay=0.3,
97
- slot=self._urlIndexDelayed,
98
- )
99
-
100
88
  # display control
101
89
  self._controls = DisplayControl(parent=self)
102
90
  self._controlsDW = qt.QDockWidget(self)
@@ -110,6 +98,33 @@ class DataViewer(qt.QMainWindow):
110
98
  self._controls.sigDisplayModeChanged.connect(self._updateDisplay)
111
99
  self._controls.sigDisplayModeChanged.connect(self.sigConfigChanged)
112
100
 
101
+ # upgrade of the slider (see details in '__sliderPressed' docstring).
102
+ horizontal_slider = self._viewer._slider
103
+ horizontal_slider._slider.sliderReleased.connect(self.__sliderReleased)
104
+ horizontal_slider._slider.sliderPressed.connect(self.__sliderPressed)
105
+
106
+ def __sliderPressed(self):
107
+ """
108
+ today each time the slider value is modified it will load the frame and display it
109
+ but in our case we cannot afford it as it will take too much memory.
110
+ So we will disconnect the callback (self._viewer.setCurrentUrlIndex) until the slider is active
111
+ and reactivate it afterwards
112
+ """
113
+ self._viewer._slider.sigCurrentUrlIndexChanged.disconnect(
114
+ self._viewer.setCurrentUrlIndex
115
+ )
116
+
117
+ def __sliderReleased(self):
118
+ """
119
+ See details in '__sliderPressed'
120
+ """
121
+ horizontal_slider = self._viewer._slider
122
+ self._viewer._slider.sigCurrentUrlIndexChanged.connect(
123
+ self._viewer.setCurrentUrlIndex
124
+ )
125
+ # set the url with the latest value to display the requested frame
126
+ self._viewer.setCurrentUrlIndex(horizontal_slider.value())
127
+
113
128
  def getPlotWidget(self):
114
129
  return self._viewer.getPlotWidget()
115
130
 
@@ -134,9 +149,6 @@ class DataViewer(qt.QMainWindow):
134
149
  self._viewer = None
135
150
  super().close()
136
151
 
137
- def _urlIndexDelayed(self, *args, **kwargs):
138
- self._viewer.setCurrentUrlIndex(args[0][0])
139
-
140
152
  def getScan(self):
141
153
  if self._scan:
142
154
  return self._scan()
@@ -25,6 +25,7 @@
25
25
  """
26
26
  contains gui relative frame difference display
27
27
  """
28
+ from __future__ import annotations
28
29
 
29
30
  __authors__ = ["H. Payno"]
30
31
  __license__ = "MIT"
@@ -148,7 +149,7 @@ class _FrameSelector(qt.QWidget):
148
149
  return
149
150
 
150
151
  urls = []
151
- angles = []
152
+ urls_to_angles: dict[str, float] = {}
152
153
  self._currentFrameUrlsText.clear()
153
154
  self._frameUrlCB.clear()
154
155
  if type_selected == self.FrameType.DARKS:
@@ -162,7 +163,7 @@ class _FrameSelector(qt.QWidget):
162
163
  angles_and_urls = self._scan.get_proj_angle_url(with_alignment=False)
163
164
  for angle, url in angles_and_urls.items():
164
165
  urls.append(url)
165
- angles.append(angle)
166
+ urls_to_angles[url.path()] = angle
166
167
  else:
167
168
  urls = self._scan.projections.values()
168
169
  elif type_selected == self.FrameType.ALIGN_PROJ:
@@ -174,14 +175,14 @@ class _FrameSelector(qt.QWidget):
174
175
  raise ValueError(f"Type {type_selected} not managed")
175
176
  urls = sorted(urls, key=lambda url: url.path())
176
177
 
177
- # if thre is some angles missing, avoiding setting any angle because they are probably wrong
178
+ # if there is some angles missing, avoiding setting any angle because they are probably wrong
178
179
  # this will probably fail with EDF but this is legacy
179
- if len(angles) != len(urls):
180
- angles = []
180
+ if len(urls_to_angles) != len(urls):
181
+ urls_to_angles.clear()
181
182
 
182
- for i_url, url in enumerate(urls):
183
- if len(angles) > 0:
184
- text = f"angle={angles[i_url]}&"
183
+ for url in urls:
184
+ if len(urls_to_angles) > 0:
185
+ text = f"angle={urls_to_angles[url.path()]}&"
185
186
  else:
186
187
  text = ""
187
188
  if url.data_slice() is not None: