Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • workflow/ewoksapps/est
  • rovezzi/est
2 results
Show changes
Commits on Source (111)
[flake8]
extend-ignore = E203,E402,E501,E701,W503
max-line-length = 88
exclude = [".eggs"]
[flake8_nb]
ignore = E501, E203, W503, E402
max-line-length = 88
......@@ -7,6 +7,7 @@
!.gitignore
!.gitlab-ci.yml
!.readthedocs.yaml
!.flake8
# Byte / compiled / optimized
*.py[cod]
......
include:
- project: workflow/ewoksadmin/ewoksci
- project: dau/ci/pyci
file: .gitlab-ci-template.yml
variables:
WARNING1: "ignore::DeprecationWarning:silx.resources" # Fix in https://github.com/silx-kit/silx/pull/4078
WARNING2: "ignore::DeprecationWarning:pkg_resources" # Distribution.activate
WARNING3: "ignore::DeprecationWarning:Orange.util" # Fix in https://github.com/biolab/orange3/commit/671f184be00169e0a5196e9871ee0156dd35c19f
WARNING4: "ignore::DeprecationWarning:orangewidget.gui" # Fix in https://github.com/biolab/orange-widget-base/commit/2c6b4cfbba035f7f89bb886a02d8330f2710ce7d
WARNING5: "ignore::DeprecationWarning:jupyter_client.session"
WARNING6: "ignore::DeprecationWarning:silx.gui.widgets.ElidedLabel"
IGNORE_WARNINGS: "-W ${WARNING1} -W ${WARNING2} -W ${WARNING3} -W ${WARNING4} -W ${WARNING5} -W ${WARNING6}"
WARNING1: 'ignore::DeprecationWarning:silx.resources' # Fix in https://github.com/silx-kit/silx/pull/4078
WARNING2: 'ignore::DeprecationWarning:pkg_resources' # Distribution.activate
WARNING3: 'ignore::DeprecationWarning:Orange.util' # Fix in https://github.com/biolab/orange3/commit/671f184be00169e0a5196e9871ee0156dd35c19f
WARNING4: 'ignore::DeprecationWarning:orangewidget.gui' # Fix in https://github.com/biolab/orange-widget-base/commit/2c6b4cfbba035f7f89bb886a02d8330f2710ce7d
WARNING5: 'ignore::DeprecationWarning:jupyter_client.session'
WARNING6: 'ignore::DeprecationWarning:silx.gui.widgets.ElidedLabel'
WARNING7: 'ignore::DeprecationWarning:orangecanvas.scheme.readwrite' # https://github.com/biolab/orange-canvas-core/issues/304
WARNING8: "ignore::DeprecationWarning:orangecanvas.utils.localization"
WARNING9: "ignore::DeprecationWarning:orangecanvas.utils.localization.si"
IGNORE_WARNINGS: '-W ${WARNING1} -W ${WARNING2} -W ${WARNING3} -W ${WARNING4} -W ${WARNING5} -W ${WARNING6} -W ${WARNING7} -W ${WARNING8} -W ${WARNING9}'
flake8_nb:
extends: .flake8_nb
......@@ -18,48 +21,69 @@ test-3.8:
extends: .test-3.8_glx
timeout: 20m
variables:
PYTEST_WARNINGS: "-W error ${IGNORE_WARNINGS}"
PIP_INSTALL_OPTIONS: "--pre -e" # because of --import-mode=importlib"
PYTEST_WARNINGS: '-W error ${IGNORE_WARNINGS}'
PIP_INSTALL_OPTIONS: '-e' # no --pre because of Orange3 and cython 3.1.0a1, -e because of --import-mode=importlib"
test-3.8-minimal:
extends: .test-3.8_glx
test-3.9:
extends: .test-3.9_glx
timeout: 20m
variables:
PYTEST_WARNINGS: "-W error ${IGNORE_WARNINGS}"
PYTEST_COV: ""
PYTEST_WARNINGS: '-W error ${IGNORE_WARNINGS}'
PIP_INSTALL_OPTIONS: '--pre -e' # -e because of --import-mode=importlib"
test-3.8-pymca:
extends: .test-3.8_glx
test-3.9-minimal:
extends: .test-3.9_glx
timeout: 20m
variables:
PYTEST_WARNINGS: "-W error ${IGNORE_WARNINGS}"
PYTEST_COV: ""
PYTEST_WARNINGS: '-W error ${IGNORE_WARNINGS}'
PYTEST_COV: ''
test-3.8-larch:
extends: .test-3.8_glx
test-3.9-pymca:
extends: .test-3.9_glx
timeout: 20m
variables:
PYTEST_WARNINGS: "-W error ${IGNORE_WARNINGS}"
PYTEST_COV: ""
PYTEST_WARNINGS: '-W error ${IGNORE_WARNINGS}'
PYTEST_COV: ''
test-3.8-larch-example:
extends: .test-3.8_glx
test-3.9-larch:
extends: .test-3.9_glx
timeout: 20m
variables:
PYTEST_WARNINGS: '-W error ${IGNORE_WARNINGS}'
PYTEST_COV: ''
test-3.9-larch-example:
extends: .test-3.9_glx
script:
- python -m pip install --pre .[larch] pyqt5
- ./scripts/larch_example/run.sh
test-3.11:
extends: .test-3.10_glx
timeout: 20m
variables:
PYTEST_WARNINGS: '-W error ${IGNORE_WARNINGS}'
PIP_INSTALL_OPTIONS: '--pre -e' # -e because of --import-mode=importlib"
test-3.12:
extends: .test-3.12_glx
timeout: 20m
variables:
PYTEST_WARNINGS: '-W error ${IGNORE_WARNINGS}'
PIP_INSTALL_OPTIONS: '--pre -e' # -e because of --import-mode=importlib"
build_sdist:
extends: .build_sdist
test_sdist-3.9:
extends: .test_sdist-3.9_glx
variables:
PYTEST_WARNINGS: "-W error ${IGNORE_WARNINGS}"
PYTEST_WARNINGS: '-W error ${IGNORE_WARNINGS}'
build_doc:
extends: .build_doc
script:
- python -m pip install sphinx # solves issue with pip dependency solver
- python -m pip install sphinx # solves issue with pip dependency solver
- python -m pip install .[doc]
- sphinx-build doc html
......@@ -68,3 +92,9 @@ pages:
assets:
extends: .assets
pypi:
extends: .pypi
testpypi:
extends: .testpypi
# CHANGELOG.md
## Unreleased
## 1.0.0
Changes:
- Add rebinning to the `Larch_autobk` task.
- Support concatenated multi-XAS scans that can be uni- or bi-directional.
- Support concatenated multi-XAS scans with fixed section size as well as monotonic section detection.
New Features:
- Ewoks task `SplitBlissScan` to split concatenated multi-XAS scan HDF5 data.
- `get_data_from_url` supports online data access.
## 0.7.2
Bug fixes:
- Fix icon reading
## 0.7.1
New Features:
- Add support for Python 3.12
## 0.7.0
New Features:
- Read bi-directional scan data with monotonic section detection.
Changes:
- Remove `pkg_resources` usage and related changes in Orange packages
- Refactor HDF5 and ASCII data reading
# Est: Spectroscopy workflows
`est` is a library providing tools to define a workflow of treatments for X-ray Absorption Structure analysis.
Treatments are based on eather [PyMca](https://github.com/vasole/pymca)_ or [Larch](https://xraypy.github.io/xraylarch/)
`est` is a library to define workflows for X-ray Absorption Structure analysis.
The analysis can be based on either [PyMca](https://github.com/vasole/pymca) or [Larch](https://xraypy.github.io/xraylarch/)
The library offers a convenient object for connecting those two.
![An example workflow](https://ewoksest.readthedocs.io/en/latest/_images/workflow_example.png)
An [Orange3](https://github.com/biolab/orange3) add-on is also provided by the library to help user defining graphically the workflow they want to process.
## Quick start
## Installation
``` python
pip install est[full]
```python
pip install est[full] pyqt5
ewoks-canvas
```
## Test
```bash
pytest --pyargs est.tests
```
See then the _Example workflows_ to get started.
## Documentation
......
File moved
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
from est import __version__ as release
from importlib.metadata import version as get_version
project = "est"
release = get_version(project)
version = ".".join(release.split(".")[:2])
copyright = "2019-2023, ESRF"
copyright = "2019-2024, ESRF"
author = "ESRF"
docstitle = f"{project} {version}"
......
Summary
-------
`est` provides `ewoks tasks <https://ewoks.readthedocs.io/en/latest>`_ that can be connected
together to define a workflow for X-ray Absorption Structure analysis. Tasks are based on
either `PyMca <https://github.com/vasole/pymca>`_ or `Larch <https://xraypy.github.io/xraylarch/>`_.
......@@ -14,3 +17,4 @@ help user defining graphically the workflow they want to run.
user_guide/index.rst
api.rst
app/index.rst
CHANGELOG.rst
[build-system]
requires = [
"setuptools>=46.4<63",
"wheel",
]
requires = ["setuptools>=46.4"]
build-backend = "setuptools.build_meta"
[tool.pytest.ini_options]
# consider_namespace_packages = true # for pytest>=8.1
addopts = "--import-mode=importlib" # for all pytest versions, pytest-cov needs `pip install -e .`
addopts = "--import-mode=importlib" # for all pytest versions, pytest-cov needs `pip install -e .`
[project]
name = "est"
version = "1.0.0"
keywords = ['orange3 add-on', 'ewoks']
authors = [{name = "ESRF", email = "dau-pydev@esrf.fr"}]
description = "Spectroscopy workflows"
readme = {file = "README.md", content-type = "text/markdown"}
license = {file = "LICENSE"}
classifiers = [
"Intended Audience :: Science/Research",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
]
requires-python = ">=3.8"
dependencies = [
"numpy",
"scipy",
"h5py >=3.1",
"silx >=0.15",
"Pint",
"ewoks[orange] >=0.1",
"ewoksorange >=0.7.0",
"importlib_resources; python_version < '3.9'",
]
[project.urls]
Homepage = "https://gitlab.esrf.fr/workflow/ewoksapps/est/"
Documentation = "https://ewoksest.readthedocs.io/"
Repository = "https://gitlab.esrf.fr/workflow/ewoksapps/est/"
Issues = "https://gitlab.esrf.fr/workflow/ewoksapps/est/issues"
Changelog = "https://gitlab.esrf.fr/workflow/ewoksapps/est/-/blob/main/CHANGELOG.md"
[project.optional-dependencies]
pymca = [
"pymca >=5.8.1",
]
larch = [
"xraylarch >=0.9.71",
]
orange = [
"ewoksorange[orange] >=0.7.0",
]
full = [
"est[pymca]",
"est[larch]",
"est[orange]",
]
test_no_larch_or_pymca = [
"pytest >=7",
"testbook",
"ipykernel",
"pyqt5",
]
test = [
"est[full]",
"est[test_no_larch_or_pymca]",
]
dev = [
"est[test]",
"black >=25",
"flake8 >=4",
"flake8_nb >= 0.3.1",
"pillow >= 10.0",
]
doc = [
"est[test]",
"est[full]",
"sphinx >=4.5",
"sphinx-autodoc-typehints >=1.16",
"ipykernel",
"nbsphinx",
"nbsphinx_link",
"docutils < 0.21", # v0.21 incompatible with nbsphinx_link: https://github.com/vidartf/nbsphinx-link/issues/22
"pydata_sphinx_theme",
]
[tool.setuptools]
package-dir = { "" = "src" }
[tool.setuptools.packages.find]
where = ["src"]
[tool.setuptools.package-data]
"*"= ['*.ows', '*.png', '*.svg', '*.dat', '*.xmu', '*.ipynb']
[tool.coverage.run]
omit = ['*/tests/*']
[project.scripts]
est = "est.__main__:main"
[project.entry-points."orange3.addon"]
"est-add-on" = "orangecontrib.est"
[project.entry-points."orange.widgets"]
"Est" = "orangecontrib.est.widgets"
[project.entry-points."orange.widgets.tutorials"]
"Est" = "orangecontrib.est.tutorials"
[project.entry-points."orange.canvas.help"]
"html-index" = "orangecontrib.est.widgets:WIDGET_HELP_PATH"
[project.entry-points."ewoks.tasks.class"]
"est.core.process.*" = "est1"
"est.core.process.*.*" = "est2"
......@@ -3,9 +3,6 @@ from pathlib import Path
from est import resources
RESOURCE_ROOT = Path(__file__).resolve().parent.parent / "est" / "resources"
def download(url: str, filename: Path):
with urlopen(url) as response:
with open(filename, "wb") as out_file:
......@@ -16,23 +13,23 @@ def download(url: str, filename: Path):
def download_larch_cu():
url = "https://raw.githubusercontent.com/xraypy/xraylarch/master/examples/xafs/cu_rt01.xmu"
filename = RESOURCE_ROOT / "exafs" / "cu_rt01.xmu"
download(url, filename)
resources.generate_resource("exafs/cu_rt01.xmu", overwrite=True)
with resources.resource_path("exafs", "cu_rt01.xmu") as filename:
download(url, filename)
resources.generate_resource("exafs", "cu_rt01.xmu", overwrite=True)
def download_pymca_cu():
url = "https://raw.githubusercontent.com/vasole/pymca/master/PyMca5/PyMcaData/EXAFS_Cu.dat"
filename = RESOURCE_ROOT / "exafs" / "EXAFS_Cu.dat"
download(url, filename)
resources.generate_resource("exafs/EXAFS_Cu.dat", overwrite=True)
with resources.resource_path("exafs", "EXAFS_Cu.dat") as filename:
download(url, filename)
resources.generate_resource("exafs", "EXAFS_Cu.dat", overwrite=True)
def download_pymca_ge():
url = "https://raw.githubusercontent.com/vasole/pymca/master/PyMca5/PyMcaData/EXAFS_Ge.dat"
filename = RESOURCE_ROOT / "exafs" / "EXAFS_Ge.dat"
download(url, filename)
resources.generate_resource("exafs/EXAFS_Ge.dat", overwrite=True)
with resources.resource_path("exafs", "EXAFS_Ge.dat") as filename:
download(url, filename)
resources.generate_resource("exafs", "EXAFS_Ge.dat", overwrite=True)
if __name__ == "__main__":
......
from ewokscore import execute_graph
from ewoksutils.task_utils import task_inputs
if __name__ == "__main__":
inputs = {
"filename": "/data/scisoft/ewoks/ch7280/id24-dcm/20250131/RAW_DATA/Ru_WVC1/Ru_WVC1_1_RT_air/Ru_WVC1_1_RT_air.h5",
"scan_number": 1,
"monotonic_channel": "measurement/energy_enc",
"out_filename": "/tmp/test.h5",
"subscan_size": 3001,
}
node = {
"task_type": "class",
"task_identifier": "est.core.process.split.SplitBlissScan",
}
graph = {"graph": {"graph_version": "1.1", "id": "split"}, "nodes": [node]}
print(execute_graph(graph, inputs=task_inputs(inputs=inputs)))
[metadata]
name = est
version = attr: est.__version__
author = data analysis unit
author_email = henri.payno@esrf.fr
description = Spectroscopy workflows
long_description = file: README.md
long_description_content_type = text/markdown
license = MIT
url = https://gitlab.esrf.fr/workflow/ewoksapps/est
project_urls =
Source = https://gitlab.esrf.fr/workflow/ewoksapps/est/
Documentation = https://ewoksest.readthedocs.io/
Tracker = https://gitlab.esrf.fr/workflow/ewoksapps/est/issues/
classifiers =
Intended Audience :: Science/Research
License :: OSI Approved :: MIT License
Programming Language :: Python :: 3
keywords =
orange3 add-on,ewoks
[options]
package_dir=
=src
packages=find_namespace:
python_requires = >=3.8
install_requires =
numpy
scipy
h5py >=3.1,<3.10
silx >=0.15
Pint
ewoks[orange] >=0.1
ewoksorange >= 0.7.0rc0
[options.packages.find]
where=src
[options.entry_points]
console_scripts =
est=est.__main__:main
orange3.addon =
est-add-on=orangecontrib.est
orange.widgets =
Est=orangecontrib.est.widgets
orange.widgets.tutorials =
Est=est.resources.workflows
orange.canvas.help =
html-index=orangecontrib.est.widgets:WIDGET_HELP_PATH
ewoks.tasks.class =
est.core.process.*=est1
est.core.process.*.*=est2
[options.package_data]
* = *.ows, *.png, *.svg, *.dat, *.xmu, *.ipynb
[options.extras_require]
pymca =
pymca >=5.8.1
larch =
xraylarch >=0.9.71
orange =
Orange3
full =
%(pymca)s
%(larch)s
%(orange)s
test_no_larh_or_pymca =
pytest >=7
testbook
ipykernel
pyqt5
test =
%(full)s
%(test_no_larh_or_pymca)s
dev =
%(test)s
black >=22
flake8 >=4
flake8_nb >= 0.3.1
pillow >= 10.0
doc =
%(test)s
%(full)s
sphinx >=4.5
sphinx-autodoc-typehints >=1.16
ipykernel
nbsphinx
nbsphinx_link
pydata_sphinx_theme < 0.15
# E501 (line too long) ignored for now
# E203 and W503 incompatible with black formatting (https://black.readthedocs.io/en/stable/compatible_configs.html#flake8)
[flake8]
ignore = E501, E203, W503
max-line-length = 88
exclude =
.eggs
# E402 (module import not at top of file) ignored
[flake8_nb]
ignore = E501, E203, W503, E402
max-line-length = 88
[coverage:run]
omit =
*/tests/*
import setuptools
if __name__ == "__main__":
setuptools.setup()
__version__ = "0.6.0"
......@@ -8,7 +8,7 @@ Your environment should provide a command `est`. You can reach help with
import logging
import sys
from silx.utils.launcher import Launcher
from est import __version__
from importlib.metadata import version as get_version
def main():
......@@ -20,7 +20,8 @@ def main():
:rtype: int
:returns: The execution status
"""
launcher = Launcher(prog="est", version=__version__)
est_version = get_version("est")
launcher = Launcher(prog="est", version=est_version)
launcher.add_command(
"canvas",
module_name="ewoksorange.canvas.main",
......
......@@ -8,7 +8,9 @@ from silx.io.url import DataUrl
from est.units import ur
from est import settings
from est.io import read_xas, write_xas
from est.io import read_xas
from est.io import write_xas
from est.io.concatenated import read_concatenated_xas
from est.core.types import XASObject
from est.core.types import dimensions as dimensions_mod
from est.io.information import InputInformation
......@@ -27,7 +29,10 @@ DEFAULT_CONF_PATH = "/configuration"
def read_from_input_information(
information: InputInformation, timeout: float = settings.DEFAULT_READ_TIMEOUT
) -> XASObject:
spectra, energy, configuration = read_xas(information=information, timeout=timeout)
if information.is_concatenated:
spectra, energy, configuration = read_concatenated_xas(information, timeout)
else:
spectra, energy, configuration = read_xas(information, timeout)
xas_obj = XASObject(spectra=spectra, energy=energy, configuration=configuration)
I0_url = information.I0_url
......@@ -59,6 +64,10 @@ def read_from_url(
I1_url: DataUrl | None = None,
I2_url: DataUrl | None = None,
mu_ref_url: DataUrl | None = None,
is_concatenated: bool = False,
trim_concatenated_n_points: int = 0,
skip_concatenated_n_spectra: int = 0,
concatenated_spectra_section_size: int = 0,
timeout: float = settings.DEFAULT_READ_TIMEOUT,
) -> XASObject:
"""
......@@ -83,6 +92,10 @@ def read_from_url(
I1_url=I1_url,
I2_url=I2_url,
mu_ref_url=mu_ref_url,
is_concatenated=is_concatenated,
trim_concatenated_n_points=trim_concatenated_n_points,
skip_concatenated_n_spectra=skip_concatenated_n_spectra,
concatenated_spectra_section_size=concatenated_spectra_section_size,
)
return read_from_input_information(input_information, timeout=timeout)
......@@ -129,6 +142,8 @@ def dump_xas(h5_file: str, xas_obj: Union[dict, XASObject]) -> None:
"""
if isinstance(xas_obj, dict):
xas_obj = XASObject.from_dict(xas_obj)
if not isinstance(xas_obj, XASObject):
raise TypeError(str(type(xas_obj)))
if not h5_file:
_logger.warning("no output file defined, please give path to the output file")
......
from typing import Sequence, List, Tuple
import numpy
def split_piecewise_monotonic(values: Sequence[float]) -> List[slice]:
"""This method returns the disjoint slices representing monotonic sections.
The provided values are assumed to be piecewise monotonic which means that
it consists of sections that are either monotonically increasing (∀ i ≤ j, x[i] ≤ x[j])
or monotonically decreasing (∀ i ≤ j, x[i] ≥ x[j]). Note that a list of identical
values is both monotonically increasing and decreasing.
"""
section_sizes = []
section_slopes = []
values_left = values
while len(values_left):
section_size, section_slope = _first_monotonic_size(values_left)
section_sizes.append(section_size)
section_slopes.append(section_slope)
values_left = values_left[section_size:]
monotone_start = [0]
monotone_stop = []
monotone_step = []
last_section_index = len(section_sizes) - 1
stops = numpy.cumsum(section_sizes)
for section_index, (stop, section_slope) in enumerate(zip(stops, section_slopes)):
monotone_step.append(1 if section_slope >= 0 else -1)
if section_index == last_section_index:
monotone_stop.append(stop)
break
# The pivot index is the index at which either
# - slope>=0 with the previous index and slope<0 with the next index
# - slope<=0 with the previous index and slope>0 with the next index
pivot_index = stop - 1
if values[pivot_index - 1] == values[pivot_index]:
# The pivot index ■ is part of the next section
#
# ● ─ ■
# / \ -> same slope up and down
# ● ●
#
monotone_stop.append(stop - 1)
monotone_start.append(stop - 1)
continue
# Determine whether the pivot index is part of this section or
# be part of the next section
slope_before = values[pivot_index] - values[pivot_index - 1]
diff_slope_before = abs(section_slope - slope_before)
slope_after = values[pivot_index + 1] - values[pivot_index]
next_section_slope = section_slopes[section_index + 1]
diff_slope_after = abs(next_section_slope - slope_after)
if diff_slope_after <= diff_slope_before:
# The pivot index ■ is part of the next section
#
# ■
# ● \ -> slope after matches the next section slope better
# / ● then the slope before matches the current section slope
# ● \
# / ●
#
monotone_stop.append(stop - 1)
monotone_start.append(stop - 1)
else:
# The pivot index ■ is part of this section
#
# ■
# / ● -> slope before matches the current section slope better
# ● \ than the slope after matches the next section slope
# / ●
# ● \
#
monotone_stop.append(stop)
monotone_start.append(stop)
return [
_create_slice(start, stop, step)
for start, stop, step in zip(monotone_start, monotone_stop, monotone_step)
]
def _create_slice(start: int, stop: int, step: int):
if step >= 0:
return slice(start, stop, step)
assert start >= 0
assert stop >= 0
start, stop = stop, start
start -= 1
if stop == 0:
stop = None
else:
stop -= 1
return slice(start, stop, step)
def _mean_nonzero(values: Sequence[float]) -> float:
"""Average non-zero value. Returns `nan` for an empty list and `0.` when
all values are zero.
"""
values = numpy.asarray(values)
if not values.size:
return numpy.nan
non_zero = values != 0
if non_zero.any():
return numpy.mean(values[non_zero])
return 0.0
def _first_monotonic_size(values: Sequence[float]) -> Tuple[int, int]:
"""Determine the length of the first monotonically increasing or decreasing slice
starting from the first value.
"""
slopes = numpy.diff(values)
maxsize = len(values)
if maxsize < 3:
return maxsize, _mean_nonzero(slopes)
slope_signs = numpy.sign(slopes)
first_slope_sign = slope_signs[0]
for value_idx, slope_sign in enumerate(slope_signs[1:], 1):
if slope_sign and first_slope_sign and slope_sign != first_slope_sign:
# Non-zero slope changed sign: end of monotonic series
return value_idx + 1, _mean_nonzero(slopes[:value_idx])
if not first_slope_sign:
first_slope_sign = slope_sign
return maxsize, _mean_nonzero(slopes)
"""contains `energy roi` related functions / classes"""
from typing import Union, Optional
from est.core.types.xasobject import XASObject
from .process import Process
def xas_energy_roi(xas_obj):
"""
apply roi on the XASObject.spectra
:param xas_obj: object containing the configuration and spectra to process
:type: Union[:class:`.XASObject`, dict]
:return: spectra dict
:rtype: :class:`.XASObject`
"""
def xas_energy_roi(xas_obj: Union[XASObject, dict]) -> Optional[XASObject]:
process = EnergyROIProcess(inputs={"xas_obj": xas_obj})
process.run()
return process.get_output_value("xas_obj", None)
......@@ -20,37 +14,27 @@ def xas_energy_roi(xas_obj):
class EnergyROIProcess(
Process,
name="energy roi",
input_names=["xas_obj"],
optional_input_names=["energy_roi"],
output_names=["xas_obj"],
):
@staticmethod
def program_name():
def program_name() -> str:
return "energy-roi"
def definition(self):
def definition(self) -> str:
return "apply a ROI on energy range"
def run(self):
"""
:param xas_obj: object containing the configuration and spectra to process
:type: Union[:class:`.XASObject`, dict]
:return: spectra dict
:rtype: :class:`.XASObject`
"""
xas_obj = self.inputs.xas_obj
_xas_obj = self.getXasObject(xas_obj=xas_obj)
assert isinstance(_xas_obj, XASObject)
xas_obj = self.getXasObject(xas_obj=self.inputs.xas_obj)
energy_roi = self.get_input_value("energy_roi", dict())
self.progress = 0.0
self._apply_roi(_xas_obj, energy_roi.get("minE"), energy_roi.get("maxE"))
self._apply_roi(xas_obj, energy_roi.get("minE"), energy_roi.get("maxE"))
self.progress = 100.0
self.outputs.xas_obj = _xas_obj
self.outputs.xas_obj = xas_obj
def _apply_roi(self, xas_obj, emin, emax):
if emin is None and emax is None:
......
import numpy
def array_undo_mask(masked: numpy.ndarray, mask: numpy.ndarray) -> numpy.ndarray:
"""
Restore an array to its original shape after applying a boolean mask.
:param masked: The array containing only the unmasked values.
:param mask: A boolean array indicating the original positions of the `masked` values.
:return: A reconstructed array of the same shape as `mask`, where values from `masked`
are restored to their original positions, and other elements are filled
with `NaN` (for floats) or `0` (for ints).
**Example:**
>>> import numpy as np
>>> original = np.array([1.0, 2.0, np.nan, 4.0, np.nan])
>>> mask = ~np.isnan(original) # Boolean mask
>>> masked = original[mask] # Extract non-NaN values
>>> restored = array_undo_mask(masked, mask)
>>> print(restored)
[ 1. 2. nan 4. nan ]
"""
if not mask.any():
return masked
if numpy.issubdtype(masked.dtype, int):
raise TypeError("Integers are not supported")
result = numpy.full(mask.shape, numpy.nan, dtype=masked.dtype)
result[mask] = masked
return result