darfix 4.2.0__py3-none-any.whl → 4.3.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 (144) hide show
  1. darfix/core/data_selection.py +11 -2
  2. darfix/core/dataset.py +72 -157
  3. darfix/core/grainplot.py +44 -56
  4. darfix/core/{imageStack.py → image_stack.py} +9 -15
  5. darfix/core/moment_types.py +6 -0
  6. darfix/core/{noiseremoval.py → noise_removal.py} +25 -24
  7. darfix/core/noise_removal_type.py +14 -0
  8. darfix/core/positioners.py +6 -0
  9. darfix/core/rocking_curves.py +6 -3
  10. darfix/core/rocking_curves_map.py +1 -1
  11. darfix/core/{shiftcorrection.py → shift_correction.py} +1 -2
  12. darfix/core/state_of_operation.py +7 -46
  13. darfix/core/utils.py +0 -39
  14. darfix/dtypes.py +1 -9
  15. darfix/gui/{binningWidget.py → binning_widget.py} +2 -29
  16. darfix/gui/{blindSourceSeparationWidget.py → blind_source_separation_widget.py} +4 -16
  17. darfix/gui/{chooseDimensions.py → choose_dimensions.py} +1 -1
  18. darfix/gui/concatenate_scans.py +4 -4
  19. darfix/gui/data_selection/{hdf5_data_selection_widgets.py → hdf5_dataset_selection_widget.py} +3 -56
  20. darfix/gui/data_selection/line_edits.py +54 -8
  21. darfix/gui/data_selection/scan_selection_widgets.py +24 -11
  22. darfix/gui/data_selection/utils.py +11 -0
  23. darfix/gui/data_selection/{WorkingDirSelectionWidget.py → working_dir_selection_widget.py} +15 -14
  24. darfix/gui/{dimensionsWidget.py → dimensions_widget.py} +1 -1
  25. darfix/gui/{displayComponentsWidget.py → display_components_widget.py} +1 -1
  26. darfix/gui/{filterByDimension.py → filter_by_dimension.py} +1 -1
  27. darfix/gui/{grainplot/dimensionRangeSlider2D.py → grain_plot/dimension_range_slider_2d.py} +2 -2
  28. darfix/gui/{grainplot/grainPlotWidget.py → grain_plot/grain_plot_widget.py} +1 -1
  29. darfix/gui/{grainplot/mosaicityWidget.py → grain_plot/mosaicity_widget.py} +21 -23
  30. darfix/gui/{magnificationWidget.py → magnification_widget.py} +1 -1
  31. darfix/gui/{noiseremoval → noise_removal}/noise_removal_widget.py +12 -16
  32. darfix/gui/{noiseremoval → noise_removal}/operation_list_widget.py +2 -2
  33. darfix/gui/{noiseremoval → noise_removal}/parameters_widget.py +6 -6
  34. darfix/gui/{PCAWidget.py → pca_widget.py} +2 -4
  35. darfix/gui/{projectionWidget.py → projection_widget.py} +1 -1
  36. darfix/gui/rocking_curves/{rockingCurvesPlot.py → rocking_curves_plot.py} +13 -13
  37. darfix/gui/rocking_curves/{rockingCurvesWidget.py → rocking_curves_widget.py} +10 -18
  38. darfix/gui/{roiSelectionWidget.py → roi_selection_widget.py} +9 -101
  39. darfix/gui/{shiftcorrection/shiftCorrectionWidget.py → shift_correction/shift_correction_widget.py} +4 -7
  40. darfix/gui/utils/data_path_completer.py +7 -7
  41. darfix/gui/utils/data_path_selection.py +4 -4
  42. darfix/gui/utils/{rangeSlider.py → range_slider.py} +1 -1
  43. darfix/gui/{weakBeamWidget.py → weak_beam_widget.py} +13 -28
  44. darfix/gui/{zSumWidget.py → zsum_widget.py} +1 -2
  45. darfix/main.py +19 -3
  46. darfix/processing/rocking_curves.py +12 -13
  47. darfix/tasks/binning.py +6 -17
  48. darfix/tasks/blind_source_separation.py +121 -0
  49. darfix/tasks/blindsourceseparation.py +8 -131
  50. darfix/tasks/copy.py +0 -2
  51. darfix/tasks/data_partition.py +39 -0
  52. darfix/tasks/datapartition.py +8 -50
  53. darfix/tasks/dimension_definition.py +197 -0
  54. darfix/tasks/dimensiondefinition.py +8 -197
  55. darfix/tasks/grain_plot.py +93 -0
  56. darfix/tasks/grainplot.py +8 -103
  57. darfix/tasks/hdf5_data_selection.py +5 -11
  58. darfix/tasks/hdf5_scans_concatenation.py +4 -4
  59. darfix/tasks/noise_removal.py +88 -0
  60. darfix/tasks/noiseremoval.py +8 -86
  61. darfix/tasks/pca.py +1 -3
  62. darfix/tasks/projection.py +1 -6
  63. darfix/tasks/rocking_curves.py +10 -5
  64. darfix/tasks/roi.py +0 -2
  65. darfix/tasks/shift_correction.py +45 -0
  66. darfix/tasks/shiftcorrection.py +8 -43
  67. darfix/tasks/transformation.py +0 -2
  68. darfix/tasks/weak_beam.py +71 -0
  69. darfix/tasks/weakbeam.py +8 -67
  70. darfix/tasks/zsum.py +1 -1
  71. darfix/tests/conftest.py +1 -1
  72. darfix/tests/gui/test_data_path_completer.py +4 -4
  73. darfix/tests/gui/test_dimension_range_slider_2d.py +2 -2
  74. darfix/tests/gui/test_range_slider_with_spinboxes.py +1 -1
  75. darfix/tests/orange/test_ewoks.py +13 -9
  76. darfix/tests/orange/widgets/test_hdf5_data_selection.py +93 -0
  77. darfix/tests/tasks/test_data_copy.py +0 -2
  78. darfix/tests/tasks/{test_dimensiondefinition.py → test_dimension_definition.py} +1 -1
  79. darfix/tests/tasks/test_weak_beam.py +9 -0
  80. darfix/tests/test_components_matching.py +2 -2
  81. darfix/tests/test_dataset.py +2 -28
  82. darfix/tests/test_dimension.py +1 -1
  83. darfix/tests/test_generate_grain_maps_nxdict.py +4 -5
  84. darfix/tests/test_image_operations.py +4 -4
  85. darfix/tests/test_image_registration.py +17 -17
  86. darfix/tests/test_image_stack.py +2 -13
  87. darfix/tests/test_mask.py +1 -1
  88. darfix/tests/test_moments.py +2 -2
  89. darfix/tests/test_rocking_curves.py +1 -3
  90. darfix/tests/test_shift.py +7 -7
  91. darfix/tests/test_workflow.py +4 -4
  92. darfix/tests/test_zsum.py +3 -6
  93. {darfix-4.2.0.dist-info → darfix-4.3.0.dist-info}/METADATA +5 -3
  94. {darfix-4.2.0.dist-info → darfix-4.3.0.dist-info}/RECORD +141 -135
  95. orangecontrib/darfix/widgets/__init__.py +10 -1
  96. orangecontrib/darfix/widgets/binning.py +3 -3
  97. orangecontrib/darfix/widgets/blindsourceseparation.py +4 -6
  98. orangecontrib/darfix/widgets/concatenateHDF5.py +1 -1
  99. orangecontrib/darfix/widgets/datacopy.py +1 -1
  100. orangecontrib/darfix/widgets/datapartition.py +7 -102
  101. orangecontrib/darfix/widgets/{datasetWidgetBase.py → dataset_widget_base.py} +17 -5
  102. orangecontrib/darfix/widgets/dimensions.py +6 -6
  103. orangecontrib/darfix/widgets/grainplot.py +3 -3
  104. orangecontrib/darfix/widgets/hdf5dataselection.py +34 -14
  105. orangecontrib/darfix/widgets/metadata.py +2 -2
  106. orangecontrib/darfix/widgets/noiseremoval.py +4 -4
  107. orangecontrib/darfix/widgets/{operationWidgetBase.py → operation_widget_base.py} +2 -2
  108. orangecontrib/darfix/widgets/pca.py +2 -2
  109. orangecontrib/darfix/widgets/projection.py +2 -2
  110. orangecontrib/darfix/widgets/rockingcurves.py +5 -2
  111. orangecontrib/darfix/widgets/roiselection.py +24 -106
  112. orangecontrib/darfix/widgets/rsmhistogram.py +2 -2
  113. orangecontrib/darfix/widgets/shiftcorrection.py +3 -3
  114. orangecontrib/darfix/widgets/transformation.py +4 -4
  115. orangecontrib/darfix/widgets/weakbeam.py +20 -103
  116. orangecontrib/darfix/widgets/zsum.py +3 -5
  117. darfix/gui/dataPartitionWidget.py +0 -167
  118. darfix/gui/data_selection/DataSelectionBase.py +0 -59
  119. darfix/tests/tasks/test_datapartition.py +0 -52
  120. /darfix/core/{componentsMatching.py → components_matching.py} +0 -0
  121. /darfix/core/{datapathfinder.py → data_path_finder.py} +0 -0
  122. /darfix/core/{imageRegistration.py → image_registration.py} +0 -0
  123. /darfix/gui/{grainplot → grain_plot}/__init__.py +0 -0
  124. /darfix/gui/{grainplot → grain_plot}/_oridist_toolbar_buttons.py +0 -0
  125. /darfix/gui/{grainplot → grain_plot}/flashlight.py +0 -0
  126. /darfix/gui/{grainplot → grain_plot}/flashlight_mode_action.py +0 -0
  127. /darfix/gui/{grainplot → grain_plot}/oridist_toolbar.py +0 -0
  128. /darfix/gui/{grainplot → grain_plot}/utils.py +0 -0
  129. /darfix/gui/{metadataWidget.py → metadata_widget.py} +0 -0
  130. /darfix/gui/{operationProcess.py → parallel/operation_process.py} +0 -0
  131. /darfix/gui/{operationThread.py → parallel/operation_thread.py} +0 -0
  132. /darfix/gui/rocking_curves/{fitComboBox.py → fit_combobox.py} +0 -0
  133. /darfix/gui/{roiLimitsToolbar.py → roi_limits_toolbar.py} +0 -0
  134. /darfix/gui/{rsmHistogramWidget.py → rsm_histogram_widget.py} +0 -0
  135. /darfix/gui/{rsmWidget.py → rsm_widget.py} +0 -0
  136. /darfix/gui/{shiftcorrection → shift_correction}/__init__.py +0 -0
  137. /darfix/gui/{shiftcorrection/shiftInput.py → shift_correction/shift_input.py} +0 -0
  138. /darfix/gui/utils/{standardButtonBox.py → standard_buttonbox.py} +0 -0
  139. /darfix/processing/{imageOperations.py → image_operations.py} +0 -0
  140. /darfix/tests/{test_datapathfinder.py → test_data_path_finder.py} +0 -0
  141. {darfix-4.2.0.dist-info → darfix-4.3.0.dist-info}/WHEEL +0 -0
  142. {darfix-4.2.0.dist-info → darfix-4.3.0.dist-info}/entry_points.txt +0 -0
  143. {darfix-4.2.0.dist-info → darfix-4.3.0.dist-info}/licenses/LICENSE +0 -0
  144. {darfix-4.2.0.dist-info → darfix-4.3.0.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,6 @@ from silx.gui.plot.StackView import StackViewMainWindow
4
4
 
5
5
  import darfix
6
6
  from darfix import dtypes
7
- from darfix.core.state_of_operation import Operation
8
7
  from darfix.gui.utils.custom_doublespinbox import createCustomDoubleSpinBox
9
8
  from darfix.gui.utils.message import missing_dataset_msg
10
9
 
@@ -29,8 +28,6 @@ class BinningWidget(qt.QMainWindow):
29
28
  def __init__(self, parent=None):
30
29
  qt.QMainWindow.__init__(self, parent)
31
30
 
32
- self._display_tooltip_msg = True
33
-
34
31
  widget = qt.QWidget()
35
32
  layout = qt.QGridLayout()
36
33
 
@@ -38,11 +35,9 @@ class BinningWidget(qt.QMainWindow):
38
35
  # original dataset, the one treated. Keep it to be able to re-apply modifications
39
36
  self._update_dataset = None
40
37
  # dataset with applied modifications. This is the one displayed
41
- self.indices = None
42
- self.bg_indices = None
43
38
  self.bg_dataset = None
44
39
 
45
- self._scaleLE = createCustomDoubleSpinBox(1)
40
+ self._scaleLE = createCustomDoubleSpinBox(0.5)
46
41
  _buttons = qt.QDialogButtonBox(parent=self)
47
42
  self._okB = _buttons.addButton(_buttons.Ok)
48
43
  self._applyB = _buttons.addButton(_buttons.Apply)
@@ -63,7 +58,7 @@ class BinningWidget(qt.QMainWindow):
63
58
  normalization=darfix.config.DEFAULT_COLORMAP_NORM,
64
59
  )
