tomwer 1.3.0a2__py3-none-any.whl → 1.3.0a4__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.
@@ -147,10 +147,29 @@ class ResultsLocalRun(ResultsWithStd):
147
147
  raise TypeError(
148
148
  f"results_urls is expected to be an Iterable not {type(results_identifiers)}"
149
149
  )
150
- for identifier in results_identifiers:
151
- if not isinstance(identifier, VolumeIdentifier):
152
- raise TypeError("identifiers are expected to be VolumeIdentifier")
153
- self.__results_identifiers = results_identifiers
150
+
151
+ # check all identifiers
152
+ from tomwer.core.volume.volumefactory import VolumeFactory
153
+
154
+ def check_identifier(identifier):
155
+ if isinstance(identifier, str):
156
+ vol = VolumeFactory.create_tomo_object_from_identifier(
157
+ identifier=identifier
158
+ )
159
+ return vol.get_identifier()
160
+ elif not isinstance(identifier, VolumeIdentifier):
161
+ raise TypeError(
162
+ f"identifiers are expected to be VolumeIdentifier. Get {type(identifier)} instead."
163
+ )
164
+ else:
165
+ return identifier
166
+
167
+ self.__results_identifiers = tuple(
168
+ [
169
+ check_identifier(identifier=identifier)
170
+ for identifier in results_identifiers
171
+ ]
172
+ )
154
173
 
155
174
  @property
156
175
  def results_identifiers(self) -> tuple:
@@ -1,84 +1,82 @@
1
- # coding: utf-8
2
- # /*##########################################################################
3
- # Copyright (C) 2016 European Synchrotron Radiation Facility
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
22
- #
23
- #############################################################################*/
24
-
25
-
26
- __authors__ = ["H. Payno"]
27
- __license__ = "MIT"
28
- __date__ = "21/06/2021"
29
-
30
-
31
1
  import logging
32
2
  import os
33
- import shutil
34
- import tempfile
35
- import unittest
36
-
3
+ import pytest
37
4
  import numpy
5
+ from time import sleep
6
+
38
7
  from silx.gui import qt
39
- from silx.gui.utils.testutils import TestCaseQt
40
8
  from tomoscan.esrf.volume.hdf5volume import HDF5Volume
9
+ from tomoscan.esrf.volume.tiffvolume import TIFFVolume, has_tifffile
41
10
 
11
+ from tomwer.tests.conftest import qtapp # noqa F401
42
12
  from tomwer.core.utils.scanutils import MockNXtomo
43
13
  from tomwer.gui.visualization.volumeviewer import VolumeViewer
44
14
 
45
15
  logging.disable(logging.INFO)
46
16
 
47
17
 
48
- class TestDiffViewer(TestCaseQt):
49
- """unit test for the :class:_ImageStack widget"""
18
+ @pytest.mark.skipif(not has_tifffile, reason="tifffile not available")
19
+ def test_volume_viewer(
20
+ qtapp, # noqa F811
21
+ tmp_path,
22
+ ):
23
+ """
24
+ test the volume viewer setting a scan having an HDF5 volume linked to it
25
+ """
26
+
27
+ widget = VolumeViewer(parent=None)
28
+ widget.setAttribute(qt.Qt.WA_DeleteOnClose)
29
+
30
+ tmp_dir = tmp_path / "test_volume_viewer"
31
+ tmp_dir.mkdir()
32
+
33
+ # step 1 - test setting a scan containing a HDF5Volume
34
+ scan = MockNXtomo(
35
+ scan_path=os.path.join(str(tmp_dir), "myscan"),
36
+ n_proj=20,
37
+ n_ini_proj=20,
38
+ dim=10,
39
+ ).scan
40
+ volume = HDF5Volume(
41
+ file_path=os.path.join(scan.path, "volume.hdf5"),
42
+ data_path="entry",
43
+ data=numpy.random.random(60 * 10 * 10).reshape(60, 10, 10),
44
+ )
45
+ volume.save()
50
46
 
51
- def setUp(self):
52
- TestCaseQt.setUp(self)
53
- self._widget = VolumeViewer(parent=None)
54
- self._widget.setAttribute(qt.Qt.WA_DeleteOnClose)
47
+ scan.set_latest_vol_reconstructions(
48
+ [
49
+ volume,
50
+ ]
51
+ )
55
52
 
