image_plot_property.py 14.8 KB
Newer Older
1
2
3
4
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
Benoit Formet's avatar
Benoit Formet committed
5
# Copyright (c) 2015-2020 Beamline Control Unit, ESRF
6
7
8
9
10
11
12
13
14
# Distributed under the GNU LGPLv3. See LICENSE for more info.

from __future__ import annotations
from typing import Union
from typing import List
from typing import Dict
from typing import Optional

import logging
15
import weakref
16
17
18
19
20
21
22

from silx.gui import qt

from bliss.flint.model import flint_model
from bliss.flint.model import plot_model
from bliss.flint.model import plot_item_model
from bliss.flint.model import scan_model
23
from bliss.flint.utils import qt_backport
24
from . import delegates
25
from . import _property_tree_helper
26
27
28
29
30


_logger = logging.getLogger(__name__)


31
32
33
class _DataItem(_property_tree_helper.ScanRowItem):
    def __init__(self):
        super(_DataItem, self).__init__()
34
35
36
37
38
39
40
41
        self.__used = delegates.HookedStandardItem("")
        self.__displayed = delegates.HookedStandardItem("")
        self.__style = qt.QStandardItem("")
        self.__remove = qt.QStandardItem("")

        self.__plotModel: Optional[plot_model.Plot] = None
        self.__plotItem: Optional[plot_model.Item] = None
        self.__channel: Optional[scan_model.Channel] = None
42
        self.__device: Optional[scan_model.Device] = None
43
44
45
        self.__treeView: Optional[qt.QTreeView] = None
        self.__flintModel: Optional[flint_model.FlintState] = None

46
47
48
49
        self.setOtherRowItems(
            self.__used, self.__displayed, self.__style, self.__remove
        )

Valentin Valls's avatar
Valentin Valls committed
50
51
52
    def __hash__(self):
        return hash(id(self))

53
54
55
56
57
58
59
60
61
62
63
64
    def setEnvironment(
        self, treeView: qt.QTreeView, flintState: flint_model.FlintState
    ):
        self.__treeView = treeView
        self.__flintModel = flintState

    def setPlotModel(self, plotModel: plot_model.Plot):
        self.__plotModel = plotModel

    def __usedChanged(self, item: qt.QStandardItem):
        if self.__plotItem is not None:
            # There is a plot item already
65
66
            assert self.__plotModel is not None
            self.__plotModel.removeItem(self.__plotItem)
67
68
69
70
71
        else:
            assert self.__channel is not None
            assert self.__plotModel is not None
            plot = self.__plotModel

72
73
74
75
76
77
78
79
            with plot.transaction():
                items = list(plot.items())
                for i in items:
                    plot.removeItem(i)
                channelName = self.__channel.name()
                newItem = plot_item_model.ImageItem(plot)
                newItem.setImageChannel(plot_model.ChannelRef(plot, channelName))
                plot.addItem(newItem)
80
81
82
83
84
85
86
87
88

            self.__plotItem = newItem

    def __visibilityViewChanged(self, item: qt.QStandardItem):
        if self.__plotItem is not None:
            state = item.data(delegates.VisibilityRole)
            self.__plotItem.setVisible(state == qt.Qt.Checked)

    def setDevice(self, device: scan_model.Device):
89
        self.__device = device
90
        self.setDeviceLookAndFeel(device)
91
92
        self.__used.setCheckable(False)

93
94
95
96
97
98
99
100
101
102
103
        if device.type() == scan_model.DeviceType.VIRTUAL_ROI:
            state = qt.Qt.Unchecked
            self.__displayed.modelUpdated = None
            self.__displayed.setData(state, role=delegates.VisibilityRole)
            self.__displayed.modelUpdated = weakref.WeakMethod(
                self.__visibilityViewChanged
            )
            if self.__treeView.isPersistentEditorOpen(self.__displayed.index()):
                self.__treeView.closePersistentEditor(self.__displayed.index())
            self.__treeView.openPersistentEditor(self.__displayed.index())

104
105
106
    def device(self):
        return self.__device

107
    def setChannel(self, channel: scan_model.Channel):
108
        self.__channel = channel
109
        self.setChannelLookAndFeel(channel)
110
        self.__used.modelUpdated = None
111
        self.__used.setCheckable(True)