65
60
  )
66
- layout.addWidget(qt.QLabel("Scale: "), 0, 0)
61
+ layout.addWidget(qt.QLabel("Binning factor: "), 0, 0)
67
62
  layout.addWidget(self._scaleLE, 0, 1)
68
63
  layout.addWidget(self._sv, 1, 0, 1, 2)
69
64
  layout.addWidget(_buttons, 2, 0, 1, 2)
@@ -93,26 +88,8 @@ class BinningWidget(qt.QMainWindow):
93
88
  def setDataset(self, dataset: dtypes.Dataset):
94
89
  self._dataset = dataset.dataset
95
90
  self._update_dataset = dataset.dataset
96
- self.indices = dataset.indices
97
- self.bg_indices = dataset.bg_indices
98
91
  self.bg_dataset = dataset.bg_dataset
99
92
  self.setStack()
100
- if self._display_tooltip_msg:
101
- self.display_tooltip_msg()
102
-
103
- def display_tooltip_msg(self):
104
- msg = qt.QMessageBox()
105
- msg.setIcon(qt.QMessageBox.Information)
106
- msg.setText(
107
- "Binning can be used to reduce the computation time of the operations in the workflow.\n"
108
- + "The scale is the factor to which the images will be rescaled.\n"
109
- + "After the correct parameters are found, you can remove the binning widget from the workflow"
110
- + " and execute it either with the GUI or using ewoks.\nBinning should be applied after any"
111
- + " ROI to have original images size and not the binned one.\n"
112
- )
113
- msg.setWindowTitle("Fit succeeded!")
114
- msg.setStandardButtons(qt.QMessageBox.Ok)
115
- msg.exec()
116
93
 
