diff --git a/src/est/core/io.py b/src/est/core/io.py index 7d6c5f11e233ef12d712abde5d022a84798e199e..fed17229b2d862023d96ae801e253ec44810d76b 100644 --- a/src/est/core/io.py +++ b/src/est/core/io.py @@ -64,6 +64,8 @@ def read_from_url( I2_url: DataUrl | None = None, mu_ref_url: DataUrl | None = None, is_concatenated: bool = False, + skip_concatenated_n_points: int = 0, + skip_concatenated_n_spectra: int = 0, timeout: float = settings.DEFAULT_READ_TIMEOUT, ) -> XASObject: """ @@ -89,6 +91,8 @@ def read_from_url( I2_url=I2_url, mu_ref_url=mu_ref_url, is_concatenated=is_concatenated, + skip_concatenated_n_points=skip_concatenated_n_points, + skip_concatenated_n_spectra=skip_concatenated_n_spectra, ) return read_from_input_information(input_information, timeout=timeout) diff --git a/src/est/core/process/plotspectrumdata.py b/src/est/core/process/plotspectrumdata.py index fd6a67aadc623a8edfafb759013893c036451f85..50f0a61ddbaf210bcff796a60960d35b8e88647e 100644 --- a/src/est/core/process/plotspectrumdata.py +++ b/src/est/core/process/plotspectrumdata.py @@ -106,7 +106,7 @@ class PlotSpectrumData( output_names=["plot_data"], ): def run(self): - spectrum = next(iter(self.inputs.xas_obj.spectra.data.flat)) - self.outputs.plot_data = process_plotspectrumdata( - spectrum, plot_names=self.inputs.plot_names - ) + self.outputs.plot_data = [ + process_plotspectrumdata(spectrum, plot_names=self.inputs.plot_names) + for spectrum in self.inputs.xas_obj.spectra.data.flat + ] diff --git a/src/est/core/types/spectra.py b/src/est/core/types/spectra.py index b23047417abee2b414cb48dda0a18d84960be5c3..17362e7ab4b79584b79ba6acc0a22479965e7ac2 100644 --- a/src/est/core/types/spectra.py +++ b/src/est/core/types/spectra.py @@ -141,55 +141,54 @@ class Spectra: if len(self.data.flat) == 0: return None - else: - def allocate_array(relative_to_len): - if relative_to_len is not None: - return numpy.zeros((relative_to_len, len(self.data.flat))) - else: - return numpy.zeros(len(self.data.flat)) - - key = str(data_info) - array = None - for i_spectrum, spectrum in enumerate(self.data.flat): - try: - if "." in key: - subkeys = key.split(".") - key_ = subkeys[-1] - subkeys = subkeys[:-1] - value = get_param(spectrum, subkeys[0]) - for subkey in subkeys[1:]: - value = get_param(value, subkey) - value = get_param(value, key_) - else: - value = get_param(spectrum, key) - except Exception: - _logger.info("fail to access to {}".format(key)) - break + def allocate_array(relative_to_len): + if relative_to_len is not None: + return numpy.zeros((relative_to_len, len(self.data.flat))) + else: + return numpy.zeros(len(self.data.flat)) + + key = str(data_info) + array = None + for i_spectrum, spectrum in enumerate(self.data.flat): + try: + if "." in key: + subkeys = key.split(".") + key_ = subkeys[-1] + subkeys = subkeys[:-1] + value = get_param(spectrum, subkeys[0]) + for subkey in subkeys[1:]: + value = get_param(value, subkey) + value = get_param(value, key_) else: - if isinstance(value, pint.Quantity): - value = value.m_as(ur.eV) - if symboltable is not None and isinstance(value, symboltable.Group): - _logger.info("pass larch details, not managed for now") - continue - # create array if necessary - if array is None: - if relative_to is None: - array = allocate_array(relative_to_len=None) - elif value is None: - array = allocate_array(relative_to_len=None) - else: - array = allocate_array(relative_to_len=len(value)) - if relative_to is not None: - array[:, i_spectrum] = value - else: - array[i_spectrum] = value - shape = list(self.data.shape) - shape.insert(0, -1) + value = get_param(spectrum, key) + except Exception: + _logger.info("fail to access to {}".format(key)) + break + + if isinstance(value, pint.Quantity): + value = value.m_as(ur.eV) + if symboltable is not None and isinstance(value, symboltable.Group): + _logger.info("pass larch details, not managed for now") + continue + if array is None: - return array + if relative_to is None or value is None: + array = allocate_array(relative_to_len=None) + else: + array = allocate_array(relative_to_len=len(value)) + + if relative_to is not None: + array[:, i_spectrum] = value else: - return array.reshape(shape) + array[i_spectrum] = value + + if array is None: + return array + + shape = list(self.data.shape) + shape.insert(0, -1) + return array.reshape(shape) def keys(self) -> tuple: """ diff --git a/src/est/core/types/xasobject.py b/src/est/core/types/xasobject.py index 118bc1e0015810367019a6dd8d5ff66cf8fab25e..4de131bd4e60e9c1b31adcdd32d994059a4ecd70 100644 --- a/src/est/core/types/xasobject.py +++ b/src/est/core/types/xasobject.py @@ -180,7 +180,7 @@ class XASObject: return res def absorbed_beam(self) -> numpy.ndarray: - return self.spectra.map_to(data_info="mu") + return self.spectra.map_to("mu") def load_from_dict(self, ddict: dict) -> XASObject: """load XAS values from a dict""" diff --git a/src/est/core/utils/converter.py b/src/est/core/utils/converter.py index 2e811d07c45339980c5aa5b086f1ea6c360aabb6..0d3deced3084ded0f14068fda65ff5f593143f6b 100644 --- a/src/est/core/utils/converter.py +++ b/src/est/core/utils/converter.py @@ -32,7 +32,7 @@ class Converter: ) # TODO: prendre normalized_mu and normalized_energy if exists, # otherwise take mu and energy... - spectra = xas_object.spectra.map_to(data_info="mu") + spectra = xas_object.spectra.map_to("mu") # invert dimensions and axis to fit spectroscopy add-on X = spectra.reshape((spectra.shape[0], -1)) X = numpy.swapaxes(X, 0, 1) diff --git a/src/est/gui/XasObjectViewer.py b/src/est/gui/XasObjectViewer.py index 649985d29341dca07696c9de44185f321994a204..c6667826e5f3144ca2d8bf0a6b3155773c94a4be 100644 --- a/src/est/gui/XasObjectViewer.py +++ b/src/est/gui/XasObjectViewer.py @@ -212,9 +212,7 @@ class MapViewer(qt.QWidget): if self._xasObj is None: return # set the map view - spectra_volume = self._xasObj.spectra.map_to( - data_info=self.getActiveKey(), - ) + spectra_volume = self._xasObj.spectra.map_to(self.getActiveKey()) self._mainWindow.setStack(spectra_volume) def getPlot(self): diff --git a/src/est/gui/xas_object_definition/dialog.py b/src/est/gui/xas_object_definition/dialog.py index 3a2d3f56e4e128f6ad8538a9f45312feffb99a60..3bbd35fbb30090ec127873b70d0d908c71233608 100644 --- a/src/est/gui/xas_object_definition/dialog.py +++ b/src/est/gui/xas_object_definition/dialog.py @@ -196,6 +196,8 @@ class XASObjectDialog(qt.QWidget): I2_url=advanceHDF5Info.getI2Url(), mu_ref_url=advanceHDF5Info.getMuRefUrl(), is_concatenated=self._h5Dialog.isSpectraConcatenated(), + skip_concatenated_n_points=self._h5Dialog.getSkipConcatenatedNPoints(), + skip_concatenated_n_spectra=self._h5Dialog.getSkipConcatenatedNSpectra(), ) def getChannelUrl(self): @@ -210,3 +212,9 @@ class XASObjectDialog(qt.QWidget): def setConcatenatedSpectra(self, value: bool): self._h5Dialog.setConcatenatedSpectra(value) + + def setSkipConcatenatedNPoints(self, value: int): + self._h5Dialog.setSkipConcatenatedNPoints(value) + + def setSkipConcatenatedNSpectra(self, value: int): + self._h5Dialog.setSkipConcatenatedNSpectra(value) diff --git a/src/est/gui/xas_object_definition/hdf5.py b/src/est/gui/xas_object_definition/hdf5.py index b93f8536545e10c5f9ff6332505c5c8c04368a11..6984c8c88571ca18bcc73c6f6fa4cd804f4f1c14 100644 --- a/src/est/gui/xas_object_definition/hdf5.py +++ b/src/est/gui/xas_object_definition/hdf5.py @@ -72,6 +72,18 @@ class XASObjectFromH5(qt.QTabWidget): def setConcatenatedSpectra(self, value: bool): self._basicInformation._concatenatedCheckbox.setChecked(value) + def getSkipConcatenatedNPoints(self): + return self._basicInformation._skipConcatenatedNPoints.value() + + def setSkipConcatenatedNPoints(self, value: int): + self._basicInformation._skipConcatenatedNPoints.setValue(value) + + def getSkipConcatenatedNSpectra(self): + return self._basicInformation._skipConcatenatedNSpectra.value() + + def setSkipConcatenatedNSpectra(self, value: int): + self._basicInformation._skipConcatenatedNSpectra.setValue(value) + class _MandatoryXASObjectInfo(qt.QWidget): """Widget containing mandatory information""" @@ -89,15 +101,42 @@ class _MandatoryXASObjectInfo(qt.QWidget): self._bufWidget = qt.QWidget(parent=self) self._bufWidget.setLayout(qt.QHBoxLayout()) + + # Spacer to push elements spacer = qt.QWidget(parent=self) spacer.setSizePolicy(qt.QSizePolicy.Expanding, qt.QSizePolicy.Minimum) self._bufWidget.layout().addWidget(spacer) + + # Dimension selection widget self._dimensionSelection = _SpectraDimensions(parent=self._bufWidget) self._bufWidget.layout().addWidget(self._dimensionSelection) + + # Concatenated checkbox self._concatenatedCheckbox = qt.QCheckBox( "Concatenated spectra", parent=self._bufWidget ) self._bufWidget.layout().addWidget(self._concatenatedCheckbox) + + # Labels and spin boxes + self._concatenatedWidget = qt.QWidget(parent=self) + self._concatenatedWidget.setLayout(qt.QVBoxLayout()) + + label1 = qt.QLabel("Skip first N concatenated spectra", parent=self._bufWidget) + self._skipConcatenatedNSpectra = qt.QSpinBox(parent=self._bufWidget) + self._concatenatedWidget.layout().addWidget(label1) + self._concatenatedWidget.layout().addWidget(self._skipConcatenatedNSpectra) + + label2 = qt.QLabel( + "Skip N first and last points of concatenated spectra", + parent=self._bufWidget, + ) + self._skipConcatenatedNPoints = qt.QSpinBox(parent=self._bufWidget) + self._concatenatedWidget.layout().addWidget(label2) + self._concatenatedWidget.layout().addWidget(self._skipConcatenatedNPoints) + + self._bufWidget.layout().addWidget(self._concatenatedWidget) + + # Add buffer widget to main layout self.layout().addWidget(self._bufWidget, 1, 1) # channel/energy url @@ -122,19 +161,29 @@ class _MandatoryXASObjectInfo(qt.QWidget): self._configSelector._qLineEdit.editingFinished.connect(self._editingIsFinished) self._dimensionSelection.sigDimensionChanged.connect(self._editingIsFinished) self._energyUnit.currentIndexChanged.connect(self._editingIsFinished) - self._concatenatedCheckbox.clicked.connect(self._onBidimensionalCheckboxChange) + self._concatenatedCheckbox.stateChanged.connect( + self._onConcatenatedCheckboxChange + ) + self._skipConcatenatedNPoints.valueChanged.connect(self._editingIsFinished) + self._skipConcatenatedNSpectra.valueChanged.connect(self._editingIsFinished) # expose API self.setDimensions = self._dimensionSelection.setDimensions self.getDimensions = self._dimensionSelection.getDimensions - def _onBidimensionalCheckboxChange(self): - self._dimensionSelection.setDisabled(self._concatenatedCheckbox.isChecked()) + self._syncConcatenatedCheckbox() + + def _syncConcatenatedCheckbox(self): + concatenated = self._concatenatedCheckbox.isChecked() + self._concatenatedWidget.setEnabled(concatenated) + self._dimensionSelection.setDisabled(concatenated) + + def _onConcatenatedCheckboxChange(self): + self._syncConcatenatedCheckbox() self._editingIsFinished() def getSpectraUrl(self): """ - :return: the DataUrl of the spectra :rtype: DataUrl """ @@ -142,7 +191,6 @@ class _MandatoryXASObjectInfo(qt.QWidget): def getEnergyUrl(self): """ - :return: the DataUrl of energy / channel :rtype: DataUrl """ diff --git a/src/est/io/concatenated.py b/src/est/io/concatenated.py index 0e7f8a36bae54978efe81ed6920fd44aa524d4bf..156f9bac5a8030763dbe60dddc818fd127a94305 100644 --- a/src/est/io/concatenated.py +++ b/src/est/io/concatenated.py @@ -1,5 +1,10 @@ from __future__ import annotations + +from typing import Tuple, Dict, Any + +import pint import numpy +import numpy.ma from est import settings from est.io.information import InputInformation @@ -10,14 +15,14 @@ from est.core.monotonic import piecewise_monotonic_interpolation_values def read_concatenated_xas( information: InputInformation, timeout: float = settings.DEFAULT_READ_TIMEOUT -): +) -> Tuple[numpy.ndarray, pint.Quantity, Dict[str, Any]]: """ Method to read spectra acquired with the energy ramping up and down, with any number of repetitions. - When the scan is uni-directional, the ramp is always up. When the scan is concatenated the ramp is + When the scan is uni-directional, the ramp is always up. When the scan is bi-directional the ramp is alternating up and down. - The spectra are then interpolated to produce a 3D spectra (nb_energy_pts, nb_of_ramps, 1) + The spectra are then interpolated to produce a 3D data aray (nb_energy_pts, nb_of_ramps, 1). Limitations: the original spectra and energy datasets must be 1D. """ @@ -30,18 +35,30 @@ def read_concatenated_xas( config = None ramp_slices = split_piecewise_monotonic(raw_energy) + energy = piecewise_monotonic_interpolation_values(raw_energy, ramp_slices) - interpolated_spectra = numpy.zeros( + if information.skip_concatenated_n_spectra: + ramp_slices = ramp_slices[information.skip_concatenated_n_spectra :] + + interpolated_spectra = numpy.ma.masked_all( (len(energy), len(ramp_slices), 1), dtype=raw_spectra.dtype ) for i, ramp_slice in enumerate(ramp_slices): - interpolated_spectra[:, i, 0] = numpy.interp( + raw_energy_i = raw_energy[ramp_slice] + raw_spectrum_i = raw_spectra[ramp_slice] + if information.skip_concatenated_n_points: + raw_spectrum_i[: information.skip_concatenated_n_points] = numpy.nan + raw_spectrum_i[-information.skip_concatenated_n_points :] = numpy.nan + spectrum_i = numpy.interp( energy, - raw_energy[ramp_slice], - raw_spectra[ramp_slice], + raw_energy_i, + raw_spectrum_i, left=numpy.nan, right=numpy.nan, ) + interpolated_spectra[:, i, 0] = numpy.ma.masked_array( + spectrum_i, mask=~numpy.isfinite(spectrum_i) + ) return interpolated_spectra, energy * information.energy_unit, config diff --git a/src/est/io/information.py b/src/est/io/information.py index c33d0df6b3445a84e25daee157d4be2e2faeb724..b84493896418f880b8de06720b28e02636228990 100644 --- a/src/est/io/information.py +++ b/src/est/io/information.py @@ -23,6 +23,8 @@ class InputInformation: I2_url: DataUrl | None = None, mu_ref_url: DataUrl | None = None, is_concatenated: bool = False, + skip_concatenated_n_points: int = 0, + skip_concatenated_n_spectra: int = 0, ): # main information self.spectra_url = spectra_url @@ -31,6 +33,8 @@ class InputInformation: self.dimensions = dimensions_mod.parse_dimensions(dimensions) self.energy_unit = energy_unit self.is_concatenated = is_concatenated + self.skip_concatenated_n_points = skip_concatenated_n_points + self.skip_concatenated_n_spectra = skip_concatenated_n_spectra # "fancy information" self.I0_url = I0_url @@ -56,6 +60,8 @@ class InputInformation: "I2_url": dump_url(self.I2_url), "mu_ref_url": dump_url(self.mu_ref_url), "is_concatenated": self.is_concatenated, + "skip_concatenated_n_points": self.skip_concatenated_n_points, + "skip_concatenated_n_spectra": self.skip_concatenated_n_spectra, } @staticmethod @@ -79,4 +85,6 @@ class InputInformation: I2_url=load_url("I2_url"), mu_ref_url=load_url("mu_ref_url"), is_concatenated=ddict.get("is_concatenated", False), + skip_concatenated_n_points=ddict.get("skip_concatenated_n_points", 0), + skip_concatenated_n_spectra=ddict.get("skip_concatenated_n_spectra", 0), ) diff --git a/src/est/tests/test_plotspectrumdata.py b/src/est/tests/test_plotspectrumdata.py index e17d5d3d8ea5c16eda779d4483e758aa7cb8e337..2805b96d51332e7da40a80e316ba345e8d2196c8 100644 --- a/src/est/tests/test_plotspectrumdata.py +++ b/src/est/tests/test_plotspectrumdata.py @@ -66,8 +66,12 @@ def test_larch_plotspectrumdata(filename_cu_from_pymca): ) result = execute_graph(_LARCH_WORKFLOW, inputs=inputs) - assert len(result["plot_data"]) == 7 - for name, data in result["plot_data"].items(): + assert len(result["plot_data"]) == 1 + + scan_data = result["plot_data"][0] + + assert len(scan_data) == 7 + for name, data in scan_data.items(): if name in ("chi", "chi_weighted_k"): n = 324 elif name == "ft_mag": @@ -79,13 +83,13 @@ def test_larch_plotspectrumdata(filename_cu_from_pymca): assert data["xlabel"] assert data["ylabel"] - assert "k^2" in result["plot_data"]["chi_weighted_k"]["ylabel"] + assert "k^2" in scan_data["chi_weighted_k"]["ylabel"] if False: import matplotlib.pyplot as plt fig, axs = plt.subplots(2, 3, figsize=(10, 10), constrained_layout=True) - for data, ax in zip(result["plot_data"].values(), axs.flatten()): + for data, ax in zip(scan_data.values(), axs.flatten()): ax.plot(data["x"], data["y"]) ax.set_xlabel(data["xlabel"]) ax.set_ylabel(data["ylabel"]) diff --git a/src/est/tests/test_roi.py b/src/est/tests/test_roi.py index 47b710e9dc5066e2ad744934a337abf7cdcebdf9..750dc2f2ea6e552ebde72315eb8203ddc568811e 100644 --- a/src/est/tests/test_roi.py +++ b/src/est/tests/test_roi.py @@ -22,7 +22,7 @@ def test_roi(tmpdir): dimensions=(2, 1, 0), ) - original_spectra = xas_obj.spectra.map_to(data_info="mu").copy() + original_spectra = xas_obj.spectra.map_to("mu").copy() assert original_spectra.shape == (16, 100, 30) res_xas_obj = xas_roi( xas_obj, @@ -30,6 +30,6 @@ def test_roi(tmpdir): roi_size=(10, 20), ) assert res_xas_obj.n_spectrum == 20 * 10 - reduces_spectra = res_xas_obj.spectra.map_to(data_info="mu").copy() + reduces_spectra = res_xas_obj.spectra.map_to("mu").copy() assert reduces_spectra.shape == (16, 20, 10) numpy.testing.assert_array_equal(original_spectra[:, 50:70, 20:30], reduces_spectra) diff --git a/src/est/tests/test_types.py b/src/est/tests/test_types.py index 5bc0f4f40687440360acc6e1e1773b4d9ffd8800..9fdc0ef8fa36da3add90fac4caa883d33fbdd9fb 100644 --- a/src/est/tests/test_types.py +++ b/src/est/tests/test_types.py @@ -75,7 +75,7 @@ def test_create_from_several_spectrums(tmpdir): ) obj2 = XASObject.from_dict(ddict) assert xas_obj.n_spectrum == obj2.n_spectrum - obj2_mu_spectra = obj2.spectra.map_to(data_info="mu") + obj2_mu_spectra = obj2.spectra.map_to("mu") numpy.testing.assert_array_equal(original_spectra, obj2_mu_spectra) assert obj2 == xas_obj diff --git a/src/est/tests/test_xas_object.py b/src/est/tests/test_xas_object.py index ddba00414b501d0f5ebff0b4e7c6430de077b757..79f1116c7e4fee8a621b45cfa1ea435309e694c9 100644 --- a/src/est/tests/test_xas_object.py +++ b/src/est/tests/test_xas_object.py @@ -66,7 +66,7 @@ def test_create_from_several_spectrums(serialize_data): ) obj2 = XASObject.from_dict(ddict) assert xas_obj.n_spectrum == obj2.n_spectrum - obj2_mu_spectra = obj2.spectra.map_to(data_info="mu") + obj2_mu_spectra = obj2.spectra.map_to("mu") numpy.testing.assert_array_equal(original_spectra, obj2_mu_spectra) assert obj2 == xas_obj diff --git a/src/orangecontrib/est/widgets/utils/xas_input.py b/src/orangecontrib/est/widgets/utils/xas_input.py index 53cefd60146af629fa5a10c4a0729a4b36a7ac32..37dc07ad22f38ba2cbcba0c8e8b53e4c67b1b79b 100644 --- a/src/orangecontrib/est/widgets/utils/xas_input.py +++ b/src/orangecontrib/est/widgets/utils/xas_input.py @@ -80,10 +80,15 @@ class XASInputOW(OWEwoksWidgetNoThread, ewokstaskclass=ReadXasObject): self._inputDialog.setEnergyUrl(input_information.channel_url) if input_information.mu_ref_url is not None: advanceHDF5Info.setMuRefUrl(input_information.mu_ref_url) - if input_information.is_concatenated is not None: - self._inputDialog.getMainWindow().setConcatenatedSpectra( - input_information.is_concatenated - ) + self._inputDialog.getMainWindow().setSkipConcatenatedNPoints( + input_information.skip_concatenated_n_points + ) + self._inputDialog.getMainWindow().setSkipConcatenatedNSpectra( + input_information.skip_concatenated_n_spectra + ) + self._inputDialog.getMainWindow().setConcatenatedSpectra( + input_information.is_concatenated + ) else: input_type = est.io.InputType.ascii_spectrum self._inputDialog.setAsciiFile(input_information.spectra_url.file_path())