112
        self.__used.modelUpdated = weakref.WeakMethod(self.__usedChanged)
113
114
115
116

    def setPlotItem(self, plotItem):
        self.__plotItem = plotItem

117
        isRoiItem = isinstance(plotItem, plot_item_model.RoiItem)
118

119
120
121
122
123
124
125
126
127
        if not isRoiItem:
            self.__used.modelUpdated = None
            self.__used.setData(plotItem, role=delegates.PlotItemRole)
            self.__used.setCheckState(qt.Qt.Checked)
            self.__used.modelUpdated = weakref.WeakMethod(self.__usedChanged)

            self.__style.setData(plotItem, role=delegates.PlotItemRole)
            self.__style.setData(self.__flintModel, role=delegates.FlintModelRole)
            self.__remove.setData(plotItem, role=delegates.PlotItemRole)
128
129
130
131

        if plotItem is not None:
            isVisible = plotItem.isVisible()
            state = qt.Qt.Checked if isVisible else qt.Qt.Unchecked
132
            self.__displayed.modelUpdated = None
133
            self.__displayed.setData(state, role=delegates.VisibilityRole)
134
135
136
            self.__displayed.modelUpdated = weakref.WeakMethod(
                self.__visibilityViewChanged
            )
137
138
        else:
            self.__displayed.modelUpdated = None
139
            self.__displayed.setData(None, role=delegates.VisibilityRole)
140

141
142
        if self.__channel is None:
            self.setPlotItemLookAndFeel(plotItem)
143

144
145
146
        if self.__treeView.isPersistentEditorOpen(self.__displayed.index()):
            _logger.error("close")
            self.__treeView.closePersistentEditor(self.__displayed.index())
147
        self.__treeView.openPersistentEditor(self.__displayed.index())
148
149
150
151
152
        if not isRoiItem:
            self.__treeView.openPersistentEditor(self.__remove.index())
            if self.__treeView.isPersistentEditorOpen(self.__style.index()):
                self.__treeView.closePersistentEditor(self.__style.index())
            self.__treeView.openPersistentEditor(self.__style.index())
153
154
        # FIXME: why do we have to do that?
        self.__treeView.resizeColumnToContents(self.__style.column())
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170


class ImagePlotPropertyWidget(qt.QWidget):

    NameColumn = 0
    UseColumn = 1
    VisibleColumn = 2
    StyleColumn = 3
    RemoveColumn = 4

    def __init__(self, parent=None):
        super(ImagePlotPropertyWidget, self).__init__(parent=parent)
        self.__scan: Optional[scan_model.Scan] = None
        self.__flintModel: Optional[flint_model.FlintState] = None
        self.__plotModel: Optional[plot_model.Plot] = None

171
        self.__tree = qt_backport.QTreeView(self)
172
173
174
175
176
        self.__tree.setEditTriggers(qt.QAbstractItemView.NoEditTriggers)
        self.__tree.setUniformRowHeights(True)

        self.__visibilityDelegate = delegates.VisibilityPropertyItemDelegate(self)
        self.__removeDelegate = delegates.RemovePropertyItemDelegate(self)
177
        self.__styleDelegate = delegates.StyleItemDelegate(self, editable=False)
178
179
180
181
182
183

        model = qt.QStandardItemModel(self)
        self.__tree.setModel(model)

        self.__focusWidget = None

Valentin Valls's avatar
Valentin Valls committed
184
        self.__tree.setFrameShape(qt.QFrame.NoFrame)
185
        layout = qt.QVBoxLayout(self)
Valentin Valls's avatar
Valentin Valls committed
186
        layout.setContentsMargins(0, 0, 0, 0)
187
188
189
190
191
        layout.addWidget(self.__tree)

    def setFlintModel(self, flintModel: flint_model.FlintState = None):
        self.__flintModel = flintModel

Valentin Valls's avatar
Valentin Valls committed
192
193
194
    def focusWidget(self):
        return self.__focusWidget

195
196
197
    def setFocusWidget(self, widget):
        if self.__focusWidget is not None:
            widget.plotModelUpdated.disconnect(self.__plotModelUpdated)
198
            widget.scanModelUpdated.disconnect(self.__currentScanChanged)