117
94
  def setStack(self, dataset=None):
118
95
  """
@@ -144,10 +121,6 @@ class BinningWidget(qt.QMainWindow):
144
121
  self._startComputation()
145
122
  self.sigApply.emit()
146
123
 
147
- def abort(self):
148
- self._abortB.setEnabled(False)
149
- self._update_dataset.stop_operation(Operation.BINNING)
150
-
151
124
  def apply(self):
152
125
  self.sigComputed.emit()
153
126
 
@@ -6,10 +6,10 @@ import numpy
6
6
  from silx.gui import qt
7
7
 
8
8
  from darfix import dtypes
9
- from darfix.tasks.blindsourceseparation import Method
9
+ from darfix.tasks.blind_source_separation import Method
10
10
 
11
- from .displayComponentsWidget import DisplayComponentsWidget
12
- from .operationThread import OperationThread
11
+ from .display_components_widget import DisplayComponentsWidget
12
+ from .parallel.operation_thread import OperationThread
13
13
 
14
14
 
15
15
  class BSSWidget(qt.QMainWindow):
@@ -104,8 +104,6 @@ class BSSWidget(qt.QMainWindow):
104
104
 
105
105
  def setDataset(self, dataset: dtypes.Dataset):
106
106
  self.dataset = dataset.dataset
107
- self.indices = dataset.indices
108
- self.bg_indices = dataset.bg_indices
109
107
  self.bg_dataset = dataset.bg_dataset
110
108
  self.computeButton.setEnabled(True)
111
109
  self.detectButton.setEnabled(True)
@@ -113,13 +111,9 @@ class BSSWidget(qt.QMainWindow):
113
111
  def _nbComponentEdited(self, *args, **kwargs):
114
112
  self.sigNbComponentsChanged.emit(int(self.nComponentsLE.text()))
115
113
 
116
- def _displayComponents(
117
- self, dataset: dtypes.ImageDataset, indices, bg_indices, comp, W
118
- ):
114
+ def _displayComponents(self, dataset: dtypes.ImageDataset, comp, W):
119
115
  """
