Coverage for /Users/Newville/Codes/xraylarch/larch/qtrixs/plotrixs.py: 0%
181 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
« prev ^ index » next coverage.py v7.3.2, created at 2023-11-09 10:08 -0600
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
4"""
5Plot RIXS data
6==============
7"""
8from silx.gui import qt
9from silx.gui.plot.actions import PlotAction
10from silx.gui.plot.tools.roi import RegionOfInterestManager
11from silx.gui.plot.tools.roi import RegionOfInterestTableWidget
12# from silx.gui.plot.items.roi import RectangleROI
13from silx.gui.plot.items import LineMixIn, SymbolMixIn
14from larch.utils.logging import getLogger
15from larch.qtlib.plotarea import PlotArea, MdiSubWindow
16from larch.qtlib.plot1D import Plot1D
17from larch.qtlib.plot2D import Plot2D
18from .profiletoolbar import (_DEFAULT_OVERLAY_COLORS, RixsProfileToolBar)
19from .view import RixsListView
20from .model import RixsListModel
22_logger = getLogger("larch.qtrixs.plotrixs")
25class RixsROIManager(RegionOfInterestManager):
27 def __init__(self, plot, color='pink'):
28 super(RixsROIManager, self).__init__(plot)
29 self.setColor(color)
30 self.sigRoiAdded.connect(self.updateAddedRegionOfInterest)
32 def updateAddedRegionOfInterest(self, roi):
33 """Called for each added region of interest: set the name"""
34 if roi.getLabel() == '':
35 roi.setLabel('%d' % len(self.getRois()))
36 if isinstance(roi, LineMixIn):
37 roi.setLineWidth(2)
38 roi.setLineStyle('--')
39 if isinstance(roi, SymbolMixIn):
40 roi.setSymbol('+')
41 roi.setSymbolSize(3)
44class RixsROIDockWidget(qt.QDockWidget):
46 def __init__(self, plot, parent=None):
48 assert isinstance(plot, RixsPlot2D), "'plot' should be an instance of RixsPlot2D"
49 _title = f"Plot {plot._index} : cursors infos"
50 super(RixsROIDockWidget, self).__init__(_title, parent=parent)
52 self._roiManager = RixsROIManager(plot)
54 #: Create the table widget displaying infos
55 self._roiTable = RegionOfInterestTableWidget()
56 self._roiTable.setRegionOfInterestManager(self._roiManager)
58 #: Create a toolbar containing buttons for all ROI 'drawing' modes
59 self._roiToolbar = qt.QToolBar()
60 self._roiToolbar.setIconSize(qt.QSize(16, 16))
62 for roiClass in self._roiManager.getSupportedRoiClasses():
63 # Create a tool button and associate it with the QAction of each
64 # mode
65 action = self._roiManager.getInteractionModeAction(roiClass)
66 self._roiToolbar.addAction(action)
68 # Add the region of interest table and the buttons to a dock widget
69 self._widget = qt.QWidget()
70 self._layout = qt.QVBoxLayout()
71 self._widget.setLayout(self._layout)
72 self._layout.addWidget(self._roiToolbar)
73 self._layout.addWidget(self._roiTable)
75 self.setWidget(self._widget)
76 self.visibilityChanged.connect(self.roiDockVisibilityChanged)
78 def roiDockVisibilityChanged(self, visible):
79 """Handle change of visibility of the roi dock widget
81 If dock becomes hidden, ROI interaction is stopped.
82 """
83 if not visible:
84 self._roiManager.stop()
87class RixsRotateAction(PlotAction):
88 """QAction rotating a Rixs plane
90 :param plot: :class:`.PlotWidget` instance on which to operate
91 :param parent: See :class:`QAction`
92 """
94 def __init__(self, plot, parent=None):
95 PlotAction.__init__(self,
96 plot,
97 icon='compare-align-auto',
98 text='Rixs_et',
99 tooltip='Rotate RIXS plane to energy transfer',
100 triggered=self.rotateImage,
101 parent=parent)
102 raise NotImplementedError
104 def rotateImage(self):
105 """"""
106 return
109class RixsPlot2D(Plot2D):
110 """RIXS equivalent of Plot2D"""
112 def __init__(self, parent=None, backend=None, logger=None,
113 profileWindow=None, overlayColors=None, title="RixsPlot2D"):
114 """Constructor"""
115 super(RixsPlot2D, self).__init__(parent=parent, backend=backend, title=title)
117 self._title = title
118 self._logger = logger or _logger
119 self._profileWindow = profileWindow or Plot1D(title="Profiles")
120 self._overlayColors = overlayColors or _DEFAULT_OVERLAY_COLORS
122 #: cleaning toolbar
123 self.getMaskAction().setVisible(False)
124 self.getYAxisInvertedAction().setVisible(False)
125 self.getKeepDataAspectRatioAction().setVisible(False)
126 self.getColorBarAction().setVisible(False)
128 # Change default profile toolbar
129 self.removeToolBar(self.profile)
130 self.profile = RixsProfileToolBar(plot=self,
131 profileWindow=self._profileWindow,
132 overlayColors=self._overlayColors)
133 self.addToolBar(self.profile)
134 self.setKeepDataAspectRatio(True)
135 self.getDefaultColormap().setName('YlOrBr')
138class RixsPlotArea(PlotArea):
139 """RIXS equivalent of PlotArea"""
141 def __init__(self, parent=None, profileWindow=None, overlayColors=None,
142 logger=None):
143 super(RixsPlotArea, self).__init__(parent=parent)
145 self._logger = logger or _logger
146 self._overlayColors = overlayColors or _DEFAULT_OVERLAY_COLORS
147 self._profileWindow = profileWindow or self._addProfileWindow()
148 self.addRixsPlot2D()
149 self.setMinimumSize(300, 300)
150 self.setWindowTitle('RixsPlotArea')
152 def showContextMenu(self, position):
153 menu = qt.QMenu('RixsPlotArea Menu', self)
155 action = qt.QAction('Add RixsPlot2D Window', self,
156 triggered=self.addRixsPlot2D)
157 menu.addAction(action)
159 menu.addSeparator()
161 action = qt.QAction('Cascade Windows', self,
162 triggered=self.cascadeSubWindows)
163 menu.addAction(action)
165 action = qt.QAction('Tile Windows', self,
166 triggered=self.tileSubWindows)
167 menu.addAction(action)
169 menu.exec_(self.mapToGlobal(position))
171 def _addProfileWindow(self):
172 """Add a ProfileWindow in the mdi Area"""
173 subWindow = MdiSubWindow(parent=self)
174 plotWindow = Plot1D(parent=subWindow, title='Profiles')
175 plotWindow.setIndex(len(self.plotWindows()))
176 subWindow.setWidget(plotWindow)
177 subWindow.show()
178 self.changed.emit()
179 return plotWindow
181 def addRixsPlot2D(self, profileWindow=None):
182 """Add a RixPlot2D window in the mdi Area"""
183 subWindow = MdiSubWindow(parent=self)
184 profileWindow = profileWindow or self._profileWindow
185 plotWindow = RixsPlot2D(parent=subWindow,
186 profileWindow=profileWindow,
187 overlayColors=self._overlayColors)
188 plotWindow.setIndex(len(self.plotWindows()))
189 subWindow.setWidget(plotWindow)
190 subWindow.show()
191 self.changed.emit()
192 return plotWindow
194 def getProfileWindow(self):
195 return self._profileWindow
198class RixsMainWindow(qt.QMainWindow):
200 def __init__(self, parent=None, logger=None):
202 super(RixsMainWindow, self).__init__(parent=parent)
204 self._logger = logger or _logger
206 if parent is not None:
207 #: behave as a widget
208 self.setWindowFlags(qt.Qt.Widget)
209 else:
210 #: main window
211 self.setWindowTitle('RIXS_VIEW')
213 self.setGeometry(0, 0, 1280, 960)
215 #: Model (= simple RixsData container)
216 self._model = RixsListModel()
218 #: View (= simply show list of loaded data)
219 self._view = RixsListView(parent=self)
220 self._view.setModel(self._model)
222 #: View dock widget
223 self._dockDataWidget = qt.QDockWidget(parent=self)
224 self._dockDataWidget.setObjectName('Data View')
225 self._dockDataWidget.setWidget(self._view)
226 self.addDockWidget(qt.Qt.LeftDockWidgetArea, self._dockDataWidget)
228 #: Plot Area
229 self._plotArea = RixsPlotArea(self)
230 self.setCentralWidget(self._plotArea)
231 self.setMinimumSize(600, 600)
233 #: TODO Tab widget containing Cursors Infos dock widgets
234 # self._tabCurInfos = qt.QTabWidget(parent=self)
235 # self._tabCurInfos.setLayoutDirection(qt.Qt.LeftToRight)
236 # self._tabCurInfos.setDocumentMode(False)
237 # self._tabCurInfos.setTabsClosable(False)
238 # self._tabCurInfos.setMovable(False)
239 # self._dockCurInfos = qt.QDockWidget('Plot Cursors Infos', self)
240 # self.addDockWidget(qt.Qt.BottomDockWidgetArea, self._dockCurInfos)
241 # self._dockCurInfos.setWidget(self._tabCurInfos)
243 def _plot_rixs(self, rd, pw):
244 """Plot rixs full plane"""
245 pw.addImage(rd.rixs_map, x=rd.ene_in, y=rd.ene_out,
246 title=rd.sample_name,
247 xlabel=rd.ene_in_label,
248 ylabel=rd.ene_out_label)
250 def _plot_rixs_et(self, rd, pw):
251 """Plot rixs_et full plane"""
252 pw.addImage(rd.rixs_et_map, x=rd.ene_in, y=rd.ene_et,
253 title=rd.sample_name,
254 xlabel=rd.ene_in_label,
255 ylabel=rd.ene_et_label)
257 def _plot_rixs_crop(self, rd, pw):
258 """Plot rixs_et crop_area plane"""
259 _title = f"{rd.sample_name} [CROP: {rd._crop_area}]"
260 pw.addImage(rd.rixs_map_crop, x=rd.ene_in_crop, y=rd.ene_out_crop,
261 title=_title,
262 xlabel=rd.ene_in_label,
263 ylabel=rd.ene_out_label)
265 def _plot_rixs_et_crop(self, rd, pw):
266 """Plot rixs_et crop_area plane"""
267 _title = f"{rd.sample_name} [CROP: {rd._crop_area}]"
268 pw.addImage(rd.rixs_et_map_crop,
269 x=rd.ene_in_crop,
270 y=rd.ene_et_crop,
271 title=_title,
272 xlabel=rd.ene_in_label,
273 ylabel=rd.ene_et_label)
275 def plot(self, dataIndex, plotIndex,
276 crop=False, rixs_et=False,
277 nlevels=50):
278 """Plot given data index to given plot"""
279 rd = self.getData(dataIndex)
280 pw = self.getPlotWindow(plotIndex)
281 if rd is None or pw is None:
282 return
283 pw.reset()
284 if type(crop) is tuple:
285 rd.crop(crop)
286 if crop:
287 if rixs_et:
288 self._plot_rixs_et_crop(rd, pw)
289 else:
290 self._plot_rixs_crop(rd, pw)
291 else:
292 if rixs_et:
293 self._plot_rixs_et(rd, pw)
294 else:
295 self._plot_rixs(rd, pw)
296 pw.addContours(nlevels)
298 def getData(self, index):
299 try:
300 return self._model._data[index]
301 except IndexError:
302 self._logger.error("data index is wrong")
303 self._logger.info("use 'addData' to add new data to the list")
304 return None
306 def getPlotWindow(self, index):
307 try:
308 return self._plotArea.getPlotWindow(index)
309 except IndexError:
310 self._plotArea.addRixsPlot2D()
311 self._logger.warning("plot index wrong -> created a new RixsPlot2D")
312 return self._plotArea.getPlotWindow(-1)
314 def addData(self, data):
315 """Append RixsData object to the model"""
316 self._model.appendRow(data)
318 def addRixsDOIDockWidget(self, plotIndex):
319 plot = self.getPlotWindow(plotIndex)
320 _roiDock = RixsROIDockWidget(plot, parent=self)
321 self.addDockWidget(qt.Qt.BottomDockWidgetArea, _roiDock)
323 def getPlotArea(self):
324 return self._plotArea
326 def getProfileWindow(self):
327 return self.getPlotArea().getProfileWindow()
330if __name__ == '__main__':
331 pass