56
- self.tmp_dir = tempfile.mkdtemp()
53
+ widget.setScan(scan)
54
+ assert widget._centralWidget.data() is not None
55
+ widget.clear()
56
+ assert widget._centralWidget.data() is None
57
57
 
58
- self.scan = MockNXtomo(
59
- scan_path=os.path.join(self.tmp_dir, "myscan"),
60
- n_proj=20,
61
- n_ini_proj=20,
62
- dim=10,
63
- ).scan
64
- volume = HDF5Volume(
65
- file_path=os.path.join(self.scan.path, "volume.hdf5"),
66
- data_path="entry",
67
- data=numpy.random.random(60 * 10 * 10).reshape(60, 10, 10),
68
- )
69
- volume.save()
58
+ # step 2: test setting a a tiff volume dirrectly
59
+ volume = TIFFVolume(
60
+ folder=os.path.join(tmp_dir, "my_tiff_vol"),
61
+ data=numpy.random.random(60 * 100 * 100).reshape(60, 100, 100),
62
+ )
63
+ volume.save()
70
64
 
71
- self.scan.set_latest_vol_reconstructions(
72
- [
73
- volume,
74
- ]
75
- )
65
+ # 2.1 test with the data being in cache
66
+ widget.setVolume(volume=volume)
67
+ assert widget._centralWidget.data() is not None
68
+ widget.clear()
69
+ assert widget._centralWidget.data() is None
76
70
 
77
- def tearDown(self):
78
- shutil.rmtree(self.tmp_dir)
79
- self._widget.close()
80
- self._widget = None
81
- unittest.TestCase.tearDown(self)
71
+ # 2.2 test with the data not being in cache anymore
72
+ volume.clear_cache()
82
73
 
83
- def test(self):
84
- self._widget.setScan(self.scan)
74
+ widget.setVolume(volume=volume)
75
+ # wait_for_processing_finished
76
+ while qt.QApplication.instance().hasPendingEvents():
77
+ qt.QApplication.instance().processEvents()
78
+ sleep(1.2) # wait for the thread to be processed
79
+ while qt.QApplication.instance().hasPendingEvents():
80
+ qt.QApplication.instance().processEvents()
81
+ # end waiting
82
+ assert widget._centralWidget.data() is not None
@@ -1,35 +1,4 @@
1
- # coding: utf-8
2
- # /*##########################################################################
3
- #
4
- # Copyright (c) 2016-2017 European Synchrotron Radiation Facility
5
- #
6
- # Permission is hereby granted, free of charge, to any person obtaining a copy
7
- # of this software and associated documentation files (the "Software"), to deal
8
- # in the Software without restriction, including without limitation the rights
9
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
- # copies of the Software, and to permit persons to whom the Software is
11
- # furnished to do so, subject to the following conditions:
12
- #
13
- # The above copyright notice and this permission notice shall be included in
14
- # all copies or substantial portions of the Software.
15
- #
16
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
- # THE SOFTWARE.
23
- #
24
- # ###########################################################################*/
25
-
26
- __authors__ = ["H. Payno"]
27
- __license__ = "MIT"
28
- __date__ = "05/08/2020"
29
-
30
-
31
1
  import logging
32
- import os
33
2
  import weakref
34
3
  import h5py
35
4
  from typing import Union
@@ -49,9 +18,13 @@ from tomoscan.volumebase import VolumeBase
49
18
  from tomoscan.io import HDF5File
50
19
 
51
20
  from tomwer.core.scan.scanbase import TomwerScanBase
52
- from tomwer.core.utils.ftseriesutils import get_vol_file_shape
53
21
  from tomwer.gui.visualization.reconstructionparameters import ReconstructionParameters
54
22
 
23
+ try:
24
+ from silx.gui.widgets.WaitingOverlay import WaitingOverlay
25
+ except ImportError:
26
+ from tomwer.third_part.WaitingOverlay import WaitingOverlay
27
+
55
28
  _logger = logging.getLogger(__name__)
56
29
 
57
30
 
@@ -247,6 +220,9 @@ class _TomoApplicationContext(DataViewHooks):
247
220
  class VolumeViewer(qt.QMainWindow):
248
221
  def __init__(self, parent):