120
116
  :param dataset: dataset for which we want to display the components
121
- :param indices: dataset indices
122
- :param bg_indices: background indices
123
117
  :param comp: components
124
118
  :param W: Matrix with the rocking curves values
125
119
  """
@@ -143,12 +137,6 @@ class BSSWidget(qt.QMainWindow):
143
137
  self.computeButton.setEnabled(True)
144
138
  self.nComponentsLE.setEnabled(True)
145
139
  self.detectButton.setEnabled(True)
146
- if bg_indices is not None:
147
- # If filter data is activated, the matrix W has reduced dimensionality, so reshaping is not possible
148
- # Create empty array with shape the total number of frames
149
- W = numpy.zeros((dataset.nframes, n_comp))
150
- # Set actual values of W where threshold of filter is True
151
- W[indices] = W
152
140
  self._displayComponentsWidget.setComponents(
153
141
  comp,
154
142
  W,
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Callable
4
4
 
5
- from ewoksorange.gui.parameterform import block_signals
5
+ from ewoksorange.gui.widgets.parameter_form import block_signals
6
6
  from silx.gui import qt
7
7
 
8
8
  from darfix.core.dimension import AcquisitionDims
@@ -4,15 +4,15 @@ import logging
4
4
  import os
5
5
 
6
6
  import h5py
7
- from ewoksorange.gui.parameterform import block_signals
7
+ from ewoksorange.gui.widgets.parameter_form import block_signals
8
8
  from silx.gui import qt
9
9
 
10
- from darfix.core.datapathfinder import DETECTOR_KEYWORD
11
- from darfix.core.datapathfinder import SCAN_KEYWORD
10
+ from darfix.core.data_path_finder import DETECTOR_KEYWORD
11
+ from darfix.core.data_path_finder import SCAN_KEYWORD
12
12
  from darfix.gui.configuration.action import AdvancedConfigurationAction
13
13
  from darfix.gui.configuration.action import RequiredConfigurationAction
14
14
  from darfix.gui.configuration.level import ConfigurationLevel
15
- from darfix.gui.data_selection.hdf5_data_selection_widgets import (
15
+ from darfix.gui.data_selection.hdf5_dataset_selection_widget import (
16
16
  HDF5DatasetSelectionWidget,
17
17
  )
18
18
  from darfix.gui.utils.fileselection import FileSelector
@@ -3,9 +3,9 @@ from __future__ import annotations
3
3
  import h5py
4
4
  from silx.gui import qt
5
5
 
6
- from darfix.core.datapathfinder import DETECTOR_KEYWORD
7
- from darfix.core.datapathfinder import FIRST_SCAN_KEYWORD
8
- from darfix.core.datapathfinder import LAST_SCAN_KEYWORD
6
+ from darfix.core.data_path_finder import DETECTOR_KEYWORD
7
+ from darfix.core.data_path_finder import FIRST_SCAN_KEYWORD
8
+ from darfix.core.data_path_finder import LAST_SCAN_KEYWORD
9
9
  from darfix.gui.utils.data_path_selection import DataPathSelection
10
10
  from darfix.gui.utils.fileselection import FileSelector
11
11
  from darfix.tasks.hdf5_scans_concatenation import ConcatenateHDF5Scans
@@ -96,56 +96,3 @@ class HDF5DatasetSelectionWidget(qt.QWidget):
96
96
 
97
97
  def getInputFile(self) -> str | None:
98
98
  return self._inputFileSelector.getFilePath()
99
-
100
-
101
- class HDF5RawDatasetSelectionWidget(HDF5DatasetSelectionWidget):
102
- """Same as the 'HDF5DatasetSelectionWidget' but adds an option to keep data on disk and to provide the title"""
103
-
104
- sigKeepDataOnDiskChanged = qt.Signal(bool)
105
- sigTitleChanged = qt.Signal(str)
106
-
107
- def __init__(self, parent: qt.QWidget | None = None) -> None:
108
- super().__init__(parent)
109
- self._keepDataOnDisk = qt.QCheckBox("Keep data on disk")
110
- self.layout().addWidget(self._keepDataOnDisk, 1, 3, 2, 2)
111
-
112
- self._titleLabel = qt.QLabel("Workflow title")
113
- self.layout().addWidget(self._titleLabel, 3, 0, 1, 1)
114
- self._title = qt.QLineEdit("")
115
- self.layout().addWidget(self._title, 3, 1, 1, 4)
116
-
117
- # redefine layout because we want to display 'title' just after the file path selection
118
- self.layout().addWidget(self._detectorDataPath, 4, 0, 1, 5)
119
- self.layout().addWidget(self._positionerDataPath, 5, 0, 1, 5)
120
-
121
- # connect signal / slot
122
- self._keepDataOnDisk.toggled.connect(self.sigKeepDataOnDiskChanged)
123
- self._title.editingFinished.connect(self._titleChanged)
124
-
125
- def isKeepingDataOnDisk(self) -> bool:
126
- return self._keepDataOnDisk.isChecked()
127
-
128
- def setKeepDataOnDisk(self, value: bool):
129
- self._keepDataOnDisk.setChecked(value)
130
-
131
- def getWorkflowTitle(self) -> str:
132
- return self._title.text()
133
-
134
- def setWorkflowTitle(self, title: str):
135
- self._title.setText(title)
136
- self._titleChanged()
137
-
138
- def _titleChanged(self, *args, **kwargs):
139
- self.sigTitleChanged.emit(self._title.text())
140
-
141
-
142
- class HDF5DarkDatasetSelectionWidget(HDF5DatasetSelectionWidget):
143
- """Same as the 'HDF5DatasetSelectionWidget' but hide metadata selection that is unused for dark"""
144
-
145
- def __init__(self, parent: qt.QWidget | None = None) -> None:
146
- super().__init__(parent)
147
- # no positioner possible for dark so hide the selection
148
- self._positionerDataPath.hide()
149
-
150
- def getMetadataPathSelection(self) -> None:
151
- return None
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import h5py
3
4
  from silx.gui import qt
4
5
  from silx.gui.dialog.DatasetDialog import DatasetDialog
5
6
  from silx.gui.dialog.GroupDialog import GroupDialog
@@ -15,52 +16,84 @@ class _BaseLineEdit(qt.QWidget):
15
16
  dialogSelected = qt.Signal(str)
16
17
  editingFinished = qt.Signal()
17
18
 
19
+ _ERROR_LINE_EDIT_STYLE_SHEET = "border: 2px solid red"
20
+ _INVALID_INPUT_TXT = "Invalid input."
21
+
18
22
  def __init__(self, parent: qt.QWidget | None = None, **kwargs) -> None:
19
23
  super().__init__(parent, **kwargs)
20
24
 
21
25
  self._lineEdit = qt.QLineEdit()
26
+ self._defaultLineEditStyle = self._lineEdit.styleSheet()
27
+ self._errorMsgLabel = qt.QLabel()
28
+ self._errorMsgLabel.setStyleSheet("color : red")
29
+ self._errorMsgLabel.setText(self._INVALID_INPUT_TXT)
30
+ self._errorMsgLabel.hide()
22
31
  browseButton = qt.QPushButton("Browse...")
23
32
 
24
33
  layout = qt.QHBoxLayout(self)
25
34
  layout.setContentsMargins(0, 0, 0, 0)
26
35
  layout.addWidget(self._lineEdit)
27
36
  layout.addWidget(browseButton)
37
+ layout.addWidget(self._errorMsgLabel)
28
38
 
29
39
  browseButton.clicked.connect(self._openDialog)
30
- self._lineEdit.editingFinished.connect(self.editingFinished)
40
+ self._lineEdit.editingFinished.connect(self._onEditFinished)
31
41
 
32
42
  def _getDialogResult(self) -> str | None:
33
43
  raise NotImplementedError()
34
44
 
45
+ def _isValid(self, result: str) -> bool:
46
+ raise NotImplementedError()
47
+
35
48
  def _openDialog(self):
36
49
  result = self._getDialogResult()
37
50
  if not result:
38
51
  return
39
52
 
40
53
  self._lineEdit.setText(result)
41
- self.dialogSelected.emit(result)
54
+
55
+ if self.validateLineEdit():
56
+ self.dialogSelected.emit(result)
57
+
58
+ def _onEditFinished(self):
59
+ if self.validateLineEdit():
60
+ self.editingFinished.emit()
61
+
62
+ def validateLineEdit(self) -> bool:
63
+ isValid = self._isValid(self._lineEdit.text())
64
+ if isValid:
65
+ self._lineEdit.setStyleSheet(self._defaultLineEditStyle)
66
+ self._errorMsgLabel.hide()
67
+ else:
68
+ self._lineEdit.setStyleSheet(self._ERROR_LINE_EDIT_STYLE_SHEET)
69
+ self._errorMsgLabel.show()
70
+
71
+ return isValid
42
72
 
43
73
  def setText(self, text: str):
44
74
  self._lineEdit.setText(text)
75
+ self._onEditFinished()
45
76
 
46
77
  def getText(self) -> str:
47
78
  return self._lineEdit.text()
48
79
 
49
80
 
50
- class FileLineEdit(_BaseLineEdit):
81
+ class H5FileLineEdit(_BaseLineEdit):
51
82
  """A line edit for file paths that can filled via a file selection dialog"""
52
83
 
53
- dialogSelected = qt.Signal(str)
54
- editingFinished = qt.Signal()
84
+ _INVALID_INPUT_TXT = "File does not exist or is not a valid H5 file."
55
85
 
56
86
  def _getDialogResult(self) -> None | str:
57
- dialog = qt.QFileDialog()
87
+ dialog = qt.QFileDialog(filter="h5 file (*.h5 *.hdf *.hdf5 *.nx *.nxs)")
58
88
  result = dialog.exec()
59
89
 
60
90
  if not result:
61
91
  return None
62
92
  return dialog.selectedFiles()[0]
63
93
 
94
+ def _isValid(self, result: str) -> bool:
95
+ return h5py.is_hdf5(result)
96
+
64
97
 
65
98
  class DatasetLineEdit(_BaseLineEdit):
66
99
  """
