shiftCorrectionWidget.py 12.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2016-2017 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/


__authors__ = ["J. Garriga"]
__license__ = "MIT"
29
__date__ = "05/07/2021"
30

31
# import os
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
32
import numpy
33

34
from silx.gui import qt
35
from silx.gui.colors import Colormap
36
37
from silx.gui.plot.StackView import StackViewMainWindow

38
import darfix
39
from darfix.core.dataset import Operation
40

Henri Payno's avatar
Henri Payno committed
41
from .operationThread import OperationThread
42
from .utils import ChooseDimensionDock
43

44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
class ShiftCorrectionDialog(qt.QDialog):
    """
    Dialog with `ShiftCorrectionWidget` as main window and standard buttons.
    """

    okSignal = qt.Signal()

    def __init__(self, parent=None):
        qt.QDialog.__init__(self, parent)
        self.setWindowFlags(qt.Qt.Widget)
        types = qt.QDialogButtonBox.Ok
        self._buttons = qt.QDialogButtonBox(parent=self)
        self._buttons.setStandardButtons(types)
        self._buttons.setEnabled(False)
        resetB = self._buttons.addButton(self._buttons.Reset)
        self.mainWindow = ShiftCorrectionWidget(parent=self)
        self.mainWindow.setAttribute(qt.Qt.WA_DeleteOnClose)
        self.setLayout(qt.QVBoxLayout())
        self.layout().addWidget(self.mainWindow)
        self.layout().addWidget(self._buttons)

        self._buttons.accepted.connect(self.okSignal.emit)
        resetB.clicked.connect(self.mainWindow.resetStack)
        self.mainWindow.computingSignal.connect(self._toggleButton)

    def setDataset(self, dataset, indices=None, bg_indices=None, bg_dataset=None):

        if dataset is not None:
            self._buttons.setEnabled(True)
            self.mainWindow.setDataset(dataset, indices, bg_indices, bg_dataset)

    def _toggleButton(self, state):
        self._buttons.button(qt.QDialogButtonBox.Ok).setEnabled(not state)

78

79
class ShiftCorrectionWidget(qt.QMainWindow):
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
80
81
82
    """
    A widget to apply shift correction to a stack of images
    """
83
    computingSignal = qt.Signal()
84
85

    def __init__(self, parent=None):
86
87
88
        qt.QMainWindow.__init__(self, parent)

        self.setWindowFlags(qt.Qt.Widget)
89
        self._shift = [0, 0]
90
        self._dimension = None
91
92
93
94
        self._update_dataset = None
        self.indices = None
        self.bg_indices = None
        self.bg_dataset = None
95

96
97
        self._inputDock = _InputDock()
        self._inputDock.widget.correctionB.setEnabled(False)
98

99
        self._sv = StackViewMainWindow()
100
        self._sv.setColormap(Colormap(name=darfix.config.DEFAULT_COLORMAP_NAME,
101
                                      normalization="linear"))
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
        self.setCentralWidget(self._sv)
        self._chooseDimensionDock = ChooseDimensionDock(self)
        spacer1 = qt.QWidget(parent=self)
        spacer1.setLayout(qt.QVBoxLayout())
        spacer1.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding)
        spacer2 = qt.QWidget(parent=self)
        spacer2.setLayout(qt.QVBoxLayout())
        spacer2.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding)
        self._chooseDimensionDock.widget.layout().addWidget(spacer1)
        self._inputDock.widget.layout().addWidget(spacer2)
        self._chooseDimensionDock.hide()
        self.addDockWidget(qt.Qt.RightDockWidgetArea, self._chooseDimensionDock)
        self.addDockWidget(qt.Qt.RightDockWidgetArea, self._inputDock)

        self._inputDock.widget.correctionB.clicked.connect(self.correct)
117
        self._inputDock.widget.abortB.clicked.connect(self.abort)
118
119
120
        self._inputDock.widget._findShiftB.clicked.connect(self._findShift)
        self._chooseDimensionDock.widget.filterChanged.connect(self._filterStack)
        self._chooseDimensionDock.widget.stateDisabled.connect(self._wholeStack)
121

122
    def setDataset(self, dataset, indices=None, bg_indices=None, bg_dataset=None):