249
222
  qt.QMainWindow.__init__(self, parent)
223
+
224
+ self._volume_loaded_in_background = (None, None)
225
+ # store the volume loaded in the background and the thread used for it as (volume_identifier, thread)
250
226
  self._centralWidget = DataViewerFrame(parent=self)
251
227
  self.__context = _TomoApplicationContext(self)
252
228
  self._centralWidget.setGlobalHooks(self.__context)
@@ -254,6 +230,11 @@ class VolumeViewer(qt.QMainWindow):
254
230
  qt.QSizePolicy.Expanding, qt.QSizePolicy.Expanding
255
231
  )
256
232
  self.setCentralWidget(self._centralWidget)
233
+ # waiter overlay to notify user loading is on-going
234
+ self._waitingOverlay = WaitingOverlay(self._centralWidget)
235
+ self._waitingOverlay.setIconSize(qt.QSize(30, 30))
236
+
237
+ # display scan information when possible
257
238
  self._infoWidget = _ScanInfo(parent=self)
258
239
 
259
240
  # top level dock widget to display information regarding the scan
@@ -308,9 +289,12 @@ class VolumeViewer(qt.QMainWindow):
308
289
  if volume is None:
309
290
  return
310
291
  self._set_volumes(volumes=(volume,))
311
- # TODO in the future: do the equivalent of setScan from the volume metadata
312
292
 
313
293
  def _get_data_volume(self, volume: VolumeBase):
294
+ """
295
+ load the data of the requested volume.
296
+ :return: (data: Optional[str], state: str) state can be "loaded", "loading" or "failed"
297
+ """
314
298
  if not isinstance(volume, VolumeBase):
315
299
  raise TypeError(
316
300
  f"volume is expected to be an instance of {VolumeBase}. Not {type(volume)}"
@@ -321,15 +305,61 @@ class VolumeViewer(qt.QMainWindow):
321
305
  self._h5_file = HDF5File(filename=volume.data_url.file_path(), mode="r")
322
306
  if volume.data_url.data_path() in self._h5_file:
323
307
  data = self._h5_file[volume.data_url.data_path()]
308
+ state = "loaded"
324
309
  else:
325
310
  data = None
311
+ state = "failed"
312
+ elif volume.data is not None:
313
+ data = volume.data
314
+ state = "loaded"
326
315
  else:
327
- _logger.warning(
328
- "Attempt to set a non HDF5 volume to the viewer. This requires to load all the data in memory. This can take a while"
329
- )
330
- data = volume.data if volume.data is not None else volume.load_data()
316
+ volume_id, _ = self._volume_loaded_in_background
317
+ if volume_id == volume.get_identifier().to_str():
318
+ # special case if the user send several time the volume which is currently loading
319
+ # in this case we just want to ignore the request to avoid reloading the volume
320
+ # can happen in the case of a large volume that take some time to be loaded.
321
+ pass
322
+ else:
323
+ _logger.warning(
324
+ "Attempt to set a non HDF5 volume to the viewer. This requires to load all the data in memory. This can take a while"
325
+ )
326
+ self._stopExistingLoaderThread()
327
+ self._loadAndDisplay(volume)
328
+ state = "loading"
329
+ data = None
330
+
331
+ return data, state
332
+
333
+ def _loaderThreadFinished(self):
334
+ """Callback activated when a VolumeLoader thread is finished"""
335
+ sender = self.sender()
336
+ if not isinstance(sender, VolumeLoader):
337
+ raise TypeError("sender is expected to be a VolumeLoader")
338
+
339
+ if sender.volume.data is None:
340
+ _logger.error(f"Failed to load volume {sender.volume.get_identifier()}")
331
341
 
332
- return data
342
+ self._stopExistingLoaderThread()
343
+ self.setVolume(sender.volume)
344
+
345
+ def _loadAndDisplay(self, volume):
346
+ """Load a thread and add a callback when loading is done"""
347
+ loader_thread = VolumeLoader(volume=volume)
348
+ self._volume_loaded_in_background = (
349
+ volume.get_identifier().to_str(),
350
+ loader_thread,
351
+ )
352
+ loader_thread.finished.connect(self._loaderThreadFinished)
353
+ loader_thread.start()
354
+
355
+ def _stopExistingLoaderThread(self):
356
+ """Will stop any existing loader thread. Make sure we load one volume at most at the time"""
357
+ _, loader_thread = self._volume_loaded_in_background
358
+ if loader_thread is not None:
359
+ loader_thread.finished.disconnect(self._loaderThreadFinished)
360
+ if loader_thread.isRunning():
361
+ loader_thread.quit()
362
+ self._volume_loaded_in_background = (None, None)
333
363
 
334
364
  def _set_volumes(self, volumes: tuple):
335
365
  self.clear()
@@ -351,21 +381,30 @@ class VolumeViewer(qt.QMainWindow):
351
381
  f"Volume should be an instance of a Volume, a VolumeIdentifier or a string refering to a VolumeIdentifier. {type(volume)} provided"
352
382
  )