@@ -68,8 +101,7 @@ class DatasetLineEdit(_BaseLineEdit):
68
101
  The file needs to be set beforehand with `setFile`
69
102
  """
70
103
 
71
- dialogSelected = qt.Signal(str)
72
- editingFinished = qt.Signal()
104
+ _INVALID_INPUT_TXT = "Not a valid H5 dataset."
73
105
 
74
106
  def __init__(self, parent: qt.QWidget | None = None, **kwargs) -> None:
75
107
  super().__init__(parent, **kwargs)
@@ -91,6 +123,12 @@ class DatasetLineEdit(_BaseLineEdit):
91
123
  url = dialog.getSelectedDataUrl()
92
124
  return url.data_path() if url else None
93
125
 
126
+ def _isValid(self, result: str) -> bool:
127
+ if not h5py.is_hdf5(self._file):
128
+ return False
129
+ with h5py.File(self._file) as f:
130
+ return isinstance(f.get(result), h5py.Dataset)
131
+
94
132
 
95
133
  class GroupLineEdit(_BaseLineEdit):
96
134
  """
@@ -98,6 +136,8 @@ class GroupLineEdit(_BaseLineEdit):
98
136
  The file needs to be set beforehand with `setFile`
99
137
  """
100
138
 
139
+ _INVALID_INPUT_TXT = "Not a valid H5 group."
140
+
101
141
  dialogSelected = qt.Signal(str)
102
142
  editingFinished = qt.Signal()
103
143
 
@@ -120,3 +160,9 @@ class GroupLineEdit(_BaseLineEdit):
120
160
 
121
161
  url = dialog.getSelectedDataUrl()
122
162
  return url.data_path() if url else None
163
+
164
+ def _isValid(self, result: str) -> bool:
165
+ if not h5py.is_hdf5(self._file):
166
+ return False
167
+ with h5py.File(self._file) as f:
168
+ return isinstance(f.get(result), h5py.Group)
@@ -1,19 +1,17 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import logging
4
- import os
5
4
 
6
5
  import silx.io
7
- from ewoksorange.gui.qtsignals import block_signals
6
+ from ewoksorange.gui.widgets.parameter_form import block_signals
8
7
  from silx.gui import qt
9
8
 
10
9
  from darfix.gui.data_selection.utils import find_detector_name
11
10
  from darfix.gui.data_selection.utils import find_scan_names
12
- from darfix.gui.utils.message import show_error_msg
13
11
 
14
12
  from .line_edits import DatasetLineEdit
15
- from .line_edits import FileLineEdit
16
13
  from .line_edits import GroupLineEdit
14
+ from .line_edits import H5FileLineEdit
17
15
 
18
16
  _logger = logging.getLogger(__file__)
19
17
 
@@ -25,10 +23,12 @@ class ScanSelectionWidget(qt.QWidget):
25
23
  Autofills the detector path when the scan is updated.
26
24
  """