123
124
125
126
127
128
        """
        Dataset setter. Saves the dataset and updates the stack with the dataset
        data

        :param Dataset dataset: dataset
        """
129
        self.dataset = dataset
130
131
132
133
        self._update_dataset = dataset
        self.indices = indices
        self.bg_indices = bg_indices
        self.bg_dataset = bg_dataset
134
        self._inputDock.widget.correctionB.setEnabled(True)
135
        if len(self.dataset.data.shape) > 3:
136
            self._chooseDimensionDock.show()
137
            self._chooseDimensionDock.widget.setDimensions(self._update_dataset.dims)
138
        if not self._chooseDimensionDock.widget._checkbox.isChecked():
139
            self._wholeStack()
140
141

    def getDataset(self):
142
        return self._update_dataset, self.indices, self.bg_indices, self.bg_dataset
143

144
    def correct(self):
145
        """
Julia Garriga Ferrer's avatar
Docs    
Julia Garriga Ferrer committed
146
        Function that starts the thread to compute the shift given
147
148
        at the input widget
        """
149
150
        dx = self._inputDock.widget.getDx()
        dy = self._inputDock.widget.getDy()
151
        self.shift = [dy, dx]
152
        dimension = self._dimension if not self._inputDock.widget.checkbox.isChecked() else None
153
        frames = numpy.arange(self._update_dataset.get_data(indices=self.indices, dimension=dimension).shape[0])
154
        self.thread_correction = OperationThread(self, self._update_dataset.apply_shift)
155
        self.thread_correction.setArgs(numpy.outer(self.shift, frames), dimension, indices=self.indices)
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
156
        self.thread_correction.finished.connect(self._updateData)
157
        self._inputDock.widget.correctionB.setEnabled(False)
158
        self._inputDock.widget.abortB.show()
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
159
        self.thread_correction.start()
160
        self.computingSignal.emit()
161

162
163
164
165
    def abort(self):
        self._inputDock.widget.abortB.setEnabled(False)
        self._update_dataset.stop_operation(Operation.SHIFT)

166
167
168
    def resetStack(self):
        self._update_dataset = self.dataset
        self.setStack()
169
    def updateProgress(self, progress):
170
        self.sigProgressChanged.emit(progress)
171

172
    def _findShift(self):
173
        self.thread_detection = OperationThread(self, self._update_dataset.find_shift)
174
        self._inputDock.widget._findShiftB.setEnabled(False)
175
        self.thread_detection.setArgs(self._dimension, indices=self.indices)
176
177
        self.thread_detection.finished.connect(self._updateShift)
        self.thread_detection.start()
178
        self.computingSignal.emit()
179

Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
180
    def _updateShift(self):
181
        self._inputDock.widget._findShiftB.setEnabled(True)
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
182
        self.thread_detection.finished.disconnect(self._updateShift)
183
        self.shift = numpy.round(self.thread_detection.data[:, 1], 5)
184
        self.computingSignal.emit()
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
185

186
    def _updateData(self):
187
188
189
        """
        Updates the stack with the data computed in the thread
        """
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
190
        self.thread_correction.finished.disconnect(self._updateData)
191
192
        self._inputDock.widget.abortB.hide()
        self._inputDock.widget.abortB.setEnabled(True)
193
        self._inputDock.widget.correctionB.setEnabled(True)
194
        self.computingSignal.emit()
195
196
197
        if self.thread_correction.data:
            self._update_dataset = self.thread_correction.data
            assert self._update_dataset is not None
198
199
            if self._inputDock.widget.checkbox.isChecked():
                self._chooseDimensionDock.widget._checkbox.setChecked(False)
200
            self.setStack(self._update_dataset)
201
202
        else:
            print("\nCorrection aborted")
203

204
    def setStack(self, dataset=None):
205
        """
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
206
        Sets new data to the stack.
207
        Mantains the current frame showed in the view.
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
208
209

        :param Dataset dataset: if not None, data set to the stack will be from the given dataset.
210
        """
211
212
        if dataset is None:
            dataset = self.dataset
213
        nframe = self._sv.getFrameNumber()
214
215
216
217
        if self.indices is None:
            self._sv.setStack(dataset.get_data() if dataset is not None else None)
        else:
            self._sv.setStack(dataset.get_data(self.indices) if dataset is not None else None)
