From 55e5ea0079bbe5d4f9b41fd1438b997dc7d9d323 Mon Sep 17 00:00:00 2001 From: domna Date: Fri, 23 Feb 2024 16:54:02 +0100 Subject: [PATCH 01/25] Move dicts to mapping file --- pynxtools_mpes/mappings.py | 55 ++++++++++++++++++++++++++++++++++++ pynxtools_mpes/reader.py | 57 ++------------------------------------ 2 files changed, 58 insertions(+), 54 deletions(-) create mode 100644 pynxtools_mpes/mappings.py diff --git a/pynxtools_mpes/mappings.py b/pynxtools_mpes/mappings.py new file mode 100644 index 0000000..b2fcc7d --- /dev/null +++ b/pynxtools_mpes/mappings.py @@ -0,0 +1,55 @@ +""" +Mapping dictionaries for the MPES conversion. +""" + +DEFAULT_UNITS = { + "X": "step", + "Y": "step", + "t": "step", + "tofVoltage": "V", + "extractorVoltage": "V", + "extractorCurrent": "A", + "cryoTemperature": "K", + "sampleTemperature": "K", + "dldTimeBinSize": "ns", + "delay": "ps", + "timeStamp": "s", + "energy": "eV", + "kx": "1/A", + "ky": "1/A", +} + +CONVERT_DICT = { + "Instrument": "INSTRUMENT[instrument]", + "Analyzer": "ELECTRONANALYSER[electronanalyser]", + "Manipulator": "MANIPULATOR[manipulator]", + "Beam": "beam_TYPE[beam]", + "unit": "@units", + "Sample": "SAMPLE[sample]", + "Source": "source_TYPE[source]", + "User": "USER[user]", + "energy_resolution": "energy_resolution/resolution", + "momentum_resolution": "RESOLUTION[momentum_resolution]/resolution", + "temporal_resolution": "RESOLUTION[temporal_resolution]/resolution", + "spatial_resolution": "RESOLUTION[spatial_resolution]/resolution", + "sample_temperature": "temperature_sensor/value", +} + +REPLACE_NESTED = { + "SAMPLE[sample]/chemical_formula": "SAMPLE[sample]/SUBSTANCE[substance]/molecular_formula_hill", + "source_TYPE[source]/Probe": "source_TYPE[source_probe]", + "source_TYPE[source]/Pump": "source_TYPE[source_pump]", + "beam_TYPE[beam]/Probe": "beam_TYPE[beam_probe]", + "beam_TYPE[beam]/Pump": "beam_TYPE[beam_pump]", + "sample_history": "sample_history/notes", + "ELECTRONANALYSER[electronanalyser]/RESOLUTION[momentum_resolution]": ( + "ELECTRONANALYSER[electronanalyser]/momentum_resolution" + ), + "ELECTRONANALYSER[electronanalyser]/RESOLUTION[spatial_resolution]": ( + "ELECTRONANALYSER[electronanalyser]/spatial_resolution" + ), + "SAMPLE[sample]/gas_pressure": "INSTRUMENT[instrument]/pressure_gauge/value", + "SAMPLE[sample]/temperature": ( + "INSTRUMENT[instrument]/MANIPULATOR[manipulator]/temperature_sensor/value" + ), +} diff --git a/pynxtools_mpes/reader.py b/pynxtools_mpes/reader.py index 003fb77..9f1eb73 100644 --- a/pynxtools_mpes/reader.py +++ b/pynxtools_mpes/reader.py @@ -32,22 +32,7 @@ parse_flatten_json, ) -DEFAULT_UNITS = { - "X": "step", - "Y": "step", - "t": "step", - "tofVoltage": "V", - "extractorVoltage": "V", - "extractorCurrent": "A", - "cryoTemperature": "K", - "sampleTemperature": "K", - "dldTimeBinSize": "ns", - "delay": "ps", - "timeStamp": "s", - "energy": "eV", - "kx": "1/A", - "ky": "1/A", -} +from pynxtools_mpes.mappings import CONVERT_DICT, DEFAULT_UNITS, REPLACE_NESTED def recursive_parse_metadata( @@ -98,7 +83,7 @@ def h5_to_xarray(faddr: str, mode: str = "r") -> xr.DataArray: data = np.asarray(h5_file["binned"]["BinnedData"]) except KeyError as exc: raise ValueError( - f"Wrong Data Format, the BinnedData was not found.", + "Wrong Data Format, the BinnedData was not found.", ) from exc # Reading the axes @@ -111,7 +96,7 @@ def h5_to_xarray(faddr: str, mode: str = "r") -> xr.DataArray: bin_names.append(h5_file["axes"][axis].attrs["name"]) except KeyError as exc: raise ValueError( - f"Wrong Data Format, the axes were not found.", + "Wrong Data Format, the axes were not found.", ) from exc # load metadata @@ -168,42 +153,6 @@ def iterate_dictionary(dic, key_string): return None -CONVERT_DICT = { - "Instrument": "INSTRUMENT[instrument]", - "Analyzer": "ELECTRONANALYSER[electronanalyser]", - "Manipulator": "MANIPULATOR[manipulator]", - "Beam": "beam_TYPE[beam]", - "unit": "@units", - "Sample": "SAMPLE[sample]", - "Source": "source_TYPE[source]", - "User": "USER[user]", - "energy_resolution": "energy_resolution/resolution", - "momentum_resolution": "RESOLUTION[momentum_resolution]/resolution", - "temporal_resolution": "RESOLUTION[temporal_resolution]/resolution", - "spatial_resolution": "RESOLUTION[spatial_resolution]/resolution", - "sample_temperature": "temperature_sensor/value", -} - -REPLACE_NESTED = { - "SAMPLE[sample]/chemical_formula": "SAMPLE[sample]/SUBSTANCE[substance]/molecular_formula_hill", - "source_TYPE[source]/Probe": "source_TYPE[source_probe]", - "source_TYPE[source]/Pump": "source_TYPE[source_pump]", - "beam_TYPE[beam]/Probe": "beam_TYPE[beam_probe]", - "beam_TYPE[beam]/Pump": "beam_TYPE[beam_pump]", - "sample_history": "sample_history/notes", - "ELECTRONANALYSER[electronanalyser]/RESOLUTION[momentum_resolution]": ( - "ELECTRONANALYSER[electronanalyser]/momentum_resolution" - ), - "ELECTRONANALYSER[electronanalyser]/RESOLUTION[spatial_resolution]": ( - "ELECTRONANALYSER[electronanalyser]/spatial_resolution" - ), - "SAMPLE[sample]/gas_pressure": "INSTRUMENT[instrument]/pressure_gauge/value", - "SAMPLE[sample]/temperature": ( - "INSTRUMENT[instrument]/MANIPULATOR[manipulator]/temperature_sensor/value" - ), -} - - def handle_h5_and_json_file(file_paths, objects): """Handle h5 or json input files.""" x_array_loaded = xr.DataArray() From 1d9201da45e11b854934a653fe9fd73bd3badaab Mon Sep 17 00:00:00 2001 From: domna Date: Fri, 23 Feb 2024 16:55:30 +0100 Subject: [PATCH 02/25] Import from multiformat reader --- pynxtools_mpes/reader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pynxtools_mpes/reader.py b/pynxtools_mpes/reader.py index 9f1eb73..7f55f20 100644 --- a/pynxtools_mpes/reader.py +++ b/pynxtools_mpes/reader.py @@ -25,7 +25,7 @@ import numpy as np import xarray as xr import yaml -from pynxtools.dataconverter.readers.base.reader import BaseReader +from pynxtools.dataconverter.readers.multi.reader import MultiFormatReader from pynxtools.dataconverter.readers.utils import ( FlattenSettings, flatten_and_replace, @@ -241,7 +241,7 @@ def fill_data_indices_in_config(config_file_dict, x_array_loaded): config_file_dict.pop(key) -class MPESReader(BaseReader): +class MPESReader(MultiFormatReader): """MPES-specific reader class""" # pylint: disable=too-few-public-methods From 1c7e2701f054ee1073291aa2a30f9613fef985f6 Mon Sep 17 00:00:00 2001 From: domna Date: Fri, 23 Feb 2024 18:35:16 +0100 Subject: [PATCH 03/25] Update pynxtools dependency --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4da5418..ffc1006 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "h5py>=3.6.0", "PyYAML>=6.0", "xarray>=0.20.2", - "pynxtools>=0.0.10" + "pynxtools@git+https://github.com/FAIRmat-NFDI/pynxtools#egg=multi-format-reader" ] [project.urls] From 8a4abaa264da568670fa0d1091d3916ff11bfff1 Mon Sep 17 00:00:00 2001 From: domna Date: Wed, 17 Jul 2024 10:00:13 +0200 Subject: [PATCH 04/25] Update mappings from main --- pynxtools_mpes/mappings.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/pynxtools_mpes/mappings.py b/pynxtools_mpes/mappings.py index b2fcc7d..d769118 100644 --- a/pynxtools_mpes/mappings.py +++ b/pynxtools_mpes/mappings.py @@ -23,31 +23,40 @@ "Instrument": "INSTRUMENT[instrument]", "Analyzer": "ELECTRONANALYSER[electronanalyser]", "Manipulator": "MANIPULATOR[manipulator]", - "Beam": "beam_TYPE[beam]", + "Beam": "beamTYPE[beam]", "unit": "@units", "Sample": "SAMPLE[sample]", - "Source": "source_TYPE[source]", + "Source": "sourceTYPE[source]", "User": "USER[user]", "energy_resolution": "energy_resolution/resolution", "momentum_resolution": "RESOLUTION[momentum_resolution]/resolution", "temporal_resolution": "RESOLUTION[temporal_resolution]/resolution", "spatial_resolution": "RESOLUTION[spatial_resolution]/resolution", + "angular_resolution": "RESOLUTION[angular_resolution]/resolution", "sample_temperature": "temperature_sensor/value", + "drain_current": "drain_current_amperemeter/value", + "photon_energy": "energy", } REPLACE_NESTED = { "SAMPLE[sample]/chemical_formula": "SAMPLE[sample]/SUBSTANCE[substance]/molecular_formula_hill", - "source_TYPE[source]/Probe": "source_TYPE[source_probe]", - "source_TYPE[source]/Pump": "source_TYPE[source_pump]", - "beam_TYPE[beam]/Probe": "beam_TYPE[beam_probe]", - "beam_TYPE[beam]/Pump": "beam_TYPE[beam_pump]", - "sample_history": "sample_history/notes", + "sourceTYPE[source]/Probe": "sourceTYPE[source_probe]", + "sourceTYPE[source]/Pump": "sourceTYPE[source_pump]", + "beamTYPE[beam]/Probe": "beamTYPE[beam_probe]", + "beamTYPE[beam]/Pump": "beamTYPE[beam_pump]", + "sample_history": "history/notes/description", + "ELECTRONANALYSER[electronanalyser]/RESOLUTION[energy_resolution]": ( + "ELECTRONANALYSER[electronanalyser]/energy_resolution" + ), "ELECTRONANALYSER[electronanalyser]/RESOLUTION[momentum_resolution]": ( "ELECTRONANALYSER[electronanalyser]/momentum_resolution" ), "ELECTRONANALYSER[electronanalyser]/RESOLUTION[spatial_resolution]": ( "ELECTRONANALYSER[electronanalyser]/spatial_resolution" ), + "ELECTRONANALYSER[electronanalyser]/RESOLUTION[angular_resolution]": ( + "ELECTRONANALYSER[electronanalyser]/angular_resolution" + ), "SAMPLE[sample]/gas_pressure": "INSTRUMENT[instrument]/pressure_gauge/value", "SAMPLE[sample]/temperature": ( "INSTRUMENT[instrument]/MANIPULATOR[manipulator]/temperature_sensor/value" From 7c15b8b30283db6f3cc113f2e8056888c847f6d4 Mon Sep 17 00:00:00 2001 From: domna Date: Wed, 17 Jul 2024 10:00:59 +0200 Subject: [PATCH 05/25] Adapt reader to multiformat reader --- pynxtools_mpes/reader.py | 299 +++++++++++---------------------------- 1 file changed, 79 insertions(+), 220 deletions(-) diff --git a/pynxtools_mpes/reader.py b/pynxtools_mpes/reader.py index 31b3347..c83bddc 100644 --- a/pynxtools_mpes/reader.py +++ b/pynxtools_mpes/reader.py @@ -17,45 +17,21 @@ # """MPES reader implementation for the DataConverter.""" -import errno import logging -import os from functools import reduce -from typing import Any, Tuple, Union +from typing import Any, Dict, Tuple, Union import h5py import numpy as np import xarray as xr -import yaml from pynxtools.dataconverter.readers.multi.reader import MultiFormatReader -from pynxtools.dataconverter.readers.utils import ( - FlattenSettings, - flatten_and_replace, - parse_flatten_json, -) +from pynxtools.dataconverter.readers.utils import parse_yml from pynxtools_mpes.mappings import CONVERT_DICT, DEFAULT_UNITS, REPLACE_NESTED logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) -DEFAULT_UNITS = { - "X": "step", - "Y": "step", - "t": "step", - "tofVoltage": "V", - "extractorVoltage": "V", - "extractorCurrent": "A", - "cryoTemperature": "K", - "sampleTemperature": "K", - "dldTimeBinSize": "ns", - "delay": "ps", - "timeStamp": "s", - "energy": "eV", - "kx": "1/A", - "ky": "1/A", -} - def recursive_parse_metadata( node: Union[h5py.Group, h5py.Dataset], @@ -175,105 +151,6 @@ def iterate_dictionary(dic, key_string): return None -CONVERT_DICT = { - "Instrument": "INSTRUMENT[instrument]", - "Analyzer": "ELECTRONANALYSER[electronanalyser]", - "Manipulator": "MANIPULATOR[manipulator]", - "Beam": "beamTYPE[beam]", - "unit": "@units", - "Sample": "SAMPLE[sample]", - "Source": "sourceTYPE[source]", - "User": "USER[user]", - "energy_resolution": "energy_resolution/resolution", - "momentum_resolution": "RESOLUTION[momentum_resolution]/resolution", - "temporal_resolution": "RESOLUTION[temporal_resolution]/resolution", - "spatial_resolution": "RESOLUTION[spatial_resolution]/resolution", - "angular_resolution": "RESOLUTION[angular_resolution]/resolution", - "sample_temperature": "temperature_sensor/value", - "drain_current": "drain_current_amperemeter/value", - "photon_energy": "energy", -} - -REPLACE_NESTED = { - "SAMPLE[sample]/chemical_formula": "SAMPLE[sample]/SUBSTANCE[substance]/molecular_formula_hill", - "sourceTYPE[source]/Probe": "sourceTYPE[source_probe]", - "sourceTYPE[source]/Pump": "sourceTYPE[source_pump]", - "beamTYPE[beam]/Probe": "beamTYPE[beam_probe]", - "beamTYPE[beam]/Pump": "beamTYPE[beam_pump]", - "sample_history": "history/notes/description", - "ELECTRONANALYSER[electronanalyser]/RESOLUTION[energy_resolution]": ( - "ELECTRONANALYSER[electronanalyser]/energy_resolution" - ), - "ELECTRONANALYSER[electronanalyser]/RESOLUTION[momentum_resolution]": ( - "ELECTRONANALYSER[electronanalyser]/momentum_resolution" - ), - "ELECTRONANALYSER[electronanalyser]/RESOLUTION[spatial_resolution]": ( - "ELECTRONANALYSER[electronanalyser]/spatial_resolution" - ), - "ELECTRONANALYSER[electronanalyser]/RESOLUTION[angular_resolution]": ( - "ELECTRONANALYSER[electronanalyser]/angular_resolution" - ), - "SAMPLE[sample]/gas_pressure": "INSTRUMENT[instrument]/pressure_gauge/value", - "SAMPLE[sample]/temperature": ( - "INSTRUMENT[instrument]/MANIPULATOR[manipulator]/temperature_sensor/value" - ), -} - - -def handle_h5_and_json_file(file_paths, objects): - """Handle h5 or json input files.""" - x_array_loaded = xr.DataArray() - config_file_dict = {} - eln_data_dict = {} - - for file_path in file_paths: - try: - file_extension = file_path[file_path.rindex(".") :] - except ValueError as exc: - raise ValueError( - f"The file path {file_path} must have an extension.", - ) from exc - - extentions = [".h5", ".json", ".yaml", ".yml"] - if file_extension not in extentions: - print( - f"WARNING \n" - f"The reader only supports files of type {extentions}, " - f"but {file_path} does not match.", - ) - - if not os.path.exists(file_path): - raise FileNotFoundError( - errno.ENOENT, - os.strerror(errno.ENOENT), - file_path, - ) - - if file_extension == ".h5": - x_array_loaded = h5_to_xarray(file_path) - elif file_extension == ".json": - config_file_dict = parse_flatten_json(file_path) - elif file_extension in [".yaml", ".yml"]: - with open(file_path, encoding="utf-8") as feln: - eln_data_dict = flatten_and_replace( - FlattenSettings( - dic=yaml.safe_load(feln), - convert_dict=CONVERT_DICT, - replace_nested=REPLACE_NESTED, - ) - ) - - if objects is not None: - # For the case of a single object - assert isinstance( - objects, - xr.core.dataarray.DataArray, - ), "The given object must be an xarray" - x_array_loaded = objects - - return x_array_loaded, config_file_dict, eln_data_dict - - def rgetattr(obj, attr): """Get attributes recursively""" @@ -311,104 +188,86 @@ def fill_data_indices_in_config(config_file_dict, x_array_loaded): class MPESReader(MultiFormatReader): """MPES-specific reader class""" - # pylint: disable=too-few-public-methods - # Whitelist for the NXDLs that the reader supports and can process supported_nxdls = ["NXmpes", "NXmpes_arpes"] - def read( # pylint: disable=too-many-branches - self, - template: dict = None, - file_paths: Tuple[str] = None, - objects: Tuple[Any] = None, - ) -> dict: - """Reads data from given file or alternatively an xarray object - and returns a filled template dictionary""" - - if not file_paths: - raise IOError("No input files were given to MPES Reader.") - - ( - x_array_loaded, - config_file_dict, - eln_data_dict, - ) = handle_h5_and_json_file(file_paths, objects) - - fill_data_indices_in_config(config_file_dict, x_array_loaded) - - optional_groups_to_remove = [] - - for key, value in config_file_dict.items(): - if isinstance(value, str) and value.startswith("!"): - optional_groups_to_remove.append(key) - value = value[1:] - - if isinstance(value, str) and ":" in value: - precursor = value.split(":")[0] - value = value[value.index(":") + 1 :] - - # Filling in the data and axes along with units from xarray - if precursor == "@data": - try: - template[key] = rgetattr( - obj=x_array_loaded, - attr=value, - ) - if key.split("/")[-1] == "@axes": - template[key] = list(template[key]) - - except ValueError: - print( - f"Incorrect axis name corresponding to " f"the path {key}", - ) - - except AttributeError: - print( - f"Incorrect naming syntax or the xarray doesn't " - f"contain entry corresponding to the path {key}", - ) - - # Filling in the metadata from xarray - elif precursor == "@attrs": - if key not in eln_data_dict: - try: # Tries to fill the metadata - template[key] = iterate_dictionary( - x_array_loaded.attrs, - value, - ) - - except KeyError: - print( - f"[info]: Path {key} not found. " - f"Skipping the entry.", - ) - - if isinstance(template.get(key), str) and template[key].startswith( - "@link:" - ): - template[key] = {"link": template[key][6:]} - else: - # Fills in the fixed metadata - template[key] = value - - # Filling in ELN metadata and overwriting the common paths by - # giving preference to the ELN metadata - for key, value in eln_data_dict.items(): - template[key] = value - - # remove groups that have required children missing - for key in optional_groups_to_remove: - if template.get(key) is None: - group_to_delete = key.rsplit("/", 1)[0] - logger.info( - f"[info]: Required element {key} not provided. " - f"Removing the parent group {group_to_delete}.", - ) - for temp_key in template.keys(): - if temp_key.startswith(group_to_delete): - del template[temp_key] - - return template + def __init__(self): + super().__init__() + self.eln_data = None + + self.extensions = { + ".yml": self.handle_eln_file, + ".yaml": self.handle_eln_file, + ".h5": self.handle_hdf5_file, + } + + def handle_hdf5_file(self, file_path: str) -> Dict[str, Any]: + """Handle hdf5 file""" + self.data_xarray = h5_to_xarray(file_path) + + return {} + + def handle_eln_file(self, file_path: str) -> Dict[str, Any]: + self.eln_data = parse_yml( + file_path, + convert_dict=CONVERT_DICT, + replace_nested=REPLACE_NESTED, + ) + + return {} + + def get_eln_data(self, path: str) -> Any: + """Returns data from the given eln path.""" + if self.eln_data is None: + return None + + return self.eln_data.get(path) + + def setup_template(self) -> Dict[str, Any]: + return {} + + def handle_objects(self, objects: Tuple[Any]) -> Dict[str, Any]: + if isinstance(objects, xr.DataArray): + # Should normally be a tuple, but in the + # past a single xarray object was passed. + # This if-clause exists for backwards compatibility + self.data_xarray = objects + return {} + if ( + isinstance(objects, tuple) + and len(objects) > 0 + and isinstance(objects[0], xr.DataArray) + ): + self.data_xarray = objects[0] + return {} + + logging.info( + f"Error while reading objects: {objects} does not contain an xarray object." + " Skipping the objects." + ) + return {} + + def get_data(self, path: str) -> Any: + try: + value = rgetattr(obj=self.data_xarray, attr=path) + if path.split("/")[-1] == "@axes": + return list(value) + return value + + except ValueError: + print(f"Incorrect axis name corresponding to the path {path}") + + except AttributeError: + print( + "Incorrect naming syntax or the xarray doesn't " + f"contain entry corresponding to the path {path}" + ) + + def get_attr(self, path: str) -> Any: + try: + return iterate_dictionary(self.data_xarray.attrs, path) + except KeyError: + logging.info(f"Path {path} not found. Skipping the entry.") READER = MPESReader From 7dbb5ea61e1b418e6deb799e9e8fcb10eaf491e7 Mon Sep 17 00:00:00 2001 From: domna Date: Wed, 17 Jul 2024 17:56:03 +0200 Subject: [PATCH 06/25] Adaptions for a working multi reader --- pynxtools_mpes/reader.py | 18 +++++++++++++++--- tests/data/config_file.json | 1 - tests/data/example.nxs | Bin 4375960 -> 4375960 bytes 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/pynxtools_mpes/reader.py b/pynxtools_mpes/reader.py index c83bddc..2d7b4ff 100644 --- a/pynxtools_mpes/reader.py +++ b/pynxtools_mpes/reader.py @@ -19,7 +19,7 @@ import logging from functools import reduce -from typing import Any, Dict, Tuple, Union +from typing import Any, Dict, List, Optional, Tuple, Union import h5py import numpy as np @@ -190,15 +190,18 @@ class MPESReader(MultiFormatReader): # Whitelist for the NXDLs that the reader supports and can process supported_nxdls = ["NXmpes", "NXmpes_arpes"] + config_file: Optional[str] = None - def __init__(self): - super().__init__() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.eln_data = None self.extensions = { ".yml": self.handle_eln_file, ".yaml": self.handle_eln_file, + ".json": self.set_config_file, ".h5": self.handle_hdf5_file, + ".hdf5": self.handle_hdf5_file, } def handle_hdf5_file(self, file_path: str) -> Dict[str, Any]: @@ -207,6 +210,12 @@ def handle_hdf5_file(self, file_path: str) -> Dict[str, Any]: return {} + def set_config_file(self, file_path: str) -> Dict[str, Any]: + if self.config_file is not None: + logger.info(f"Config file already set. Skipping the new file {file_path}.") + self.config_file = file_path + return {} + def handle_eln_file(self, file_path: str) -> Dict[str, Any]: self.eln_data = parse_yml( file_path, @@ -263,6 +272,9 @@ def get_data(self, path: str) -> Any: f"contain entry corresponding to the path {path}" ) + def get_data_dims(self) -> List[str]: + return list(map(str, self.data_xarray.dims)) + def get_attr(self, path: str) -> Any: try: return iterate_dictionary(self.data_xarray.attrs, path) diff --git a/tests/data/config_file.json b/tests/data/config_file.json index cae3472..a522fc0 100644 --- a/tests/data/config_file.json +++ b/tests/data/config_file.json @@ -2,7 +2,6 @@ "/@default": "entry", "/ENTRY[entry]/@default": "data", "/ENTRY[entry]/definition": "NXmpes", - "/ENTRY[entry]/definition/@version": "None", "/ENTRY[entry]/title": "@attrs:metadata/entry_title", "/ENTRY[entry]/start_time": "@attrs:metadata/timing/acquisition_start", "/ENTRY[entry]/experiment_institution": "Fritz Haber Institute - Max Planck Society", diff --git a/tests/data/example.nxs b/tests/data/example.nxs index 7a9c454c5028445fae01e6f719f66f96c61e59c7..f9ad70fdf76de417a5110260fcfee4bc3112713a 100644 GIT binary patch delta 361 zcmZ{eJ5B;&7)2QrL_mb`h4OGv5fujidEfw|z^~C#N(&43U}8;i3KL3;p*114Hk#Rh z3(y_tJ}AThc7DmtO>R!|oqn(9?~l?&!Osk{1PC%mh=imTKNGH>0mSf delta 363 zcmZ{e%`O9R6vYkw(o*gCXiHl&sG?OfovGHSsz1RBOG_I&VQEY9+cnnzE=jtuw9w32 z(g)xXJc7gn7%^Kn=jNPyPjc?CUqKY;jm4U9a`+tX75$J zCzZDRNQ%R#ogVrtUFxbRMr?nS$ErBX7&Xnb91oUJY19p~W|oYqV;EU^uZmRpKRf4# zgnYItrs008q8W1=dGTEEj(_fpBl*%&g7RS?!mfQFt}eXwQ%{^Ky8G1=^@RUo#*XAG)yaT}MKqiAk>AkLK^yC-2~ From 9bcfa51fe07bf44811e31bd831dfa47dddbeb7c1 Mon Sep 17 00:00:00 2001 From: domna Date: Wed, 17 Jul 2024 18:15:42 +0200 Subject: [PATCH 07/25] Update example file with latest defs --- tests/data/example.nxs | Bin 4375960 -> 4375944 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/data/example.nxs b/tests/data/example.nxs index f9ad70fdf76de417a5110260fcfee4bc3112713a..8cca44b216d144ea914f1474648aefbb919d29b1 100644 GIT binary patch delta 1582 zcmZA1drVtZ7y$5|dsn!(yb2pq)-f)RLc0RHEfgv~;C?KwXd*0ej=3zbOnGS3VxogT zjG%F2SvK3;z=4eMFj)dFalPyCpm9k_X5!+^@DJlKG4h8dC?ng94>S_|Zg0ynoBVqE zednHY&pF?{9X)e~8$ux`849~vNJ#7735rhP|LX{S}Zm*@#Z z2hEoW8HjpPPi*JgWC&{oYH(I?cYBN>BynLV5l5M!yL}Wo} zC>>>>Oq7MJC>!OV?Pv$eMUS97WJCFAC$ghmr~vIog-Ak0s2G)?Qsh8os2n+w3++K} zRDmi{721nr^J%OG?4XkIaar0yD-bd`^apifAIN8F4 zg6Cpl-%Kj)F4D$4HZBKLxVb=vPX2A@NhPs+7~% z2SlLqsv?yn%M#gC;!`B6c{>rW6D-t4b@7VGitZ>sn#pZOep9eOGwks1`bzp(&mVdl zVEupUt&N@jOK(@$*t*`%v)R9uR6+!%{iEb@eCrfWcvKDkA~Mg0@(!nV+|B2BQ0DXjz#(<5fm0mqmcYsyMYttcJTKPCvfNo?veDi-C`qsLyStJ(8pwg?a{cul zU%l5;<*ljn)yi(!TQAGI=wJ@mGav3^-{-)|3=*UVR+YT$t(%*TMfB0r5|onxs_D%r zU6P=scqUP;AZJ{ZJalWR5sz~0=_2SPxKY~YfKs9TrPtol$Ix*H$Pc2M4%nX=?@P`w z6V@ix#&Z%M_z<*98$?~U3uzCg1^UcIrFl3VO2sypaj$N*_qdbYFVhd+L`4@KU@URy z%dFo8CkTnr`+K0Aw^isbGPQb%NEJ6G{8f5FjocKl`e8S0nu(K!J+PNdu?3Gl;UZnC zhAuKhl^VD~R%n_J4&XIMeQ+GByx_x0LN)P878^vGU5oL0gAtbq_{bcq1tN;pI%BD= z>ZOy-IDD=F_G7cr0B?#N$v*l@F3~%`XoPEGyZWFxC zoBW`jy<9-AG=Ud$KR3l^uiOW(@>cBnA=rYi-P%5Qzm>OP{I%;-S~UU|zJuy36+_8l z(qkjI{(-T0HkQbSm^K3WguXEhpRgc>#%(k<4nzFZ`S>=i(*nPKLH|jm79!-2CXPN? z&#h8W?E+}jG7g`!-IMU{4qWJT6pmy&uNr&e@P0Zc%eV?J5Mk?Ketixc4?W&&Y7V9{ T#jU?<+I$_FaEE8FLmK%H);Ia% delta 1601 zcmY+^e@q)y902gXJ4^5QwE`k#z}8ZrEk)MSmKLVc`$8Nt9T1IUn+BF80veP4qhpDS zBgTz6XG$*P%i_dhLLwm%YQ;xpmUM9@G8V-@jLx{o{xK?$5aW+dUGRJTmC)qVclUkw zzW46?-rd-R3&JD`+u7u}qnkydr?!Q1`vmB;Up)Cw?UmHIJsSOvspj2Ac`M!17aP^j zGZv1y4JS*)As38CtJKh}j0i*|4-h@cAqFB5ndFi@ zl1~aqA=yQYq=*!g67nD^B@dA@Vj|^aH>n_%q>Ahzdx@D?NHwvN8d6K@h>h5ZgVYlz zX&?`iMzW8%h?_JK5AhNoX(oOWAT6Yo>?eF>gSZTCOt!Ey|3#`mxL{8_G|5_6ujfE3_C({Ky~LUB~$FpIY0;PyMB} zc7FbEtxfRD39bE-&)!oEjG1`!AEitX2O^a4c{TK_%-#2u*9A2+D(6Vbgn?a@KGjXkpTS|e2&RdW~Qjr!9o6-2ps~8;B`F= z$^&BhDbSq*hcUFG*s-&bS@>)YoTItq0Qbo7qmC;b# z=gnYaA&hFx7~VERclBg?@ItyUnZe&@3&p!E@B+hN4l5V76?OkeBX`%pHoc^NV^QXE zleY5O4|}apg0}>C5|^r>Mx>D1cGH5A74ost3I__4mu1E+jYsFTinBF-w+_k#X2g4T zD(7llD&H0#cECx-R`G5<*hG7S_A0BZR*41B?W9O&V=}UEOQL-->V$1Q^^CeSvLDq zQ|eLY&UWEU8yui4n{Dul9Ln_IOQoy}7lUw3j_-_pp}=Nt;_*}l^CA3iiuRtq9ZrkJ z0H_z|tMG$%XrhGQwkPveb->G_nY!+Tdib3Vc(VtuB7CZ@?UB$t0g@O(ZGv(*Sxuep z4E9dYw8NK^(Rex<;nNdP&iKtq_*q1ko*l!dr{FXE>JZCC`r?j=3-2bM(>N`O(v)_H z=58jHo6 Date: Wed, 17 Jul 2024 19:52:50 +0200 Subject: [PATCH 08/25] Remove pyyaml from dependencies (handled by pynx via the multi format reader) --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 33b5582..51259a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,6 @@ classifiers = [ ] dependencies = [ "h5py>=3.6.0", - "PyYAML>=6.0", "xarray>=0.20.2", "pynxtools@git+https://github.com/FAIRmat-NFDI/pynxtools#egg=multi-format-reader" ] From 6b52fba84adcfe4747e72b6df1e324b38d8f62f3 Mon Sep 17 00:00:00 2001 From: domna Date: Wed, 17 Jul 2024 19:57:33 +0200 Subject: [PATCH 09/25] Use uv for dependency installation --- .github/workflows/pytest.yml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 2080283..d79dfca 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -9,6 +9,9 @@ on: pull_request: branches: [main] +env: + UV_SYSTEM_PYTHON: true + jobs: pytest: runs-on: ubuntu-latest @@ -19,19 +22,17 @@ jobs: steps: - uses: actions/checkout@v3 - with: - fetch-depth: 0 - submodules: recursive - name: Set up Python ${{ matrix.python_version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_version }} - name: Install dependencies run: | - python -m pip install --upgrade pip + curl -LsSf https://astral.sh/uv/install.sh | sh + uv pip install coverage coveralls - name: Install package run: | - pip install ".[dev]" + uv pip install ".[dev]" - name: Test with pytest run: | pytest tests From c28be04751adac0f418b5762645ad4ac635c107f Mon Sep 17 00:00:00 2001 From: domna Date: Wed, 17 Jul 2024 20:02:25 +0200 Subject: [PATCH 10/25] Remove unecessary coveralls installation --- .github/workflows/pytest.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index d79dfca..379ae98 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -26,12 +26,9 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_version }} - - name: Install dependencies - run: | - curl -LsSf https://astral.sh/uv/install.sh | sh - uv pip install coverage coveralls - name: Install package run: | + curl -LsSf https://astral.sh/uv/install.sh | sh uv pip install ".[dev]" - name: Test with pytest run: | From 8de47689f0b4fee72e0b10f4e1a7e91dfe39ef47 Mon Sep 17 00:00:00 2001 From: domna Date: Wed, 17 Jul 2024 20:02:34 +0200 Subject: [PATCH 11/25] Change git install notation --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 51259a9..1c61018 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ dependencies = [ "h5py>=3.6.0", "xarray>=0.20.2", - "pynxtools@git+https://github.com/FAIRmat-NFDI/pynxtools#egg=multi-format-reader" + "pynxtools@git+https://github.com/FAIRmat-NFDI/pynxtools @ multi-format-reader" ] [project.urls] From 880fe97ba320d46608e7156892afa7d86cdf8ebd Mon Sep 17 00:00:00 2001 From: domna Date: Wed, 17 Jul 2024 20:04:29 +0200 Subject: [PATCH 12/25] Try another notation --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1c61018..1334bce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ dependencies = [ "h5py>=3.6.0", "xarray>=0.20.2", - "pynxtools@git+https://github.com/FAIRmat-NFDI/pynxtools @ multi-format-reader" + "pynxtools@git+https://github.com/FAIRmat-NFDI/pynxtools@multi-format-reader#egg=pynxtools" ] [project.urls] From 8c0f685b92bd548c891042eb4d1e10ecd79c0a9c Mon Sep 17 00:00:00 2001 From: domna Date: Thu, 18 Jul 2024 08:43:42 +0200 Subject: [PATCH 13/25] Remove unecessary functions --- pynxtools_mpes/reader.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/pynxtools_mpes/reader.py b/pynxtools_mpes/reader.py index 2d7b4ff..9aecab1 100644 --- a/pynxtools_mpes/reader.py +++ b/pynxtools_mpes/reader.py @@ -164,31 +164,9 @@ def _getattr(obj, attr): return reduce(_getattr, [obj] + attr.split(".")) -def fill_data_indices_in_config(config_file_dict, x_array_loaded): - """Add data indices key value pairs to the config_file - dictionary from the xarray dimensions if not already - present. - """ - for key in list(config_file_dict): - if "*" in key: - value = config_file_dict[key] - for dim in x_array_loaded.dims: - new_key = key.replace("*", dim) - new_value = value.replace("*", dim) - - if ( - new_key not in config_file_dict.keys() - and new_value not in config_file_dict.values() - ): - config_file_dict[new_key] = new_value - - config_file_dict.pop(key) - - class MPESReader(MultiFormatReader): """MPES-specific reader class""" - # Whitelist for the NXDLs that the reader supports and can process supported_nxdls = ["NXmpes", "NXmpes_arpes"] config_file: Optional[str] = None @@ -232,9 +210,6 @@ def get_eln_data(self, path: str) -> Any: return self.eln_data.get(path) - def setup_template(self) -> Dict[str, Any]: - return {} - def handle_objects(self, objects: Tuple[Any]) -> Dict[str, Any]: if isinstance(objects, xr.DataArray): # Should normally be a tuple, but in the From 25983bae61f04ad379d77fd5b335fe4759ba4766 Mon Sep 17 00:00:00 2001 From: domna Date: Mon, 22 Jul 2024 09:29:47 +0200 Subject: [PATCH 14/25] Update get_data_dims --- pynxtools_mpes/reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pynxtools_mpes/reader.py b/pynxtools_mpes/reader.py index 9aecab1..35e2be8 100644 --- a/pynxtools_mpes/reader.py +++ b/pynxtools_mpes/reader.py @@ -247,7 +247,7 @@ def get_data(self, path: str) -> Any: f"contain entry corresponding to the path {path}" ) - def get_data_dims(self) -> List[str]: + def get_data_dims(self, path: str) -> List[str]: return list(map(str, self.data_xarray.dims)) def get_attr(self, path: str) -> Any: From 430255eb5e2255933b2f40d80fd981b06ba680e1 Mon Sep 17 00:00:00 2001 From: domna Date: Tue, 23 Jul 2024 19:35:05 +0200 Subject: [PATCH 15/25] Add keys to callbacks --- pynxtools_mpes/reader.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pynxtools_mpes/reader.py b/pynxtools_mpes/reader.py index 35e2be8..56a288d 100644 --- a/pynxtools_mpes/reader.py +++ b/pynxtools_mpes/reader.py @@ -203,7 +203,7 @@ def handle_eln_file(self, file_path: str) -> Dict[str, Any]: return {} - def get_eln_data(self, path: str) -> Any: + def get_eln_data(self, key: str, path: str) -> Any: """Returns data from the given eln path.""" if self.eln_data is None: return None @@ -231,7 +231,7 @@ def handle_objects(self, objects: Tuple[Any]) -> Dict[str, Any]: ) return {} - def get_data(self, path: str) -> Any: + def get_data(self, key: str, path: str) -> Any: try: value = rgetattr(obj=self.data_xarray, attr=path) if path.split("/")[-1] == "@axes": @@ -247,10 +247,10 @@ def get_data(self, path: str) -> Any: f"contain entry corresponding to the path {path}" ) - def get_data_dims(self, path: str) -> List[str]: + def get_data_dims(self, key: str, path: str) -> List[str]: return list(map(str, self.data_xarray.dims)) - def get_attr(self, path: str) -> Any: + def get_attr(self, key: str, path: str) -> Any: try: return iterate_dictionary(self.data_xarray.attrs, path) except KeyError: From 9f303d0a13b3ca568716d911df270561296a7a97 Mon Sep 17 00:00:00 2001 From: domna Date: Thu, 25 Jul 2024 20:46:31 +0200 Subject: [PATCH 16/25] Add parent_key to parse_yml --- pynxtools_mpes/reader.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pynxtools_mpes/reader.py b/pynxtools_mpes/reader.py index 56a288d..c8a50f7 100644 --- a/pynxtools_mpes/reader.py +++ b/pynxtools_mpes/reader.py @@ -199,6 +199,7 @@ def handle_eln_file(self, file_path: str) -> Dict[str, Any]: file_path, convert_dict=CONVERT_DICT, replace_nested=REPLACE_NESTED, + parent_key="/ENTRY", ) return {} From 2253039cad88fe816b02336323c10440b6ae9520 Mon Sep 17 00:00:00 2001 From: domna Date: Thu, 25 Jul 2024 21:11:08 +0200 Subject: [PATCH 17/25] Use ENTRY instead of ENTRY[entry] --- tests/data/config_file.json | 60 ++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/data/config_file.json b/tests/data/config_file.json index cf0934d..6a412c5 100644 --- a/tests/data/config_file.json +++ b/tests/data/config_file.json @@ -1,26 +1,26 @@ { "/@default": "entry", - "/ENTRY[entry]/@default": "data", - "/ENTRY[entry]/definition": "NXmpes", - "/ENTRY[entry]/title": "@attrs:metadata/entry_title", - "/ENTRY[entry]/start_time": "@attrs:metadata/timing/acquisition_start", - "/ENTRY[entry]/experiment_institution": "Fritz Haber Institute - Max Planck Society", - "/ENTRY[entry]/experiment_facility": "Time Resolved ARPES", - "/ENTRY[entry]/experiment_laboratory": "Clean Room 4", - "/ENTRY[entry]/entry_identifier": "@attrs:metadata/entry_identifier", - "/ENTRY[entry]/end_time": "@attrs:metadata/timing/acquisition_stop", - "/ENTRY[entry]/duration": "@attrs:metadata/timing/acquisition_duration", - "/ENTRY[entry]/duration/@units": "s", - "/ENTRY[entry]/collection_time": "@attrs:metadata/timing/collection_time", - "/ENTRY[entry]/collection_time/@units": "s", - "/ENTRY[entry]/USER[user]": { + "/ENTRY/@default": "data", + "/ENTRY/definition": "NXmpes", + "/ENTRY/title": "@attrs:metadata/entry_title", + "/ENTRY/start_time": "@attrs:metadata/timing/acquisition_start", + "/ENTRY/experiment_institution": "Fritz Haber Institute - Max Planck Society", + "/ENTRY/experiment_facility": "Time Resolved ARPES", + "/ENTRY/experiment_laboratory": "Clean Room 4", + "/ENTRY/entry_identifier": "@attrs:metadata/entry_identifier", + "/ENTRY/end_time": "@attrs:metadata/timing/acquisition_stop", + "/ENTRY/duration": "@attrs:metadata/timing/acquisition_duration", + "/ENTRY/duration/@units": "s", + "/ENTRY/collection_time": "@attrs:metadata/timing/collection_time", + "/ENTRY/collection_time/@units": "s", + "/ENTRY/USER[user]": { "name": "@attrs:metadata/user0/name", "role": "@attrs:metadata/user0/role", "affiliation": "@attrs:metadata/user0/affiliation", "address": "@attrs:metadata/user0/address", "email": "@attrs:metadata/user0/email" }, - "/ENTRY[entry]/INSTRUMENT[instrument]": { + "/ENTRY/INSTRUMENT[instrument]": { "name": "Time-of-flight momentum microscope equipped delay line detector, at the endstation of the high rep-rate HHG source at FHI", "name/@short_name": "TR-ARPES @ FHI", "energy_resolution": { @@ -100,7 +100,7 @@ } } }, - "/ENTRY[entry]/INSTRUMENT[instrument]/ELECTRONANALYSER[electronanalyser]/COLLECTIONCOLUMN[collectioncolumn]": { + "/ENTRY/INSTRUMENT[instrument]/ELECTRONANALYSER[electronanalyser]/COLLECTIONCOLUMN[collectioncolumn]": { "projection": "@attrs:metadata/instrument/analyzer/projection", "scheme": "momentum dispersive", "lens_mode": "@attrs:metadata/instrument/analyzer/lens_mode", @@ -138,14 +138,14 @@ } } }, - "/ENTRY[entry]/INSTRUMENT[instrument]/ELECTRONANALYSER[electronanalyser]/ENERGYDISPERSION[energydispersion]": { + "/ENTRY/INSTRUMENT[instrument]/ELECTRONANALYSER[electronanalyser]/ENERGYDISPERSION[energydispersion]": { "pass_energy": "@attrs:metadata/file/KTOF:Lens:TOF:V", "pass_energy/@units": "eV", "scheme": "tof", "tof_distance": 0.9, "tof_distance/@units": "m" }, - "/ENTRY[entry]/INSTRUMENT[instrument]/ELECTRONANALYSER[electronanalyser]/DETECTOR[detector]": { + "/ENTRY/INSTRUMENT[instrument]/ELECTRONANALYSER[electronanalyser]/DETECTOR[detector]": { "amplifier_type": "MCP", "detector_type": "DLD", "sensor_pixels": [ @@ -160,7 +160,7 @@ "detector_voltage": "@attrs:metadata/file/KTOF:Lens:UDLD:V", "detector_voltage/@units": "V" }, - "/ENTRY[entry]/INSTRUMENT[instrument]/sourceTYPE[source_probe]": { + "/ENTRY/INSTRUMENT[instrument]/sourceTYPE[source_probe]": { "name": "HHG @ TR-ARPES @ FHI", "probe": "photon", "type": "HHG laser", @@ -169,7 +169,7 @@ "frequency/@units": "kHz", "associated_beam": "/entry/instrument/beam_probe" }, - "/ENTRY[entry]/INSTRUMENT[instrument]/beamTYPE[beam_probe]": { + "/ENTRY/INSTRUMENT[instrument]/beamTYPE[beam_probe]": { "distance": 0.0, "distance/@units": "mm", "incident_energy": "@attrs:metadata/instrument/beam/probe/incident_energy", @@ -184,7 +184,7 @@ "extent/@units": "µm", "associated_source": "/entry/instrument/source_probe" }, - "/ENTRY[entry]/INSTRUMENT[instrument]/sourceTYPE[source_pump]": { + "/ENTRY/INSTRUMENT[instrument]/sourceTYPE[source_pump]": { "name": "OPCPA @ TR-ARPES @ FHI", "probe": "visible light", "type": "Optical Laser", @@ -193,7 +193,7 @@ "frequency/@units": "kHz", "associated_beam": "/entry/instrument/beam_pump" }, - "/ENTRY[entry]/INSTRUMENT[instrument]/beamTYPE[beam_pump]": { + "/ENTRY/INSTRUMENT[instrument]/beamTYPE[beam_pump]": { "distance": 0.0, "distance/@units": "mm", "incident_energy": "@attrs:metadata/instrument/beam/pump/incident_energy", @@ -216,7 +216,7 @@ "fluence/@units": "mJ/cm^2", "associated_source": "/entry/instrument/source_pump" }, - "/ENTRY[entry]/INSTRUMENT[instrument]/MANIPULATOR[manipulator]": { + "/ENTRY/INSTRUMENT[instrument]/MANIPULATOR[manipulator]": { "temperature_sensor": { "name": "sample_temperature", "measurement": "temperature", @@ -260,7 +260,7 @@ ] } }, - "/ENTRY[entry]/SAMPLE[sample]": { + "/ENTRY/SAMPLE[sample]": { "preparation_date": "@attrs:metadata/sample/preparation_date", "history/notes/type": "text/plain", "history/notes/description": "@attrs:metadata/sample/sample_history", @@ -344,7 +344,7 @@ ] } }, - "/ENTRY[entry]/PROCESS_MPES[process]/DISTORTION[distortion]": { + "/ENTRY/PROCESS_MPES[process]/DISTORTION[distortion]": { "symmetry": "@attrs:metadata/momentum_correction/rotsym", "symmetry/@units": "", "original_centre": "@attrs:metadata/momentum_correction/pcent", @@ -356,7 +356,7 @@ "rdeform_field": "@attrs:metadata/momentum_correction/rdeform_field", "rdeform_field/@units": "" }, - "/ENTRY[entry]/PROCESS_MPES[process]/REGISTRATION[registration]": { + "/ENTRY/PROCESS_MPES[process]/REGISTRATION[registration]": { "depends_on": "/entry/process/registration/tranformations/rot_z", "TRANSFORMATIONS[tranformations]": { "AXISNAME[trans_x]": "@attrs:metadata/momentum_correction/adjust_params/xtrans", @@ -377,7 +377,7 @@ "AXISNAME[rot_z]/@depends_on": "trans_y" } }, - "/ENTRY[entry]/PROCESS_MPES[process]/energy_calibration":{ + "/ENTRY/PROCESS_MPES[process]/energy_calibration":{ "coefficients": "@attrs:metadata/energy_correction/calibration/coeffs", "coefficients/@units": "", "fit_function": "@attrs:metadata/energy_correction/calibration/fit_function", @@ -387,7 +387,7 @@ "calibrated_axis/@units": "eV", "physical_quantity": "energy" }, - "/ENTRY[entry]/PROCESS_MPES[process]/CALIBRATION[kx_calibration]": { + "/ENTRY/PROCESS_MPES[process]/CALIBRATION[kx_calibration]": { "scaling": "@attrs:metadata/momentum_correction/calibration/scale_kx", "scaling/@units": "", "offset": "@attrs:metadata/momentum_correction/offset_kx", @@ -396,7 +396,7 @@ "calibrated_axis/@units": "1/angstrom", "physical_quantity": "momentum" }, - "/ENTRY[entry]/PROCESS_MPES[process]/CALIBRATION[ky_calibration]": { + "/ENTRY/PROCESS_MPES[process]/CALIBRATION[ky_calibration]": { "scaling": "@attrs:metadata/momentum_correction/calibration/scale_ky", "scaling/@units": "", "offset": "@attrs:metadata/momentum_correction/offset_ky", @@ -405,7 +405,7 @@ "calibrated_axis/@units": "1/angstrom", "physical_quantity": "momentum" }, - "/ENTRY[entry]/data": { + "/ENTRY/data": { "@axes": "@data:dims", "AXISNAME_indices[@*_indices]": "@data:*.index", "@signal": "data", From 700aa70f98059c60552f5e246ccb263580d5943b Mon Sep 17 00:00:00 2001 From: Florian Dobener Date: Fri, 26 Jul 2024 13:22:11 +0200 Subject: [PATCH 18/25] Update pynxtools_mpes/reader.py Co-authored-by: Lukas Pielsticker <50139597+lukaspie@users.noreply.github.com> --- pynxtools_mpes/reader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pynxtools_mpes/reader.py b/pynxtools_mpes/reader.py index c8a50f7..3ad4d31 100644 --- a/pynxtools_mpes/reader.py +++ b/pynxtools_mpes/reader.py @@ -209,7 +209,7 @@ def get_eln_data(self, key: str, path: str) -> Any: if self.eln_data is None: return None - return self.eln_data.get(path) + return self.eln_data.get(key.replace(f"/ENTRY[{self.callbacks.entry_name}]", "/ENTRY")) def handle_objects(self, objects: Tuple[Any]) -> Dict[str, Any]: if isinstance(objects, xr.DataArray): From 1f52deab3f419bbd16ec42d7e9aa21b98f5606a5 Mon Sep 17 00:00:00 2001 From: domna Date: Fri, 26 Jul 2024 13:28:14 +0200 Subject: [PATCH 19/25] Ruff format --- pynxtools_mpes/reader.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pynxtools_mpes/reader.py b/pynxtools_mpes/reader.py index 3ad4d31..493f92b 100644 --- a/pynxtools_mpes/reader.py +++ b/pynxtools_mpes/reader.py @@ -209,7 +209,9 @@ def get_eln_data(self, key: str, path: str) -> Any: if self.eln_data is None: return None - return self.eln_data.get(key.replace(f"/ENTRY[{self.callbacks.entry_name}]", "/ENTRY")) + return self.eln_data.get( + key.replace(f"/ENTRY[{self.callbacks.entry_name}]", "/ENTRY") + ) def handle_objects(self, objects: Tuple[Any]) -> Dict[str, Any]: if isinstance(objects, xr.DataArray): From 6d396d9c6aa289ab0b6d1d04c0230d5118ca4e44 Mon Sep 17 00:00:00 2001 From: domna Date: Fri, 26 Jul 2024 13:33:49 +0200 Subject: [PATCH 20/25] Upgrade ruff to 0.5.5 and add pre-commit hook --- .pre-commit-config.yaml | 9 +++++++++ dev-requirements.txt | 28 +++++++++++----------------- pyproject.toml | 2 +- 3 files changed, 21 insertions(+), 18 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..81a6893 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,9 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.5.5 + hooks: + # Run the linter. + - id: ruff + # Run the formatter. + - id: ruff-format \ No newline at end of file diff --git a/dev-requirements.txt b/dev-requirements.txt index a0fe340..9bcb621 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,9 +1,5 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --extra=dev --output-file=dev-requirements.txt pyproject.toml -# +# This file was autogenerated by uv via the following command: +# uv pip compile --extra=dev --output-file=dev-requirements.txt pyproject.toml anytree==2.12.1 # via pynxtools ase==3.22.1 @@ -27,8 +23,8 @@ fonttools==4.49.0 # via matplotlib h5py==3.10.0 # via - # pynxtools # pynxtools-mpes (pyproject.toml) + # pynxtools importlib-metadata==7.0.1 # via pynxtools iniconfig==2.0.0 @@ -67,11 +63,13 @@ pandas==2.2.0 # xarray pillow==10.0.1 # via matplotlib +pip==24.1.2 + # via pip-tools pip-tools==7.4.0 # via pynxtools-mpes (pyproject.toml) pluggy==1.4.0 # via pytest -pynxtools==0.4.0 +pynxtools @ git+https://github.com/FAIRmat-NFDI/pynxtools@ac83f51e96acefb54eae617871a9ee2a0a3a37a0#egg=pynxtools # via pynxtools-mpes (pyproject.toml) pyparsing==3.1.1 # via matplotlib @@ -88,13 +86,13 @@ python-dateutil==2.8.2 pytz==2024.1 # via pandas pyyaml==6.0.1 - # via - # pynxtools - # pynxtools-mpes (pyproject.toml) -ruff==0.3.4 + # via pynxtools +ruff==0.5.5 # via pynxtools-mpes (pyproject.toml) scipy==1.12.0 # via ase +setuptools==71.1.0 + # via pip-tools six==1.16.0 # via # anytree @@ -116,11 +114,7 @@ wheel==0.42.0 # via pip-tools xarray==2024.2.0 # via - # pynxtools # pynxtools-mpes (pyproject.toml) + # pynxtools zipp==3.17.0 # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -# pip -# setuptools diff --git a/pyproject.toml b/pyproject.toml index 1334bce..b2b9b87 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ [project.optional-dependencies] dev = [ "mypy", - "ruff==0.3.4", + "ruff==0.5.5", "pytest", "types-pyyaml", "pip-tools", From 1fefb4e3384c73f3b1fdd0df62749b62475af5c5 Mon Sep 17 00:00:00 2001 From: domna Date: Fri, 26 Jul 2024 13:37:16 +0200 Subject: [PATCH 21/25] Upgrade workflows --- .github/workflows/pylint.yml | 19 ++++++++----------- .github/workflows/pytest.yml | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 4db8d04..53e0ea2 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -2,26 +2,23 @@ name: linting on: [push] +env: + UV_SYSTEM_PYTHON: true + jobs: linting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python 3.10 - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: "3.10" - - name: Install dependencies - run: | - git submodule sync --recursive - git submodule update --init --recursive --jobs=4 - python -m pip install --upgrade pip - name: Install package run: | - python -m pip install --no-deps . - - name: Install requirements - run: | - python -m pip install -r dev-requirements.txt + curl -LsSf https://astral.sh/uv/install.sh | sh + uv pip install --no-deps . + uv pip install -r dev-requirements.txt - name: ruff run: | ruff pynxtools_mpes tests diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 379ae98..4efeddb 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -21,7 +21,7 @@ jobs: python_version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python_version }} uses: actions/setup-python@v5 with: From 740fc35ce5ccfbd2fb98599989405b5960da49ea Mon Sep 17 00:00:00 2001 From: domna Date: Fri, 26 Jul 2024 13:37:55 +0200 Subject: [PATCH 22/25] Use ruff check --- .github/workflows/pylint.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 53e0ea2..e5696a0 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -19,10 +19,10 @@ jobs: curl -LsSf https://astral.sh/uv/install.sh | sh uv pip install --no-deps . uv pip install -r dev-requirements.txt - - name: ruff + - name: ruff check run: | - ruff pynxtools_mpes tests - - name: ruff formatting + ruff check pynxtools_mpes tests + - name: ruff format run: | ruff format --check pynxtools_mpes tests - name: mypy From 5c25635a77c37570bb3307ff1062eef5d769e19b Mon Sep 17 00:00:00 2001 From: rettigl Date: Fri, 26 Jul 2024 22:03:24 +0200 Subject: [PATCH 23/25] use pynxtools logger, and consistently use this logger --- pynxtools_mpes/reader.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pynxtools_mpes/reader.py b/pynxtools_mpes/reader.py index 493f92b..f0cdc62 100644 --- a/pynxtools_mpes/reader.py +++ b/pynxtools_mpes/reader.py @@ -29,8 +29,7 @@ from pynxtools_mpes.mappings import CONVERT_DICT, DEFAULT_UNITS, REPLACE_NESTED -logger = logging.getLogger(__name__) -logger.setLevel(logging.INFO) +logger = logging.getLogger("pynxtools") def recursive_parse_metadata( @@ -228,7 +227,7 @@ def handle_objects(self, objects: Tuple[Any]) -> Dict[str, Any]: self.data_xarray = objects[0] return {} - logging.info( + logger.info( f"Error while reading objects: {objects} does not contain an xarray object." " Skipping the objects." ) @@ -242,10 +241,10 @@ def get_data(self, key: str, path: str) -> Any: return value except ValueError: - print(f"Incorrect axis name corresponding to the path {path}") + logger.warning(f"Incorrect axis name corresponding to the path {path}") except AttributeError: - print( + logger.warning( "Incorrect naming syntax or the xarray doesn't " f"contain entry corresponding to the path {path}" ) @@ -257,7 +256,7 @@ def get_attr(self, key: str, path: str) -> Any: try: return iterate_dictionary(self.data_xarray.attrs, path) except KeyError: - logging.info(f"Path {path} not found. Skipping the entry.") + logger.info(f"Path {path} not found. Skipping the entry.") READER = MPESReader From 323463efb698353417cdb60412c30013eae2b6e5 Mon Sep 17 00:00:00 2001 From: Laurenz Rettig <53396064+rettigl@users.noreply.github.com> Date: Mon, 12 Aug 2024 12:49:51 +0200 Subject: [PATCH 24/25] Remove mappings (#25) * remove mappings, and adopt eln retrieval to use path rather than key * enable tests temporarily * Add units to h5 test data * re-disable tests * Add tests for new eln data formalism * Cleanup eln testing file --------- Co-authored-by: domna --- pynxtools_mpes/mappings.py | 64 ----------- pynxtools_mpes/reader.py | 22 +--- scripts/regenerate_examples.sh | 8 +- tests/data/config_file.json | 6 +- tests/data/eln_data.yaml | 111 +------------------ tests/data/example.nxs | Bin 4375944 -> 4375944 bytes tests/data/example_eln.nxs | Bin 0 -> 4375816 bytes tests/data/xarray_saved_small_calibration.h5 | Bin 6533728 -> 6539872 bytes tests/test_reader.py | 27 +++-- 9 files changed, 36 insertions(+), 202 deletions(-) delete mode 100644 pynxtools_mpes/mappings.py create mode 100644 tests/data/example_eln.nxs diff --git a/pynxtools_mpes/mappings.py b/pynxtools_mpes/mappings.py deleted file mode 100644 index d769118..0000000 --- a/pynxtools_mpes/mappings.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -Mapping dictionaries for the MPES conversion. -""" - -DEFAULT_UNITS = { - "X": "step", - "Y": "step", - "t": "step", - "tofVoltage": "V", - "extractorVoltage": "V", - "extractorCurrent": "A", - "cryoTemperature": "K", - "sampleTemperature": "K", - "dldTimeBinSize": "ns", - "delay": "ps", - "timeStamp": "s", - "energy": "eV", - "kx": "1/A", - "ky": "1/A", -} - -CONVERT_DICT = { - "Instrument": "INSTRUMENT[instrument]", - "Analyzer": "ELECTRONANALYSER[electronanalyser]", - "Manipulator": "MANIPULATOR[manipulator]", - "Beam": "beamTYPE[beam]", - "unit": "@units", - "Sample": "SAMPLE[sample]", - "Source": "sourceTYPE[source]", - "User": "USER[user]", - "energy_resolution": "energy_resolution/resolution", - "momentum_resolution": "RESOLUTION[momentum_resolution]/resolution", - "temporal_resolution": "RESOLUTION[temporal_resolution]/resolution", - "spatial_resolution": "RESOLUTION[spatial_resolution]/resolution", - "angular_resolution": "RESOLUTION[angular_resolution]/resolution", - "sample_temperature": "temperature_sensor/value", - "drain_current": "drain_current_amperemeter/value", - "photon_energy": "energy", -} - -REPLACE_NESTED = { - "SAMPLE[sample]/chemical_formula": "SAMPLE[sample]/SUBSTANCE[substance]/molecular_formula_hill", - "sourceTYPE[source]/Probe": "sourceTYPE[source_probe]", - "sourceTYPE[source]/Pump": "sourceTYPE[source_pump]", - "beamTYPE[beam]/Probe": "beamTYPE[beam_probe]", - "beamTYPE[beam]/Pump": "beamTYPE[beam_pump]", - "sample_history": "history/notes/description", - "ELECTRONANALYSER[electronanalyser]/RESOLUTION[energy_resolution]": ( - "ELECTRONANALYSER[electronanalyser]/energy_resolution" - ), - "ELECTRONANALYSER[electronanalyser]/RESOLUTION[momentum_resolution]": ( - "ELECTRONANALYSER[electronanalyser]/momentum_resolution" - ), - "ELECTRONANALYSER[electronanalyser]/RESOLUTION[spatial_resolution]": ( - "ELECTRONANALYSER[electronanalyser]/spatial_resolution" - ), - "ELECTRONANALYSER[electronanalyser]/RESOLUTION[angular_resolution]": ( - "ELECTRONANALYSER[electronanalyser]/angular_resolution" - ), - "SAMPLE[sample]/gas_pressure": "INSTRUMENT[instrument]/pressure_gauge/value", - "SAMPLE[sample]/temperature": ( - "INSTRUMENT[instrument]/MANIPULATOR[manipulator]/temperature_sensor/value" - ), -} diff --git a/pynxtools_mpes/reader.py b/pynxtools_mpes/reader.py index f0cdc62..fb5ad1e 100644 --- a/pynxtools_mpes/reader.py +++ b/pynxtools_mpes/reader.py @@ -27,8 +27,6 @@ from pynxtools.dataconverter.readers.multi.reader import MultiFormatReader from pynxtools.dataconverter.readers.utils import parse_yml -from pynxtools_mpes.mappings import CONVERT_DICT, DEFAULT_UNITS, REPLACE_NESTED - logger = logging.getLogger("pynxtools") @@ -121,15 +119,15 @@ def h5_to_xarray(faddr: str, mode: str = "r") -> xr.DataArray: f"ax{axis}" ].attrs["unit"] except (KeyError, TypeError): - xarray[bin_names[axis]].attrs["unit"] = DEFAULT_UNITS[bin_names[axis]] + xarray[bin_names[axis]].attrs["unit"] = "" try: xarray.attrs["units"] = h5_file["binned"]["BinnedData"].attrs["units"] xarray.attrs["long_name"] = h5_file["binned"]["BinnedData"].attrs[ "long_name" ] except (KeyError, TypeError): - xarray.attrs["units"] = "counts" - xarray.attrs["long_name"] = "photoelectron counts" + xarray.attrs["units"] = "" + xarray.attrs["long_name"] = "" if metadata is not None: xarray.attrs["metadata"] = metadata @@ -146,8 +144,7 @@ def iterate_dictionary(dic, key_string): if not len(keys) == 1: return iterate_dictionary(dic[keys[0]], keys[1]) else: - raise KeyError - return None + return None def rgetattr(obj, attr): @@ -196,8 +193,6 @@ def set_config_file(self, file_path: str) -> Dict[str, Any]: def handle_eln_file(self, file_path: str) -> Dict[str, Any]: self.eln_data = parse_yml( file_path, - convert_dict=CONVERT_DICT, - replace_nested=REPLACE_NESTED, parent_key="/ENTRY", ) @@ -208,9 +203,7 @@ def get_eln_data(self, key: str, path: str) -> Any: if self.eln_data is None: return None - return self.eln_data.get( - key.replace(f"/ENTRY[{self.callbacks.entry_name}]", "/ENTRY") - ) + return self.eln_data.get(path) def handle_objects(self, objects: Tuple[Any]) -> Dict[str, Any]: if isinstance(objects, xr.DataArray): @@ -253,10 +246,7 @@ def get_data_dims(self, key: str, path: str) -> List[str]: return list(map(str, self.data_xarray.dims)) def get_attr(self, key: str, path: str) -> Any: - try: - return iterate_dictionary(self.data_xarray.attrs, path) - except KeyError: - logger.info(f"Path {path} not found. Skipping the entry.") + return iterate_dictionary(self.data_xarray.attrs, path) READER = MPESReader diff --git a/scripts/regenerate_examples.sh b/scripts/regenerate_examples.sh index f7295b9..83bfbc1 100755 --- a/scripts/regenerate_examples.sh +++ b/scripts/regenerate_examples.sh @@ -7,7 +7,13 @@ function update_mpes_example { dataconverter xarray_saved_small_calibration.h5 config_file.json --reader $READER --nxdl $NXDL --output example.nxs } +function update_mpes_eln_example { + echo "Update mpes example with eln file" + dataconverter xarray_saved_small_calibration.h5 config_file.json eln_data.yaml --reader $READER --nxdl $NXDL --output example_eln.nxs +} + project_dir=$(dirname $(dirname $(realpath $0))) cd $project_dir/tests/data -update_mpes_example \ No newline at end of file +update_mpes_example +update_mpes_eln_example \ No newline at end of file diff --git a/tests/data/config_file.json b/tests/data/config_file.json index 6a412c5..a8bfd3a 100644 --- a/tests/data/config_file.json +++ b/tests/data/config_file.json @@ -2,7 +2,7 @@ "/@default": "entry", "/ENTRY/@default": "data", "/ENTRY/definition": "NXmpes", - "/ENTRY/title": "@attrs:metadata/entry_title", + "/ENTRY/title": "['@eln:/ENTRY/title', '@attrs:metadata/entry_title']", "/ENTRY/start_time": "@attrs:metadata/timing/acquisition_start", "/ENTRY/experiment_institution": "Fritz Haber Institute - Max Planck Society", "/ENTRY/experiment_facility": "Time Resolved ARPES", @@ -24,8 +24,8 @@ "name": "Time-of-flight momentum microscope equipped delay line detector, at the endstation of the high rep-rate HHG source at FHI", "name/@short_name": "TR-ARPES @ FHI", "energy_resolution": { - "resolution": 140.0, - "resolution/@units": "meV", + "resolution": "['@eln:/ENTRY/Instrument/energy_resolution', '140.0']", + "resolution/@units": "['@eln:/ENTRY/Instrument/energy_resolution/@units', 'meV']", "physical_quantity": "energy", "type": "estimated" }, diff --git a/tests/data/eln_data.yaml b/tests/data/eln_data.yaml index d6a61b1..19d9af6 100644 --- a/tests/data/eln_data.yaml +++ b/tests/data/eln_data.yaml @@ -1,109 +1,8 @@ -title: Valence Band Dynamics - 1030 nm linear p-polarized pump, 0.6 mJ/cm2 absorbed fluence +title: Title from ELN file Instrument: energy_resolution: - unit: meV - value: 140.0 - momentum_resolution: - unit: 1/angstrom - value: 0.08 - temporal_resolution: - unit: fs - value: 35.0 - Analyzer: - energy_resolution: - unit: eV - value: 110.0 - momentum_resolution: - unit: 1/angstrom - value: 0.08 - slow_axes: delay - spatial_resolution: - unit: µm - value: 10.0 - Manipulator: - sample_temperature: - unit: K - value: 300.0 - Source: - Probe: - frequency: - unit: KHz - value: 500.0 - photon_energy: - unit: eV - value: 21.7 - Pump: - frequency: - unit: KHz - value: 500.0 - photon_energy: - unit: eV - value: 1.55 - Beam: - Probe: - extent: - unit: µm - value: - - 80.0 - - 80.0 - incident_energy: - unit: eV - value: 21.7 - incident_energy_spread: - unit: eV - value: 0.11 - incident_polarization: - - 1 - - 1 - - 0 - - 0 - pulse_duration: - unit: fs - value: 20.0 - Pump: - extent: - unit: µm - value: - - 230.0 - - 265.0 - incident_energy: - unit: eV - value: 1.55 - incident_energy_spread: - unit: eV - value: 0.08 - incident_polarization: - - 1 - - -1 - - 0 - - 0 - incident_wavelength: - unit: nm - value: 800.0 - average_power: - unit: mW - value: 300.0 - pulse_energy: - unit: µJ - value: 0.6 - fluence: - unit: mJ / cm ** 2 - value: 0.15 - pulse_duration: - unit: fs - value: 35.0 + unit: eV + value: 0.14 Sample: - chemical_formula: WSe2 - description: Sample - name: WSe2 Single Crystal - preparation_date: "2019-01-13T09:00:00+00:00" - pressure: - unit: bar - value: 5.0e-14 - sample_history: Cleaved -User: - address: Faradayweg 4-6, 14915 Berlin - affiliation: Fritz Haber Institute of the Max Planck Society - email: maklar@fhi-berlin.mpg.de - name: Julian Maklar - role: Principal Investigator + name: My ELN sample name + preparation_date: "2019-01-13T09:00:00+00:00" \ No newline at end of file diff --git a/tests/data/example.nxs b/tests/data/example.nxs index 8cca44b216d144ea914f1474648aefbb919d29b1..cbf0321f272197cefeb0e81180ac34ac8e4c6372 100644 GIT binary patch delta 203 zcmZw7NlwCG0D#em^Nbao07X<#5$s~q9w~#tXi{f!=|z=k!=-KJ9h2acc7-O;n0z$V=X66ooVYhci~dkm7cz9 zu^@3Hb!*_xP$qZp!J{Y7Uc7!6lc+ZAg`^phJPp(OS(;^G(2bKkj_V)qQBke@H~joY H#pl-_R5Cqw delta 203 zcmZw7NiqXb007VuQ_Qr)lpu&P692b!I@;VPqv|bEl|8qxP^#R;X*htz+r80vTp9g& zF`;78lxZ_&Rn3{VV9}CgD^{&ZtgETpuxZP-9Su#p_UvmpaOghTFYV{qAap`ngwT9{P0D)hzwrH& HlpmkJQ}aD+ diff --git a/tests/data/example_eln.nxs b/tests/data/example_eln.nxs new file mode 100644 index 0000000000000000000000000000000000000000..d7baa113b62653d8c934d7d79ea0d6aed15d7592 GIT binary patch literal 4375816 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