27
25
 
26
+ sigNewFileSelected = qt.Signal()
27
+
28
28
  def __init__(self, parent: qt.QWidget | None = None, **kwargs) -> None:
29
29
  super().__init__(parent=parent, **kwargs)
30
30
 
31
- self._fileLineEdit = FileLineEdit()
31
+ self._fileLineEdit = H5FileLineEdit()
32
32
  self._scanNameComboBox = qt.QComboBox()
33
33
  self._detectorLineEdit = DatasetLineEdit()
34
34
 
@@ -44,10 +44,7 @@ class ScanSelectionWidget(qt.QWidget):
44
44
 
45
45
  def _onNewFileEdit(self):
46
46
  path = self._fileLineEdit.getText()
47
- if os.path.exists(path):
48
- self._onNewFile(path)
49
- else:
50
- show_error_msg(f"File '{path}' does not exist")
47
+ self._onNewFile(path)
51
48
 
52
49
  def _onNewFile(self, filePath: str):
53
50
  self._detectorLineEdit.setFile(filePath)
@@ -61,6 +58,7 @@ class ScanSelectionWidget(qt.QWidget):
61
58
  with block_signals(self._scanNameComboBox):
62
59
  self._scanNameComboBox.clear()
63
60
  self._scanNameComboBox.addItems(scanNames)