353
383
 
354
- data = self._get_data_volume(volume)
384
+ data, state = self._get_data_volume(volume)
355
385
  # set volume dataset
356
- if data is not None:
357
- self._set_volume(data)
358
- # set reconstruction parameters
359
- try:
360
- # warning: limitation expected for .vol as it gets two configuration file. The default one is vol.info and does not contains
361
- # any of the metadata 'distance', 'pixel size'... but it is here for backward compatiblity
362
- self._reconsWidget.setVolumeMetadata(
363
- volume.metadata or volume.load_metadata()
364
- )
365
- except Exception as e:
366
- _logger.info(
367
- f"Unable to set reconstruction parameters from {volume.data_url}. Not handled for pyhst reconstructions. Error is {e}"
386
+ if state == "loading":
387
+ self._waitingOverlay.show()
388
+ pass
389
+ elif state == "loaded":
390
+ self._waitingOverlay.hide()
391
+ if data is not None:
392
+ self._set_volume(data)
393
+ elif state == "failed":
394
+ _logger.warning(
395
+ f"Failed to load data from {volume.get_identifier().to_str()}"
368
396
  )
397
+ # set reconstruction parameters
398
+ try:
399
+ # warning: limitation expected for .vol as it gets two configuration file. The default one is vol.info and does not contains
400
+ # any of the metadata 'distance', 'pixel size'... but it is here for backward compatiblity
401
+ self._reconsWidget.setVolumeMetadata(
402
+ volume.metadata or volume.load_metadata()
403
+ )
404
+ except Exception as e:
405
+ _logger.info(
406
+ f"Unable to set reconstruction parameters from {volume.data_url}. Not handled for pyhst reconstructions. Error is {e}"
407
+ )
369
408
 
370
409
  def _set_volume(self, volume: Union[numpy.ndarray, h5py.Dataset]):
371
410
  self._centralWidget.setData(volume)
@@ -380,68 +419,28 @@ class VolumeViewer(qt.QMainWindow):
380
419
  self._close_h5_file()
381
420
  # self._centralWidget.setData(None)
382
421
  self._infoWidget.clear()
422
+ self._centralWidget.setData(None)
423
+ # if clear stop loading any volume
424
+ self._stopExistingLoaderThread()
383
425
 
384
426
  def sizeHint(self):
385
427
  return qt.QSize(600, 600)
386
428
 
387
- # TODO: should be merged with dataviewer._load_vol function ?
388
- def _load_vol(self, url):
389
- """
390
- load a .vol file
391
- """
392
- if url.file_path().lower().endswith(".vol.info"):
393
- info_file = url.file_path()
394
- raw_file = url.file_path().replace(".vol.info", ".vol")
395
- else:
396
- assert url.file_path().lower().endswith(".vol")
397
- raw_file = url.file_path()
398
- info_file = url.file_path().replace(".vol", ".vol.info")
399
429
 
400
- if not os.path.exists(raw_file):
401
- data = None
402
- mess = f"Can't find raw data file {raw_file} associated with {info_file}"
403
- _logger.warning(mess)
404
- elif not os.path.exists(info_file):
405
- mess = f"Can't find info file {info_file} associated with {raw_file}"
406
- _logger.warning(mess)
407
- data = None
408
- else:
409
- shape = get_vol_file_shape(info_file)
410
- if None in shape:
411
- _logger.warning(f"Fail to retrieve data shape for {info_file}.")
412
- data = None
413
- else:
414
- try:
415
- numpy.zeros(shape)
416
- except MemoryError:
417
- data = None
418
- _logger.warning(f"Raw file {raw_file} is too large for being read")
419
- else:
420
- data = numpy.fromfile(
421
- raw_file, dtype=numpy.float32, count=-1, sep=""
422
- )
423
- try:
424
- data = data.reshape(shape)
425
- except ValueError:
426
- _logger.warning(
427
- f"unable to fix shape for raw file {raw_file}. "
428
- "Look for information in {info_file}"
429
- )
430
- try:
431
- sqr = int(numpy.sqrt(len(data)))
432
- shape = (1, sqr, sqr)
433
- data = data.reshape(shape)
434
- except ValueError:
435
- _logger.info(
436
- f"deduction of shape size for {raw_file} " "failed"
437
- )
438
- data = None
439
- else:
440
- _logger.warning(
441
- f"try deducing shape size for {raw_file} "
442
- "might be an incorrect interpretation"
443
- )
444
- if url.data_slice() is None:
445
- return data
446
- else:
447
- return data[url.data_slice()]
430
+ class VolumeLoader(qt.QThread):
431
+ """
432
+ simple thread that load a volume in memory
433
+ """
434
+
435
+ def __init__(self, volume: VolumeBase) -> None:
436
+ super().__init__()
437
+ if not isinstance(volume, VolumeBase):
438
+ raise TypeError()
439
+ self.__volume = volume
440
+
441
+ def run(self):
442
+ self.__volume.load_data(store=True)
443
+
444
+ @property
445
+ def volume(self):
446
+ return self.__volume
@@ -0,0 +1,110 @@
1
+ from typing import Optional
2
+ from silx.gui.widgets.WaitingPushButton import WaitingPushButton
3
+ from silx.gui import qt
4
+ from silx.gui.qt import inspect as qt_inspect
5
+ from silx.gui.plot import PlotWidget
6
+
7
+
8
+ class WaitingOverlay(qt.QWidget):
9
+ """Widget overlaying another widget with a processing wheel icon.
10
+
11
+ :param parent: widget on top of which to display the "processing/waiting wheel"
12
+ """
13
+
14
+ def __init__(self, parent: qt.QWidget) -> None:
15
+ super().__init__(parent)
16
+ self.setContentsMargins(0, 0, 0, 0)
17
+
18
+ self._waitingButton = WaitingPushButton(self)
19
+ self._waitingButton.setDown(True)
20
+ self._waitingButton.setWaiting(True)
21
+ self._waitingButton.setStyleSheet(
22
+ "QPushButton { background-color: rgba(150, 150, 150, 40); border: 0px; border-radius: 10px; }"
23
+ )
24
+ self._registerParent(parent)
25
+
26
+ def text(self) -> str:
27
+ """Returns displayed text"""
28
+ return self._waitingButton.text()
29
+
30
+ def setText(self, text: str):
31
+ """Set displayed text"""
32
+ self._waitingButton.setText(text)
33
+ self._resize()
34
+
35
+ def _listenedWidget(self, parent: qt.QWidget) -> qt.QWidget:
36
+ """Returns widget to register event filter to according to parent"""
37
+ if isinstance(parent, PlotWidget):
38
+ return parent.getWidgetHandle()
39
+ return parent
40
+
41
+ def _backendChanged(self):
42
+ self._listenedWidget(self.parent()).installEventFilter(self)
43
+ self._resizeLater()
44
+
45
+ def _registerParent(self, parent: Optional[qt.QWidget]):
46
+ if parent is None:
47
+ return
48
+ self._listenedWidget(parent).installEventFilter(self)
49
+ if isinstance(parent, PlotWidget):
50
+ parent.sigBackendChanged.connect(self._backendChanged)
51
+ self._resize()
52
+
53
+ def _unregisterParent(self, parent: Optional[qt.QWidget]):
54
+ if parent is None:
55
+ return
56
+ if isinstance(parent, PlotWidget):
57
+ parent.sigBackendChanged.disconnect(self._backendChanged)
58
+ self._listenedWidget(parent).removeEventFilter(self)
59
+
60
+ def setParent(self, parent: qt.QWidget):
61
+ self._unregisterParent(self.parent())
62
+ super().setParent(parent)
63
+ self._registerParent(parent)
64
+
65
+ def showEvent(self, event: qt.QShowEvent):
66
+ super().showEvent(event)
67
+ self._waitingButton.setVisible(True)
68
+
69
+ def hideEvent(self, event: qt.QHideEvent):
70
+ super().hideEvent(event)
71
+ self._waitingButton.setVisible(False)
72
+
73
+ def _resize(self):
74
+ if not qt_inspect.isValid(self):
75
+ return # For _resizeLater in case the widget has been deleted
76
+
77
+ parent = self.parent()
78
+ if parent is None:
79
+ return
80
+
81
+ size = self._waitingButton.sizeHint()
82
+ if isinstance(parent, PlotWidget):
83
+ offset = parent.getWidgetHandle().mapTo(parent, qt.QPoint(0, 0))
84
+ left, top, width, height = parent.getPlotBoundsInPixels()
85
+ rect = qt.QRect(
86
+ qt.QPoint(
87
+ int(offset.x() + left + width / 2 - size.width() / 2),
88
+ int(offset.y() + top + height / 2 - size.height() / 2),
89
+ ),
90
+ size,
91
+ )
92
+ else:
93
+ position = parent.size()
94
+ position = (position - size) / 2
95
+ rect = qt.QRect(qt.QPoint(position.width(), position.height()), size)
96
+ self.setGeometry(rect)
97
+ self.raise_()
98
+
99
+ def _resizeLater(self):
100
+ qt.QTimer.singleShot(0, self._resize)
101
+
102
+ def eventFilter(self, watched: qt.QWidget, event: qt.QEvent):
103
+ if event.type() == qt.QEvent.Resize:
104
+ self._resize()
105
+ self._resizeLater() # Defer resize for the receiver to have handled it
106
+ return super().eventFilter(watched, event)
107
+
108
+ # expose Waiting push button API
109
+ def setIconSize(self, size):
110
+ self._waitingButton.setIconSize(size)
File without changes
tomwer/version.py CHANGED
@@ -79,7 +79,7 @@ MAJOR = 1
79
79
  MINOR = 3
80
80
  MICRO = 0
81
81
  RELEV = "alpha" # <16
82
- SERIAL = 2 # <16
82
+ SERIAL = 4 # <16
83
83
 
84
84
  date = __date__
85
85
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tomwer
3
- Version: 1.3.0a2
3
+ Version: 1.3.0a4
4
4
  Summary: "tomography workflow tools"
5
5
  Home-page: https://gitlab.esrf.fr/tomotools/tomwer
6
6
  Author: data analysis unit
@@ -1,4 +1,4 @@
1
- tomwer-1.3.0a2-py3.11-nspkg.pth,sha256=xeeGR3TjdoVxdFeF6T-zSwZWh6Et--EYuPWu67LxL_c,574
1
+ tomwer-1.3.0a4-py3.11-nspkg.pth,sha256=xeeGR3TjdoVxdFeF6T-zSwZWh6Et--EYuPWu67LxL_c,574
2
2
  orangecontrib/tomwer/__init__.py,sha256=B4DXy1gY_wXmNYa2aOfapmJb2mEuCAjoaNEGnpBs70g,148
3
3
  orangecontrib/tomwer/state_summary.py,sha256=5_dPzweL3r0ye4ZfJo6IV2ThJI8fQhWoO2ySdJJajj8,1711
4
4
  orangecontrib/tomwer/orange/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -224,7 +224,7 @@ orangecontrib/tomwer/widgets/visualization/icons/volumeviewer.svg,sha256=2uT9_px
224
224
  tomwer/__init__.py,sha256=82Jp1abyG4UWdGuT4nNU7LxaUV6xxkOte5pIz3w69Do,1745
225
225
  tomwer/__main__.py,sha256=65ohoCNpuwo7nvCGM5OeYazVDaD0LoAA3EoOzpoLyms,8878
226
226
  tomwer/utils.py,sha256=EgVwJ5CQVjoBvcKNwyVoXv_P4ciI11oxb8fNyy82Lck,8465
227
- tomwer/version.py,sha256=e83z3lhRJSVPUSSA-6QK2VOK13gvWA3DW1f_SGy-qts,4386
227
+ tomwer/version.py,sha256=THhNUCxekjXuJcZeoI_ISVnJDaj5AlLt7YRO_mx6DSo,4386
228
228
  tomwer/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
229
229
  tomwer/app/axis.py,sha256=UQNxYpHzxnGdNpkV6sJoO_0tPGCxJkylghwbA7ENsM0,5948
230
230
  tomwer/app/canvas.py,sha256=RbQqgE7DuNjv4nGG6BNfnSevQO5_lCl7N71hGcLoxwE,1561
@@ -331,7 +331,7 @@ tomwer/core/process/reconstruction/lamino/tofu.py,sha256=ur3-OchDL1AZ-WP590Sjx_4
331
331
  tomwer/core/process/reconstruction/nabu/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
332
332
  tomwer/core/process/reconstruction/nabu/castvolume.py,sha256=-V0FubAiBEdMPDgp3ovr0dAYeEzwrQbw_W0R3w8P4YQ,10237
333
333
  tomwer/core/process/reconstruction/nabu/helical.py,sha256=gauUkoPiShvnvMQrCQXv28g0yLe-GceML5kYMSXmNIg,1997
334
- tomwer/core/process/reconstruction/nabu/nabucommon.py,sha256=2Puy0tbVJXAe6xuYN8pHdXVVDzI6kvcbvlH8Eorra-4,23637
334
+ tomwer/core/process/reconstruction/nabu/nabucommon.py,sha256=EO4sEQ_BXU7-VqYTeqa2QVgwfcFnzJIGcso0YxpAFXU,24215
335
335
  tomwer/core/process/reconstruction/nabu/nabuscores.py,sha256=V3xHOYqiJHAJdnY0DRz8hO1HRSR9wcBRQyM-FZA08Nw,24333
336
336
  tomwer/core/process/reconstruction/nabu/nabuslices.py,sha256=jP074_K2Px_QlOx4gMgwONj8j0LwlrVUBnWvzGZv7QU,36802
337
337
  tomwer/core/process/reconstruction/nabu/nabuvolume.py,sha256=0Gkkee35PwlvGAukVRIlQJKoz0dXcmuwVKOxEnXECzw,21498
@@ -613,7 +613,7 @@ tomwer/gui/visualization/scanoverview.py,sha256=xmq_nhbnGcedDreqIrZ8ZXXtRhC6KI4q
613
613
  tomwer/gui/visualization/sinogramviewer.py,sha256=N0jI8aERGIvvpKYXNHi8fLpl-0FBMIXTsSf1IbvGttA,9901
614
614
  tomwer/gui/visualization/tomoobjoverview.py,sha256=YzocBQXhgnzI8IsJg5junE7218WcJdvSNH4r6DQgo74,1991
615
615
  tomwer/gui/visualization/volumeoverview.py,sha256=6Hv1TtsDlaGQPCJKW-IkGy_r5Buue-O95tl1KkpMD-0,2491
616
- tomwer/gui/visualization/volumeviewer.py,sha256=eOFIultoKryz2B3tslG3YFzo945n94zSviEz8vxaapk,16750
616
+ tomwer/gui/visualization/volumeviewer.py,sha256=MtaJqAq3q3gKxN92cGRdxl9XH6XlFXha4VKMen9IiDo,16311
617
617
  tomwer/gui/visualization/diffviewer/__init__.py,sha256=rZ7qOTfAChU3FouCdkZllXT9ZZqTdo1XhLZMfmOqUAE,39
618
618
  tomwer/gui/visualization/diffviewer/diffviewer.py,sha256=Y8tDT1l-AkBSpTkBes31r_bhjbBeAlO__3OAc0Ob6Io,21812
619
619
  tomwer/gui/visualization/diffviewer/shiftwidget.py,sha256=2hpGo6BvubcSbw2QOT823sL74AEWyU32UxnvVZ4bDok,20928
@@ -624,7 +624,7 @@ tomwer/gui/visualization/test/test_nx_tomo_metadata_viewer.py,sha256=0RLS8iMXAVv
624
624
  tomwer/gui/visualization/test/test_reconstruction_parameters.py,sha256=su4MWiWw2FgbrW5_7j2HimYeyljvtU8RBzSkU-lN89I,3227
625
625
  tomwer/gui/visualization/test/test_sinogramviewer.py,sha256=3XDlixF_-ywh12SyjFSZctX9KRnTBtiQ1LZ10WPiBGU,3068
626
626
  tomwer/gui/visualization/test/test_stacks.py,sha256=HFLwaJ4LCHKuhRjMB4FNhIrI3W96WWy4z32tlgeO5pE,4694
627
- tomwer/gui/visualization/test/test_volumeviewer.py,sha256=7W5jGYhCPlRhlYQmjU4KYojT3jHIn6Oq4-vLn5IiYSE,2763
627
+ tomwer/gui/visualization/test/test_volumeviewer.py,sha256=Cf0EIQ8D_A9scznodWQGZfzkKgsuImzE44Q3A2e_PPs,2374
628
628
  tomwer/io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
629
629
  tomwer/io/utils/__init__.py,sha256=SrC5etCrm-3oMkUc4PPRqSdkIwTvKdtnBTfxh35_exI,272
630
630
  tomwer/io/utils/h5pyutils.py,sha256=-_A6wqvSjZWffvpkATfsS4TG4mm7PbtQ3Dsvk6ZZcxE,2971
@@ -814,10 +814,12 @@ tomwer/tests/datasets.py,sha256=Gz-gUkrKmE6WXjyXQUKKdSn-czEArrGZxgiXDbM76IA,335
814
814
  tomwer/tests/test_scripts.py,sha256=4Oyl_Xq1f81w-EiYSEoAI3rHUX6vMQimzFrKI-yWZ4k,5203
815
815
  tomwer/tests/test_utils.py,sha256=D0rNDSK6csEOYBY_7gD-4A3jp8rYAm8L1_Xg34A9I2s,305
816
816
  tomwer/tests/utils.py,sha256=RAXx5A99WD4Vyuv_wjHBdr-Xu7UiThHRKw2eiB5GX10,107
817
- tomwer-1.3.0a2.dist-info/LICENSE,sha256=yR_hIZ1MfDh9x2_s23uFqBH7m5DgrBl9nJKkE37YChM,1877
818
- tomwer-1.3.0a2.dist-info/METADATA,sha256=mXisEAC7qpv4zjVCbPPeadzb94_zH4Y9gjGqhZ99LR4,11426
819
- tomwer-1.3.0a2.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
820
- tomwer-1.3.0a2.dist-info/entry_points.txt,sha256=fIcDnCxjgwzfIylLYhUsFyiNZjZMxsfRQBxi4f-cJg8,440
821
- tomwer-1.3.0a2.dist-info/namespace_packages.txt,sha256=Iut-JTfT11SZHHm77_ZeszD7pZDWXcTweCbvrJpqDyQ,14
822
- tomwer-1.3.0a2.dist-info/top_level.txt,sha256=Yz5zKh0FPiImtzHYcPuztG1AO8-6KEpUWgoChGbA0Ys,21
823
- tomwer-1.3.0a2.dist-info/RECORD,,
817
+ tomwer/third_part/WaitingOverlay.py,sha256=GnqiytcJDp_24Cmz_2nZAP5HfpL3Yh7AzR2ATIusGsg,3906
818
+ tomwer/third_part/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
819
+ tomwer-1.3.0a4.dist-info/LICENSE,sha256=yR_hIZ1MfDh9x2_s23uFqBH7m5DgrBl9nJKkE37YChM,1877
820
+ tomwer-1.3.0a4.dist-info/METADATA,sha256=eZYYjSKtzz2J5LrMezuXOVMZpeVWJGA-QpbI94YX96k,11426
821
+ tomwer-1.3.0a4.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
822
+ tomwer-1.3.0a4.dist-info/entry_points.txt,sha256=fIcDnCxjgwzfIylLYhUsFyiNZjZMxsfRQBxi4f-cJg8,440
823
+ tomwer-1.3.0a4.dist-info/namespace_packages.txt,sha256=Iut-JTfT11SZHHm77_ZeszD7pZDWXcTweCbvrJpqDyQ,14
824
+ tomwer-1.3.0a4.dist-info/top_level.txt,sha256=Yz5zKh0FPiImtzHYcPuztG1AO8-6KEpUWgoChGbA0Ys,21
825
+ tomwer-1.3.0a4.dist-info/RECORD,,