199
200
201
        self.__focusWidget = widget
        if self.__focusWidget is not None:
            widget.plotModelUpdated.connect(self.__plotModelUpdated)
202
            widget.scanModelUpdated.connect(self.__currentScanChanged)
Valentin Valls's avatar
Valentin Valls committed
203
            plotModel = widget.plotModel()
204
            scanModel = widget.scan()
Valentin Valls's avatar
Valentin Valls committed
205
206
        else:
            plotModel = None
207
            scanModel = None
Valentin Valls's avatar
Valentin Valls committed
208
        self.__plotModelUpdated(plotModel)
209
        self.__currentScanChanged(scanModel)
210
211
212
213
214
215
216
217
218
219
220
221
222
223

    def __plotModelUpdated(self, plotModel):
        self.setPlotModel(plotModel)

    def setPlotModel(self, plotModel: plot_model.Plot):
        if self.__plotModel is not None:
            self.__plotModel.structureChanged.disconnect(self.__structureChanged)
            self.__plotModel.itemValueChanged.disconnect(self.__itemValueChanged)
        self.__plotModel = plotModel
        if self.__plotModel is not None:
            self.__plotModel.structureChanged.connect(self.__structureChanged)
            self.__plotModel.itemValueChanged.connect(self.__itemValueChanged)
        self.__updateTree()

224
225
    def __currentScanChanged(self, scanModel):
        self.__setScan(scanModel)
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241

    def __structureChanged(self):
        self.__updateTree()

    def __itemValueChanged(
        self, item: plot_model.Item, eventType: plot_model.ChangeEventType
    ):
        pass

    def plotModel(self) -> Union[None, plot_model.Plot]:
        return self.__plotModel

    def __setScan(self, scan):
        self.__scan = scan
        self.__updateTree()

Valentin Valls's avatar
Valentin Valls committed
242
243
244
245
246
247
248
249
250
251
252
    def __genScanTree(
        self,
        model: qt.QStandardItemModel,
        scan: scan_model.Scan,
        channelFilter: scan_model.ChannelType,
    ) -> Dict[str, _DataItem]:
        assert self.__tree is not None
        assert self.__flintModel is not None
        assert self.__plotModel is not None
        scanTree = {}
        channelItems: Dict[str, _DataItem] = {}
253
        deviceItems: Dict[str, _DataItem] = {}
Valentin Valls's avatar
Valentin Valls committed
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274

        devices: List[qt.QStandardItem] = []
        channelsPerDevices: Dict[qt.QStandardItem, int] = {}

        for device in scan.devices():
            item = _DataItem()
            item.setEnvironment(self.__tree, self.__flintModel)
            scanTree[device] = item

            master = device.master()
            if master is None:
                # Root device
                parent = model
            else:
                itemMaster = scanTree.get(master, None)
                if itemMaster is None:
                    parent = model
                    _logger.warning("Device list is not well ordered")
                else:
                    parent = itemMaster

275
            parent.appendRow(item.rowItems())
Valentin Valls's avatar
Valentin Valls committed
276
277
            # It have to be done when model index are initialized
            item.setDevice(device)
278
            deviceItems[device.fullName()] = item
Valentin Valls's avatar
Valentin Valls committed
279
280
281
282
283
284
285
286
287
288
289
            devices.append(item)

            channels = []
            for channel in device.channels():
                if channel.type() != channelFilter:
                    continue
                channels.append(channel)

            for channel in channels:
                channelItem = _DataItem()
                channelItem.setEnvironment(self.__tree, self.__flintModel)
290
                item.appendRow(channelItem.rowItems())
Valentin Valls's avatar
Valentin Valls committed
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
                # It have to be done when model index are initialized
                channelItem.setChannel(channel)
                channelItem.setPlotModel(self.__plotModel)
                channelItems[channel.name()] = channelItem

            # Update channel use
            parent = item
            channelsPerDevices[parent] = 0
            while parent is not None:
                if parent in channelsPerDevices:
                    channelsPerDevices[parent] += len(channels)
                parent = parent.parent()
                if parent is None:
                    break

        # Clean up unused devices
307
308
309
310
        for deviceItem in reversed(devices):
            device = deviceItem.device()
            if device.type() == scan_model.DeviceType.VIRTUAL_ROI:
                continue