218
        self._sv.setFrameNumber(nframe)
219

220
221
    def clearStack(self):
        self._sv.setStack(None)
222
        self._inputDock.widget.correctionB.setEnabled(False)
223

224
    def _filterStack(self, dim=0, val=0):
225
        self._inputDock.widget.checkbox.show()
226
227
        self._dimension = [dim, val]
        data = self._update_dataset.get_data(self.indices, self._dimension)
228
        if data.shape[0]:
229
            self._sv.setStack(data)
230
        else:
231
            self._sv.setStack(None)
232
233

    def _wholeStack(self):
234
        self._dimension = None
235
        self._inputDock.widget.checkbox.hide()
236
        self.setStack(self._update_dataset)
237

238
    def getStack(self):
239
240
241
242
243
        """
        Stack getter

        :returns: StackViewMainWindow:
        """
244
        return self._sv
245

246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
    def getStackViewColormap(self):
        """
        Returns the colormap from the stackView

        :rtype: silx.gui.colors.Colormap
        """
        return self._sv.getColormap()

    def setStackViewColormap(self, colormap):
        """
        Sets the stackView colormap

        :param colormap: Colormap to set
        :type colormap: silx.gui.colors.Colormap
        """
        self._sv.setColormap(colormap)

263
264
265
266
267
268
269
    @property
    def shift(self):
        return self._shift

    @shift.setter
    def shift(self, shift):
        self._shift = shift
270
271
        self._inputDock.widget.setDx(shift[1])
        self._inputDock.widget.setDy(shift[0])
272
273
274
275
276
277
278
279


class _InputDock(qt.QDockWidget):

    def __init__(self, parent=None):
        qt.QDockWidget.__init__(self, parent)
        self.widget = _InputWidget()
        self.setWidget(self.widget)
280

281
282
283
284
285
286
287
288

class _InputWidget(qt.QWidget):
    """
    Widget used to obtain the double parameters for the shift correction.
    """
    def __init__(self, parent=None):
        super(_InputWidget, self).__init__(parent)

289
290
291
        self._findShiftB = qt.QPushButton("Find shift")
        labelx = qt.QLabel("Horizontal shift:")
        labely = qt.QLabel("Vertical shift:")
292
293
        self.dxLE = qt.QLineEdit("0.0")
        self.dyLE = qt.QLineEdit("0.0")
294
        self.correctionB = qt.QPushButton("Correct")
295
296
        self.abortB = qt.QPushButton("Abort")
        self.abortB.hide()
297
298
299
        self.checkbox = qt.QCheckBox("Apply to whole dataset")
        self.checkbox.setChecked(True)
        self.checkbox.hide()
300
301
302

        self.dxLE.setValidator(qt.QDoubleValidator())
        self.dyLE.setValidator(qt.QDoubleValidator())
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
303

304
        layout = qt.QGridLayout()
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
305

306
307
308
309
310
311
        layout.addWidget(self._findShiftB, 0, 0, 1, 2)
        layout.addWidget(labelx, 1, 0)
        layout.addWidget(labely, 2, 0)
        layout.addWidget(self.dxLE, 1, 1)
        layout.addWidget(self.dyLE, 2, 1)
        layout.addWidget(self.correctionB, 4, 0, 1, 2)
312
        layout.addWidget(self.abortB, 4, 0, 1, 2)
313
        layout.addWidget(self.checkbox, 3, 1)
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
314

315
316
        self.setLayout(layout)

317
318
319
320
321
322
    def setDx(self, dx):
        """
        Set the shift in the x axis
        """
        self.dxLE.setText(str(dx))

323
    def getDx(self):
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
324
325
        """
        Get the shift in the x axis
326

Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
327
328
        :return float:
        """
329
330
        return float(self.dxLE.text())

331
332
333
334
335
336
    def setDy(self, dy):
        """
        Set the shift in the x axis
        """
        self.dyLE.setText(str(dy))

337
    def getDy(self):
Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
338
339
        """
        Get the shift in the y axis
340

Julia Garriga Ferrer's avatar
Julia Garriga Ferrer committed
341
342
        :return float:
        """
343
        return float(self.dyLE.text())