61
+ self.sigNewFileSelected.emit()
64
62
 
65
63
  def _updateDetectorPath(self, scanNumber: str):
66
64
  scanUrl = f"{self._fileLineEdit.getText()}::/{scanNumber}"
@@ -69,10 +67,13 @@ class ScanSelectionWidget(qt.QWidget):
69
67
 
70
68
  if detectorName:
71
69
  self._detectorLineEdit.setText(f"/{scanNumber}/measurement/{detectorName}")
70
+ else:
71
+ self._detectorLineEdit.setText("")
72
+
73
+ self._detectorLineEdit.validateLineEdit()
72
74
 
73
75
  def setFilePath(self, file: str):
74
76
  self._fileLineEdit.setText(file)
75
- self._onNewFile(file)
76
77
 
77
78
  def getFilePath(self) -> str:
78
79
  return self._fileLineEdit.getText()
@@ -86,6 +87,12 @@ class ScanSelectionWidget(qt.QWidget):
86
87
  def getDetectorPath(self) -> str:
87
88
  return self._detectorLineEdit.getText()
88
89
 
90
+ def validateSelection(self) -> bool:
91
+ return (
92
+ self._fileLineEdit.validateLineEdit()
93
+ and self._detectorLineEdit.validateLineEdit()
94
+ )
95
+
89
96
 
90
97
  class ScanSelectionWidgetWithPositioners(ScanSelectionWidget):
91
98
  """
@@ -103,14 +110,20 @@ class ScanSelectionWidgetWithPositioners(ScanSelectionWidget):
103
110
  self._scanNameComboBox.currentTextChanged.connect(self._updatePositionersPath)
104
111
 
105
112
  def _onNewFile(self, file_path: str):
106
- super()._onNewFile(file_path)
107
113
  self._positionersLineEdit.setFile(file_path)
114
+ super()._onNewFile(file_path)
108
115
 
109
116
  def _updatePositionersPath(self, scan_number: str):
110
117
  self._positionersLineEdit.setText(f"/{scan_number}/instrument/positioners")
118
+ self._positionersLineEdit.validateLineEdit()
111
119
 
112
120
  def setPositionersPath(self, data_url: str):
113
121
  self._positionersLineEdit.setText(data_url)
114
122
 
115
123
  def getPositionersPath(self) -> str:
116
124
  return self._positionersLineEdit.getText()
125
+
126
+ def validateSelection(self) -> bool:
127
+ return (
128
+ super().validateSelection() and self._positionersLineEdit.validateLineEdit()
129
+ )
@@ -39,6 +39,17 @@ def find_detector_name(scan: h5py.Group) -> str | None:
39
39
  # Found the LIMA detector
40
40
  return instrument_name
41
41
 
42
+ _logger.debug(
43
+ "Could not find a LIMA detector. Does this file have a NeXus Format ? Try to find any 3D dataset in measurement group."
44
+ )
45
+
46
+ # If nothing found, try at least to find a 3D dataset in measurement (Required for concatenate scans file as it does not have a Nexus Format).
47
+ for measurement_name, measurement in scan.get("measurement", {}).items():
48
+ if not isinstance(measurement, h5py.Dataset):
49
+ continue
50
+ if measurement.ndim == 3:
51
+ return measurement_name
52
+
42
53
  _logger.warning(
43
54
  f"Could not find valid detector in {scan.file}::{instrument_group.name} among {tuple(instrument_group.keys())}"
44
55
  )
@@ -12,23 +12,23 @@ class WorkingDirSelectionWidget(qt.QWidget):
12
12
  Widget used to obtain a directory name
13
13
  """
