diff --git a/src/hdfmap/eval_functions.py b/src/hdfmap/eval_functions.py index af1d708..3e44f5f 100644 --- a/src/hdfmap/eval_functions.py +++ b/src/hdfmap/eval_functions.py @@ -13,6 +13,7 @@ import h5py from types import EllipsisType +from .objects import Dataset from .logging import create_logger # parameters @@ -20,6 +21,7 @@ DEFAULT: typing.Any = np.array('--') # default return in eval SEP = '/' # HDF path separator OMIT = ['/value', '/data'] # omit these names in paths when determining identifier +LOCAL_NAME = 'local_name' # dataset attribute name for alt_name (DLS specific) logger = create_logger(__name__) # regex patterns re_special_characters = re.compile(r'\W') # finds all special non-alphanumberic characters @@ -33,6 +35,16 @@ logger.warning("Nexus timestamps are not convertable by datetime.fromisoformat in python version <3.11") +def generate_alt_name(hdf_dataset: h5py.Dataset) -> str | None: + """Generate alt_name of dataset if 'local_name' in attributes""" + if LOCAL_NAME in hdf_dataset.attrs: + alt_name = hdf_dataset.attrs[LOCAL_NAME] + if hasattr(alt_name, 'decode'): + alt_name = alt_name.decode() + return expression_safe_name(alt_name.split('.')[-1]) + return None + + def generate_identifier(hdf_path: str | bytes) -> str: """ Generate a valid python identifier from a hdf dataset path or other string @@ -223,7 +235,7 @@ def dataset2str(dataset: h5py.Dataset, index: int | slice | tuple[slice, ...] = return str(np.squeeze(dataset[index])) # other np.ndarray -def dataset_attribute(dataset: h5py.Dataset, attribute: str) -> str: +def dataset_attribute(dataset: h5py.Dataset | Dataset, attribute: str) -> str: """ Return attribute of dataset """ @@ -266,6 +278,36 @@ def find_or_expressions(expr: str) -> set[str]: return set(alternates) +def replace_or_expressions(expr: str, path_map: dict[str, str], path_check: h5py.File | list | dict, + data: dict[str, typing.Any]) -> str: + """ + Replaces instances of (a|b|c)-OR-expressions within the string. + The first item of the OR-expression that is found in data or if the + associated path is found in path_check will replace the expression. + + path_map = {'x': '/entry/data/x', 'y': '/entry/data/y'} + path_check = h5py.File('mydata.h5') # file contains x path but not y path + expression = '(y|x)' + new = replace_or_expressions(expression, path_map, path_check, {}) + # new == 'x' + + :param expr: string expression with components to replace + :param path_map: dict mapping from variable name to path as found in path_check + :param path_check: Hdf file to check path in, or could be a list or dict of paths. + :param data: dict mapping of variable name to data + :return: expression with replaced components + """ + while or_expressions := find_or_expressions(expr): + for or_exp in or_expressions: + names = or_exp.strip('()').split('|') + name = next( + (n for n in names if n in data), + next((n for n in names if path_map.get(n, '') in path_check), names[-1]) + ) + expr = expr.replace(or_exp, name) + return expr + + def replace_expression_vars(expr: str, mapping: dict[str, str]) -> str: """ Replace variable names in an expression @@ -293,6 +335,57 @@ def replace_expression_vars(expr: str, mapping: dict[str, str]) -> str: return expr +def replace_defaults(expr: str, path_map: dict[str, str]) -> str: + """ + Replace default specifiers in an expression + A default specifier is given as 'a?(b)', where a will be + returned if 'a' is in path_map, otherwise 'b' is returned. + + path_map = {'x': '/entry/data/x', 'y': '/entry/data/y'} + expression = 'x + y?(10) + z?(20)' + result = replace_defaults(expression, path_map) + # result == 'x + y + 20' + + :param expr: string expression with components to replace + :param path_map: dict mapping from variable name to path + :return: expression with replaced components + """ + for match in re_dataset_default.finditer(expr): + name, name_default = match.groups() + if name not in path_map: + expr = expr.replace(match.group(), name_default) + else: + expr = expr.replace(match.group(), name) + return expr + + +def replace_attributes(expr: str, path_map: dict[str, str], + hdf_file: h5py.File | dict[str, Dataset]) -> tuple[str, dict]: + """ + Find sub-strings of the form 'name@attribute', and replace this with + the attribute associated with the dataset specified by name. + + path_map = {'x': '/entry/data/x', 'y': '/entry/data/y'} + hdf_file = h5py.File('mydata.h5'), where dataset('/entry/data/x') as attribute 'units'='mm' + expression = 'x x@units' + result = replace_attributes(expression, path_map, hdf_file) + # result == 'x mm' + + :param expr: string expression with components to replace + :param path_map: dict mapping from variable name to path + :param hdf_file: h5py.File object + :return: expression with replaced components + """ + attributes = { + f"attr__{name}_{attr}": dataset_attribute(hdf_file[path], attr) + for name, attr in re_dataset_attributes.findall(expr) # name@attr + if (path := path_map.get(name, '')) in hdf_file + } + # replace name@attribute in expression + expr = re_dataset_attributes.sub(r'attr__\g<1>_\g<2>', expr) + return expr, attributes + + def extra_hdf_data(hdf_file: h5py.File) -> dict: """Extract filename, filepath and other additional data fom hdf file""" filepath = getattr(hdf_file, 'filename', 'unknown') @@ -385,36 +478,17 @@ def prepare_expression(hdf_file: h5py.File, expression: str, hdf_namespace: dict :param replace_names: dict of {'variable_name': expression} :return: str expression """ + if data_namespace is None: + data_namespace = {} # note the link to the parent namespace must be preserved # replace names with expressions expression = replace_expression_vars(expression, replace_names) - - if data_namespace is None: - data_namespace = {} - # find name@attribute in expression - attributes = { - f"attr__{name}_{attr}": dataset_attribute(hdf_file[path], attr) - for name, attr in re_dataset_attributes.findall(expression) # name@attr - if (path := hdf_namespace.get(name, '')) in hdf_file - } + # replace name@attribute in expression and add values to data_namespace + expression, attributes = replace_attributes(expression, hdf_namespace, hdf_file) data_namespace.update(attributes) # adds data in the parent function - # replace name@attribute in expression - expression = re_dataset_attributes.sub(r'attr__\g<1>_\g<2>', expression) # find values with defaults '..?(..)' - for match in re_dataset_default.finditer(expression): - name, name_default = match.groups() - if name not in hdf_namespace: - expression = expression.replace(match.group(), name_default) - else: - expression = expression.replace(match.group(), name) + expression = replace_defaults(expression, hdf_namespace) # find alternate names '(opt1|opt2|opt3)' - while or_expressions := find_or_expressions(expression): - for or_exp in or_expressions: - names = or_exp.strip('()').split('|') - name = next( - (n for n in names if n in attributes), - next((n for n in names if hdf_namespace.get(n, '') in hdf_file), names[-1]) - ) - expression = expression.replace(or_exp, name) + expression = replace_or_expressions(expression, hdf_namespace, hdf_file, attributes) return expression @@ -544,37 +618,3 @@ def format_hdf(hdf_file: h5py.File, expression: str, hdf_namespace: dict[str, st ) -class HdfMapInterpreter(asteval.Interpreter): - """ - HdfMap implementation of asteval.Interpreter - - Expression is parsed for patterns and loads HDF data before evaluation - - m = HdfMap('file.nxs') - ii = HdfMapInterpreter(m, replace_names={}, default='', **kwargs) - out = ii.eval('expression') - - :param hdfmap: HdfMap instance (including hdfmap.filename pointing to the HDF file) - :param replace_names: dict of {'variable_name': expression} - :param default: returned if varname not in namespace - :param kwargs: keyword arguments passed to asteval.Interpreter - """ - def __init__(self, hdfmap, replace_names: dict[str, str], default: typing.Any = DEFAULT, **kws): - super().__init__(**kws) - self.hdfmap = hdfmap - self.replace_names = replace_names - self.default_value = default - self.use_stored_data = False - - def eval(self, expr, lineno=0, show_errors=True, raise_errors=False): - with self.hdfmap.load_hdf() as hdf: - new_expression = prepare_expression_load_data( - hdf_file=hdf, - expression=expr, - hdf_namespace=self.hdfmap.combined, - data_namespace=self.symtable, - replace_names=self.replace_names, - default=self.default_value, - use_stored_data=self.use_stored_data - ) - return super().eval(new_expression, lineno, show_errors, raise_errors) diff --git a/src/hdfmap/hdfmap_class.py b/src/hdfmap/hdfmap_class.py index eb58c44..4d7f513 100644 --- a/src/hdfmap/hdfmap_class.py +++ b/src/hdfmap/hdfmap_class.py @@ -3,6 +3,7 @@ """ import json import typing +import re from collections import defaultdict import numpy as np @@ -11,49 +12,20 @@ from . import load_hdf from .data_holder import disp_dict, DataHolder from .logging import create_logger -from .eval_functions import (expression_safe_name, extra_hdf_data, eval_hdf, HdfMapInterpreter, +from .eval_functions import (extra_hdf_data, eval_hdf, prepare_expression_load_data, format_hdf, dataset2data, dataset2str, is_image, attrs2dict, - DEFAULT, SEP, generate_identifier, build_hdf_path) - + DEFAULT, SEP, generate_identifier, build_hdf_path, generate_alt_name, + replace_or_expressions, replace_expression_vars, replace_defaults, + find_identifiers) +from .objects import Group, Dataset # parameters -LOCAL_NAME = 'local_name' # dataset attribute name for alt_name IMAGE_DATA = 'IMAGE' # namespace name for default image data # logger logger = create_logger(__name__) -class Group(typing.NamedTuple): - nx_class: str - name: str - attrs: dict - datasets: list[str] - parent: "Group | None" - default: bool - external_file: str | None - - -class Dataset(typing.NamedTuple): - name: str - names: list[str] - size: int - shape: tuple[int] - attrs: dict - parent: Group - external_file: str | None - - -def generate_alt_name(hdf_dataset: h5py.Dataset) -> str | None: - """Generate alt_name of dataset if 'local_name' in attributes""" - if LOCAL_NAME in hdf_dataset.attrs: - alt_name = hdf_dataset.attrs[LOCAL_NAME] - if hasattr(alt_name, 'decode'): - alt_name = alt_name.decode() - return expression_safe_name(alt_name.split('.')[-1]) - return None - - class HdfMap: """ HdfMap object, container for paths of different objects in an HDF file @@ -621,7 +593,30 @@ def first_last_scannables(self, last_n: int = 1) -> tuple[dict[str, str], dict[s last = {name: self.scannables[name] for name in scannable_names[::-1][:last_n]} return first, last - def get_path(self, name_or_path): + def get_default_name(self, name_or_path: str) -> str | None: + """Return the default name of a dataset or group identifier""" + path = self.get_path(name_or_path) + if path and path in self.datasets: + return self.datasets[path].name + elif path and path in self.groups: + return self.groups[path].name + return None + + def merge_default_names(self, expression: str) -> str: + """Replace names in the expression with dataset default names""" + expression = replace_expression_vars(expression, self.alternate_names) + if expression.startswith('/'): + return self.get_default_name(expression) or expression + expression = replace_defaults(expression, self.combined) + expression = replace_or_expressions(expression, self.combined, self.datasets, {}) + ids = find_identifiers(expression) + if not ids: + return expression + default_ids = {name: self.get_default_name(name) or name for name in ids} + re_ids = re.compile("|".join(default_ids)) + return re_ids.sub(lambda m: default_ids[m.group(0)], expression) + + def get_path(self, name_or_path) -> str | None: """Return hdf path of object in HdfMap""" if name_or_path in self.datasets or name_or_path in self.groups: return name_or_path @@ -837,9 +832,14 @@ def generate_ids(self, *names: str, modify_missing: bool = True) -> list[str]: Will return the path identifier of the given name if the name is in the namespace, otherwise a valid identifier will be generated. - xlabel, ylabel = generate_axis_labels('axes', 'signal') - generate_axis_labels('my/data/label', modify_missing=True) #-> ['label', ] - generate_axis_labels('(x-y)/y', modify_missing=False) #-> ['(x-y)/y', ] + xlabel, ylabel = generate_ids('axes', 'signal') + generate_ids('my/data/label', modify_missing=True) #-> ['label', ] + generate_ids('(x-y)/y', modify_missing=False) #-> ['(x-y)/y', ] + + See also: get_default_name + + Both generate_ids and get_default_name return names from paths or alternate names, + however generate_ids just takes the last element of the path. :param names: names to generate axis labels for :param modify_missing: if True, modifies names even if they are not in namespace @@ -1095,7 +1095,7 @@ def get_dataholder(self, hdf_file: h5py.File, flatten_scannables: bool = False) def eval(self, hdf_file: h5py.File, expression: str, default=DEFAULT, local_data: dict | None = None, prefer_local: bool | None = None, - raise_errors: bool = True): + raise_errors: bool = True) -> typing.Any: """ Evaluate an expression using the namespace of the hdf file :param hdf_file: h5py.File object @@ -1141,30 +1141,37 @@ def format_hdf(self, hdf_file: h5py.File, expression: str, default=DEFAULT, raise_errors=raise_errors ) - def create_interpreter(self, default=DEFAULT, local_data: dict | None = None, prefer_local: bool | None = None): + def generate_eval_expression(self, hdf_file: h5py.File, expression: str, default=DEFAULT, + local_data: dict | None = None, prefer_local: bool | None = None) -> tuple[str, dict[str, typing.Any]]: """ - Create an interpreter object for the current file - The interpreter is a sub-class of asteval.Interpreter that parses expressions for hdfmap eval patters - and loads data when required. + Evaluate an expression using the namespace of the hdf file, + returning the evaluated expression and dictionary of data for identifiers - The hdf file self.filename is used to extract data and is only opened during evaluation. + expression, data = HdfMap.generate_eval_expression(hdf, 'signal / (monitor|ic1monitor)') - ii = HdfMap.create_interpreter() - out = ii.eval('expression') + This function serves as a useful way to debug expressions for the eval_hdf function. + Note that the hdf file object must be included as the way the expression is evaluated + means individual expression components are checked against the HdfMap namespace and + the hdf file, allowing lazy loading of data (loading only the data needed). + :param hdf_file: h5py.File object + :param expression: str expression to be evaluated :param default: returned if varname not in namespace - :param local_data: replaces the HdfMap local_data attribute for this file + :param local_data: dict of additional data to pass to the expression, as {'varname': data} :param prefer_local: uses values in local_data first if available when True + :return: expression, dict - data namespace """ - interpreter = HdfMapInterpreter( - hdfmap=self, + local_data = self._local_data.copy() if local_data is None else local_data.copy() + new_expression = prepare_expression_load_data( + hdf_file=hdf_file, + expression=expression, + hdf_namespace=self.combined, + data_namespace=local_data, replace_names=self.alternate_names, default=default, - user_symbols=self._local_data if local_data is None else local_data, - use_numpy=True + use_stored_data=self._use_local_data if prefer_local is None else prefer_local, ) - interpreter.use_stored_data = self._use_local_data if prefer_local is None else prefer_local - return interpreter + return new_expression, local_data def create_dataset_summary(self, hdf_file: h5py.File) -> str: """Create summary of all datasets in file""" diff --git a/src/hdfmap/objects.py b/src/hdfmap/objects.py new file mode 100644 index 0000000..6f6428e --- /dev/null +++ b/src/hdfmap/objects.py @@ -0,0 +1,28 @@ +""" +Definition of HdfMap objects for HDF Groups and Datasets + +These objects are a simplification of the HDF objects and won't require +the file to be open to view them, plus they are more easily serialised. +""" + +from typing import NamedTuple + + +class Group(NamedTuple): + nx_class: str + name: str + attrs: dict + datasets: list[str] + parent: "Group | None" + default: bool + external_file: str | None + + +class Dataset(NamedTuple): + name: str + names: list[str] + size: int + shape: tuple[int, ...] + attrs: dict + parent: Group + external_file: str | None diff --git a/src/hdfmap/reloader_class.py b/src/hdfmap/reloader_class.py index 01cfcdd..e2c0ec1 100644 --- a/src/hdfmap/reloader_class.py +++ b/src/hdfmap/reloader_class.py @@ -1,14 +1,21 @@ """ Reloader class +The ReLoader class is an object that contains a HdfMap and a filename, +allowing the file to be opened only when data is requested. """ import os +from typing import Any + import h5py import numpy as np +from asteval import Interpreter from . import load_hdf, HdfMap, NexusMap from .file_functions import create_hdf_map, create_nexus_map -from .eval_functions import DEFAULT +from .eval_functions import DEFAULT, prepare_expression_load_data + +Index = int | tuple | slice class HdfLoader: @@ -17,12 +24,14 @@ class HdfLoader: namespace, allowing data to be called from the file using variable names, loading only the required datasets for each operation. - ### E.G. hdf = HdfLoader('file.hdf') [data1, data2] = hdf.get_data(*['dataset_name_1', 'dataset_name_2']) data = hdf.eval('dataset_name_1 * 100 + 2') string = hdf.format('my data is {dataset_name_1:.2f}') print(hdf.summary()) + + :param hdf_filename: path to HDF file + :param hdf_map: HdfMap instance """ def __init__(self, hdf_filename: str, hdf_map: HdfMap | NexusMap | None = None): @@ -96,7 +105,7 @@ def find_names(self, string: str) -> list[str]: """ return self.map.find_names(string) - def get_data(self, *name_or_path, index: slice = (), default=None, direct_load=False): + def get_data(self, *name_or_path, index: Index = (), default=None, direct_load=False): """ Return data from dataset in file, converted into either datetime, str or squeezed numpy.array objects See hdfmap.eval_functions.dataset2data for more information. @@ -112,7 +121,7 @@ def get_data(self, *name_or_path, index: slice = (), default=None, direct_load=F return out[0] return out - def get_string(self, *name_or_path, index: slice = (), default='', units=False): + def get_string(self, *name_or_path, index: Index = (), default='', units=False): """ Return data from dataset in file, converted into summary string See hdfmap.eval_functions.dataset2data for more information. @@ -128,7 +137,7 @@ def get_string(self, *name_or_path, index: slice = (), default='', units=False): return out[0] return out - def get_image(self, index: slice = None) -> np.ndarray: + def get_image(self, index: Index | None = None) -> np.ndarray | None: """ Get image data from file, using default image path :param index: (slice,) or None to take the middle image @@ -151,6 +160,36 @@ def summary(self) -> str: with self._load() as hdf: return self.map.create_dataset_summary(hdf) + def generate_expression(self, expression: str, default=DEFAULT, + prefer_local: bool | None = None) -> tuple[str, dict[str, Any]]: + """ + Evaluate an expression using the namespace of the hdf file, + returning the evaluated expression and dictionary of data for identifiers + + expression, data = self.generate_eval_expression('signal / (monitor|ic1monitor)') + + This function serves as a useful way to debug expressions for the eval_hdf function. + Note that the hdf file object must be included as the way the expression is evaluated + means individual expression components are checked against the HdfMap namespace and + the hdf file, allowing lazy loading of data (loading only the data needed). + + :param expression: str expression to be evaluated + :param default: returned if varname not in namespace + :param prefer_local: uses values in local_data first if available when True + :return: expression, dict - data namespace + """ + prefer_local = self._prefer_local_data if prefer_local is None else prefer_local + if prefer_local and expression in self._local_data: + return expression, {expression: self._local_data[expression]} + with self._load() as hdf: + return self.map.generate_eval_expression( + hdf_file=hdf, + expression=expression, + default=default, + local_data=self._local_data, # doesn't update local data + prefer_local=prefer_local, + ) + def eval(self, expression: str, default=DEFAULT, prefer_local: bool | None = None, raise_errors: bool = True): """ Evaluate an expression using the namespace of the hdf file @@ -219,11 +258,14 @@ class NexusLoader(HdfLoader): contains the filename and hdfmap for a NeXus file, the hdfmap contains all the dataset paths and a namespace, allowing data to be called from the file using variable names, loading only the required datasets for each operation. - E.G. + hdf = NexusLoader('file.hdf') [data1, data2] = hdf.get_data(['dataset_name_1', 'dataset_name_2']) data = hdf.eval('dataset_name_1 * 100 + 2') string = hdf.format('my data is {dataset_name_1:.2f}') + + :param nxs_filename: path to HDF file + :param hdf_map: NexusMap instance, or None to generate """ map: NexusMap @@ -236,3 +278,44 @@ def get_plot_data(self) -> dict: """Return dict of useful plot data""" with self._load() as hdf: return self.map.get_plot_data(hdf) + + +class HdfMapInterpreter(Interpreter): + """ + HdfMap implementation of asteval.Interpreter + + Expression is parsed for patterns and loads HDF data before evaluation. + + ii = HdfMapInterpreter('file.nxs', replace_names={}, default='', **kwargs) + out = ii.eval('expression') + + :param filename: path to HDF file + :param hdfmap: HdfMap instance + :param replace_names: dict of {'variable_name': expression} + :param default: returned if varname not in namespace + :param kwargs: keyword arguments passed to asteval.Interpreter + """ + def __init__(self, filename: str, hdfmap: HdfMap | NexusMap | None = None, + replace_names: dict[str, str] | None = None, + default: Any = DEFAULT, **kws): + super().__init__(**kws) + self.filename = filename + if hdfmap is None: + hdfmap = create_nexus_map(filename) if filename.endswith('.nxs') else create_hdf_map(filename) + self.hdfmap = hdfmap + self.replace_names: dict[str, str] = replace_names or {} + self.default_value = default + self.use_stored_data = False + + def eval(self, expr, lineno=0, show_errors=True, raise_errors=False): + with load_hdf(self.filename) as hdf: + new_expression = prepare_expression_load_data( + hdf_file=hdf, + expression=expr, + hdf_namespace=self.hdfmap.combined, + data_namespace=self.symtable or {}, + replace_names=self.replace_names, + default=self.default_value, + use_stored_data=self.use_stored_data + ) + return super().eval(new_expression, lineno, show_errors, raise_errors) diff --git a/tests/test_hdfmap_class.py b/tests/test_hdfmap_class.py index ad5237d..1bf4448 100644 --- a/tests/test_hdfmap_class.py +++ b/tests/test_hdfmap_class.py @@ -68,6 +68,18 @@ def test_get_path(hdf_map): assert hdf_map.get_path('NXdata') == '/entry1/measurement', 'class is wrong' +def test_get_default_name(hdf_map): + assert hdf_map.get_default_name('measurement_sum') == 'sum' + + +def test_merge_default_names(hdf_map): + assert hdf_map.merge_default_names('measurement_kth') == 'kth' + assert hdf_map.merge_default_names('(asdf|ddfj|kphi)') == 'kphi' + assert hdf_map.merge_default_names('(asdf|ddfj|bob?(22))') == '22' + assert hdf_map.merge_default_names('mirrors_m2pitch, roi2_h') == 'm2pitch, h' + assert hdf_map.merge_default_names('/entry1/pil3_100k/sum') == 'sum' + + def test_get_group_path(hdf_map): assert hdf_map.get_group_path('sum') == '/entry1/pil3_100k' @@ -324,7 +336,25 @@ def test_roi(hdf_map): assert abs(out - 195839) < 0.001, "ROI rmbkg gives wrong result" -def test_interpreter(hdf_map): - ii = hdf_map.create_interpreter() - assert abs(ii('abs(mean(max(roi2_sum)))') - 359573) < 0.001, "Expression output gives wrong result" - assert 'hkl' in ii('scan_command'), 'Expression output gives wrong result' +def test_generate_eval_expression(hdf_map): + + expression = 'signal / (transmission|Transmission) / count_time' + hdf_map.add_named_expression(count_time='int(mean((count_time|counttime|t?(0.5))))') + + with hdfmap.load_hdf(FILE_HKL) as hdf: + new_expression, data = hdf_map.generate_eval_expression( + hdf_file=hdf, + expression=expression, + default=0, + local_data={'Transmission': 0.1}, + prefer_local=False, + ) + print(new_expression) + print(data) + assert new_expression == 'signal / Transmission / int(mean(count_time))' + assert len(data) == 5 + assert data.get('Transmission') == pytest.approx(1) + assert data.get('signal') == pytest.approx(0) # signal not defined for HdfMap + assert sum(data.get('count_time', 0)) == pytest.approx(101) + assert data.get('filename') == '1049598.nxs' + diff --git a/tests/test_nexus.py b/tests/test_nexus.py index 32b4b07..ae09c75 100644 --- a/tests/test_nexus.py +++ b/tests/test_nexus.py @@ -127,6 +127,10 @@ def test_generate_ids(hdf_map): assert expression == 'signal/Transmission' +def test_merge_default_names(hdf_map): + assert hdf_map.merge_default_names('signal/gains_atten_Transmission') == 'rc/Transmission' + + def test_info_nexus(hdf_map): info = hdf_map.info_nexus() assert 'NXmx: [\'/entry\']' in info diff --git a/tests/test_reloader_class.py b/tests/test_reloader_class.py index 8a3e8b7..4354cd1 100644 --- a/tests/test_reloader_class.py +++ b/tests/test_reloader_class.py @@ -1,7 +1,9 @@ import os -from hdfmap import HdfLoader, NexusLoader +from hdfmap import HdfLoader, NexusLoader, NexusMap +from hdfmap.reloader_class import HdfMapInterpreter DATA_FOLDER = os.path.join(os.path.dirname(__file__), 'data') +FILE_HKL = DATA_FOLDER + "/1049598.nxs" # hkl scan, pilatus FILE_NEW_NEXUS = DATA_FOLDER + '/1040323.nxs' # new nexus format FILE_3D_NEXUS = DATA_FOLDER + '/i06-353130.nxs' # new nexus format @@ -40,3 +42,8 @@ def test_nexus_reloader(): 'signal_data', 'axes_labels', 'signal_labels', 'data', 'title'} assert data.keys() >= KEYS + +def test_interpreter(): + ii = HdfMapInterpreter(filename=FILE_HKL) + assert abs(ii('abs(mean(max(roi2_sum)))') - 359573) < 0.001, "Expression output gives wrong result" + assert 'hkl' in ii('scan_command'), 'Expression output gives wrong result'