311
312
            if device.type() == scan_model.DeviceType.VIRTUAL_MCA_DETECTOR:
                continue
313
            if device.name() in ["roi_counters", "roi_profiles"]:
Valentin Valls's avatar
Valentin Valls committed
314
                continue
315
            if deviceItem not in channelsPerDevices:
Valentin Valls's avatar
Valentin Valls committed
316
                continue
317
318
319
            if channelsPerDevices[deviceItem] > 0:
                continue
            parent = deviceItem.parent()
Valentin Valls's avatar
Valentin Valls committed
320
321
            if parent is None:
                parent = model
322
            parent.removeRows(deviceItem.row(), 1)
Valentin Valls's avatar
Valentin Valls committed
323

324
        return deviceItems, channelItems
Valentin Valls's avatar
Valentin Valls committed
325

326
327
328
329
330
331
332
    def __updateTree(self):
        # FIXME: expanded/collapsed items have to be restored

        model = self.__tree.model()
        model.clear()

        if self.__plotModel is None:
333
334
            model.setHorizontalHeaderLabels([""])
            foo = qt.QStandardItem("")
335
336
337
338
339
340
341
342
343
            model.appendRow(foo)
            return

        model.setHorizontalHeaderLabels(
            ["Name", "Use", "Displayed", "Style", "Remove", ""]
        )
        self.__tree.setItemDelegateForColumn(
            self.VisibleColumn, self.__visibilityDelegate
        )
344
        self.__tree.setItemDelegateForColumn(self.StyleColumn, self.__styleDelegate)
345
346
347
348
349
350
351
        self.__tree.setItemDelegateForColumn(self.RemoveColumn, self.__removeDelegate)
        header = self.__tree.header()
        header.setSectionResizeMode(self.NameColumn, qt.QHeaderView.ResizeToContents)
        header.setSectionResizeMode(self.UseColumn, qt.QHeaderView.ResizeToContents)
        header.setSectionResizeMode(self.VisibleColumn, qt.QHeaderView.ResizeToContents)
        header.setSectionResizeMode(self.StyleColumn, qt.QHeaderView.ResizeToContents)
        header.setSectionResizeMode(self.RemoveColumn, qt.QHeaderView.ResizeToContents)
Valentin Valls's avatar
Valentin Valls committed
352
353
        header.setMinimumSectionSize(10)
        header.moveSection(self.StyleColumn, self.VisibleColumn)
354
355
356

        scan = self.__scan
        if scan is not None:
357
358
359
            deviceItems, channelItems = self.__genScanTree(
                model, scan, scan_model.ChannelType.IMAGE
            )
Valentin Valls's avatar
Valentin Valls committed
360
        else:
361
            deviceItems, channelItems = {}, {}
362
363
364
365

        itemWithoutLocation = qt.QStandardItem("Not linked to this scan")
        model.appendRow(itemWithoutLocation)

366
367
368
369
370
371
372
373
        plotImageItems = []

        for plotItem in self.__plotModel.items():
            if isinstance(plotItem, plot_item_model.ImageItem):
                plotImageItems.append(plotItem)
                dataChannel = plotItem.imageChannel()
                if dataChannel is None:
                    continue
374

375
376
377
378
379
380
381
382
383
384
385
                dataChannelName = dataChannel.name()
                if dataChannelName in channelItems:
                    channelItem = channelItems[dataChannelName]
                    channelItem.setPlotItem(plotItem)
                else:
                    item = _DataItem()
                    item.setEnvironment(self.__tree, self.__flintModel)
                    itemWithoutLocation.appendRow(item.rowItems())
                    # It have to be done when model index are initialized
                    item.setPlotItem(plotItem)

386
387
388
389
390
            elif isinstance(plotItem, plot_item_model.RoiItem):
                deviceItem = deviceItems.get(plotItem.deviceName())
                if deviceItem is not None:
                    deviceItem.setPlotItem(plotItem)

391
392
393
394
        if len(plotImageItems) >= 2:
            _logger.warning(
                "More than one image is provided by this plot. A single image is supported, others will be ignored."
            )
395

Valentin Valls's avatar
Valentin Valls committed
396
397
398
        if itemWithoutLocation.rowCount() == 0:
            model.removeRows(itemWithoutLocation.row(), 1)

399
        self.__tree.expandAll()