14
14
 
15
- dirChanged = qt.Signal()
16
-
17
15
  def __init__(self, parent=None):
18
16
  qt.QWidget.__init__(self, parent)
19
17
 
20
- self._dir = qt.QLineEdit("", parent=self)
21
- self._dir.editingFinished.connect(self.dirChanged)
18
+ self._directoryLineEdit = qt.QLineEdit("", parent=self)
22
19
  self.completer = create_completer()
23
- self._dir.setCompleter(self.completer)
20
+ self._directoryLineEdit.setCompleter(self.completer)
24
21
 
25
22
  self._addButton = qt.QPushButton("Select working directory", parent=self)
26
- self._addButton.pressed.connect(self._selectWorkingDirectory)
27
23
  self.setLayout(qt.QHBoxLayout())
28
24
 
29
- self.layout().addWidget(self._dir)
25
+ self.layout().addWidget(self._directoryLineEdit)
30
26
  self.layout().addWidget(self._addButton)
31
27
 
28
+ # Connect Signals / Slots
29
+
30
+ self._addButton.pressed.connect(self._selectWorkingDirectory)
31
+
32
32
  def _selectWorkingDirectory(self):
33
33
  """
34
34
  Select a folder to be used as working directory (task results will be saved at this location)
@@ -37,14 +37,15 @@ class WorkingDirSelectionWidget(qt.QWidget):
37
37
  fileDialog.setOption(qt.QFileDialog.ShowDirsOnly)
38
38
  fileDialog.setFileMode(qt.QFileDialog.Directory)
39
39
  if fileDialog.exec():
40
- self._dir.setText(fileDialog.directory().absolutePath())
41
- self.dirChanged.emit()
40
+ self._directoryLineEdit.setText(fileDialog.directory().absolutePath())
42
41
  else:
43
42
  _logger.warning("Could not open directory")
44
43
 
45
- def getDir(self):
46
- return str(self._dir.text())
44
+ def getDir(self) -> str:
45
+ return str(self._directoryLineEdit.text())
46
+
47
+ def setDir(self, working_directory: str):
48
+ self._directoryLineEdit.setText(str(working_directory))
47
49
 
48
- def setDir(self, _dir):
49
- self._dir.setText(str(_dir))
50
- self.dirChanged.emit()
50
+ def setDefaultDir(self, default_dir: str):
51
+ self._directoryLineEdit.setPlaceholderText(default_dir + " (default)")
@@ -6,7 +6,7 @@ import numpy
6
6
  from silx.gui import qt
7
7
 
8
8
  from darfix.core.dimension import Dimension
9
- from darfix.tasks.dimensiondefinition import DimensionDefinition
9
+ from darfix.tasks.dimension_definition import DimensionDefinition
10
10
 
11
11
  _logger = logging.getLogger(__file__)
12
12
 
@@ -13,7 +13,7 @@ from silx.gui.plot import StackView
13
13
  import darfix
14
14
  from darfix.io.utils import write_components
15
15
 
16
- from .chooseDimensions import ChooseDimensionDock
16
+ from .choose_dimensions import ChooseDimensionDock
17
17
  from .utils.utils import select_output_hdf5_file_with_dialog
18
18
 
19
19
 
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from ewoksorange.gui.parameterform import block_signals
3
+ from ewoksorange.gui.widgets.parameter_form import block_signals
4
4
  from silx.gui import qt
5
5
 
6
6
  from darfix.core.dimension import AcquisitionDims
@@ -2,12 +2,12 @@ from __future__ import annotations
2
2
 
3
3
  from functools import partial
4
4
 
5
- from ewoksorange.gui.parameterform import block_signals
5
+ from ewoksorange.gui.widgets.parameter_form import block_signals
6
6
  from silx.gui import qt
7
7
 
8
8
  from darfix.core.dimension import AcquisitionDims
9
9
  from darfix.core.dimension import Dimension
10
- from darfix.gui.utils.rangeSlider import RangeSliderWithSpinBox
10
+ from darfix.gui.utils.range_slider import RangeSliderWithSpinBox
11
11
 
12
12
 
13
13
  class _DimensionRangeSlider(qt.QWidget):
@@ -12,7 +12,7 @@ from darfix.core.moment_types import MomentType
12
12
  from ...core.grainplot import GrainPlotMaps
13
13
  from ...core.grainplot import generate_grain_maps_nxdict
14
14
  from ..utils.utils import select_output_hdf5_file_with_dialog
15
- from .mosaicityWidget import MosaicityWidget
15
+ from .mosaicity_widget import MosaicityWidget
16
16
  from .utils import MapType
17
17
  from .utils import add_image_with_transformation
18
18