From 30724e83d9c5a22725091136a9ce28021421a068 Mon Sep 17 00:00:00 2001 From: payno Date: Thu, 4 Jul 2019 08:39:12 +0200 Subject: [PATCH 1/6] [gui] add simple widget to select a spectrum from .dat or urls from .hdf5 for creating a XASObject --- xas/gui/xas_object_definition.py | 213 +++++++++++++++++++++++++++++++ xas/io/io.py | 6 + 2 files changed, 219 insertions(+) create mode 100644 xas/gui/xas_object_definition.py diff --git a/xas/gui/xas_object_definition.py b/xas/gui/xas_object_definition.py new file mode 100644 index 0000000..df192d7 --- /dev/null +++ b/xas/gui/xas_object_definition.py @@ -0,0 +1,213 @@ +# 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__ = ["H. Payno"] +__license__ = "MIT" +__date__ = "03/07/2019" + + +from silx.gui.dialog.DataFileDialog import DataFileDialog +from silx.gui import qt +import os +import logging +from xas.io import read_pymca_xas_from_file, InputType + +_logger = logging.getLogger(__name__) + + +class _InputType(qt.QWidget): + def __init__(self, parent=None): + qt.QWidget.__init__(self, parent) + self.setLayout(qt.QHBoxLayout()) + self.layout().addWidget(qt.QLabel('input type:')) + self._inputTypeCB = qt.QComboBox(parent=self) + for input_type in InputType.values(): + self._inputTypeCB.addItem(input_type) + self.layout().addWidget(self._inputTypeCB) + + # expose API + self.currentChanged = self._inputTypeCB.currentIndexChanged + + def getCurrentType(self): + """Return the current input type""" + return InputType.from_value(self._inputTypeCB.currentText()) + + +class XASObjectDialog(qt.QWidget): + """ + Interface used to select inputs for defining a XASObject + """ + def __init__(self, parent=None): + qt.QWidget.__init__(self, parent) + self.setLayout(qt.QGridLayout()) + spacer = qt.QWidget(parent=self) + spacer.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Minimum) + self.layout().addWidget(spacer, 0, 0) + self._inputType = _InputType(parent=self) + self.layout().addWidget(self._inputType, 0, 1) + + self._datDialog = _XASObjectFrmDat(parent=self) + self.layout().addWidget(self._datDialog, 1, 0, 1, 2) + self._h5Dialog = _XASObjFrmH5(parent=self) + self.layout().addWidget(self._h5Dialog, 2, 0, 1, 2) + + spacer = qt.QWidget(parent=self) + spacer.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding) + self.layout().addWidget(spacer, 3, 0) + + # connect signal / slot + self._inputType.currentChanged.connect(self._updateWidgetVisibility) + + # expose API + + # default setting + self._updateWidgetVisibility() + + def _updateWidgetVisibility(self): + self._datDialog.setVisible(self._inputType.getCurrentType() == InputType.single_spectrum) + self._h5Dialog.setVisible(self._inputType.getCurrentType() == InputType.multiple_spectra) + + +class _XASObjectFrmDat(qt.QWidget): + """ + Interface used to define a XAS object from a single .dat file + """ + def __init__(self, parent=None): + qt.QWidget.__init__(self, parent) + self.setLayout(qt.QGridLayout()) + + self.layout().addWidget(qt.QLabel('file', parent=self), 0, 0) + self._inputLe = qt.QLineEdit('', parent=self) + self.layout().addWidget(self._inputLe, 0, 1) + self._selectPB = qt.QPushButton('select', parent=self) + self.layout().addWidget(self._selectPB, 0, 2) + + self._startPB = qt.QPushButton('restart', parent=self) + self.layout().addWidget(self._startPB, 1, 1, 1, 2) + + spacer = qt.QWidget(parent=self) + spacer.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding) + self.layout().addWidget(spacer, 2, 0) + + # signal / slot connection + self._selectPB.pressed.connect(self._selectFile) + self._inputLe.editingFinished.connect(self._processXASObject) + self._startPB.pressed.connect(self._processXASObject) + + # expose api + self.restart = self._processXASObject + + def _selectFile(self, *args, **kwargs): + old = self.blockSignals(True) + dialog = qt.QFileDialog(self) + dialog.setFileMode(qt.QFileDialog.ExistingFile) + dialog.setNameFilters(["spectra (*.dat)", ]) + + if not dialog.exec_(): + dialog.close() + return + + fileSelected = dialog.selectedFiles() + if len(fileSelected) == 0: + return + else: + assert len(fileSelected) == 1 + self.setFileSelected(fileSelected[0]) + + self.blockSignals(old) + + def setFileSelected(self, file_path): + self._inputLe.setText(file_path) + + def _processXASObject(self, *args, **kwargs): + self._input_file_setting = self._inputLe.text() + if not os.path.isfile(self._input_file_setting): + mess = self._input_file_setting + 'is not a valid file path' + _logger.warning(mess) + return + else: + try: + xas_obj = read_pymca_xas_from_file(file_path=self._input_file_setting) + except ... as e: + _logger.error(e) + else: + return xas_obj + + +class _URLSelector(qt.QWidget): + def __init__(self, parent, name, layout=None, position=None): + qt.QWidget.__init__(self, parent) + self.name = name + if layout is None: + layout = self.setLayout(qt.QGridLayout()) + position = (0, 0) + layout.addWidget(qt.QLabel(name + ':', parent=self), position[0], position[1]) + self._qLineEdit = qt.QLineEdit('', parent=self) + layout.addWidget(self._qLineEdit, position[0], position[1] + 1) + self._qPushButton = qt.QPushButton('select', parent=self) + layout.addWidget(self._qPushButton, position[0], position[1] + 2) + + # expose API + self.getUrlPath = self._qLineEdit.text + + # connect signal / slot + self._qPushButton.clicked.connect(self._selectFile) + + def _selectFile(self, *args, **kwargs): + dialog = DataFileDialog(self) + # dialog.setNameFilters(["".join((self.name, "(*.h5 *.hdf *.hdf5)")),]) + + if not dialog.exec_(): + dialog.close() + return None + + if dialog.selectedUrl() is not None: + self._qLineEdit.setText(dialog.selectedUrl()) + + +class _XASObjFrmH5(qt.QWidget): + """ + Interface used to define a XAS object from h5 files and data path + """ + def __init__(self, parent=None): + qt.QWidget.__init__(self, parent) + self.setLayout(qt.QGridLayout()) + # spectra url + self._spectraSelector = _URLSelector(parent=self, name='spectra url', + layout=self.layout(), position=(0, 0)) + # energy / channel url + self._energySelector = _URLSelector(parent=self, name='energy /channel url', + layout=self.layout(), position=(1, 0)) + # configuration url + self._configSelector = _URLSelector(parent=self, name='configuration url', + layout=self.layout(), + position=(2, 0)) + + +if __name__ == '__main__': + app = qt.QApplication([]) + dialog = XASObjectDialog() + dialog.show() + app.exec_() diff --git a/xas/io/io.py b/xas/io/io.py index 6c35119..5c39692 100644 --- a/xas/io/io.py +++ b/xas/io/io.py @@ -33,6 +33,7 @@ from silx.io import utils from silx.io.url import DataUrl from xas.core.utils.pymca import read_spectrum from xas.core.types import XASObject +from silx.utils.enum import Enum _logger = logging.getLogger(__name__) @@ -44,6 +45,11 @@ DEFAULT_CHANNEL_PATH = '/data/NXdata/Channel' DEFAULT_CONF_PATH = '/configuration' +class InputType(Enum): + single_spectrum = '*.dat' + multiple_spectra = '*.h5' + + def read_pymca_xas_from_file(file_path): """ -- GitLab From 35f81d245494bfefa344c586c4731750f678028b Mon Sep 17 00:00:00 2001 From: payno Date: Thu, 4 Jul 2019 09:59:18 +0200 Subject: [PATCH 2/6] [orange add-on] use the XASObjectDialog in the input_xas. Allow to select a .dat file (single spectrum) or spectra from .hdf5 --- orangecontrib/xas/widgets/xas_input.py | 96 +++++++++++----------- xas/gui/xas_object_definition.py | 105 +++++++++++++++++++------ xas/io/io.py | 11 ++- 3 files changed, 136 insertions(+), 76 deletions(-) diff --git a/orangecontrib/xas/widgets/xas_input.py b/orangecontrib/xas/widgets/xas_input.py index 893b192..842a03a 100644 --- a/orangecontrib/xas/widgets/xas_input.py +++ b/orangecontrib/xas/widgets/xas_input.py @@ -31,11 +31,13 @@ __date__ = "06/11/2019" from Orange.widgets import gui from Orange.widgets.widget import OWWidget from silx.gui import qt +from silx.io.url import DataUrl from xas.core.types import XASObject +from xas.gui.xas_object_definition import XASObjectDialog +import xas.io from Orange.widgets.settings import Setting import os import logging -import xas.io _logger = logging.getLogger(__file__) @@ -57,6 +59,9 @@ class XASInputOW(OWWidget): outputs = [("spectra", XASObject)] _input_file_setting = Setting(str()) + _spectra_url_setting = Setting(str()) + _energy_url_setting = Setting(str()) + _configuration_url_setting = Setting(str()) process_function = xas.io.read_pymca_xas_from_file @@ -65,14 +70,11 @@ class XASInputOW(OWWidget): self._inputWindow = qt.QWidget(parent=self) self._inputWindow.setLayout(qt.QGridLayout()) - self._inputWindow.layout().addWidget(qt.QLabel('file', parent=self), 0, 0) - self._inputLe = qt.QLineEdit('', parent=self) - self._inputWindow.layout().addWidget(self._inputLe, 0, 1) - self._selectPB = qt.QPushButton('select', parent=self) - self._inputWindow.layout().addWidget(self._selectPB, 0, 2) + self._inputDialog = XASObjectDialog(parent=self) + self._inputWindow.layout().addWidget(self._inputDialog, 0, 0, 1, 2) self._startPB = qt.QPushButton('restart', parent=self) - self._inputWindow.layout().addWidget(self._startPB, 1, 1, 1, 2) + self._inputWindow.layout().addWidget(self._startPB, 1, 1, 1, 1) spacer = qt.QWidget(parent=self) spacer.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding) @@ -81,51 +83,51 @@ class XASInputOW(OWWidget): layout = gui.vBox(self.mainArea, 'input').layout() layout.addWidget(self._inputWindow) - # signal / slot connection - self._selectPB.pressed.connect(self._selectFile) - self._inputLe.editingFinished.connect(self._emitNewFile) - self._startPB.pressed.connect(self._emitNewFile) - # deal with setting - if os.path.isfile(self._input_file_setting): - self.setFileSelected(self._input_file_setting) + self._manageSettings() # expose api self.restart = self._emitNewFile - def _selectFile(self, *args, **kwargs): - old = self.blockSignals(True) - dialog = qt.QFileDialog(self) - dialog.setFileMode(qt.QFileDialog.ExistingFile) - dialog.setNameFilters(["spectra (*.dat)", - "spectra and configruation (*.h5 *.hdf *.hdf5)"]) - - if not dialog.exec_(): - dialog.close() - return - - fileSelected = dialog.selectedFiles() - if len(fileSelected) == 0: - return - else: - assert len(fileSelected) == 1 - self.setFileSelected(fileSelected[0]) - - self.blockSignals(old) - - def setFileSelected(self, file_path): - self._inputLe.setText(file_path) + # signal / slot connection + self._startPB.pressed.connect(self._emitNewFile) + self._inputDialog.editingFinished.connect(self._storeSettings) def _emitNewFile(self, *args, **kwargs): - self._input_file_setting = self._inputLe.text() - if not os.path.isfile(self._input_file_setting): - mess = self._input_file_setting + 'is not a valid file path' - _logger.warning(mess) - return + try: + xas_obj = self._inputDialog.buildXASObject() + except ValueError as e: + qt.QMessageBox.warning(self, '', str(e)) else: - try: - xas_obj = XASInputOW.read_pymca_xas_from_file(file_path=self._input_file_setting) - except ... as e: - _logger.error(e) - else: - self.send("spectra", xas_obj) + self.send("spectra", xas_obj) + + def _manageSettings(self): + input_type = xas.io.InputType.multiple_spectra + if os.path.isfile(self._input_file_setting): + self._inputDialog.setDatFile(self._input_file_setting) + input_type = xas.io.InputType.single_spectrum + + def load_url(url_path, setter): + if url_path != '': + try: + url = DataUrl(url_path) + if url and url.is_valid(): + setter(url.path()) + except ... as e: + logging.info('fail to load ', url_path) + + load_url(self._spectra_url_setting, self._inputDialog.setSpectraUrl) + load_url(self._energy_url_setting, self._inputDialog.setEnergyUrl) + load_url(self._configuration_url_setting, self._inputDialog.setConfigurationUrl) + + # set up + self._inputDialog.setCurrentType(input_type) + + def _storeSettings(self): + self._input_file_setting = self._inputDialog.getDatFile() + self._spectra_url_setting = self._inputDialog.getSpectraUrl() + self._energy_url_setting = self._inputDialog.getEnergyUrl() + self._configuration_url_setting = self._inputDialog.getConfigurationUrl() + + def sizeHint(self): + return qt.QSize(400, 200) diff --git a/xas/gui/xas_object_definition.py b/xas/gui/xas_object_definition.py index df192d7..8e680cc 100644 --- a/xas/gui/xas_object_definition.py +++ b/xas/gui/xas_object_definition.py @@ -29,10 +29,10 @@ __date__ = "03/07/2019" from silx.gui.dialog.DataFileDialog import DataFileDialog +from silx.io.url import DataUrl from silx.gui import qt -import os +from xas.io import read_pymca_xas, read_pymca_xas_from_file, InputType import logging -from xas.io import read_pymca_xas_from_file, InputType _logger = logging.getLogger(__name__) @@ -50,15 +50,23 @@ class _InputType(qt.QWidget): # expose API self.currentChanged = self._inputTypeCB.currentIndexChanged - def getCurrentType(self): + def getInputType(self): """Return the current input type""" return InputType.from_value(self._inputTypeCB.currentText()) + def setInputType(self, input_type): + _input_type = InputType.from_value(input_type) + idx = self._inputTypeCB.findText(_input_type.value) + assert idx >= 0 + self._inputTypeCB.setCurrentIndex(idx) + class XASObjectDialog(qt.QWidget): """ Interface used to select inputs for defining a XASObject """ + editingFinished = qt.Signal() + def __init__(self, parent=None): qt.QWidget.__init__(self, parent) self.setLayout(qt.QGridLayout()) @@ -79,15 +87,52 @@ class XASObjectDialog(qt.QWidget): # connect signal / slot self._inputType.currentChanged.connect(self._updateWidgetVisibility) + self._datDialog._inputLe.textChanged.connect(self._editingIsFinished) + self._h5Dialog.editingFinished.connect(self._editingIsFinished) # expose API + self.getCurrentType = self._inputType.getInputType + self.setCurrentType = self._inputType.setInputType + self.setDatFile = self._datDialog.setFileSelected + self.getDatFile = self._datDialog.getFileSelected + self.setSpectraUrl = self._h5Dialog.setSpectraUrl + self.getSpectraUrl = self._h5Dialog.getSpectraUrl + self.setEnergyUrl = self._h5Dialog.setEnergyUrl + self.getEnergyUrl = self._h5Dialog.getEnergyUrl + self.setConfigurationUrl = self._h5Dialog.setConfigurationUrl + self.getConfigurationUrl = self._h5Dialog.getConfigurationUrl # default setting self._updateWidgetVisibility() def _updateWidgetVisibility(self): - self._datDialog.setVisible(self._inputType.getCurrentType() == InputType.single_spectrum) - self._h5Dialog.setVisible(self._inputType.getCurrentType() == InputType.multiple_spectra) + self._datDialog.setVisible(self._inputType.getInputType() == InputType.single_spectrum) + self._h5Dialog.setVisible(self._inputType.getInputType() == InputType.multiple_spectra) + + def buildXASObject(self): + if self.getCurrentType() == InputType.single_spectrum: + return read_pymca_xas_from_file(file_path=self._datDialog.getFileSelected()) + elif self.getCurrentType() == InputType.multiple_spectra: + spectra_url = self._h5Dialog.getSpectraUrl() + energy_url = self._h5Dialog.getEnergyUrl() + + def check_url(url_path, name): + if url_path in (None, ''): + raise ValueError(' '.join(('No', name, 'url defined'))) + url = DataUrl(path=url_path) + if not url.is_valid(): + raise ValueError(' '.join((name, 'url is invalid. Does the file / path still exists ?'))) + + check_url(spectra_url, 'spectra') + check_url(energy_url, 'energy / channel') + return read_pymca_xas(spectra_url=self._h5Dialog.getSpectraUrl(), + channel_url=self._h5Dialog.getEnergyUrl(), + config_url=self._h5Dialog.getConfigurationUrl()) + else: + raise ValueError('unmanaged input type') + + def _editingIsFinished(self, *args, **kwargs): + self.editingFinished.emit() class _XASObjectFrmDat(qt.QWidget): @@ -104,20 +149,15 @@ class _XASObjectFrmDat(qt.QWidget): self._selectPB = qt.QPushButton('select', parent=self) self.layout().addWidget(self._selectPB, 0, 2) - self._startPB = qt.QPushButton('restart', parent=self) - self.layout().addWidget(self._startPB, 1, 1, 1, 2) - spacer = qt.QWidget(parent=self) spacer.setSizePolicy(qt.QSizePolicy.Minimum, qt.QSizePolicy.Expanding) self.layout().addWidget(spacer, 2, 0) # signal / slot connection self._selectPB.pressed.connect(self._selectFile) - self._inputLe.editingFinished.connect(self._processXASObject) - self._startPB.pressed.connect(self._processXASObject) # expose api - self.restart = self._processXASObject + self.getFileSelected = self._inputLe.text def _selectFile(self, *args, **kwargs): old = self.blockSignals(True) @@ -141,20 +181,6 @@ class _XASObjectFrmDat(qt.QWidget): def setFileSelected(self, file_path): self._inputLe.setText(file_path) - def _processXASObject(self, *args, **kwargs): - self._input_file_setting = self._inputLe.text() - if not os.path.isfile(self._input_file_setting): - mess = self._input_file_setting + 'is not a valid file path' - _logger.warning(mess) - return - else: - try: - xas_obj = read_pymca_xas_from_file(file_path=self._input_file_setting) - except ... as e: - _logger.error(e) - else: - return xas_obj - class _URLSelector(qt.QWidget): def __init__(self, parent, name, layout=None, position=None): @@ -171,13 +197,13 @@ class _URLSelector(qt.QWidget): # expose API self.getUrlPath = self._qLineEdit.text + self.setUrlPath = self._qLineEdit.setText # connect signal / slot self._qPushButton.clicked.connect(self._selectFile) def _selectFile(self, *args, **kwargs): dialog = DataFileDialog(self) - # dialog.setNameFilters(["".join((self.name, "(*.h5 *.hdf *.hdf5)")),]) if not dialog.exec_(): dialog.close() @@ -191,6 +217,7 @@ class _XASObjFrmH5(qt.QWidget): """ Interface used to define a XAS object from h5 files and data path """ + editingFinished = qt.Signal() def __init__(self, parent=None): qt.QWidget.__init__(self, parent) self.setLayout(qt.QGridLayout()) @@ -205,6 +232,32 @@ class _XASObjFrmH5(qt.QWidget): layout=self.layout(), position=(2, 0)) + # connect signal / slot + self._spectraSelector._qLineEdit.textChanged.connect(self._editingIsFinished) + self._energySelector._qLineEdit.textChanged.connect(self._editingIsFinished) + self._configSelector._qLineEdit.textChanged.connect(self._editingIsFinished) + + def getSpectraUrl(self): + return self._spectraSelector.getUrlPath() + + def getEnergyUrl(self): + return self._energySelector.getUrlPath() + + def getConfigurationUrl(self): + return self._configSelector.getUrlPath() + + def setSpectraUrl(self, url): + self._spectraSelector.setUrlPath(url) + + def setEnergyUrl(self, url): + self._energySelector.setUrlPath(url) + + def setConfigurationUrl(self, url): + self._configSelector.setUrlPath(url) + + def _editingIsFinished(self, *args, **kwargs): + self.editingFinished.emit() + if __name__ == '__main__': app = qt.QApplication([]) diff --git a/xas/io/io.py b/xas/io/io.py index 5c39692..8f03add 100644 --- a/xas/io/io.py +++ b/xas/io/io.py @@ -89,7 +89,11 @@ def read_pymca_xas(spectra_url, channel_url, config_url=None): def get_url(original_url, name): url_ = original_url if type(url_) is str: - url_ = DataUrl(file_path=url_, scheme='PyMca') + try: + url_ = DataUrl(path=url_) + except: + url_ = DataUrl(file_path=url_, scheme='PyMca') + if not isinstance(url_, DataUrl): raise TypeError('given input for, ', name, 'is invalid') return url_ @@ -97,6 +101,8 @@ def read_pymca_xas(spectra_url, channel_url, config_url=None): _spectra_url = get_url(original_url=spectra_url, name='spectra') _energy_url = get_url(original_url=channel_url, name='energy') _config_url = config_url + if _config_url == '': + _config_url = None if not (_config_url is None or isinstance(_config_url, DataUrl)): raise TypeError('given input for configuration is invalid') @@ -105,7 +111,7 @@ def read_pymca_xas(spectra_url, channel_url, config_url=None): return None if data_url.scheme() in ('PyMca', 'PyMca5'): assert name in ('spectra', 'energy') - energy, mu = read_spectrum(spectra_url.file_path()) + energy, mu = read_spectrum(data_url.file_path()) if name == 'spectra': return mu.reshape(mu.shape[0], 1, 1) else: @@ -117,7 +123,6 @@ def read_pymca_xas(spectra_url, channel_url, config_url=None): _logger.warning('invalid url for', name, ', will not load it') spectra = load_data(_spectra_url, name='spectra') - energy = load_data(_energy_url, name='energy') configuration = load_data(_config_url, name='configuration') -- GitLab From 03d83988d9406e6a82c575db0ecb9b6001fac295 Mon Sep 17 00:00:00 2001 From: payno Date: Thu, 4 Jul 2019 15:32:37 +0200 Subject: [PATCH 3/6] [gui] add first brick for the XASObject viewer. Enable to display a map or the spectrum Spectrums are only displyed with energy and mu at the moment but should evolve with time. Maybe we would like to do the same for the map ??? --- xas/core/types.py | 11 ++ xas/gui/XasObjectViewer.py | 213 +++++++++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 xas/gui/XasObjectViewer.py diff --git a/xas/core/types.py b/xas/core/types.py index 8880c55..137301a 100644 --- a/xas/core/types.py +++ b/xas/core/types.py @@ -83,6 +83,13 @@ class XASObject(object): self.addSpectrum(spectrum) self.energy = energy + def getSpectrum(self, dim1_idx, dim2_idx): + """Util function to access the spectrum at dim1_idx, dim2_idx""" + assert dim1_idx < self.dim1 + assert dim2_idx < self.dim2 + global_idx = dim1_idx * self.dim2 + dim2_idx + return self.spectra[global_idx] + def addSpectrum(self, spectrum): self.__spectra.append(spectrum) @@ -210,6 +217,10 @@ class XASObject(object): return len(self.__spectra) +# TODO: add the spectra class. Would speed up and simplify stuff probably +class Spectra(object): + pass + class Spectrum(object): _MU_KEY = 'Mu' diff --git a/xas/gui/XasObjectViewer.py b/xas/gui/XasObjectViewer.py new file mode 100644 index 0000000..8cb227d --- /dev/null +++ b/xas/gui/XasObjectViewer.py @@ -0,0 +1,213 @@ +# 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. +# +# ###########################################################################*/ +"""Tools to visualize spectra""" + + +__authors__ = ["H. Payno"] +__license__ = "MIT" +__date__ = "04/07/2019" + + +from xas.core.types import XASObject +from xas.io import read_pymca_xas +from silx.gui import qt +from silx.gui.plot import Plot1D +from silx.gui.plot.StackView import StackViewMainWindow +from silx.utils.enum import Enum +from silx.gui.widgets.FrameBrowser import HorizontalSliderWithBrowser +from silx.gui import icons as silx_icons + + +class ViewType(Enum): + map = 0, + spectrum = 1, + + +class _SpectrumViewAction(qt.QAction): + def __init__(self, parent=None): + qt.QAction.__init__(self, 'spectrum view', parent=parent) + spectrum_icon = silx_icons.getQIcon("item-1dim") + self.setIcon(spectrum_icon) + self.setCheckable(True) + + +class _MapViewAction(qt.QAction): + def __init__(self, parent=None): + qt.QAction.__init__(self, 'map view', parent=parent) + map_icon = silx_icons.getQIcon("image") + self.setIcon(map_icon) + self.setCheckable(True) + + +class XasObjectViewer(qt.QMainWindow): + """Viewer dedicated to view a XAS object""" + def __init__(self, parent=None): + qt.QMainWindow.__init__(self, parent) + self.setWindowFlags(qt.Qt.Widget) + + # main stack widget + self._mainWidget = qt.QWidget(parent=self) + self._mainWidget.setLayout(qt.QVBoxLayout()) + self.setCentralWidget(self._mainWidget) + # map view + self._mapView = MapViewer(parent=self) + self._mainWidget.layout().addWidget(self._mapView) + + # spectrum view + self._spectrumView = SpectrumViewer(parent=self) + self._mainWidget.layout().addWidget(self._spectrumView) + + # add toolbar + toolbar = qt.QToolBar('') + toolbar.setIconSize(qt.QSize(32, 32)) + self.view_actions = qt.QActionGroup(self) + self._spectrumViewAction = _SpectrumViewAction() + self.view_actions.addAction(self._spectrumViewAction) + self._mapViewAction = _MapViewAction() + self.view_actions.addAction(self._mapViewAction) + + toolbar.addAction(self._spectrumViewAction) + toolbar.addAction(self._mapViewAction) + + self.addToolBar(qt.Qt.LeftToolBarArea, toolbar) + toolbar.setMovable(False) + + # connect signal / Slot + self._mapViewAction.triggered.connect(self._updateView) + self._spectrumViewAction.triggered.connect(self._updateView) + + # initialize + self._spectrumViewAction.setChecked(True) + self._updateView() + + def _updateView(self, *arg, **kwargs): + self._mapView.setVisible(self.getViewType() is ViewType.map) + self._spectrumView.setVisible(self.getViewType() is ViewType.spectrum) + + def getViewType(self): + if self._mapViewAction.isChecked(): + return ViewType.map + elif self._spectrumViewAction.isChecked(): + return ViewType.spectrum + + def setXASObj(self, xas_obj): + self._mapView.clear() + self._spectrumView.clear() + + # set the map view + spectra_volume = XASObject._spectra_volume(xas_obj.spectra, + xas_obj.dim1, + xas_obj.dim2) + self._mapView.setStack(spectra_volume) + + # set spectrum view + self._spectrumView.setXasObject(xas_obj) + + +class MapViewer(StackViewMainWindow): + pass + + +class _ExtendedSliderWithBrowser(HorizontalSliderWithBrowser): + def __init__(self, parent=None, name=None): + HorizontalSliderWithBrowser.__init__(self, parent) + self.layout().insertWidget(0, qt.QLabel(str(name + ':'))) + + +class SpectrumViewer(qt.QMainWindow): + def __init__(self, parent=None): + qt.QMainWindow.__init__(self, parent) + self.xas_obj = None + self._plot = Plot1D(parent=self) + self.setCentralWidget(self._plot) + + dockWidget = qt.QDockWidget(parent=self) + + frameBrowsers = qt.QWidget(parent=self) + frameBrowsers.setLayout(qt.QVBoxLayout()) + frameBrowsers.layout().setContentsMargins(0, 0, 0, 0) + + self._dim1FrameBrowser = _ExtendedSliderWithBrowser(parent=self, + name='dim 1') + frameBrowsers.layout().addWidget(self._dim1FrameBrowser) + self._dim2FrameBrowser = _ExtendedSliderWithBrowser(parent=self, + name='dim 2') + frameBrowsers.layout().addWidget(self._dim2FrameBrowser) + dockWidget.setWidget(frameBrowsers) + + self.addDockWidget(qt.Qt.BottomDockWidgetArea, dockWidget) + dockWidget.setAllowedAreas(qt.Qt.BottomDockWidgetArea) + dockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) + + # connect signal / slot + self._dim1FrameBrowser.valueChanged.connect(self._updateSpectrumDisplayed) + self._dim2FrameBrowser.valueChanged.connect(self._updateSpectrumDisplayed) + + def setXasObject(self, xas_obj): + self.xas_obj = xas_obj + self._dim1FrameBrowser.setRange(0, self.xas_obj.dim1-1) + self._dim2FrameBrowser.setRange(0, self.xas_obj.dim2-1) + + def clear(self): + self._plot.clear() + + def _updateSpectrumDisplayed(self, *args, **kwargs): + if self.xas_obj is None: + return + dim1_index = self._dim1FrameBrowser.value() + dim2_index = self._dim2FrameBrowser.value() + spectrum = self.xas_obj.getSpectrum(dim1_index, dim2_index) + self._plot.addCurve(x=spectrum.energy, y=spectrum.mu) + + def clear(self): + self._plot.clear() + self._dim1FrameBrowser.setMaximum(-1) + self._dim2FrameBrowser.setMaximum(-1) + + +if __name__ == '__main__': + from silx.io.url import DataUrl + + file_name = "S5_nuit1_merge_new_crop.h5" + url_spectra = DataUrl(file_path=file_name, + data_path='/data/NXdata/data', + scheme='silx') + url_channel = DataUrl(file_path=file_name, + data_path='/data/NXdata/Channel', + scheme='silx') + + xas_obj = read_pymca_xas(spectra_url=url_spectra, channel_url=url_channel) + + # import numpy + # from xas.core.utils import spectra as spectra_utils + # spectra = spectra_utils.create_dataset(shape=(256, 32, 64)) + # energy = numpy.linspace(start=3.25, stop=3.69, num=256) + # xas_obj = XASObject(spectra=spectra, energy=energy, configuration=None) + + app = qt.QApplication([]) + viewer = XasObjectViewer() + viewer.setXASObj(xas_obj=xas_obj) + viewer.show() + app.exec_() -- GitLab From d5e8f28c172c8827c3ba70830883186b2efc28e8 Mon Sep 17 00:00:00 2001 From: payno Date: Thu, 4 Jul 2019 16:30:51 +0200 Subject: [PATCH 4/6] [gui][MapViewer] set default colormap to temperature and keep aspect ratio --- xas/gui/XasObjectViewer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/xas/gui/XasObjectViewer.py b/xas/gui/XasObjectViewer.py index 8cb227d..3d318ba 100644 --- a/xas/gui/XasObjectViewer.py +++ b/xas/gui/XasObjectViewer.py @@ -38,6 +38,7 @@ from silx.gui.plot.StackView import StackViewMainWindow from silx.utils.enum import Enum from silx.gui.widgets.FrameBrowser import HorizontalSliderWithBrowser from silx.gui import icons as silx_icons +from silx.gui.colors import Colormap class ViewType(Enum): @@ -127,7 +128,10 @@ class XasObjectViewer(qt.QMainWindow): class MapViewer(StackViewMainWindow): - pass + def __init__(self, parent=None): + StackViewMainWindow.__init__(self, parent=parent) + self.setKeepDataAspectRatio(True) + self.setColormap(Colormap(name='temperature')) class _ExtendedSliderWithBrowser(HorizontalSliderWithBrowser): -- GitLab From 9cf53972145b1fa0313b047e135f249213a88198 Mon Sep 17 00:00:00 2001 From: payno Date: Fri, 5 Jul 2019 09:44:40 +0200 Subject: [PATCH 5/6] [orange-add on] integrate the XasObjectViewer in the pymca orange add-on --- orangecontrib/xas/widgets/exafs.py | 118 +++++++++++++------- orangecontrib/xas/widgets/ft.py | 65 +++++++---- orangecontrib/xas/widgets/k_weight.py | 99 +++++++++------- orangecontrib/xas/widgets/normalization.py | 124 ++++++++++++++------- xas/core/types.py | 26 +++++ xas/gui/XasObjectViewer.py | 48 +++++++- 6 files changed, 331 insertions(+), 149 deletions(-) diff --git a/orangecontrib/xas/widgets/exafs.py b/orangecontrib/xas/widgets/exafs.py index 360b0af..962348e 100644 --- a/orangecontrib/xas/widgets/exafs.py +++ b/orangecontrib/xas/widgets/exafs.py @@ -29,40 +29,104 @@ __date__ = "06/07/2019" from silx.gui import qt -from silx.gui.plot import Plot1D, LegendSelector +from silx.gui.plot import LegendSelector from Orange.widgets import gui from Orange.widgets.widget import OWWidget -from xas.core.types import XASObject -from PyMca5.PyMcaGui.physics.xas.XASPostEdgeParameters import XASPostEdgeParameters from Orange.widgets.settings import Setting +from xas.core.types import XASObject, Spectrum import xas.core.process.exafs +from xas.gui.XasObjectViewer import XasObjectViewer, _CurveOperation, ViewType +from PyMca5.PyMcaGui.physics.xas.XASPostEdgeParameters import XASPostEdgeParameters import logging _logger = logging.getLogger(__file__) +def _exafs_signal_plot(spectrum): + assert isinstance(spectrum, Spectrum) + missing_keys = spectrum.get_missing_keys(('EXAFSKValues', 'EXAFSSignal')) + if missing_keys: + _logger.error('missing keys:', missing_keys, 'unable to compute exafs signal plot') + return + k = spectrum["EXAFSKValues"] + if "KMin" not in spectrum: + spectrum["KMin"] = k.min() + if "KMax" not in spectrum: + spectrum["KMax"] = k.max() + + idx = (spectrum["EXAFSKValues"] >= spectrum["KMin"]) & \ + (spectrum["EXAFSKValues"] <= spectrum["KMax"]) + x = spectrum["EXAFSKValues"][idx] + y = spectrum["EXAFSSignal"][idx] + return _CurveOperation(x=x, y=y, legend="EXAFSSignal") + + +def _exafs_postedge_plot(spectrum): + assert isinstance(spectrum, Spectrum) + missing_keys = spectrum.get_missing_keys(('EXAFSKValues', 'PostEdgeB')) + if missing_keys: + _logger.error('missing keys:', missing_keys, 'unable to compute exafs postedge plot') + return + k = spectrum["EXAFSKValues"] + if "KMin" not in spectrum: + spectrum["KMin"] = k.min() + if "KMax" not in spectrum: + spectrum["KMax"] = k.max() + + idx = (spectrum["EXAFSKValues"] >= spectrum["KMin"]) & \ + (spectrum["EXAFSKValues"] <= spectrum["KMax"]) + + x = spectrum["EXAFSKValues"][idx] + y = spectrum["PostEdgeB"][idx] + return _CurveOperation(x=x, y=y, legend="PostEdge") + + +def _exafs_knots_plot(spectrum): + assert isinstance(spectrum, Spectrum) + missing_keys = spectrum.get_missing_keys(('KnotsX', 'KnotsY')) + if missing_keys: + _logger.error('missing keys:', missing_keys, 'unable to compute exafs knot plot') + return + x = spectrum["KnotsX"] + y = spectrum["KnotsY"] + return _CurveOperation(x=x, y=y, legend="Knots", linestyle="", symbol="o") + + class ExafsWindow(qt.QMainWindow): def __init__(self, parent=None): qt.QMainWindow.__init__(self, parent) - self.plot = Plot1D() - self.setCentralWidget(self.plot) + self.xasObjViewer = XasObjectViewer() + self.xasObjViewer._spectrumView._plot.getXAxis().setLabel("K") + self.xasObjViewer._spectrumView._plot.getYAxis().setLabel("Normalized Units") + self.setCentralWidget(self.xasObjViewer) + + # pymca window self._pymcaWindow = XASPostEdgeParameters(parent=self) dockWidget = qt.QDockWidget(parent=self) - dockWidget.setWidget(self._pymcaWindow) self.addDockWidget(qt.Qt.RightDockWidgetArea, dockWidget) dockWidget.setAllowedAreas(qt.Qt.RightDockWidgetArea | qt.Qt.LeftDockWidgetArea) dockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) self.setWindowFlags(qt.Qt.Widget) - # legend + + # legend selector self.legendDockWidget = LegendSelector.LegendsDockWidget(parent=self, - plot=self.plot) + plot=self.xasObjViewer._spectrumView._plot) self.legendDockWidget.setAllowedAreas(qt.Qt.RightDockWidgetArea | qt.Qt.LeftDockWidgetArea) self.legendDockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) self.addDockWidget(qt.Qt.RightDockWidgetArea, self.legendDockWidget) + self.setWindowFlags(qt.Qt.Widget) + + # plot settings + for ope in (_exafs_signal_plot, _exafs_postedge_plot, _exafs_knots_plot,): + self.xasObjViewer._spectrumView.addCurveOperation(ope) + def getNCurves(self): - return len(self.plot.getAllCurves()) + return len(self.xasObjViewer.getAllCurves()) + + def _updateLegendView(self): + self.legendDockWidget.setVisible(self.xasObjViewer.getViewType() is ViewType.spectrum) class ExafsOW(OWWidget): @@ -95,8 +159,8 @@ class ExafsOW(OWWidget): layout.addWidget(self._window) self._latest_xas_obj = None - self._window.plot.getXAxis().setLabel("K") - self._window.plot.getYAxis().setLabel("Normalized Units") + self._window.xasObjViewer._spectrumView._plot.getXAxis().setLabel("K") + self._window.xasObjViewer._spectrumView._plot.getYAxis().setLabel("Normalized Units") # manage settings if self._pymcaSettings != dict(): @@ -119,40 +183,10 @@ class ExafsOW(OWWidget): process_obj = ExafsOW.process_function() process_obj.setProperties({'_pymcaSettings': self._window._pymcaWindow.getParameters()}) xas_obj = process_obj.process(xas_obj) - self._updatePlot(xas_obj.spectra) + self._window.xasObjViewer.setXASObj(xas_obj) # emit signal for the plot self.send("spectra", xas_obj) - def _updatePlot(self, ddict): - plot = self._window.plot - plot.clear() - - k = ddict["EXAFSKValues"] - if "KMin" not in ddict: - ddict["KMin"] = k.min() - if "KMax" not in ddict: - ddict["KMax"] = k.max() - - idx = (ddict["EXAFSKValues"] >= ddict["KMin"]) & \ - (ddict["EXAFSKValues"] <= ddict["KMax"]) - - plot.addCurve(x=ddict["EXAFSKValues"][idx], - y=ddict["EXAFSSignal"][idx], - legend="EXAFSSignal", - resetzoom=False) - - plot.addCurve(ddict["EXAFSKValues"][idx], - ddict["PostEdgeB"][idx], - legend="PostEdge", - resetzoom=False) - - plot.addCurve(ddict["KnotsX"], - ddict["KnotsY"], - legend="Knots", - linestyle="", - symbol="o", - resetzoom=True) - def _update_settings(self): self._pymcaSettings = self._window._pymcaWindow.getParameters() diff --git a/orangecontrib/xas/widgets/ft.py b/orangecontrib/xas/widgets/ft.py index 1cd3a10..4c68f9c 100644 --- a/orangecontrib/xas/widgets/ft.py +++ b/orangecontrib/xas/widgets/ft.py @@ -29,41 +29,77 @@ __date__ = "06/07/2019" from silx.gui import qt -from silx.gui.plot import Plot1D, LegendSelector +from silx.gui.plot import LegendSelector from Orange.widgets import gui from Orange.widgets.widget import OWWidget from Orange.widgets.settings import Setting -from xas.core.types import XASObject +from xas.core.types import XASObject, Spectrum import xas.core.process.ft +from xas.gui.XasObjectViewer import XasObjectViewer, _CurveOperation, ViewType from PyMca5.PyMcaGui.physics.xas.XASFourierTransformParameters import XASFourierTransformParameters import logging _logger = logging.getLogger(__file__) +def _ft_intensity_plot(spectrum): + assert isinstance(spectrum, Spectrum) + missing_keys = spectrum.ft.get_missing_keys(('FTRadius', 'FTIntensity')) + if missing_keys: + _logger.error('missing keys:', missing_keys, 'unable to compute spectrum plot') + return + return _CurveOperation(x=spectrum.ft['FTRadius'], + y=spectrum.ft['FTIntensity'], + legend="FT Intensity") + + +def _ft_imaginary_plot(spectrum): + assert isinstance(spectrum, Spectrum) + missing_keys = spectrum.ft.get_missing_keys(('FTRadius', 'FTImaginary')) + if missing_keys: + _logger.error('missing keys:', missing_keys, 'unable to compute spectrum plot') + return + return _CurveOperation(x=spectrum.ft['FTRadius'], + y=spectrum.ft['FTImaginary'], + legend="FT Imaginary", + color="red") + + class FTWindow(qt.QMainWindow): def __init__(self, parent=None): qt.QMainWindow.__init__(self, parent) - self.plot = Plot1D() - self.setCentralWidget(self.plot) + + # xas object viewer + self.xasObjViewer = XasObjectViewer() + self.xasObjViewer._spectrumView._plot.getXAxis().setLabel("R (Angstrom)") + self.xasObjViewer._spectrumView._plot.getYAxis().setLabel("Arbitrary Units") + self.setCentralWidget(self.xasObjViewer) self._pymcaWindow = XASFourierTransformParameters(parent=self) dockWidget = qt.QDockWidget(parent=self) + # pymca window dockWidget.setWidget(self._pymcaWindow) self.addDockWidget(qt.Qt.RightDockWidgetArea, dockWidget) dockWidget.setAllowedAreas(qt.Qt.RightDockWidgetArea | qt.Qt.LeftDockWidgetArea) dockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) self.setWindowFlags(qt.Qt.Widget) - # legend + + # legend selector self.legendDockWidget = LegendSelector.LegendsDockWidget(parent=self, - plot=self.plot) + plot=self.xasObjViewer._spectrumView._plot) self.legendDockWidget.setAllowedAreas(qt.Qt.RightDockWidgetArea | qt.Qt.LeftDockWidgetArea) self.legendDockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) self.addDockWidget(qt.Qt.RightDockWidgetArea, self.legendDockWidget) + # plot settings + for ope in (_ft_intensity_plot, _ft_imaginary_plot): + self.xasObjViewer._spectrumView.addCurveOperation(ope) + def getNCurves(self): return len(self.plot.getAllCurves()) + def _updateLegendView(self): + self.legendDockWidget.setVisible(self.xasObjViewer.getViewType() is ViewType.spectrum) class FTOW(OWWidget): """ @@ -95,9 +131,6 @@ class FTOW(OWWidget): layout = gui.vBox(self.mainArea, 'fourier transform').layout() layout.addWidget(self._window) - self._window.plot.getXAxis().setLabel("R (Angstrom)") - self._window.plot.getYAxis().setLabel("Arbitrary Units") - # manage settings if self._pymcaSettings != dict(): self._window._pymcaWindow.setParameters(self._pymcaSettings) @@ -119,22 +152,10 @@ class FTOW(OWWidget): process_obj.setProperties({'_pymcaSettings': self._window._pymcaWindow.getParameters()}) xas_obj = process_obj.process(xas_obj) - self._updatePlot(ddict=xas_obj.spectrum['FT']) + self._window.xasObjViewer.setXASObj(xas_obj) # emit signal for the plot self.send("spectra", xas_obj) - def _updatePlot(self, ddict): - self._window.plot.clear() - self._window.plot.addCurve(x=ddict["FTRadius"], - y=ddict["FTIntensity"], - legend="FT Intensity",) - - self._window.plot.addCurve(x=ddict["FTRadius"], - y=ddict["FTImaginary"], - legend="FT Imaginary", - color="red") - self._window.plot.resetZoom() - def _update_settings(self): self._pymcaSettings = self._window._pymcaWindow.getParameters() diff --git a/orangecontrib/xas/widgets/k_weight.py b/orangecontrib/xas/widgets/k_weight.py index bb4e2b8..36268f9 100644 --- a/orangecontrib/xas/widgets/k_weight.py +++ b/orangecontrib/xas/widgets/k_weight.py @@ -28,44 +28,93 @@ __license__ = "MIT" __date__ = "06/07/2019" -from silx.gui.plot import Plot1D, LegendSelector +from silx.gui.plot import LegendSelector from Orange.widgets import gui from Orange.widgets.widget import OWWidget from Orange.widgets.settings import Setting from silx.gui import qt -from xas.core.types import XASObject +from xas.core.types import XASObject, Spectrum import xas.core.process.k_weight +from xas.gui.XasObjectViewer import XasObjectViewer, _CurveOperation import logging _logger = logging.getLogger(__file__) +def _normalized_exafs(spectrum): + assert isinstance(spectrum, Spectrum) + missing_keys = spectrum.get_missing_keys(('EXAFSKValues', 'EXAFSNormalized')) + if missing_keys: + _logger.error('missing keys:', missing_keys, + 'unable to compute normalized EXAFS') + return None + + if spectrum["KWeight"]: + if spectrum["KWeight"] == 1: + ylabel = "EXAFS Signal * k" + else: + ylabel = "EXAFS Signal * k^%d" % spectrum["KWeight"] + else: + ylabel = "EXAFS Signal" + + idx = (spectrum["EXAFSKValues"] >= spectrum["KMin"]) & \ + (spectrum["EXAFSKValues"] <= spectrum["KMax"]) + + return _CurveOperation(x=spectrum["EXAFSKValues"][idx], + y=spectrum["EXAFSNormalized"][idx], + legend="Normalized EXAFS", + ylabel=ylabel) + + +def _ft_window_plot(spectrum): + assert isinstance(spectrum, Spectrum) + missing_keys = spectrum.ft.get_missing_keys(('K', 'WindowWeight')) + if missing_keys: + _logger.error('missing keys:', missing_keys, + 'unable to compute normalized EXAFS') + return None + + return _CurveOperation(x=spectrum.ft['K'], + y=spectrum.ft["WindowWeight"], + legend="FT Window", + yaxis="right", + color="red") + + class KWeightWindow(qt.QMainWindow): def __init__(self, parent=None): qt.QMainWindow.__init__(self, parent) - self.plot = Plot1D() - self.setCentralWidget(self.plot) + # xas object viewer + self.xasObjViewer = XasObjectViewer() + self.xasObjViewer._spectrumView._plot.getXAxis().setLabel("K") + self.setCentralWidget(self.xasObjViewer) + # k wright widget self._k_widget = qt.QWidget(parent=self) self._k_widget.setLayout(qt.QHBoxLayout()) self._k_widget.layout().addWidget(qt.QLabel('k weight')) self._k_spin_box = qt.QSpinBox(parent=self) self._k_spin_box.setRange(0, 3) self._k_widget.layout().addWidget(self._k_spin_box) - dockWidget = qt.QDockWidget(parent=self) dockWidget.setWidget(self._k_widget) self.addDockWidget(qt.Qt.RightDockWidgetArea, dockWidget) dockWidget.setAllowedAreas(qt.Qt.RightDockWidgetArea | qt.Qt.LeftDockWidgetArea) dockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) - self.setWindowFlags(qt.Qt.Widget) - # legend + + # legend selector self.legendDockWidget = LegendSelector.LegendsDockWidget(parent=self, - plot=self.plot) + plot=self.xasObjViewer._spectrumView._plot) self.legendDockWidget.setAllowedAreas(qt.Qt.RightDockWidgetArea | qt.Qt.LeftDockWidgetArea) self.legendDockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) self.addDockWidget(qt.Qt.RightDockWidgetArea, self.legendDockWidget) + self.setWindowFlags(qt.Qt.Widget) + + # plot settings + for ope in (_normalized_exafs, _ft_window_plot): + self.xasObjViewer._spectrumView.addCurveOperation(ope) + def getNCurves(self): return len(self.plot.getAllCurves()) @@ -100,8 +149,6 @@ class KWeightOW(OWWidget): layout.addWidget(self._window) self._latest_xas_obj = None - self._window.plot.getXAxis().setLabel("K") - # manage settings if self._kWeightSetting != 3: self._window._k_spin_box.setValue(self._kWeightSetting) @@ -122,40 +169,10 @@ class KWeightOW(OWWidget): process_obj = KWeightOW.process_function() process_obj.setProperties({'_kWeightSetting': self._window._k_spin_box.value()}) xas_obj = process_obj.process(xas_obj) - self._updatePlot(xas_obj.spectrum) + self._window.xasObjViewer.setXASObj(xas_obj) # emit signal for the plot self.send("spectra", xas_obj) - def _updatePlot(self, ddict): - if ddict is None: - return - plot = self._window.plot - - if ddict["KWeight"]: - if ddict["KWeight"] == 1: - ylabel = "EXAFS Signal * k" - else: - ylabel = "EXAFS Signal * k^%d" % ddict["KWeight"] - else: - ylabel = "EXAFS Signal" - self._window.plot.getYAxis().setLabel(ylabel) - - idx = (ddict["EXAFSKValues"] >= ddict["KMin"]) & \ - (ddict["EXAFSKValues"] <= ddict["KMax"]) - plot.addCurve(x=ddict["EXAFSKValues"][idx], - y=ddict["EXAFSNormalized"][idx], - legend="Normalized EXAFS", - ylabel=ylabel, - replace=True) - - plot.addCurve(x=ddict["FT"]["K"], - y=ddict["FT"]["WindowWeight"], - legend="FT Window", - yaxis="right", - color="red", - replace=False) - plot.resetZoom() - def _update_settings(self): self._kWeightSetting = self._window._k_spin_box.value() diff --git a/orangecontrib/xas/widgets/normalization.py b/orangecontrib/xas/widgets/normalization.py index dfc1bb9..c5c88a5 100644 --- a/orangecontrib/xas/widgets/normalization.py +++ b/orangecontrib/xas/widgets/normalization.py @@ -29,24 +29,86 @@ __date__ = "06/07/2019" from silx.gui import qt -from silx.gui.plot import Plot1D, LegendSelector +from silx.gui.plot import LegendSelector from Orange.widgets import gui from Orange.widgets.widget import OWWidget from Orange.widgets.settings import Setting from PyMca5.PyMcaGui.physics.xas.XASNormalizationParameters import XASNormalizationParameters from PyMca5.PyMcaPhysics.xas.XASClass import XASClass -from xas.core.types import XASObject +from xas.core.types import XASObject, Spectrum +from xas.gui.XasObjectViewer import XasObjectViewer, _CurveOperation, ViewType import xas.core.process.normalization import logging _logger = logging.getLogger(__file__) +def _spectrum_plot(spectrum): + assert isinstance(spectrum, Spectrum) + missing_keys = spectrum.get_missing_keys(('Edge',)) + if missing_keys: + _logger.error('missing keys:', missing_keys, 'unable to compute spectrum plot') + return + e0 = spectrum['Edge'] + x = spectrum.energy - e0 + y = spectrum.mu + return _CurveOperation(x=x, y=y, legend='Spectrum', yaxis=None) + + +def _normalized_spectrum_plot(spectrum): + assert isinstance(spectrum, Spectrum) + missing_keys = spectrum.get_missing_keys(('Edge', 'NormalizedEnergy', + 'NormalizedPlotMin', 'NormalizedPlotMax', + 'NormalizedMu')) + if missing_keys: + _logger.error('missing keys:', missing_keys, 'unable to compute normalized spectrum plot') + return None + + idx = (spectrum["NormalizedEnergy"] >= spectrum["NormalizedPlotMin"]) & \ + (spectrum["NormalizedEnergy"] <= spectrum["NormalizedPlotMax"]) + e0 = spectrum['Edge'] + x = spectrum["NormalizedEnergy"][idx] - e0 + y = spectrum["NormalizedMu"][idx] + return _CurveOperation(x=x, y=y, legend='Normalized', yaxis='right') + + +def _post_plot(spectrum): + assert isinstance(spectrum, Spectrum) + missing_keys = spectrum.get_missing_keys(('Edge', 'NormalizedEnergy', + 'NormalizedSignal')) + if missing_keys: + _logger.error('missing keys:', missing_keys, 'unable to compute post plot') + return None + + e0 = spectrum['Edge'] + x = spectrum['NormalizedEnergy'] - e0 + y = spectrum["NormalizedSignal"] + return _CurveOperation(x=x, y=y, legend='Post', yaxis=None) + + +def _pre_plot(spectrum): + assert isinstance(spectrum, Spectrum) + missing_keys = spectrum.get_missing_keys(('Edge', 'NormalizedEnergy', + 'NormalizedBackground')) + if missing_keys: + _logger.error('missing keys:', missing_keys, 'unable to compute pre plot') + return None + + e0 = spectrum['Edge'] + x = spectrum['NormalizedEnergy'] - e0 + y = spectrum["NormalizedBackground"] + return _CurveOperation(x=x, y=y, legend='Pre', yaxis=None) + + class NormalizationWindow(qt.QMainWindow): def __init__(self, parent=None): qt.QMainWindow.__init__(self, parent) - self.plot = Plot1D() - self.setCentralWidget(self.plot) + + # xas object viewer + self.xasObjViewer = XasObjectViewer() + self.xasObjViewer._spectrumView._plot.getXAxis().setLabel("Energy (eV)") + self.xasObjViewer._spectrumView._plot.getYAxis().setLabel("Absorption (a.u.)") + self.setCentralWidget(self.xasObjViewer) self._pymcaWindow = XASNormalizationParameters(parent=self) dockWidget = qt.QDockWidget(parent=self) @@ -56,17 +118,25 @@ class NormalizationWindow(qt.QMainWindow): dockWidget.setAllowedAreas(qt.Qt.RightDockWidgetArea | qt.Qt.LeftDockWidgetArea) dockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) - # legend + # legend selector self.legendDockWidget = LegendSelector.LegendsDockWidget(parent=self, - plot=self.plot) + plot=self.xasObjViewer._spectrumView._plot) self.legendDockWidget.setAllowedAreas(qt.Qt.RightDockWidgetArea | qt.Qt.LeftDockWidgetArea) self.legendDockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) self.addDockWidget(qt.Qt.RightDockWidgetArea, self.legendDockWidget) self.setWindowFlags(qt.Qt.Widget) + # plot settings + for ope in (_spectrum_plot, _normalized_spectrum_plot, _post_plot, + _pre_plot): + self.xasObjViewer._spectrumView.addCurveOperation(ope) + def getNCurves(self): - return len(self.plot.getAllCurves()) + return len(self.xasObjViewer._spectrumView._plot.getAllCurves()) + + def _updateLegendView(self): + self.legendDockWidget.setVisible(self.xasObjViewer.getViewType() is ViewType.spectrum) class NormalizationOW(OWWidget): @@ -99,9 +169,7 @@ class NormalizationOW(OWWidget): self._window = NormalizationWindow(parent=self) layout = gui.vBox(self.mainArea, 'fourier transform').layout() layout.addWidget(self._window) - self._window.plot.setWindowTitle('spectra') - self._window.plot.getXAxis().setLabel("Energy (eV)") - self._window.plot.getYAxis().setLabel("Absorption (a.u.)") + self._window.xasObjViewer.setWindowTitle('spectra') self.pymca_xas = XASClass() @@ -120,11 +188,13 @@ class NormalizationOW(OWWidget): if xas_obj is None: return + # TODO: move process to multiprocessing + add advancement progress self._latest_xas_obj = xas_obj.copy() process_class = NormalizationOW.process_function() process_class.setProperties({'_pymcaSettings': self._window._pymcaWindow.getParameters()}) xas_obj = process_class(xas_obj) - self._updatePlot(xas_obj.spectrum) + self._window.xasObjViewer.setXASObj(xas_obj=xas_obj) + # emit signal for the plot self.send("spectra", xas_obj) @@ -133,37 +203,5 @@ class NormalizationOW(OWWidget): if self._latest_xas_obj: self.process(self._latest_xas_obj) - def _updatePlot(self, ddict): - self._window.plot.clear() - if ddict is None: - return - assert isinstance(ddict, dict) - idx = (ddict["NormalizedEnergy"] >= ddict["NormalizedPlotMin"]) & \ - (ddict["NormalizedEnergy"] <= ddict["NormalizedPlotMax"]) - - # first clear connected plot - # then create all the requested curve - e0 = ddict["Edge"] - - self._window.plot.addCurve(x=ddict["Energy"] - e0, - y=ddict["Mu"], - legend="Spectrum") - - self._window.plot.addCurve(x=ddict["NormalizedEnergy"][idx] - e0, - y=ddict["NormalizedMu"][idx], - legend="Normalized", - yaxis='right') - - self._window.plot.addCurve(x=ddict["NormalizedEnergy"] - e0, - y=ddict["NormalizedSignal"], - legend="Post", - resetzoom=False) - - self._window.plot.addCurve(x=ddict["NormalizedEnergy"] - e0, - y=ddict["NormalizedBackground"], - legend='Pre', - resetzoom=False) - self._window.plot.resetZoom() - def _update_settings(self): self._pymcaSettings = self._window._pymcaWindow.getParameters() diff --git a/xas/core/types.py b/xas/core/types.py index 137301a..3ca5e59 100644 --- a/xas/core/types.py +++ b/xas/core/types.py @@ -88,6 +88,8 @@ class XASObject(object): assert dim1_idx < self.dim1 assert dim2_idx < self.dim2 global_idx = dim1_idx * self.dim2 + dim2_idx + assert global_idx < len(self.spectra) + assert global_idx >= 0 return self.spectra[global_idx] def addSpectrum(self, spectrum): @@ -221,6 +223,7 @@ class XASObject(object): class Spectra(object): pass + class Spectrum(object): _MU_KEY = 'Mu' @@ -387,6 +390,17 @@ class Spectrum(object): for key, value in obj.items(): self[key] = value + def get_missing_keys(self, keys): + """Return missing keys on the spectrum""" + missing = [] + for key in keys: + if key not in self: + missing.append(key) + if len(missing) is 0: + return None + else: + return missing + class _FT(object): @@ -460,3 +474,15 @@ class _FT(object): } res.update(self.__other_parameters) return res + + def get_missing_keys(self, keys): + """Return missing keys on the spectrum""" + missing = [] + for key in keys: + if key not in self: + missing.append(key) + if len(missing) is 0: + return None + else: + return missing + diff --git a/xas/gui/XasObjectViewer.py b/xas/gui/XasObjectViewer.py index 3d318ba..858a756 100644 --- a/xas/gui/XasObjectViewer.py +++ b/xas/gui/XasObjectViewer.py @@ -39,6 +39,7 @@ from silx.utils.enum import Enum from silx.gui.widgets.FrameBrowser import HorizontalSliderWithBrowser from silx.gui import icons as silx_icons from silx.gui.colors import Colormap +from collections import namedtuple class ViewType(Enum): @@ -140,13 +141,30 @@ class _ExtendedSliderWithBrowser(HorizontalSliderWithBrowser): self.layout().insertWidget(0, qt.QLabel(str(name + ':'))) +class _CurveOperation(object): + def __init__(self, x, y, legend, yaxis=None, linestyle=None, symbol=None, + color=None, ylabel=None): + self.x = x + self.y = y + self.legend = legend + self.yaxis = yaxis + self.linestyle = linestyle + self.symbol = symbol + self.color = color + self.ylabel = ylabel + + class SpectrumViewer(qt.QMainWindow): def __init__(self, parent=None): + self._curveOperations = [] + """List of callaback to produce plot regarding the XASObject. + Callback function should return a _curve_operation""" qt.QMainWindow.__init__(self, parent) self.xas_obj = None self._plot = Plot1D(parent=self) self.setCentralWidget(self._plot) + # frame browsers dockWidget = qt.QDockWidget(parent=self) frameBrowsers = qt.QWidget(parent=self) @@ -169,8 +187,18 @@ class SpectrumViewer(qt.QMainWindow): self._dim1FrameBrowser.valueChanged.connect(self._updateSpectrumDisplayed) self._dim2FrameBrowser.valueChanged.connect(self._updateSpectrumDisplayed) + def addCurveOperation(self, callback): + """register an curve to display from Xasobject keys, and a legend""" + self._curveOperations.append(callback) + + def clearCurveOperations(self): + """Remove all defined curve operation""" + self._curveOperations.clear() + def setXasObject(self, xas_obj): self.xas_obj = xas_obj + assert self.xas_obj.dim1 >= 0 + assert self.xas_obj.dim2 >= 0 self._dim1FrameBrowser.setRange(0, self.xas_obj.dim1-1) self._dim2FrameBrowser.setRange(0, self.xas_obj.dim2-1) @@ -182,8 +210,26 @@ class SpectrumViewer(qt.QMainWindow): return dim1_index = self._dim1FrameBrowser.value() dim2_index = self._dim2FrameBrowser.value() + if dim1_index < 0 or dim2_index < 0: + return + + assert dim1_index >= 0 + assert dim2_index >= 0 + spectrum = self.xas_obj.getSpectrum(dim1_index, dim2_index) - self._plot.addCurve(x=spectrum.energy, y=spectrum.mu) + for operation in self._curveOperations: + res = operation(spectrum) + if res is not None: + assert isinstance(res, _CurveOperation) + self._plot.addCurve(x=res.x, + y=res.y, + legend=res.legend, + yaxis=res.yaxis, + linestyle=res.linestyle, + symbol=res.symbol, + color=res.color, + ylabel=res.ylabel, + ) def clear(self): self._plot.clear() -- GitLab From 89029fe6af9a3e0af34aba8142812b52b29f0c20 Mon Sep 17 00:00:00 2001 From: payno Date: Fri, 5 Jul 2019 11:32:26 +0200 Subject: [PATCH 6/6] [gui][XasObjViewer] add the possibility to display a specific map view from a key --- orangecontrib/xas/widgets/exafs.py | 17 ++++- orangecontrib/xas/widgets/ft.py | 17 ++++- orangecontrib/xas/widgets/k_weight.py | 20 +++++- orangecontrib/xas/widgets/normalization.py | 16 ++++- xas/core/types.py | 11 ++- xas/gui/XasObjectViewer.py | 82 ++++++++++++++++++---- 6 files changed, 137 insertions(+), 26 deletions(-) diff --git a/orangecontrib/xas/widgets/exafs.py b/orangecontrib/xas/widgets/exafs.py index 962348e..5a5e91b 100644 --- a/orangecontrib/xas/widgets/exafs.py +++ b/orangecontrib/xas/widgets/exafs.py @@ -95,7 +95,9 @@ def _exafs_knots_plot(spectrum): class ExafsWindow(qt.QMainWindow): def __init__(self, parent=None): qt.QMainWindow.__init__(self, parent) - self.xasObjViewer = XasObjectViewer() + mapKeys = ['Mu', 'EXAFS/EXAFSKValues', 'EXAFS/EXAFSSignal', + 'EXAFS/PostEdgeB'] + self.xasObjViewer = XasObjectViewer(mapKeys=mapKeys) self.xasObjViewer._spectrumView._plot.getXAxis().setLabel("K") self.xasObjViewer._spectrumView._plot.getYAxis().setLabel("Normalized Units") self.setCentralWidget(self.xasObjViewer) @@ -116,17 +118,28 @@ class ExafsWindow(qt.QMainWindow): self.legendDockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) self.addDockWidget(qt.Qt.RightDockWidgetArea, self.legendDockWidget) - self.setWindowFlags(qt.Qt.Widget) + # volume key selection + self.addDockWidget(qt.Qt.RightDockWidgetArea, + self.xasObjViewer._mapView.keySelectionDocker) # plot settings for ope in (_exafs_signal_plot, _exafs_postedge_plot, _exafs_knots_plot,): self.xasObjViewer._spectrumView.addCurveOperation(ope) + self.setWindowFlags(qt.Qt.Widget) + + # connect signal / slot + self.xasObjViewer.viewTypeChanged.connect(self._updateLegendView) + + # set up + self._updateLegendView() + def getNCurves(self): return len(self.xasObjViewer.getAllCurves()) def _updateLegendView(self): self.legendDockWidget.setVisible(self.xasObjViewer.getViewType() is ViewType.spectrum) + self.xasObjViewer._mapView.keySelectionDocker.setVisible(self.xasObjViewer.getViewType() is ViewType.map) class ExafsOW(OWWidget): diff --git a/orangecontrib/xas/widgets/ft.py b/orangecontrib/xas/widgets/ft.py index 4c68f9c..fe6e87a 100644 --- a/orangecontrib/xas/widgets/ft.py +++ b/orangecontrib/xas/widgets/ft.py @@ -70,7 +70,7 @@ class FTWindow(qt.QMainWindow): qt.QMainWindow.__init__(self, parent) # xas object viewer - self.xasObjViewer = XasObjectViewer() + self.xasObjViewer = XasObjectViewer(mapKeys=['Mu']) self.xasObjViewer._spectrumView._plot.getXAxis().setLabel("R (Angstrom)") self.xasObjViewer._spectrumView._plot.getYAxis().setLabel("Arbitrary Units") self.setCentralWidget(self.xasObjViewer) @@ -82,7 +82,6 @@ class FTWindow(qt.QMainWindow): self.addDockWidget(qt.Qt.RightDockWidgetArea, dockWidget) dockWidget.setAllowedAreas(qt.Qt.RightDockWidgetArea | qt.Qt.LeftDockWidgetArea) dockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) - self.setWindowFlags(qt.Qt.Widget) # legend selector self.legendDockWidget = LegendSelector.LegendsDockWidget(parent=self, @@ -91,15 +90,29 @@ class FTWindow(qt.QMainWindow): self.legendDockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) self.addDockWidget(qt.Qt.RightDockWidgetArea, self.legendDockWidget) + # volume key selection + self.addDockWidget(qt.Qt.RightDockWidgetArea, + self.xasObjViewer._mapView.keySelectionDocker) + # plot settings for ope in (_ft_intensity_plot, _ft_imaginary_plot): self.xasObjViewer._spectrumView.addCurveOperation(ope) + self.setWindowFlags(qt.Qt.Widget) + + # connect signal / slot + self.xasObjViewer.viewTypeChanged.connect(self._updateLegendView) + + # set up + self._updateLegendView() + def getNCurves(self): return len(self.plot.getAllCurves()) def _updateLegendView(self): self.legendDockWidget.setVisible(self.xasObjViewer.getViewType() is ViewType.spectrum) + self.xasObjViewer._mapView.keySelectionDocker.setVisible(self.xasObjViewer.getViewType() is ViewType.map) + class FTOW(OWWidget): """ diff --git a/orangecontrib/xas/widgets/k_weight.py b/orangecontrib/xas/widgets/k_weight.py index 36268f9..a2addb9 100644 --- a/orangecontrib/xas/widgets/k_weight.py +++ b/orangecontrib/xas/widgets/k_weight.py @@ -35,7 +35,7 @@ from Orange.widgets.settings import Setting from silx.gui import qt from xas.core.types import XASObject, Spectrum import xas.core.process.k_weight -from xas.gui.XasObjectViewer import XasObjectViewer, _CurveOperation +from xas.gui.XasObjectViewer import XasObjectViewer, _CurveOperation, ViewType import logging _logger = logging.getLogger(__file__) @@ -85,7 +85,7 @@ class KWeightWindow(qt.QMainWindow): def __init__(self, parent=None): qt.QMainWindow.__init__(self, parent) # xas object viewer - self.xasObjViewer = XasObjectViewer() + self.xasObjViewer = XasObjectViewer(mapKeys=['Mu']) self.xasObjViewer._spectrumView._plot.getXAxis().setLabel("K") self.setCentralWidget(self.xasObjViewer) @@ -109,15 +109,29 @@ class KWeightWindow(qt.QMainWindow): self.legendDockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) self.addDockWidget(qt.Qt.RightDockWidgetArea, self.legendDockWidget) - self.setWindowFlags(qt.Qt.Widget) + # volume key selection + self.addDockWidget(qt.Qt.RightDockWidgetArea, + self.xasObjViewer._mapView.keySelectionDocker) # plot settings for ope in (_normalized_exafs, _ft_window_plot): self.xasObjViewer._spectrumView.addCurveOperation(ope) + self.setWindowFlags(qt.Qt.Widget) + + # connect signal / slot + self.xasObjViewer.viewTypeChanged.connect(self._updateLegendView) + + # set up + self._updateLegendView() + def getNCurves(self): return len(self.plot.getAllCurves()) + def _updateLegendView(self, *args, **kwargs): + self.legendDockWidget.setVisible(self.xasObjViewer.getViewType() is ViewType.spectrum) + self.xasObjViewer._mapView.keySelectionDocker.setVisible(self.xasObjViewer.getViewType() is ViewType.map) + class KWeightOW(OWWidget): """ diff --git a/orangecontrib/xas/widgets/normalization.py b/orangecontrib/xas/widgets/normalization.py index c5c88a5..e8765b2 100644 --- a/orangecontrib/xas/widgets/normalization.py +++ b/orangecontrib/xas/widgets/normalization.py @@ -105,7 +105,8 @@ class NormalizationWindow(qt.QMainWindow): qt.QMainWindow.__init__(self, parent) # xas object viewer - self.xasObjViewer = XasObjectViewer() + mapKeys = ['Mu', 'NormalizedMu', 'NormalizedSignal', 'NormalizedBackground'] + self.xasObjViewer = XasObjectViewer(mapKeys=mapKeys) self.xasObjViewer._spectrumView._plot.getXAxis().setLabel("Energy (eV)") self.xasObjViewer._spectrumView._plot.getYAxis().setLabel("Absorption (a.u.)") self.setCentralWidget(self.xasObjViewer) @@ -125,18 +126,29 @@ class NormalizationWindow(qt.QMainWindow): self.legendDockWidget.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) self.addDockWidget(qt.Qt.RightDockWidgetArea, self.legendDockWidget) - self.setWindowFlags(qt.Qt.Widget) + # volume key selection + self.addDockWidget(qt.Qt.RightDockWidgetArea, + self.xasObjViewer._mapView.keySelectionDocker) # plot settings for ope in (_spectrum_plot, _normalized_spectrum_plot, _post_plot, _pre_plot): self.xasObjViewer._spectrumView.addCurveOperation(ope) + self.setWindowFlags(qt.Qt.Widget) + + # connect signal / slot + self.xasObjViewer.viewTypeChanged.connect(self._updateLegendView) + + # set up + self._updateLegendView() + def getNCurves(self): return len(self.xasObjViewer._spectrumView._plot.getAllCurves()) def _updateLegendView(self): self.legendDockWidget.setVisible(self.xasObjViewer.getViewType() is ViewType.spectrum) + self.xasObjViewer._mapView.keySelectionDocker.setVisible(self.xasObjViewer.getViewType() is ViewType.map) class NormalizationOW(OWWidget): diff --git a/xas/core/types.py b/xas/core/types.py index 3ca5e59..80edfc9 100644 --- a/xas/core/types.py +++ b/xas/core/types.py @@ -127,14 +127,14 @@ class XASObject(object): """convert the XAS object to a dict""" return { 'configuration': self.configuration, - 'spectra': self._spectra_volume(self.spectra, self.dim1, self.dim2), + 'spectra': self._spectra_volume(self.spectra, 'Mu', self.dim1, self.dim2), 'energy': self.energy, 'dim1': self.dim1, 'dim2': self.dim2, } @staticmethod - def _spectra_volume(spectra, dim_1, dim_2): + def _spectra_volume(spectra, key, dim_1, dim_2): """Convert a list of spectra (mu) to a numpy array. ..note: only convert raw data for now""" if len(spectra) is 0: @@ -142,7 +142,12 @@ class XASObject(object): else: array = numpy.zeros((len(spectra[0].energy), dim_1 * dim_2)) for i_spectrum, spectrum in enumerate(spectra): - array[:, i_spectrum] = spectrum.mu + subkeys = key.split('/') + value = spectrum[subkeys[0]] + for subkey in subkeys[1:]: + value = value[subkey] + + array[:, i_spectrum] = value return array.reshape((len(spectra[0].energy), dim_1, dim_2)) diff --git a/xas/gui/XasObjectViewer.py b/xas/gui/XasObjectViewer.py index 858a756..e3024f5 100644 --- a/xas/gui/XasObjectViewer.py +++ b/xas/gui/XasObjectViewer.py @@ -65,7 +65,11 @@ class _MapViewAction(qt.QAction): class XasObjectViewer(qt.QMainWindow): """Viewer dedicated to view a XAS object""" - def __init__(self, parent=None): + + viewTypeChanged = qt.Signal() + """emitted when the view type change""" + + def __init__(self, parent=None, mapKeys=None): qt.QMainWindow.__init__(self, parent) self.setWindowFlags(qt.Qt.Widget) @@ -74,7 +78,7 @@ class XasObjectViewer(qt.QMainWindow): self._mainWidget.setLayout(qt.QVBoxLayout()) self.setCentralWidget(self._mainWidget) # map view - self._mapView = MapViewer(parent=self) + self._mapView = MapViewer(parent=self, keys=mapKeys) self._mainWidget.layout().addWidget(self._mapView) # spectrum view @@ -107,6 +111,7 @@ class XasObjectViewer(qt.QMainWindow): def _updateView(self, *arg, **kwargs): self._mapView.setVisible(self.getViewType() is ViewType.map) self._spectrumView.setVisible(self.getViewType() is ViewType.spectrum) + self.viewTypeChanged.emit() def getViewType(self): if self._mapViewAction.isChecked(): @@ -118,21 +123,70 @@ class XasObjectViewer(qt.QMainWindow): self._mapView.clear() self._spectrumView.clear() - # set the map view - spectra_volume = XASObject._spectra_volume(xas_obj.spectra, - xas_obj.dim1, - xas_obj.dim2) - self._mapView.setStack(spectra_volume) - - # set spectrum view + self._mapView.setXasObject(xas_obj) self._spectrumView.setXasObject(xas_obj) -class MapViewer(StackViewMainWindow): - def __init__(self, parent=None): - StackViewMainWindow.__init__(self, parent=parent) - self.setKeepDataAspectRatio(True) - self.setColormap(Colormap(name='temperature')) +class MapViewer(qt.QWidget): + def __init__(self, parent=None, keys=None): + """ + + :param parent: + :param keys: volume keys to display for the xasObject (Mu, + NormalizedMu...) + """ + assert keys is not None + self._xasObj = None + qt.QWidget.__init__(self, parent=parent) + self.setLayout(qt.QVBoxLayout()) + self._mainWindow = StackViewMainWindow(parent=parent) + self.layout().addWidget(self._mainWindow) + self.layout().setContentsMargins(0, 0, 0, 0) + self.layout().setSpacing(0.0) + + self._mainWindow.setKeepDataAspectRatio(True) + self._mainWindow.setColormap(Colormap(name='temperature')) + + # define the keys combobox + self._keyWidget = qt.QWidget(parent=self) + self._keyWidget.setLayout(qt.QHBoxLayout()) + self._keyComboBox = qt.QComboBox(parent=self._keyWidget) + for key in keys: + self._keyComboBox.addItem(key) + self._keyWidget.layout().addWidget(qt.QLabel('view: ')) + self._keyWidget.layout().addWidget(self._keyComboBox) + self.keySelectionDocker = qt.QDockWidget(parent=self) + self.keySelectionDocker.setContentsMargins(0, 0, 0, 0) + self._keyWidget.layout().setContentsMargins(0, 0, 0, 0) + self._keyWidget.layout().setSpacing(0.0) + self.keySelectionDocker.setWidget(self._keyWidget) + # self._mainWindow.addDockWidget(qt.Qt.TopDockWidgetArea, dockWidget) + self.keySelectionDocker.setAllowedAreas(qt.Qt.TopDockWidgetArea) + self.keySelectionDocker.setFeatures(qt.QDockWidget.NoDockWidgetFeatures) + + # connect signal / slot + self._keyComboBox.currentTextChanged.connect(self._updateView) + + def clear(self): + self._mainWindow.clear() + + def getActiveKey(self): + return self._keyComboBox.currentText() + + def setXasObject(self, xas_obj): + self._xasObj = xas_obj + self._updateView() + + def _updateView(self, *args, **kwargs): + if self._xasObj is None: + return + # set the map view + spectra_volume = XASObject._spectra_volume(spectra=self._xasObj.spectra, + dim_1=self._xasObj.dim1, + dim_2=self._xasObj.dim2, + key=self.getActiveKey()) + self._mainWindow.setStack(spectra_volume) + class _ExtendedSliderWithBrowser(HorizontalSliderWithBrowser): -- GitLab