diff --git a/.github/workflows/plugin_test.yaml b/.github/workflows/plugin_test.yaml index 32fd9bbfd..5b7b43b7d 100644 --- a/.github/workflows/plugin_test.yaml +++ b/.github/workflows/plugin_test.yaml @@ -30,10 +30,10 @@ jobs: branch: main tests_to_run: tests/. - plugin: pynxtools-igor - branch: main + branch: update-definitions tests_to_run: tests/. - plugin: pynxtools-mpes - branch: main + branch: update-definitions tests_to_run: tests/. - plugin: pynxtools-raman branch: main diff --git a/src/pynxtools/data/NXtest.nxdl.xml b/src/pynxtools/data/NXtest.nxdl.xml index 8695a20c9..fa2917205 100644 --- a/src/pynxtools/data/NXtest.nxdl.xml +++ b/src/pynxtools/data/NXtest.nxdl.xml @@ -28,9 +28,13 @@ + A dummy entry for a float value. + + A dummy entry for a number value. + A dummy entry for a bool value. @@ -53,7 +57,20 @@ + + + + + + + + + + + + + This is a required yet empty group. @@ -72,5 +89,10 @@ This is a required group in an optional group. + + + A required NXuser entry. + + diff --git a/src/pynxtools/dataconverter/helpers.py b/src/pynxtools/dataconverter/helpers.py index 71d4a4b9f..290b4a634 100644 --- a/src/pynxtools/dataconverter/helpers.py +++ b/src/pynxtools/dataconverter/helpers.py @@ -24,7 +24,7 @@ from datetime import datetime, timezone from enum import Enum from functools import lru_cache -from typing import Any, Callable, List, Optional, Tuple, Union +from typing import Any, Callable, List, Optional, Tuple, Union, Sequence import h5py import lxml.etree as ET @@ -80,12 +80,12 @@ def _log(self, path: str, log_type: ValidationProblem, value: Optional[Any], *ar value = "" if log_type == ValidationProblem.UnitWithoutDocumentation: - logger.warning( - f"The unit, {path} = {value}, is being written but has no documentation" + logger.info( + f"The unit, {path} = {value}, is being written but has no documentation." ) elif log_type == ValidationProblem.InvalidEnum: logger.warning( - f"The value at {path} should be on of the following strings: {value}" + f"The value at {path} should be one of the following: {value}" ) elif log_type == ValidationProblem.MissingRequiredGroup: logger.warning(f"The required group, {path}, hasn't been supplied.") @@ -96,7 +96,7 @@ def _log(self, path: str, log_type: ValidationProblem, value: Optional[Any], *ar ) elif log_type == ValidationProblem.InvalidType: logger.warning( - f"The value at {path} should be one of: {value}" + f"The value at {path} should be one of the following Python types: {value}" f", as defined in the NXDL as {args[0] if args else ''}." ) elif log_type == ValidationProblem.InvalidDatetime: @@ -114,7 +114,10 @@ def _log(self, path: str, log_type: ValidationProblem, value: Optional[Any], *ar f"Expected a group at {path} but found a field or attribute." ) elif log_type == ValidationProblem.MissingDocumentation: - logger.warning(f"Field {path} written without documentation.") + if "@" in path.rsplit("/")[-1]: + logger.warning(f"Attribute {path} written without documentation.") + else: + logger.warning(f"Field {path} written without documentation.") elif log_type == ValidationProblem.MissingUnit: logger.warning( f"Field {path} requires a unit in the unit category {value}." @@ -122,7 +125,7 @@ def _log(self, path: str, log_type: ValidationProblem, value: Optional[Any], *ar elif log_type == ValidationProblem.MissingRequiredAttribute: logger.warning(f'Missing attribute: "{path}"') elif log_type == ValidationProblem.UnitWithoutField: - logger.warning(f"Unit {path} in dataset without its field {value}") + logger.warning(f"Unit {path} in dataset without its field {value}.") elif log_type == ValidationProblem.AttributeForNonExistingField: logger.warning( f"There were attributes set for the field {path}, " @@ -158,9 +161,11 @@ def collect_and_log( "NX_ANY", ): return - if self.logging: + if self.logging and path + str(log_type) + str(value) not in self.data: self._log(path, log_type, value, *args, **kwargs) - self.data.add(path) + # info messages should not fail validation + if log_type not in (ValidationProblem.UnitWithoutDocumentation,): + self.data.add(path + str(log_type) + str(value)) def has_validation_problems(self): """Returns True if there were any validation problems.""" @@ -215,7 +220,6 @@ def get_nxdl_name_for(xml_elem: ET._Element) -> Optional[str]: The name of the element. None if the xml element has no name or type attribute. """ - """""" if "name" in xml_elem.attrib: return xml_elem.attrib["name"] if "type" in xml_elem.attrib: @@ -575,86 +579,47 @@ def is_value_valid_element_of_enum(value, elist) -> Tuple[bool, list]: return True, [] -NUMPY_FLOAT_TYPES = (np.half, np.float16, np.single, np.double, np.longdouble) -NUMPY_INT_TYPES = (np.short, np.intc, np.int_) -NUMPY_UINT_TYPES = (np.ushort, np.uintc, np.uint) -# np int for np version 1.26.0 -np_int = ( - np.intc, - np.int_, - np.intp, - np.int8, - np.int16, - np.int32, - np.int64, - np.uint8, - np.uint16, - np.uint32, - np.uint64, - np.unsignedinteger, - np.signedinteger, -) -np_float = (np.float16, np.float32, np.float64, np.floating) -np_bytes = (np.bytes_, np.byte, np.ubyte) -np_char = (np.str_, np.char.chararray, *np_bytes) -np_bool = (np.bool_,) -np_complex = (np.complex64, np.complex128, np.cdouble, np.csingle) +nx_char = (str, np.character) +nx_int = (int, np.integer) +nx_float = (float, np.floating) +nx_number = nx_int + nx_float + NEXUS_TO_PYTHON_DATA_TYPES = { "ISO8601": (str,), - "NX_BINARY": ( - bytes, - bytearray, - np.ndarray, - *np_bytes, - ), - "NX_BOOLEAN": (bool, np.ndarray, *np_bool), - "NX_CHAR": (str, np.ndarray, *np_char), + "NX_BINARY": (bytes, bytearray, np.bytes_), + "NX_BOOLEAN": (bool, np.bool_), + "NX_CHAR": nx_char, "NX_DATE_TIME": (str,), - "NX_FLOAT": (float, np.ndarray, *np_float), - "NX_INT": (int, np.ndarray, *np_int), - "NX_UINT": (np.ndarray, np.unsignedinteger), - "NX_NUMBER": ( - int, - float, - np.ndarray, - *np_int, - *np_float, - dict, - ), - "NX_POSINT": ( - int, - np.ndarray, - np.signedinteger, - ), # > 0 is checked in is_valid_data_field() - "NX_COMPLEX": (complex, np.ndarray, *np_complex), - "NXDL_TYPE_UNAVAILABLE": (str,), # Defaults to a string if a type is not provided. - "NX_CHAR_OR_NUMBER": ( - str, - int, - float, - np.ndarray, - *np_char, - *np_int, - *np_float, - dict, + "NX_FLOAT": nx_float, + "NX_INT": nx_int, + "NX_UINT": (np.unsignedinteger,), + "NX_NUMBER": nx_number, + "NX_POSINT": nx_int, # > 0 is checked in is_valid_data_field() + "NX_COMPLEX": ( + complex, + np.complexfloating, ), + "NX_CHAR_OR_NUMBER": nx_char + nx_number, + "NXDL_TYPE_UNAVAILABLE": ( + nx_char, + ), # Defaults to a string if a type is not provided. } -def check_all_children_for_callable(objects: list, check: Callable, *args) -> bool: - """Checks whether all objects in list are validated by given callable.""" - for obj in objects: - if not check(obj, *args): - return False +def check_all_children_for_callable( + objects: Union[list, np.ndarray], check_function: Optional[Callable] = None, *args +) -> bool: + """Checks whether all objects in list or numpy array are validated + by given callable and types. + """ + if not isinstance(objects, np.ndarray): + objects = np.array(objects) - return True + return all([check_function(o, *args) for o in objects.flat]) def is_valid_data_type(value, accepted_types): """Checks whether the given value or its children are of an accepted type.""" - if not isinstance(value, list): - return isinstance(value, accepted_types) - return check_all_children_for_callable(value, isinstance, accepted_types) @@ -662,59 +627,55 @@ def is_positive_int(value): """Checks whether the given value or its children are positive.""" def is_greater_than(num): - return num.flat[0] > 0 if isinstance(num, np.ndarray) else num > 0 + return num > 0 - if isinstance(value, list): - return check_all_children_for_callable(value, is_greater_than) - - return value.flat[0] > 0 if isinstance(value, np.ndarray) else value > 0 + return check_all_children_for_callable( + objects=value, check_function=is_greater_than + ) -def convert_str_to_bool_safe(value): +def convert_str_to_bool_safe(value: str) -> Optional[bool]: """Only returns True or False if someone mistakenly adds quotation marks but mean a bool. - For everything else it returns None. + For everything else it raises a ValueError. """ if value.lower() == "true": return True if value.lower() == "false": return False - return None + raise ValueError(f"Could not interpret string '{value}' as boolean.") -def is_valid_data_field(value, nxdl_type, path): - # todo: Check this funciton and wtire test for it. It seems the funciton is not +def is_valid_data_field(value: Any, nxdl_type: str, nxdl_enum: list, path: str) -> Any: + # todo: Check this function and write test for it. It seems the function is not # working as expected. - """Checks whether a given value is valid according to what is defined in the NXDL. - - This function will also try to convert typical types, for example int to float, - and return the successful conversion. + """Checks whether a given value is valid according to the type defined in the NXDL. - If it fails to convert, it raises an Exception. + This function only tries to convert boolean value in str format (e.g. "true" ) to + python Boolean (True). In case, it fails to convert, it raises an Exception. - Returns two values: first, boolean (True if the the value corresponds to nxdl_type, - False otherwise) and second, result of attempted conversion or the original value - (if conversion is not needed or impossible) + Return: + value: the possibly converted data value """ - accepted_types = NEXUS_TO_PYTHON_DATA_TYPES[nxdl_type] - output_value = value + accepted_types = NEXUS_TO_PYTHON_DATA_TYPES[nxdl_type] + # Do not count the dict as it represents a link value if not isinstance(value, dict) and not is_valid_data_type(value, accepted_types): - try: - if accepted_types[0] is bool and isinstance(value, str): + # try to convert string to bool + if accepted_types[0] is bool and isinstance(value, str): + try: value = convert_str_to_bool_safe(value) - if value is None: - raise ValueError - output_value = accepted_types[0](value) - except ValueError: + except (ValueError, TypeError): + collector.collect_and_log( + path, ValidationProblem.InvalidType, accepted_types, nxdl_type + ) + else: collector.collect_and_log( path, ValidationProblem.InvalidType, accepted_types, nxdl_type ) - return False, value if nxdl_type == "NX_POSINT" and not is_positive_int(value): collector.collect_and_log(path, ValidationProblem.IsNotPosInt, value) - return False, value if nxdl_type in ("ISO8601", "NX_DATE_TIME"): iso8601 = re.compile( @@ -724,9 +685,16 @@ def is_valid_data_field(value, nxdl_type, path): results = iso8601.search(value) if results is None: collector.collect_and_log(path, ValidationProblem.InvalidDatetime, value) - return False, value - return True, output_value + # Check enumeration + if nxdl_enum is not None and value not in nxdl_enum: + collector.collect_and_log( + path, + ValidationProblem.InvalidEnum, + nxdl_enum, + ) + + return value @lru_cache(maxsize=None) diff --git a/src/pynxtools/dataconverter/nexus_tree.py b/src/pynxtools/dataconverter/nexus_tree.py index bbba22c09..77349df49 100644 --- a/src/pynxtools/dataconverter/nexus_tree.py +++ b/src/pynxtools/dataconverter/nexus_tree.py @@ -761,7 +761,7 @@ class NexusEntity(NexusNode): type: Literal["field", "attribute"] unit: Optional[NexusUnitCategory] = None dtype: NexusType = "NX_CHAR" - items: Optional[List[str]] = None + items: Optional[List[Any]] = None shape: Optional[Tuple[Optional[int], ...]] = None def _set_type(self): @@ -790,14 +790,23 @@ def _set_items(self): based on the values in the inheritance chain. The first vale found is used. """ - if not self.dtype == "NX_CHAR": - return for elem in self.inheritance: enum = elem.find(f"nx:enumeration", namespaces=namespaces) if enum is not None: self.items = [] for items in enum.findall(f"nx:item", namespaces=namespaces): - self.items.append(items.attrib["value"]) + value = items.attrib["value"] + if value[0] == "[" and value[-1] == "]": + import ast + + try: + self.items.append(ast.literal_eval(value)) + except (ValueError, SyntaxError): + raise Exception( + f"Error parsing enumeration item in the provided NXDL: {value}" + ) + else: + self.items.append(value) return def _set_shape(self): diff --git a/src/pynxtools/dataconverter/readers/example/reader.py b/src/pynxtools/dataconverter/readers/example/reader.py index fefe37f5c..7e368a264 100644 --- a/src/pynxtools/dataconverter/readers/example/reader.py +++ b/src/pynxtools/dataconverter/readers/example/reader.py @@ -58,7 +58,11 @@ def read( # outputs with --generate-template for a provided NXDL file if ( k.startswith("/ENTRY[entry]/required_group") - or k == "/ENTRY[entry]/optional_parent/req_group_in_opt_group" + or k + in ( + "/ENTRY[entry]/optional_parent/req_group_in_opt_group", + "/ENTRY[entry]/NXODD_name[nxodd_name]/anamethatRENAMES[anamethatrenames]", + ) or k.startswith("/ENTRY[entry]/OPTIONAL_group") ): continue diff --git a/src/pynxtools/dataconverter/validation.py b/src/pynxtools/dataconverter/validation.py index 4b599b43a..250a87bf4 100644 --- a/src/pynxtools/dataconverter/validation.py +++ b/src/pynxtools/dataconverter/validation.py @@ -20,7 +20,7 @@ from collections import defaultdict from functools import reduce from operator import getitem -from typing import Any, Iterable, List, Mapping, Optional, Tuple, Union +from typing import Any, Iterable, List, Mapping, MutableMapping, Optional, Tuple, Union import h5py import lxml.etree as ET @@ -134,45 +134,49 @@ def split_class_and_name_of(name: str) -> Tuple[Optional[str], str]: ), f"{name_match.group(2)}{'' if prefix is None else prefix}" -def best_namefit_of(name: str, keys: Iterable[str]) -> Optional[str]: +def best_namefit_of(name: str, nodes: Iterable[NexusNode]) -> Optional[NexusNode]: """ Get the best namefit of `name` in `keys`. Args: name (str): The name to fit against the keys. - keys (Iterable[str]): The keys to fit `name` against. + nodes (Iterable[NexusNode]): The nodes to fit `name` against. Returns: - Optional[str]: The best fitting key. None if no fit was found. + Optional[NexusNode]: The best fitting node. None if no fit was found. """ - if not keys: + if not nodes: return None - nx_name, name2fit = split_class_and_name_of(name) + concept_name, instance_name = split_class_and_name_of(name) - if name2fit in keys: - return name2fit - if nx_name is not None and nx_name in keys: - return nx_name + best_match = None - best_match, score = max( - map(lambda x: (x, get_nx_namefit(name2fit, x)), keys), key=lambda x: x[1] - ) - if score < 0: - return None + for node in nodes: + if not node.variadic: + if instance_name == node.name: + return node + else: + if concept_name and concept_name == node.name: + if instance_name == node.name: + return node + + score = get_nx_namefit(instance_name, node.name) + if score > -1: + best_match = node return best_match def validate_dict_against( - appdef: str, mapping: Mapping[str, Any], ignore_undocumented: bool = False + appdef: str, mapping: MutableMapping[str, Any], ignore_undocumented: bool = False ) -> Tuple[bool, List]: """ - Validates a mapping against the NeXus tree for applicationd definition `appdef`. + Validates a mapping against the NeXus tree for application definition `appdef`. Args: appdef (str): The appdef name to validate against. - mapping (Mapping[str, Any]): + mapping (MutableMapping[str, Any]): The mapping containing the data to validate. This should be a dict of `/` separated paths. Attributes are denoted with `@` in front of the last element. @@ -188,7 +192,7 @@ def validate_dict_against( def get_variations_of(node: NexusNode, keys: Mapping[str, Any]) -> List[str]: if not node.variadic: - if node.name in keys: + if f"{'@' if node.type == 'attribute' else ''}{node.name}" in keys: return [node.name] elif ( hasattr(node, "nx_class") @@ -198,25 +202,35 @@ def get_variations_of(node: NexusNode, keys: Mapping[str, Any]) -> List[str]: variations = [] for key in keys: - nx_name, name2fit = split_class_and_name_of(key) + concept_name, instance_name = split_class_and_name_of(key) + if node.type == "attribute": # Remove the starting @ from attributes - name2fit = name2fit[1:] if name2fit.startswith("@") else name2fit - if nx_name is not None and nx_name != node.name: + instance_name = ( + instance_name[1:] + if instance_name.startswith("@") + else instance_name + ) + + if not concept_name or concept_name != node.name: continue + if ( - get_nx_namefit(name2fit, node.name) >= 0 + get_nx_namefit(instance_name, node.name) >= 0 and key not in node.parent.get_all_direct_children_names() ): variations.append(key) - if nx_name is not None and not variations: + + if not variations: collector.collect_and_log( - nx_name, ValidationProblem.FailedNamefitting, keys + concept_name, ValidationProblem.FailedNamefitting, keys ) return variations def get_field_attributes(name: str, keys: Mapping[str, Any]) -> Mapping[str, Any]: - return {k.split("@")[1]: keys[k] for k in keys if k.startswith(f"{name}@")} + return { + f"@{k.split('@')[1]}": keys[k] for k in keys if k.startswith(f"{name}@") + } def handle_nxdata(node: NexusGroup, keys: Mapping[str, Any], prev_path: str): def check_nxdata(): @@ -248,6 +262,14 @@ def check_nxdata(): prev_path=prev_path, ) + # check NXdata attributes + for attr in ("signal", "auxiliary_signals", "axes"): + handle_attribute( + node.search_add_child_for(attr), + keys, + prev_path=prev_path, + ) + for i, axis in enumerate(axes): if axis == ".": continue @@ -342,6 +364,7 @@ def handle_group(node: NexusGroup, keys: Mapping[str, Any], prev_path: str): None, ) return + for variant in variants: if variant in [node.name for node in node.parent_of]: # Don't process if this is actually a sub-variant of this group @@ -392,17 +415,17 @@ def _follow_link( def handle_field(node: NexusNode, keys: Mapping[str, Any], prev_path: str): full_path = remove_from_not_visited(f"{prev_path}/{node.name}") variants = get_variations_of(node, keys) - if not variants: - if node.optionality == "required" and node.type in missing_type_err: - collector.collect_and_log( - full_path, missing_type_err.get(node.type), None - ) - + if ( + not variants + and node.optionality == "required" + and node.type in missing_type_err + ): + collector.collect_and_log(full_path, missing_type_err.get(node.type), None) return for variant in variants: if node.optionality == "required" and isinstance(keys[variant], Mapping): - # Check if all fields in the dict are actual attributes (startwith @) + # Check if all fields in the dict are actual attributes (startswith @) all_attrs = True for entry in keys[variant]: if not entry.startswith("@"): @@ -422,21 +445,13 @@ def handle_field(node: NexusNode, keys: Mapping[str, Any], prev_path: str): continue # Check general validity - is_valid_data_field( - mapping[f"{prev_path}/{variant}"], node.dtype, f"{prev_path}/{variant}" + mapping[f"{prev_path}/{variant}"] = is_valid_data_field( + mapping[f"{prev_path}/{variant}"], + node.dtype, + node.items, + f"{prev_path}/{variant}", ) - # Check enumeration - if ( - node.items is not None - and mapping[f"{prev_path}/{variant}"] not in node.items - ): - collector.collect_and_log( - f"{prev_path}/{variant}", - ValidationProblem.InvalidEnum, - node.items, - ) - # Check unit category if node.unit is not None: remove_from_not_visited(f"{prev_path}/{variant}/@units") @@ -460,19 +475,23 @@ def handle_field(node: NexusNode, keys: Mapping[str, Any], prev_path: str): def handle_attribute(node: NexusNode, keys: Mapping[str, Any], prev_path: str): full_path = remove_from_not_visited(f"{prev_path}/@{node.name}") variants = get_variations_of(node, keys) - if not variants: - if node.optionality == "required" and node.type in missing_type_err: - collector.collect_and_log( - full_path, missing_type_err.get(node.type), None - ) + if ( + not variants + and node.optionality == "required" + and node.type in missing_type_err + ): + collector.collect_and_log(full_path, missing_type_err.get(node.type), None) return for variant in variants: - is_valid_data_field( + mapping[ + f"{prev_path}/{variant if variant.startswith('@') else f'@{variant}'}" + ] = is_valid_data_field( mapping[ f"{prev_path}/{variant if variant.startswith('@') else f'@{variant}'}" ], node.dtype, + node.items, f"{prev_path}/{variant if variant.startswith('@') else f'@{variant}'}", ) @@ -504,18 +523,31 @@ def handle_unknown_type(node: NexusNode, keys: Mapping[str, Any], prev_path: str # TODO: Raise error or log the issue? pass - def is_documented(key: str, node: NexusNode) -> bool: + def add_best_matches_for(key: str, node: NexusNode) -> Optional[NexusNode]: + for name in key[1:].replace("@", "").split("/"): + children_to_check = [ + node.search_add_child_for(child) + for child in node.get_all_direct_children_names() + ] + node = best_namefit_of(name, children_to_check) + + if node is None: + return None + + return node + + def is_documented(key: str, tree: NexusNode) -> bool: if mapping.get(key) is None: # This value is not really set. Skip checking it's documentation. return True - for name in key[1:].replace("@", "").split("/"): - children = node.get_all_direct_children_names() - best_name = best_namefit_of(name, children) - if best_name is None: - return False + # TODO remove when nameType is implemented + if key.endswith("/@URL"): + return True - node = node.search_add_child_for(best_name) + node = add_best_matches_for(key, tree) + if node is None: + return False if isinstance(mapping[key], dict) and "link" in mapping[key]: # TODO: Follow link and check consistency with current field @@ -526,6 +558,13 @@ def is_documented(key: str, node: NexusNode) -> bool: if "@" in key and node.type != "attribute": return False + # if we arrive here, the key is supposed to be documented. + # We still do some further checks before returning. + + # Check general validity + mapping[key] = is_valid_data_field(mapping[key], node.dtype, node.items, key) + + # Check main field exists for units if ( isinstance(node, NexusEntity) and node.unit is not None @@ -535,7 +574,7 @@ def is_documented(key: str, node: NexusNode) -> bool: f"{key}", ValidationProblem.MissingUnit, node.unit ) - return is_valid_data_field(mapping[key], node.dtype, key)[0] + return True def recurse_tree( node: NexusNode, @@ -556,7 +595,7 @@ def check_attributes_of_nonexisting_field( ) -> list: """ This method runs through the mapping dictionary and checks if there are any - attributes assigned to the fields (not groups!) which are not expicitly + attributes assigned to the fields (not groups!) which are not explicitly present in the mapping. If there are any found, a warning is logged and the corresponding items are added to the list returned by the method. @@ -573,9 +612,10 @@ def check_attributes_of_nonexisting_field( for key in mapping: last_index = key.rfind("/") - if key[last_index + 1] == "@": + if key[last_index + 1] == "@" and key[last_index + 1 :] != "@units": # key is an attribute. Find a corresponding parent, check all the other # children of this parent + # ignore units here, they are checked separately attribute_parent_checked = False for key_iterating in mapping: # check if key_iterating starts with parent of the key OR any @@ -657,7 +697,7 @@ def check_type_with_tree( if (next_child_class is not None) or (next_child_name is not None): output = None for child in node.children: - # regexs to separarte the class and the name from full name of the child + # regexs to separate the class and the name from full name of the child child_class_from_node = re.sub( r"(\@.*)*(\[.*?\])*(\(.*?\))*([a-z]\_)*(\_[a-z])*[a-z]*\s*", "", @@ -745,22 +785,55 @@ def startswith_with_variations( not_visited = list(mapping) recurse_tree(tree, nested_keys) + keys_to_remove = check_attributes_of_nonexisting_field(tree) + for not_visited_key in not_visited: if not_visited_key.endswith("/@units"): - if is_documented(not_visited_key.rsplit("/", 1)[0], tree): - continue - if not_visited_key.rsplit("/", 1)[0] not in not_visited: + # check that parent exists + if not_visited_key.rsplit("/", 1)[0] not in mapping.keys(): collector.collect_and_log( not_visited_key, ValidationProblem.UnitWithoutField, not_visited_key.rsplit("/", 1)[0], ) - if not ignore_undocumented: collector.collect_and_log( not_visited_key, - ValidationProblem.UnitWithoutDocumentation, - mapping[not_visited_key], + ValidationProblem.KeyToBeRemoved, + None, ) + keys_to_remove.append(not_visited_key) + else: + # check that parent has units + node = add_best_matches_for(not_visited_key.rsplit("/", 1)[0], tree) + if node is None or node.type != "field" or node.unit is None: + collector.collect_and_log( + not_visited_key, + ValidationProblem.UnitWithoutDocumentation, + mapping[not_visited_key], + ) + + # parent key will be checked on its own if it exists, because it is in the list + continue + + if "@" in not_visited_key.rsplit("/")[-1]: + # check that parent exists + if not_visited_key.rsplit("/", 1)[0] not in mapping.keys(): + # check that parent is not a group + node = add_best_matches_for(not_visited_key.rsplit("/", 1)[0], tree) + if node is None or node.type != "group": + collector.collect_and_log( + not_visited_key.rsplit("/", 1)[0], + ValidationProblem.AttributeForNonExistingField, + None, + ) + collector.collect_and_log( + not_visited_key, + ValidationProblem.KeyToBeRemoved, + None, + ) + keys_to_remove.append(not_visited_key) + continue + if is_documented(not_visited_key, tree): continue @@ -769,7 +842,6 @@ def startswith_with_variations( not_visited_key, ValidationProblem.MissingDocumentation, None ) - keys_to_remove = check_attributes_of_nonexisting_field(tree) return (not collector.has_validation_problems(), keys_to_remove) @@ -802,6 +874,6 @@ def populate_full_tree(node: NexusNode, max_depth: Optional[int] = 5, depth: int # Backwards compatibility def validate_data_dict( - _: Mapping[str, Any], read_data: Mapping[str, Any], root: ET._Element + _: MutableMapping[str, Any], read_data: MutableMapping[str, Any], root: ET._Element ) -> bool: return validate_dict_against(root.attrib["name"], read_data)[0] diff --git a/src/pynxtools/definitions b/src/pynxtools/definitions index 3f66054d6..f7ba53f4f 160000 --- a/src/pynxtools/definitions +++ b/src/pynxtools/definitions @@ -1 +1 @@ -Subproject commit 3f66054d6b1651617fdfbb24d4b2bfa33f75de66 +Subproject commit f7ba53f4fb409b03fde6af6ccf29146392a2c142 diff --git a/tests/data/dataconverter/readers/example/testdata.json b/tests/data/dataconverter/readers/example/testdata.json index 21deb40c3..dbcf035a3 100644 --- a/tests/data/dataconverter/readers/example/testdata.json +++ b/tests/data/dataconverter/readers/example/testdata.json @@ -7,6 +7,8 @@ "float_value_units": "nm", "int_value": -3, "int_value_units": "eV", + "number_value": 3, + "number_value_units": "eV", "posint_value": 7, "posint_value_units": "kg", "definition": "NXtest", @@ -17,5 +19,9 @@ "date_value_units": "", "required_child": 1, "optional_child": 1, - "@version": "1.0" + "@version": "1.0", + "@array": [0, 1, 2], + "name": "Test", + "@group_attribute": "data", + "@signal": "data" } \ No newline at end of file diff --git a/tests/data/nexus/Ref_nexus_test.log b/tests/data/nexus/Ref_nexus_test.log index 87b245a0a..54541649f 100644 --- a/tests/data/nexus/Ref_nexus_test.log +++ b/tests/data/nexus/Ref_nexus_test.log @@ -32,34 +32,24 @@ DEBUG - classpath: ['NXentry'] DEBUG - classes: NXarpes.nxdl.xml:/ENTRY NXentry.nxdl.xml: -NXobject.nxdl.xml: DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY): DEBUG - DEBUG - documentation (NXentry.nxdl.xml:): DEBUG - - (**required**) :ref:`NXentry` describes the measurement. - - The top-level NeXus group which contains all the data and associated - information that comprise a single measurement. - It is mandatory that there is at least one - group of this type in the NeXus file. -DEBUG - documentation (NXobject.nxdl.xml:): -DEBUG - - This is the base object of NeXus. The groups and fields contained - within this file are allowed to be present in any derived base class. - - If nameType="partial", the placeholders (e.g., FIELDNAME or GROUPNAME) - can be replaced by the name of any object (field or group, - respectively) that exists within the same group. - + (**required**) :ref:`NXentry` describes the measurement. + + The top-level NeXus group which contains all the data and associated + information that comprise a single measurement. + It is mandatory that there is at least one + group of this type in the NeXus file. + DEBUG - ===== ATTRS (//entry@NX_class) DEBUG - value: NXentry DEBUG - classpath: ['NXentry'] DEBUG - classes: NXarpes.nxdl.xml:/ENTRY NXentry.nxdl.xml: -NXobject.nxdl.xml: DEBUG - @NX_class [NX_CHAR] DEBUG - DEBUG - ===== FIELD (//entry/collection_time): @@ -70,9 +60,9 @@ NXentry.nxdl.xml:/collection_time DEBUG - <> DEBUG - documentation (NXentry.nxdl.xml:/collection_time): DEBUG - - Time transpired actually collecting data i.e. taking out time when collection was - suspended due to e.g. temperature out of range - + Time transpired actually collecting data i.e. taking out time when collection was + suspended due to e.g. temperature out of range + DEBUG - ===== ATTRS (//entry/collection_time@units) DEBUG - value: s DEBUG - classpath: ['NXentry', 'NX_FLOAT'] @@ -84,247 +74,158 @@ DEBUG - classpath: ['NXentry', 'NXdata'] DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/DATA NXentry.nxdl.xml:/DATA -NXobject.nxdl.xml:/DATA NXdata.nxdl.xml: -NXobject.nxdl.xml: DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/DATA): DEBUG - DEBUG - documentation (NXentry.nxdl.xml:/DATA): DEBUG - - The data group - - .. note:: Before the NIAC2016 meeting [#]_, at least one - :ref:`NXdata` group was required in each :ref:`NXentry` group. - At the NIAC2016 meeting, it was decided to make :ref:`NXdata` - an optional group in :ref:`NXentry` groups for data files that - do not use an application definition. - It is recommended strongly that all NeXus data files provide - a NXdata group. - It is permissable to omit the NXdata group only when - defining the default plot is not practical or possible - from the available data. - - For example, neutron event data may not have anything that - makes a useful plot without extensive processing. - - Certain application definitions override this decision and - require an :ref:`NXdata` group - in the :ref:`NXentry` group. The ``minOccurs=0`` attribute - in the application definition will indicate the - :ref:`NXdata` group - is optional, otherwise, it is required. - - .. [#] NIAC2016: - https://www.nexusformat.org/NIAC2016.html, - https://github.com/nexusformat/NIAC/issues/16 - - -DEBUG - documentation (NXobject.nxdl.xml:/DATA): -DEBUG - + The data group + + .. note:: Before the NIAC2016 meeting [#]_, at least one + :ref:`NXdata` group was required in each :ref:`NXentry` group. + At the NIAC2016 meeting, it was decided to make :ref:`NXdata` + an optional group in :ref:`NXentry` groups for data files that + do not use an application definition. + It is recommended strongly that all NeXus data files provide + a NXdata group. + It is permissable to omit the NXdata group only when + defining the default plot is not practical or possible + from the available data. + + For example, neutron event data may not have anything that + makes a useful plot without extensive processing. + + Certain application definitions override this decision and + require an :ref:`NXdata` group + in the :ref:`NXentry` group. The ``minOccurs=0`` attribute + in the application definition will indicate the + :ref:`NXdata` group + is optional, otherwise, it is required. + + .. [#] NIAC2016: + https://www.nexusformat.org/NIAC2016.html, + https://github.com/nexusformat/NIAC/issues/16 + DEBUG - documentation (NXdata.nxdl.xml:): DEBUG - - The :ref:`NXdata` class is designed to encapsulate all the information required for a set of data to be plotted. - NXdata groups contain plottable data (sometimes referred to as *signals* or *dependent variables*) and their - associated axis coordinates (sometimes referred to as *axes* or *independent variables*). - - The actual names of the :ref:`DATA ` and :ref:`AXISNAME ` fields - can be chosen :ref:`freely `, as indicated by the upper case (this is a common convention in all NeXus classes). - - .. note:: ``NXdata`` provides data and coordinates to be plotted but - does not describe how the data is to be plotted or even the dimensionality of the plot. - https://www.nexusformat.org/NIAC2018Minutes.html#nxdata-plottype--attribute - - **Signals:** - - .. index:: plotting - - The :ref:`DATA ` fields contain the signal values to be plotted. The name of the field - to be used as the *default plot signal* is provided by the :ref:`signal ` attribute. - The names of the fields to be used as *secondary plot signals* are provided by the - :ref:`auxiliary_signals` attribute. - - An example with three signals, one of which being the default - - .. code-block:: - - data:NXdata - @signal = "data1" - @auxiliary_signals = ["data2", "data3"] - data1: float[10,20,30] --> the default signal - data2: float[10,20,30] - data3: float[10,20,30] - - **Axes:** - - .. index:: axes (attribute) - .. index:: coordinates - - The :ref:`AXISNAME ` fields contain the axis coordinates associated with the data values. - The names of all :ref:`AXISNAME ` fields are listed in the - :ref:`axes ` attribute. - - `Rank` - - :ref:`AXISNAME ` fields are typically one-dimensional arrays, which annotate one of the dimensions. - - An example of this would be - - .. code-block:: - - data:NXdata - @signal = "data" - @axes = ["x", "y"] --> the order matters - data: float[10,20] - x: float[10] --> coordinates along the first dimension - y: float[20] --> coordinates along the second dimension - - In this example each data point ``data[i,j]`` has axis coordinates ``[x[i], y[j]]``. - - However, the fields can also have a rank greater than 1, in which case the rank of each - :ref:`AXISNAME ` must be equal to the number of data dimensions it spans. - - An example of this would be - - .. code-block:: - - data:NXdata - @signal = "data" - @axes = ["x", "y"] --> the order does NOT matter - @x_indices = [0, 1] - @y_indices = [0, 1] - data: float[10,20] - x: float[10,20] --> coordinates along both dimensions - y: float[10,20] --> coordinates along both dimensions - - In this example each data point ``data[i,j]`` has axis coordinates ``[x[i,j], y[i,j]]``. - - `Dimensions` - - The data dimensions annotated by an :ref:`AXISNAME ` field are defined by the - :ref:`AXISNAME_indices ` attribute. When this attribute is missing, - the position(s) of the :ref:`AXISNAME ` string in the - :ref:`axes ` attribute are used. - - When all :ref:`AXISNAME ` fields are one-dimensional, and none of the data dimensions - have more than one axis, the :ref:`AXISNAME_indices ` attributes - are often omitted. If one of the data dimensions has no :ref:`AXISNAME ` field, - the string “.” can be used in the corresponding index of the axes list. - - An example of this would be - - .. code-block:: - - data:NXdata - @signal = "data" - @axes = ["x", ".", "z"] --> the order matters - data: float[10,20,30] - x: float[10] --> coordinates along the first dimension - z: float[30] --> coordinates along the third dimension - - When using :ref:`AXISNAME_indices ` this becomes - - .. code-block:: - - data:NXdata - @signal = "data" - @axes = ["x", "z"] --> the order does NOT matter - data: float[10,20,30] - @x_indices = 0 - @z_indices = 2 - x: float[10] --> coordinates along the first dimension - z: float[30] --> coordinates along the third dimension - - When providing :ref:`AXISNAME_indices ` attributes it is recommended - to do it for all axes. - - `Non-trivial axes` - - What follows are two examples where :ref:`AXISNAME_indices ` attributes - cannot be omitted. - - The first is an example where data dimensions have alternative axis coordinates. The NXdata group represents - a stack of images collected at different energies. The ``wavelength`` is an alternative axis of ``energy`` - for the last dimension (or vice versa). - - .. code-block:: - - data:NXdata - @signal = "data" - @axes = ["x", "y", "energy", "wavelength"] --> the order does NOT matter - @x_indices = 0 - @y_indices = 1 - @energy_indices = 2 - @wavelength_indices = 2 - data: float[10,20,30] - x: float[10] --> coordinates along the first dimension - y: float[20] --> coordinates along the second dimension - energy: float[30] --> coordinates along the third dimension - wavelength: float[30] --> coordinates along the third dimension - - The second is an example with coordinates that span more than one dimension. The NXdata group represents data - from 2D mesh scans performed at multiple energies. Each data point ``data[i,j,k]`` has axis coordinates - ``[x[i,j,k], y[i,j,k], energy[k]]``. - - .. code-block:: - - data:NXdata - @signal = "data" - @axes = ["x", "y", "energy"] --> the order does NOT matter - @x_indices = [0, 1, 2] - @y_indices = [0, 1, 2] - @energy_indices = 2 - data: float[10,20,30] - x: float[10,20,30] --> coordinates along all dimensions - y: float[10,20,30] --> coordinates along all dimensions - energy: float[30] --> coordinates along the third dimension - - **Uncertainties:** - - Standard deviations on data values as well as coordinates can be provided by - :ref:`FIELDNAME_errors ` fields where ``FIELDNAME`` is the name of a - :ref:`DATA ` field or an :ref:`AXISNAME ` field. - - An example of uncertainties on the signal, auxiliary signals and axis coordinates - - .. code-block:: - - data:NXdata - @signal = "data1" - @auxiliary_signals = ["data2", "data3"] - @axes = ["x", "z"] - @x_indices = 0 - @z_indices = 2 - data1: float[10,20,30] - data2: float[10,20,30] - data3: float[10,20,30] - x: float[10] - z: float[30] - data1_errors: float[10,20,30] - data2_errors: float[10,20,30] - data3_errors: float[10,20,30] - x_errors: float[10] - z_errors: float[30] - - -DEBUG - documentation (NXobject.nxdl.xml:): -DEBUG - - This is the base object of NeXus. The groups and fields contained - within this file are allowed to be present in any derived base class. - - If nameType="partial", the placeholders (e.g., FIELDNAME or GROUPNAME) - can be replaced by the name of any object (field or group, - respectively) that exists within the same group. - + :ref:`NXdata` describes the plottable data and related dimension scales. + + .. index:: plotting + + It is strongly recommended that there is at least one :ref:`NXdata` + group in each :ref:`NXentry` group. + Note that the fields named ``AXISNAME`` and ``DATA`` + can be defined with different names. + (Upper case is used to indicate that the actual name is left to the user.) + The ``signal`` and ``axes`` attributes of the + ``data`` group define which items + are plottable data and which are *dimension scales*, respectively. + + :ref:`NXdata` is used to implement one of the basic motivations in NeXus, + to provide a default plot for the data of this :ref:`NXentry`. The actual data + might be stored in another group and (hard) linked to the :ref:`NXdata` group. + + * Each :ref:`NXdata` group will define one field as the default + plottable data. The value of the ``signal`` attribute names this field. + Additional fields may be used to describe the dimension scales and + uncertainities. + The ``auxiliary_signals`` attribute is a list of the other fields + to be plotted with the ``signal`` data. + * The plottable data may be of arbitrary rank up to a maximum + of ``NX_MAXRANK=32`` (for compatibility with backend file formats). + * The plottable data will be named as the value of + the group ``signal`` attribute, such as:: + + data:NXdata + @signal = "counts" + @axes = "mr" + @mr_indices = 0 + counts: float[100] --> the default dependent data + mr: float[100] --> the default independent data + + The field named in the ``signal`` attribute **must** exist, either + directly as a NeXus field or defined through a link. + + * The group ``axes`` attribute will name the + *dimension scale* associated with the plottable data. + + If available, the standard deviations of the data are to be + stored in a data set of the same rank and dimensions, with the name ``errors``. + + * For each data dimension, there should be a one-dimensional array + of the same length. + * These one-dimensional arrays are the *dimension scales* of the + data, *i.e*. the values of the independent variables at which the data + is measured, such as scattering angle or energy transfer. + + .. index:: link + .. index:: axes (attribute) + + The preferred method to associate each data dimension with + its respective dimension scale is to specify the field name + of each dimension scale in the group ``axes`` attribute as a string list. + Here is an example for a 2-D data set *data* plotted + against *time*, and *pressure*. (An additional *temperature* data set + is provided and could be selected as an alternate for the *pressure* axis.):: + + data_2d:NXdata + @signal="data" + @axes=["time", "pressure"] + @pressure_indices=1 + @temperature_indices=1 + @time_indices=0 + data: float[1000,20] + pressure: float[20] + temperature: float[20] + time: float[1000] + + .. rubric:: Old methods to identify the plottable data + + There are two older methods of associating + each data dimension to its respective dimension scale. + Both are now out of date and + should not be used when writing new data files. + However, client software should expect to see data files + written with any of these methods. + + * One method uses the ``axes`` + attribute to specify the names of each *dimension scale*. + + * The oldest method uses the ``axis`` attribute on each + *dimension scale* to identify + with an integer the axis whose value is the number of the dimension. + + .. index: !plot; axis label + plot, axis units + units + dimension scale + + Each axis of the plot may be labeled with information from the + dimension scale for that axis. The optional ``@long_name`` attribute + is provided as the axis label default. If ``@long_name`` is not + defined, then use the name of the dimension scale. A ``@units`` attribute, + if available, may be added to the axis label for further description. + See the section :ref:`Design-Units` for more information. + + .. index: !plot; axis title + + The optional ``title`` field, if available, provides a suggested + title for the plot. If no ``title`` field is found in the :ref:`NXdata` + group, look for a ``title`` field in the parent :ref:`NXentry` group, + with a fallback to displaying the path to the :ref:`NXdata` group. + + NeXus is about how to find and annotate the data to be plotted + but not to describe how the data is to be plotted. + (https://www.nexusformat.org/NIAC2018Minutes.html#nxdata-plottype--attribute) + DEBUG - ===== ATTRS (//entry/data@NX_class) DEBUG - value: NXdata DEBUG - classpath: ['NXentry', 'NXdata'] DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/DATA NXentry.nxdl.xml:/DATA -NXobject.nxdl.xml:/DATA NXdata.nxdl.xml: -NXobject.nxdl.xml: DEBUG - @NX_class [NX_CHAR] DEBUG - DEBUG - ===== ATTRS (//entry/data@axes) @@ -333,85 +234,96 @@ DEBUG - classpath: ['NXentry', 'NXdata'] DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/DATA NXentry.nxdl.xml:/DATA -NXobject.nxdl.xml:/DATA NXdata.nxdl.xml: -NXobject.nxdl.xml: DEBUG - NXdata.nxdl.xml:@axes - [NX_CHAR] DEBUG - <> DEBUG - documentation (NXdata.nxdl.xml:/axes): DEBUG - - .. index:: plotting - - The ``axes`` attribute is a list of strings which are the names of the :ref:`AXISNAME ` fields - that contain the values of the coordinates along the :ref:`data ` dimensions. - - .. note:: When ``axes`` contains multiple strings, it must be saved as an actual array - of strings and not a single comma separated string. - + .. index:: plotting + + Array of strings holding the :ref:`names ` of + the independent data fields used in the default plot for all of + the dimensions of the :ref:`signal ` + as well as any :ref:`auxiliary signals `. + + One name is provided for every dimension in the *signal* or *auxiliary signal* fields. + + The *axes* values are the names of fields or links that *must* exist and be direct + children of this NXdata group. + + An axis slice is specified using a field named ``AXISNAME_indices`` + as described below (where the text shown here as ``AXISNAME`` is to be + replaced by the actual field name). + + When no default axis is available for a particular dimension + of the plottable data, use a "." in that position. + Such as:: + + @axes=["time", ".", "."] + + Since there are three items in the list, the *signal* field + must be a three-dimensional array (rank=3). The first dimension + is described by the values of a one-dimensional array named ``time`` + while the other two dimensions have no fields to be used as dimension scales. + + See examples provided on the NeXus wiki: + https://www.nexusformat.org/2014_axes_and_uncertainties.html + + If there are no axes at all (such as with a stack of images), + the axes attribute can be omitted. + DEBUG - ===== ATTRS (//entry/data@signal) DEBUG - value: data DEBUG - classpath: ['NXentry', 'NXdata'] DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/DATA NXentry.nxdl.xml:/DATA -NXobject.nxdl.xml:/DATA NXdata.nxdl.xml: -NXobject.nxdl.xml: DEBUG - NXdata.nxdl.xml:@signal - [NX_CHAR] DEBUG - <> DEBUG - documentation (NXdata.nxdl.xml:/signal): DEBUG - - .. index:: find the default plottable data - .. index:: plotting - .. index:: signal attribute value - - The value is the :ref:`name ` of the signal that contains - the default plottable data. This field or link *must* exist and be a direct child - of this NXdata group. - - It is recommended (as of NIAC2014) to use this attribute - rather than adding a signal attribute to the field. - See https://www.nexusformat.org/2014_How_to_find_default_data.html - for a summary of the discussion. - + .. index:: find the default plottable data + .. index:: plotting + .. index:: signal attribute value + + Declares which NeXus field is the default. + The value is the :ref:`name ` of the data field to be plotted. + This field or link *must* exist and be a direct child of this NXdata group. + + It is recommended (as of NIAC2014) to use this attribute + rather than adding a signal attribute to the field. + See https://www.nexusformat.org/2014_How_to_find_default_data.html + for a summary of the discussion. + DEBUG - ===== FIELD (//entry/data/angles): DEBUG - value: [-1.96735314 -1.91500657 -1.86266001 -1.81031344 -1.75796688 -1.70562031 ... -DEBUG - classpath: ['NXentry', 'NXdata', 'NX_CHAR_OR_NUMBER'] +DEBUG - classpath: ['NXentry', 'NXdata', 'NX_NUMBER'] DEBUG - classes: NXdata.nxdl.xml:/AXISNAME DEBUG - <> DEBUG - Dataset referenced as NXdata AXIS #0 DEBUG - documentation (NXdata.nxdl.xml:/AXISNAME): DEBUG - - Coordinate values along one or more :ref:`data ` dimensions. The rank must be equal - to the number of dimensions it spans. - - As the upper case ``AXISNAME`` indicates, the names of the ``AXISNAME`` fields can be chosen :ref:`freely `. - The :ref:`axes ` attribute can be used to find all datasets in the - ``NXdata`` that contain coordinate values. - - Most AXISNAME fields will be sequences of numbers but if an axis is better represented using names, such as channel names, - an array of NX_CHAR can be provided. - + Dimension scale defining an axis of the data. + Client is responsible for defining the dimensions of the data. + The name of this field may be changed to fit the circumstances. + Standard NeXus client tools will use the attributes to determine + how to use this field. + DEBUG - ===== ATTRS (//entry/data/angles@target) DEBUG - value: /entry/instrument/analyser/angles -DEBUG - classpath: ['NXentry', 'NXdata', 'NX_CHAR_OR_NUMBER'] +DEBUG - classpath: ['NXentry', 'NXdata', 'NX_NUMBER'] DEBUG - classes: NXdata.nxdl.xml:/AXISNAME DEBUG - @target - IS NOT IN SCHEMA DEBUG - DEBUG - ===== ATTRS (//entry/data/angles@units) DEBUG - value: 1/Å -DEBUG - classpath: ['NXentry', 'NXdata', 'NX_CHAR_OR_NUMBER'] +DEBUG - classpath: ['NXentry', 'NXdata', 'NX_NUMBER'] DEBUG - classes: NXdata.nxdl.xml:/AXISNAME -DEBUG - NXdata.nxdl.xml:/AXISNAME@units - [NX_CHAR] -DEBUG - Dataset referenced as NXdata AXIS #0 -DEBUG - documentation (NXdata.nxdl.xml:/AXISNAME/units): -DEBUG - - Unit in which the coordinate values are expressed. - See the section :ref:`Design-Units` for more information. - +DEBUG - NXdata.nxdl.xml:/AXISNAME@units - REQUIRED, but undefined unit category DEBUG - ===== FIELD (//entry/data/data): DEBUG - value: [[0. 0. 0. ... 0. 0. 0.] ... DEBUG - classpath: ['NXentry', 'NXdata', 'NX_NUMBER'] @@ -421,15 +333,15 @@ DEBUG - <> DEBUG - Dataset referenced as NXdata SIGNAL DEBUG - documentation (NXdata.nxdl.xml:/DATA): DEBUG - - .. index:: plotting - - Data values to be used as the NeXus *plottable data*. As the upper case ``DATA`` - indicates, the names of the ``DATA`` fields can be chosen :ref:`freely `. The :ref:`signal attribute ` - and :ref:`auxiliary_signals attribute` can be used to find all datasets in the ``NXdata`` - that contain data values. - - The maximum rank is ``32`` for compatibility with backend file formats. - + .. index:: plotting + + This field contains the data values to be used as the + NeXus *plottable data*. + Client is responsible for defining the dimensions of the data. + The name of this field may be changed to fit the circumstances. + Standard NeXus client tools will use the attributes to determine + how to use this field. + DEBUG - ===== ATTRS (//entry/data/data@target) DEBUG - value: /entry/instrument/analyser/data DEBUG - classpath: ['NXentry', 'NXdata', 'NX_NUMBER'] @@ -445,80 +357,60 @@ NXdata.nxdl.xml:/DATA DEBUG - NXdata.nxdl.xml:/DATA@units - REQUIRED, but undefined unit category DEBUG - ===== FIELD (//entry/data/delays): DEBUG - value: [-1.1 -1.08041237 -1.06082474 -1.04123711 -1.02164948 -1.00206186 ... -DEBUG - classpath: ['NXentry', 'NXdata', 'NX_CHAR_OR_NUMBER'] +DEBUG - classpath: ['NXentry', 'NXdata', 'NX_NUMBER'] DEBUG - classes: NXdata.nxdl.xml:/AXISNAME DEBUG - <> DEBUG - Dataset referenced as NXdata AXIS #2 DEBUG - documentation (NXdata.nxdl.xml:/AXISNAME): DEBUG - - Coordinate values along one or more :ref:`data ` dimensions. The rank must be equal - to the number of dimensions it spans. - - As the upper case ``AXISNAME`` indicates, the names of the ``AXISNAME`` fields can be chosen :ref:`freely `. - The :ref:`axes ` attribute can be used to find all datasets in the - ``NXdata`` that contain coordinate values. - - Most AXISNAME fields will be sequences of numbers but if an axis is better represented using names, such as channel names, - an array of NX_CHAR can be provided. - + Dimension scale defining an axis of the data. + Client is responsible for defining the dimensions of the data. + The name of this field may be changed to fit the circumstances. + Standard NeXus client tools will use the attributes to determine + how to use this field. + DEBUG - ===== ATTRS (//entry/data/delays@target) DEBUG - value: /entry/instrument/analyser/delays -DEBUG - classpath: ['NXentry', 'NXdata', 'NX_CHAR_OR_NUMBER'] +DEBUG - classpath: ['NXentry', 'NXdata', 'NX_NUMBER'] DEBUG - classes: NXdata.nxdl.xml:/AXISNAME DEBUG - @target - IS NOT IN SCHEMA DEBUG - DEBUG - ===== ATTRS (//entry/data/delays@units) DEBUG - value: fs -DEBUG - classpath: ['NXentry', 'NXdata', 'NX_CHAR_OR_NUMBER'] +DEBUG - classpath: ['NXentry', 'NXdata', 'NX_NUMBER'] DEBUG - classes: NXdata.nxdl.xml:/AXISNAME -DEBUG - NXdata.nxdl.xml:/AXISNAME@units - [NX_CHAR] -DEBUG - Dataset referenced as NXdata AXIS #2 -DEBUG - documentation (NXdata.nxdl.xml:/AXISNAME/units): -DEBUG - - Unit in which the coordinate values are expressed. - See the section :ref:`Design-Units` for more information. - +DEBUG - NXdata.nxdl.xml:/AXISNAME@units - REQUIRED, but undefined unit category DEBUG - ===== FIELD (//entry/data/energies): DEBUG - value: [ 2.5 2.46917808 2.43835616 2.40753425 2.37671233 2.34589041 ... -DEBUG - classpath: ['NXentry', 'NXdata', 'NX_CHAR_OR_NUMBER'] +DEBUG - classpath: ['NXentry', 'NXdata', 'NX_NUMBER'] DEBUG - classes: NXdata.nxdl.xml:/AXISNAME DEBUG - <> DEBUG - Dataset referenced as NXdata AXIS #1 DEBUG - documentation (NXdata.nxdl.xml:/AXISNAME): DEBUG - - Coordinate values along one or more :ref:`data ` dimensions. The rank must be equal - to the number of dimensions it spans. - - As the upper case ``AXISNAME`` indicates, the names of the ``AXISNAME`` fields can be chosen :ref:`freely `. - The :ref:`axes ` attribute can be used to find all datasets in the - ``NXdata`` that contain coordinate values. - - Most AXISNAME fields will be sequences of numbers but if an axis is better represented using names, such as channel names, - an array of NX_CHAR can be provided. - + Dimension scale defining an axis of the data. + Client is responsible for defining the dimensions of the data. + The name of this field may be changed to fit the circumstances. + Standard NeXus client tools will use the attributes to determine + how to use this field. + DEBUG - ===== ATTRS (//entry/data/energies@target) DEBUG - value: /entry/instrument/analyser/energies -DEBUG - classpath: ['NXentry', 'NXdata', 'NX_CHAR_OR_NUMBER'] +DEBUG - classpath: ['NXentry', 'NXdata', 'NX_NUMBER'] DEBUG - classes: NXdata.nxdl.xml:/AXISNAME DEBUG - @target - IS NOT IN SCHEMA DEBUG - DEBUG - ===== ATTRS (//entry/data/energies@units) DEBUG - value: eV -DEBUG - classpath: ['NXentry', 'NXdata', 'NX_CHAR_OR_NUMBER'] +DEBUG - classpath: ['NXentry', 'NXdata', 'NX_NUMBER'] DEBUG - classes: NXdata.nxdl.xml:/AXISNAME -DEBUG - NXdata.nxdl.xml:/AXISNAME@units - [NX_CHAR] -DEBUG - Dataset referenced as NXdata AXIS #1 -DEBUG - documentation (NXdata.nxdl.xml:/AXISNAME/units): -DEBUG - - Unit in which the coordinate values are expressed. - See the section :ref:`Design-Units` for more information. - +DEBUG - NXdata.nxdl.xml:/AXISNAME@units - REQUIRED, but undefined unit category DEBUG - ===== FIELD (//entry/definition): DEBUG - value: NXarpes DEBUG - classpath: ['NXentry', 'NX_CHAR'] @@ -529,24 +421,26 @@ DEBUG - <> DEBUG - enumeration (NXarpes.nxdl.xml:/ENTRY/definition): DEBUG - -> NXarpes DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/definition): -DEBUG - Official NeXus NXDL schema to which this file conforms. +DEBUG - + Official NeXus NXDL schema to which this file conforms. + DEBUG - documentation (NXentry.nxdl.xml:/definition): DEBUG - - (alternate use: see same field in :ref:`NXsubentry` for preferred) - - Official NeXus NXDL schema to which this entry conforms which must be - the name of the NXDL file (case sensitive without the file extension) - that the NXDL schema is defined in. - - For example the ``definition`` field for a file that conformed to the - *NXarpes.nxdl.xml* definition must contain the string **NXarpes**. - - This field is provided so that :ref:`NXentry` can be the overlay position - in a NeXus data file for an application definition and its - set of groups, fields, and attributes. - - *It is advised* to use :ref:`NXsubentry`, instead, as the overlay position. - + (alternate use: see same field in :ref:`NXsubentry` for preferred) + + Official NeXus NXDL schema to which this entry conforms which must be + the name of the NXDL file (case sensitive without the file extension) + that the NXDL schema is defined in. + + For example the ``definition`` field for a file that conformed to the + *NXarpes.nxdl.xml* definition must contain the string **NXarpes**. + + This field is provided so that :ref:`NXentry` can be the overlay position + in a NeXus data file for an application definition and its + set of groups, fields, and attributes. + + *It is advised* to use :ref:`NXsubentry`, instead, as the overlay position. + DEBUG - ===== FIELD (//entry/duration): DEBUG - value: 7200 DEBUG - classpath: ['NXentry', 'NX_INT'] @@ -554,7 +448,9 @@ DEBUG - classes: NXentry.nxdl.xml:/duration DEBUG - <> DEBUG - documentation (NXentry.nxdl.xml:/duration): -DEBUG - Duration of measurement +DEBUG - + Duration of measurement + DEBUG - ===== ATTRS (//entry/duration@units) DEBUG - value: s DEBUG - classpath: ['NXentry', 'NX_INT'] @@ -568,36 +464,25 @@ DEBUG - classes: NXentry.nxdl.xml:/end_time DEBUG - <> DEBUG - documentation (NXentry.nxdl.xml:/end_time): -DEBUG - Ending time of measurement +DEBUG - + Ending time of measurement + DEBUG - ===== FIELD (//entry/entry_identifier): DEBUG - value: Run 22118 -DEBUG - classpath: ['NXentry', 'NX_CHAR'] -DEBUG - classes: -NXentry.nxdl.xml:/entry_identifier -DEBUG - <> -DEBUG - DEPRECATED - Use the field :ref:`identifier_entry ` instead. -DEBUG - documentation (NXentry.nxdl.xml:/entry_identifier): -DEBUG - unique identifier for the measurement, defined by the facility. +DEBUG - classpath: ['NXentry'] +DEBUG - NOT IN SCHEMA +DEBUG - DEBUG - ===== FIELD (//entry/experiment_identifier): DEBUG - value: F-20170538 -DEBUG - classpath: ['NXentry', 'NX_CHAR'] -DEBUG - classes: -NXentry.nxdl.xml:/experiment_identifier -DEBUG - <> -DEBUG - DEPRECATED - Use the field :ref:`identifier_experiment ` instead. -DEBUG - documentation (NXentry.nxdl.xml:/experiment_identifier): +DEBUG - classpath: ['NXentry'] +DEBUG - NOT IN SCHEMA DEBUG - - Unique identifier for the experiment, - defined by the facility, - possibly linked to the proposals - DEBUG - ===== GROUP (//entry/instrument [NXarpes::/NXentry/NXinstrument]): DEBUG - classpath: ['NXentry', 'NXinstrument'] DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT NXentry.nxdl.xml:/INSTRUMENT NXinstrument.nxdl.xml: -NXobject.nxdl.xml: DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT): DEBUG - @@ -605,24 +490,15 @@ DEBUG - documentation (NXentry.nxdl.xml:/INSTRUMENT): DEBUG - DEBUG - documentation (NXinstrument.nxdl.xml:): DEBUG - - Collection of the components of the instrument or beamline. - - Template of instrument descriptions comprising various beamline components. - Each component will also be a NeXus group defined by its distance from the - sample. Negative distances represent beamline components that are before the - sample while positive distances represent components that are after the sample. - This device allows the unique identification of beamline components in a way - that is valid for both reactor and pulsed instrumentation. - -DEBUG - documentation (NXobject.nxdl.xml:): -DEBUG - - This is the base object of NeXus. The groups and fields contained - within this file are allowed to be present in any derived base class. - - If nameType="partial", the placeholders (e.g., FIELDNAME or GROUPNAME) - can be replaced by the name of any object (field or group, - respectively) that exists within the same group. - + Collection of the components of the instrument or beamline. + + Template of instrument descriptions comprising various beamline components. + Each component will also be a NeXus group defined by its distance from the + sample. Negative distances represent beamline components that are before the + sample while positive distances represent components that are after the sample. + This device allows the unique identification of beamline components in a way + that is valid for both reactor and pulsed instrumentation. + DEBUG - ===== ATTRS (//entry/instrument@NX_class) DEBUG - value: NXinstrument DEBUG - classpath: ['NXentry', 'NXinstrument'] @@ -630,7 +506,6 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT NXentry.nxdl.xml:/INSTRUMENT NXinstrument.nxdl.xml: -NXobject.nxdl.xml: DEBUG - @NX_class [NX_CHAR] DEBUG - DEBUG - ===== GROUP (//entry/instrument/analyser [NXarpes::/NXentry/NXinstrument/NXdetector]): @@ -639,8 +514,6 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser NXinstrument.nxdl.xml:/DETECTOR NXdetector.nxdl.xml: -NXcomponent.nxdl.xml: -NXobject.nxdl.xml: DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser): DEBUG - @@ -648,21 +521,8 @@ DEBUG - documentation (NXinstrument.nxdl.xml:/DETECTOR): DEBUG - DEBUG - documentation (NXdetector.nxdl.xml:): DEBUG - - A detector, detector bank, or multidetector. - -DEBUG - documentation (NXcomponent.nxdl.xml:): -DEBUG - - Base class for components of an instrument - real ones or simulated ones. + A detector, detector bank, or multidetector. -DEBUG - documentation (NXobject.nxdl.xml:): -DEBUG - - This is the base object of NeXus. The groups and fields contained - within this file are allowed to be present in any derived base class. - - If nameType="partial", the placeholders (e.g., FIELDNAME or GROUPNAME) - can be replaced by the name of any object (field or group, - respectively) that exists within the same group. - DEBUG - ===== ATTRS (//entry/instrument/analyser@NX_class) DEBUG - value: NXdetector DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector'] @@ -670,8 +530,6 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser NXinstrument.nxdl.xml:/DETECTOR NXdetector.nxdl.xml: -NXcomponent.nxdl.xml: -NXobject.nxdl.xml: DEBUG - @NX_class [NX_CHAR] DEBUG - DEBUG - ===== FIELD (//entry/instrument/analyser/acquisition_mode): @@ -695,12 +553,19 @@ DEBUG - -> pulse counting DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/acquisition_mode): DEBUG - DEBUG - documentation (NXdetector.nxdl.xml:/acquisition_mode): -DEBUG - The acquisition mode of the detector. +DEBUG - + The acquisition mode of the detector. + DEBUG - ===== FIELD (//entry/instrument/analyser/amplifier_type): DEBUG - value: MCP -DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector'] -DEBUG - NOT IN SCHEMA +DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_CHAR'] +DEBUG - classes: +NXdetector.nxdl.xml:/amplifier_type +DEBUG - <> +DEBUG - documentation (NXdetector.nxdl.xml:/amplifier_type): DEBUG - + Type of electron amplifier, MCP, channeltron, etc. + DEBUG - ===== FIELD (//entry/instrument/analyser/angles): DEBUG - value: [-1.96735314 -1.91500657 -1.86266001 -1.81031344 -1.75796688 -1.70562031 ... DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_NUMBER'] @@ -709,10 +574,10 @@ NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/angles DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/angles): DEBUG - - Angular axis of the analyser data - which dimension the axis applies to is defined - using the normal NXdata methods. - + Angular axis of the analyser data + which dimension the axis applies to is defined + using the normal NXdata methods. + DEBUG - ===== ATTRS (//entry/instrument/analyser/angles@target) DEBUG - value: /entry/instrument/analyser/angles DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_NUMBER'] @@ -742,29 +607,29 @@ DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/data): DEBUG - DEBUG - documentation (NXdetector.nxdl.xml:/data): DEBUG - - Data values from the detector. The rank and dimension ordering should follow a principle of - slowest to fastest measurement axes and may be explicitly specified in application definitions. - - Mechanical scanning of objects (e.g. sample position/angle, incident beam energy, etc) tends to be - the slowest part of an experiment and so any such scan axes should be allocated to the first dimensions - of the array. Note that in some cases it may be useful to represent a 2D set of scan points as a single - scan-axis in the data array, especially if the scan pattern doesn't fit a rectangular array nicely. - Repetition of an experiment in a time series tends to be used similar to a slow scan axis - and so will often be in the first dimension of the data array. - - The next fastest axes are typically the readout of the detector. A point detector will not add any dimensions - (as it is just a single value per scan point) to the data array, a strip detector will add one dimension, an - imaging detector will add two dimensions (e.g. X, Y axes) and detectors outputting higher dimensional data - will add the corresponding number of dimensions. Note that the detector dimensions don't necessarily have to - be written in order of the actual readout speeds - the slowest to fastest rule principle is only a guide. - - Finally, detectors that operate in a time-of-flight mode, such as a neutron spectrometer or a silicon drift - detector (used for X-ray fluorescence) tend to have their dimension(s) added to the last dimensions in the data array. - - The type of each dimension should should follow the order of scan points, detector pixels, - then time-of-flight (i.e. spectroscopy, spectrometry). The rank and dimension sizes (see symbol list) - shown here are merely illustrative of coordination between related datasets. - + Data values from the detector. The rank and dimension ordering should follow a principle of + slowest to fastest measurement axes and may be explicitly specified in application definitions. + + Mechanical scanning of objects (e.g. sample position/angle, incident beam energy, etc) tends to be + the slowest part of an experiment and so any such scan axes should be allocated to the first dimensions + of the array. Note that in some cases it may be useful to represent a 2D set of scan points as a single + scan-axis in the data array, especially if the scan pattern doesn't fit a rectangular array nicely. + Repetition of an experiment in a time series tends to be used similar to a slow scan axis + and so will often be in the first dimension of the data array. + + The next fastest axes are typically the readout of the detector. A point detector will not add any dimensions + (as it is just a single value per scan point) to the data array, a strip detector will add one dimension, an + imaging detector will add two dimensions (e.g. X, Y axes) and detectors outputting higher dimensional data + will add the corresponding number of dimensions. Note that the detector dimensions don't necessarily have to + be written in order of the actual readout speeds - the slowest to fastest rule principle is only a guide. + + Finally, detectors that operate in a time-of-flight mode, such as a neutron spectrometer or a silicon drift + detector (used for X-ray fluorescence) tend to have their dimension(s) added to the last dimensions in the data array. + + The type of each dimension should should follow the order of scan points, detector pixels, + then time-of-flight (i.e. spectroscopy, spectrometry). The rank and dimension sizes (see symbol list) + shown here are merely illustrative of coordination between related datasets. + DEBUG - ===== ATTRS (//entry/instrument/analyser/data@target) DEBUG - value: /entry/instrument/analyser/data DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_NUMBER'] @@ -798,9 +663,14 @@ DEBUG - NOT IN SCHEMA DEBUG - DEBUG - ===== FIELD (//entry/instrument/analyser/detector_type): DEBUG - value: DLD -DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector'] -DEBUG - NOT IN SCHEMA +DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_CHAR'] +DEBUG - classes: +NXdetector.nxdl.xml:/detector_type +DEBUG - <> +DEBUG - documentation (NXdetector.nxdl.xml:/detector_type): DEBUG - + Description of the detector type, DLD, Phosphor+CCD, CMOS. + DEBUG - ===== FIELD (//entry/instrument/analyser/dispersion_scheme): DEBUG - value: Time of flight DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector'] @@ -814,10 +684,10 @@ NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/energies DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/energies): DEBUG - - Energy axis of the analyser data - which dimension the axis applies to is defined - using the normal NXdata methods. - + Energy axis of the analyser data + which dimension the axis applies to is defined + using the normal NXdata methods. + DEBUG - ===== ATTRS (//entry/instrument/analyser/energies@target) DEBUG - value: /entry/instrument/analyser/energies DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_NUMBER'] @@ -838,7 +708,9 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/entrance_slit_setting DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/entrance_slit_setting): -DEBUG - dial setting of the entrance slit +DEBUG - + dial setting of the entrance slit + DEBUG - ===== FIELD (//entry/instrument/analyser/entrance_slit_shape): DEBUG - value: straight DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_CHAR'] @@ -857,7 +729,9 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/entrance_slit_size DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/entrance_slit_size): -DEBUG - size of the entrance slit +DEBUG - + size of the entrance slit + DEBUG - ===== ATTRS (//entry/instrument/analyser/entrance_slit_size@units) DEBUG - value: um DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_NUMBER'] @@ -901,7 +775,9 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/lens_mode DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/lens_mode): -DEBUG - setting for the electron analyser lens +DEBUG - + setting for the electron analyser lens + DEBUG - ===== FIELD (//entry/instrument/analyser/magnification): DEBUG - value: -1.5 DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector'] @@ -914,7 +790,9 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/pass_energy DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/pass_energy): -DEBUG - energy of the electrons on the mean path of the analyser +DEBUG - + energy of the electrons on the mean path of the analyser + DEBUG - ===== ATTRS (//entry/instrument/analyser/pass_energy@units) DEBUG - value: eV DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_NUMBER'] @@ -933,7 +811,9 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/region_origin DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/region_origin): -DEBUG - origin of rectangular region selected for readout +DEBUG - + origin of rectangular region selected for readout + DEBUG - ===== FIELD (//entry/instrument/analyser/region_size): DEBUG - value: [ 80 146] DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_INT'] @@ -941,12 +821,19 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/region_size DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/region_size): -DEBUG - size of rectangular region selected for readout +DEBUG - + size of rectangular region selected for readout + DEBUG - ===== FIELD (//entry/instrument/analyser/sensor_count): DEBUG - value: 4 -DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector'] -DEBUG - NOT IN SCHEMA +DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_INT'] +DEBUG - classes: +NXdetector.nxdl.xml:/sensor_count +DEBUG - <> +DEBUG - documentation (NXdetector.nxdl.xml:/sensor_count): DEBUG - + Number of imaging sensor chips on the detector. + DEBUG - ===== FIELD (//entry/instrument/analyser/sensor_size): DEBUG - value: [ 80 146] DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_INT'] @@ -954,7 +841,9 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/sensor_size DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/sensor_size): -DEBUG - number of raw active elements in each dimension +DEBUG - + number of raw active elements in each dimension + DEBUG - ===== FIELD (//entry/instrument/analyser/time_per_channel): DEBUG - value: 7200 DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_NUMBER'] @@ -962,7 +851,9 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/time_per_channel DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/analyser/time_per_channel): -DEBUG - todo: define more clearly +DEBUG - + todo: define more clearly + DEBUG - ===== ATTRS (//entry/instrument/analyser/time_per_channel@units) DEBUG - value: s DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXdetector', 'NX_NUMBER'] @@ -984,44 +875,33 @@ DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXbeam'] DEBUG - classes: NXinstrument.nxdl.xml:/BEAM NXbeam.nxdl.xml: -NXobject.nxdl.xml: DEBUG - <> DEBUG - documentation (NXinstrument.nxdl.xml:/BEAM): DEBUG - DEBUG - documentation (NXbeam.nxdl.xml:): DEBUG - - Properties of the neutron or X-ray beam at a given location. - - This group is intended to be referenced - by beamline component groups within the :ref:`NXinstrument` group or by the :ref:`NXsample` group. This group is - especially valuable in storing the results of instrument simulations in which it is useful - to specify the beam profile, time distribution etc. at each beamline component. Otherwise, - its most likely use is in the :ref:`NXsample` group in which it defines the results of the neutron - scattering by the sample, e.g., energy transfer, polarizations. Finally, There are cases where the beam is - considered as a beamline component and this group may be defined as a subgroup directly inside - :ref:`NXinstrument`, in which case it is recommended that the position of the beam is specified by an - :ref:`NXtransformations` group, unless the beam is at the origin (which is the sample). - - Note that ``incident_wavelength``, ``incident_energy``, and related fields can be a scalar values or arrays, depending on the use case. - To support these use cases, the explicit dimensionality of these fields is not specified, but it can be inferred - by the presence of and shape of accompanying fields, such as incident_wavelength_weights for a polychromatic beam. - -DEBUG - documentation (NXobject.nxdl.xml:): -DEBUG - - This is the base object of NeXus. The groups and fields contained - within this file are allowed to be present in any derived base class. - - If nameType="partial", the placeholders (e.g., FIELDNAME or GROUPNAME) - can be replaced by the name of any object (field or group, - respectively) that exists within the same group. - + Properties of the neutron or X-ray beam at a given location. + + This group is intended to be referenced + by beamline component groups within the :ref:`NXinstrument` group or by the :ref:`NXsample` group. This group is + especially valuable in storing the results of instrument simulations in which it is useful + to specify the beam profile, time distribution etc. at each beamline component. Otherwise, + its most likely use is in the :ref:`NXsample` group in which it defines the results of the neutron + scattering by the sample, e.g., energy transfer, polarizations. Finally, There are cases where the beam is + considered as a beamline component and this group may be defined as a subgroup directly inside + :ref:`NXinstrument`, in which case it is recommended that the position of the beam is specified by an + :ref:`NXtransformations` group, unless the beam is at the origin (which is the sample). + + Note that incident_wavelength and related fields can be a scalar values or arrays, depending on the use case. + To support these use cases, the explicit dimensionality of these fields is not specified, but it can be inferred + by the presense of and shape of accompanying fields, such as incident_wavelength_weights for a polychromatic beam. + DEBUG - ===== ATTRS (//entry/instrument/beam_probe_0@NX_class) DEBUG - value: NXbeam DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXbeam'] DEBUG - classes: NXinstrument.nxdl.xml:/BEAM NXbeam.nxdl.xml: -NXobject.nxdl.xml: DEBUG - @NX_class [NX_CHAR] DEBUG - DEBUG - ===== FIELD (//entry/instrument/beam_probe_0/distance): @@ -1031,7 +911,9 @@ DEBUG - classes: NXbeam.nxdl.xml:/distance DEBUG - <> DEBUG - documentation (NXbeam.nxdl.xml:/distance): -DEBUG - Distance from sample. Note, it is recommended to use NXtransformations instead. +DEBUG - + Distance from sample. Note, it is recommended to use NXtransformations instead. + DEBUG - ===== ATTRS (//entry/instrument/beam_probe_0/distance@units) DEBUG - value: cm DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXbeam', 'NX_FLOAT'] @@ -1071,7 +953,7 @@ NXbeam.nxdl.xml:/pulse_duration DEBUG - <> DEBUG - documentation (NXbeam.nxdl.xml:/pulse_duration): DEBUG - - FWHM duration of the pulses at the given location. + FWHM duration of the pulses at the diagnostic point DEBUG - ===== ATTRS (//entry/instrument/beam_probe_0/pulse_duration@units) DEBUG - value: fs @@ -1104,44 +986,33 @@ DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXbeam'] DEBUG - classes: NXinstrument.nxdl.xml:/BEAM NXbeam.nxdl.xml: -NXobject.nxdl.xml: DEBUG - <> DEBUG - documentation (NXinstrument.nxdl.xml:/BEAM): DEBUG - DEBUG - documentation (NXbeam.nxdl.xml:): DEBUG - - Properties of the neutron or X-ray beam at a given location. - - This group is intended to be referenced - by beamline component groups within the :ref:`NXinstrument` group or by the :ref:`NXsample` group. This group is - especially valuable in storing the results of instrument simulations in which it is useful - to specify the beam profile, time distribution etc. at each beamline component. Otherwise, - its most likely use is in the :ref:`NXsample` group in which it defines the results of the neutron - scattering by the sample, e.g., energy transfer, polarizations. Finally, There are cases where the beam is - considered as a beamline component and this group may be defined as a subgroup directly inside - :ref:`NXinstrument`, in which case it is recommended that the position of the beam is specified by an - :ref:`NXtransformations` group, unless the beam is at the origin (which is the sample). - - Note that ``incident_wavelength``, ``incident_energy``, and related fields can be a scalar values or arrays, depending on the use case. - To support these use cases, the explicit dimensionality of these fields is not specified, but it can be inferred - by the presence of and shape of accompanying fields, such as incident_wavelength_weights for a polychromatic beam. - -DEBUG - documentation (NXobject.nxdl.xml:): -DEBUG - - This is the base object of NeXus. The groups and fields contained - within this file are allowed to be present in any derived base class. - - If nameType="partial", the placeholders (e.g., FIELDNAME or GROUPNAME) - can be replaced by the name of any object (field or group, - respectively) that exists within the same group. - + Properties of the neutron or X-ray beam at a given location. + + This group is intended to be referenced + by beamline component groups within the :ref:`NXinstrument` group or by the :ref:`NXsample` group. This group is + especially valuable in storing the results of instrument simulations in which it is useful + to specify the beam profile, time distribution etc. at each beamline component. Otherwise, + its most likely use is in the :ref:`NXsample` group in which it defines the results of the neutron + scattering by the sample, e.g., energy transfer, polarizations. Finally, There are cases where the beam is + considered as a beamline component and this group may be defined as a subgroup directly inside + :ref:`NXinstrument`, in which case it is recommended that the position of the beam is specified by an + :ref:`NXtransformations` group, unless the beam is at the origin (which is the sample). + + Note that incident_wavelength and related fields can be a scalar values or arrays, depending on the use case. + To support these use cases, the explicit dimensionality of these fields is not specified, but it can be inferred + by the presense of and shape of accompanying fields, such as incident_wavelength_weights for a polychromatic beam. + DEBUG - ===== ATTRS (//entry/instrument/beam_pump_0@NX_class) DEBUG - value: NXbeam DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXbeam'] DEBUG - classes: NXinstrument.nxdl.xml:/BEAM NXbeam.nxdl.xml: -NXobject.nxdl.xml: DEBUG - @NX_class [NX_CHAR] DEBUG - DEBUG - ===== FIELD (//entry/instrument/beam_pump_0/average_power): @@ -1152,7 +1023,7 @@ NXbeam.nxdl.xml:/average_power DEBUG - <> DEBUG - documentation (NXbeam.nxdl.xml:/average_power): DEBUG - - Average power at the at the given location. + Average power at the diagnostic point DEBUG - ===== ATTRS (//entry/instrument/beam_pump_0/average_power@units) DEBUG - value: uW @@ -1177,7 +1048,9 @@ DEBUG - classes: NXbeam.nxdl.xml:/distance DEBUG - <> DEBUG - documentation (NXbeam.nxdl.xml:/distance): -DEBUG - Distance from sample. Note, it is recommended to use NXtransformations instead. +DEBUG - + Distance from sample. Note, it is recommended to use NXtransformations instead. + DEBUG - ===== ATTRS (//entry/instrument/beam_pump_0/distance@units) DEBUG - value: cm DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXbeam', 'NX_FLOAT'] @@ -1192,14 +1065,14 @@ NXbeam.nxdl.xml:/fluence DEBUG - <> DEBUG - documentation (NXbeam.nxdl.xml:/fluence): DEBUG - - Incident energy fluence at the given location. + Incident fluence at the diagnostic point DEBUG - ===== ATTRS (//entry/instrument/beam_pump_0/fluence@units) DEBUG - value: mJ/cm^2 DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXbeam', 'NX_FLOAT'] DEBUG - classes: NXbeam.nxdl.xml:/fluence -DEBUG - NXbeam.nxdl.xml:/fluence@units [mJ/cm^2] +DEBUG - NXbeam.nxdl.xml:/fluence@units [NX_ANY] DEBUG - ===== FIELD (//entry/instrument/beam_pump_0/photon_energy): DEBUG - value: 1.55 DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXbeam'] @@ -1233,7 +1106,7 @@ NXbeam.nxdl.xml:/pulse_duration DEBUG - <> DEBUG - documentation (NXbeam.nxdl.xml:/pulse_duration): DEBUG - - FWHM duration of the pulses at the given location. + FWHM duration of the pulses at the diagnostic point DEBUG - ===== ATTRS (//entry/instrument/beam_pump_0/pulse_duration@units) DEBUG - value: fs @@ -1249,7 +1122,7 @@ NXbeam.nxdl.xml:/pulse_energy DEBUG - <> DEBUG - documentation (NXbeam.nxdl.xml:/pulse_energy): DEBUG - - Energy of a single pulse at the given location. + Energy of a single pulse at the diagnostic point DEBUG - ===== ATTRS (//entry/instrument/beam_pump_0/pulse_energy@units) DEBUG - value: nJ @@ -1292,36 +1165,19 @@ DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXpositioner'] DEBUG - classes: NXinstrument.nxdl.xml:/POSITIONER NXpositioner.nxdl.xml: -NXcomponent.nxdl.xml: -NXobject.nxdl.xml: DEBUG - <> DEBUG - documentation (NXinstrument.nxdl.xml:/POSITIONER): DEBUG - DEBUG - documentation (NXpositioner.nxdl.xml:): DEBUG - - A generic positioner such as a motor or piezo-electric transducer. - -DEBUG - documentation (NXcomponent.nxdl.xml:): -DEBUG - - Base class for components of an instrument - real ones or simulated ones. + A generic positioner such as a motor or piezo-electric transducer. -DEBUG - documentation (NXobject.nxdl.xml:): -DEBUG - - This is the base object of NeXus. The groups and fields contained - within this file are allowed to be present in any derived base class. - - If nameType="partial", the placeholders (e.g., FIELDNAME or GROUPNAME) - can be replaced by the name of any object (field or group, - respectively) that exists within the same group. - DEBUG - ===== ATTRS (//entry/instrument/manipulator@NX_class) DEBUG - value: NXpositioner DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXpositioner'] DEBUG - classes: NXinstrument.nxdl.xml:/POSITIONER NXpositioner.nxdl.xml: -NXcomponent.nxdl.xml: -NXobject.nxdl.xml: DEBUG - @NX_class [NX_CHAR] DEBUG - DEBUG - ===== FIELD (//entry/instrument/manipulator/pos_x1): @@ -1425,8 +1281,6 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/monochromator NXinstrument.nxdl.xml:/MONOCHROMATOR NXmonochromator.nxdl.xml: -NXcomponent.nxdl.xml: -NXobject.nxdl.xml: DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/monochromator): DEBUG - @@ -1434,32 +1288,18 @@ DEBUG - documentation (NXinstrument.nxdl.xml:/MONOCHROMATOR): DEBUG - DEBUG - documentation (NXmonochromator.nxdl.xml:): DEBUG - - A wavelength defining device. - - This is a base class for everything which - selects a wavelength or energy, be it a - monochromator crystal, a velocity selector, - an undulator or whatever. - - The expected units are: - - * wavelength: angstrom - * energy: eV - - -DEBUG - documentation (NXcomponent.nxdl.xml:): -DEBUG - - Base class for components of an instrument - real ones or simulated ones. + A wavelength defining device. + + This is a base class for everything which + selects a wavelength or energy, be it a + monochromator crystal, a velocity selector, + an undulator or whatever. + + The expected units are: + + * wavelength: angstrom + * energy: eV -DEBUG - documentation (NXobject.nxdl.xml:): -DEBUG - - This is the base object of NeXus. The groups and fields contained - within this file are allowed to be present in any derived base class. - - If nameType="partial", the placeholders (e.g., FIELDNAME or GROUPNAME) - can be replaced by the name of any object (field or group, - respectively) that exists within the same group. - DEBUG - ===== ATTRS (//entry/instrument/monochromator@NX_class) DEBUG - value: NXmonochromator DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXmonochromator'] @@ -1467,8 +1307,6 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/monochromator NXinstrument.nxdl.xml:/MONOCHROMATOR NXmonochromator.nxdl.xml: -NXcomponent.nxdl.xml: -NXobject.nxdl.xml: DEBUG - @NX_class [NX_CHAR] DEBUG - DEBUG - ===== FIELD (//entry/instrument/monochromator/energy): @@ -1493,7 +1331,9 @@ NXmonochromator.nxdl.xml:/energy_error DEBUG - <> DEBUG - DEPRECATED - see https://github.com/nexusformat/definitions/issues/820 DEBUG - documentation (NXmonochromator.nxdl.xml:/energy_error): -DEBUG - energy standard deviation +DEBUG - + energy standard deviation + DEBUG - ===== ATTRS (//entry/instrument/monochromator/energy_error@units) DEBUG - value: eV DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXmonochromator', 'NX_FLOAT'] @@ -1526,15 +1366,15 @@ DEBUG - classes: NXinstrument.nxdl.xml:/name DEBUG - <> DEBUG - documentation (NXinstrument.nxdl.xml:/name): -DEBUG - Name of instrument +DEBUG - + Name of instrument + DEBUG - ===== GROUP (//entry/instrument/source [NXarpes::/NXentry/NXinstrument/NXsource]): DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource'] DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE NXinstrument.nxdl.xml:/SOURCE NXsource.nxdl.xml: -NXcomponent.nxdl.xml: -NXobject.nxdl.xml: DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE): DEBUG - @@ -1542,24 +1382,11 @@ DEBUG - documentation (NXinstrument.nxdl.xml:/SOURCE): DEBUG - DEBUG - documentation (NXsource.nxdl.xml:): DEBUG - - Radiation source emitting a beam. - - Examples include particle sources (electrons, neutrons, protons) or sources for electromagnetic radiation (photons). - This base class can also be used to describe neutron or x-ray storage ring/facilities. - -DEBUG - documentation (NXcomponent.nxdl.xml:): -DEBUG - - Base class for components of an instrument - real ones or simulated ones. + Radiation source emitting a beam. + + Examples include particle sources (electrons, neutrons, protons) or sources for electromagnetic radiation (photons). + This base class can also be used to describe neutron or x-ray storage ring/facilities. -DEBUG - documentation (NXobject.nxdl.xml:): -DEBUG - - This is the base object of NeXus. The groups and fields contained - within this file are allowed to be present in any derived base class. - - If nameType="partial", the placeholders (e.g., FIELDNAME or GROUPNAME) - can be replaced by the name of any object (field or group, - respectively) that exists within the same group. - DEBUG - ===== ATTRS (//entry/instrument/source@NX_class) DEBUG - value: NXsource DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource'] @@ -1567,8 +1394,6 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE NXinstrument.nxdl.xml:/SOURCE NXsource.nxdl.xml: -NXcomponent.nxdl.xml: -NXobject.nxdl.xml: DEBUG - @NX_class [NX_CHAR] DEBUG - DEBUG - ===== FIELD (//entry/instrument/source/bunch_distance): @@ -1578,7 +1403,9 @@ DEBUG - classes: NXsource.nxdl.xml:/bunch_distance DEBUG - <> DEBUG - documentation (NXsource.nxdl.xml:/bunch_distance): -DEBUG - For storage rings, time between bunches +DEBUG - + For storage rings, time between bunches + DEBUG - ===== ATTRS (//entry/instrument/source/bunch_distance@units) DEBUG - value: us DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource', 'NX_FLOAT'] @@ -1592,7 +1419,9 @@ DEBUG - classes: NXsource.nxdl.xml:/bunch_length DEBUG - <> DEBUG - documentation (NXsource.nxdl.xml:/bunch_length): -DEBUG - For storage rings, temporal length of the bunch +DEBUG - + For storage rings, temporal length of the bunch + DEBUG - ===== ATTRS (//entry/instrument/source/bunch_length@units) DEBUG - value: fs DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource', 'NX_FLOAT'] @@ -1636,7 +1465,9 @@ DEBUG - classes: NXsource.nxdl.xml:/current DEBUG - <> DEBUG - documentation (NXsource.nxdl.xml:/current): -DEBUG - Accelerator, X-ray tube, or storage ring current +DEBUG - + Accelerator, X-ray tube, or storage ring current + DEBUG - ===== ATTRS (//entry/instrument/source/current@units) DEBUG - value: uA DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource', 'NX_FLOAT'] @@ -1651,10 +1482,10 @@ NXsource.nxdl.xml:/energy DEBUG - <> DEBUG - documentation (NXsource.nxdl.xml:/energy): DEBUG - - Source energy. Typically, this would be the energy of - the emitted beam. For storage rings, this would be - the particle beam energy. - + Source energy. Typically, this would be the energy of + the emitted beam. For storage rings, this would be + the particle beam energy. + DEBUG - ===== ATTRS (//entry/instrument/source/energy@units) DEBUG - value: MeV DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource', 'NX_FLOAT'] @@ -1668,7 +1499,9 @@ DEBUG - classes: NXsource.nxdl.xml:/frequency DEBUG - <> DEBUG - documentation (NXsource.nxdl.xml:/frequency): -DEBUG - Frequency of pulsed source +DEBUG - + Frequency of pulsed source + DEBUG - ===== ATTRS (//entry/instrument/source/frequency@units) DEBUG - value: Hz DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource', 'NX_FLOAT'] @@ -1685,22 +1518,21 @@ DEBUG - enumeration (NXsource.nxdl.xml:/mode): DEBUG - -> Single Bunch DEBUG - -> Multi Bunch DEBUG - documentation (NXsource.nxdl.xml:/mode): -DEBUG - source operating mode +DEBUG - + source operating mode + DEBUG - ===== FIELD (//entry/instrument/source/name): DEBUG - value: FLASH DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource', 'NX_CHAR'] DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE/name NXsource.nxdl.xml:/name -NXcomponent.nxdl.xml:/name DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE/name): DEBUG - DEBUG - documentation (NXsource.nxdl.xml:/name): -DEBUG - Name of source -DEBUG - documentation (NXcomponent.nxdl.xml:/name): DEBUG - - Name of the component. + Name of source DEBUG - ===== FIELD (//entry/instrument/source/number_of_bunches): DEBUG - value: 500 @@ -1709,7 +1541,9 @@ DEBUG - classes: NXsource.nxdl.xml:/number_of_bunches DEBUG - <> DEBUG - documentation (NXsource.nxdl.xml:/number_of_bunches): -DEBUG - For storage rings, the number of bunches in use. +DEBUG - + For storage rings, the number of bunches in use. + DEBUG - ===== FIELD (//entry/instrument/source/number_of_bursts): DEBUG - value: 1 DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource'] @@ -1737,7 +1571,9 @@ DEBUG - -> proton DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE/probe): DEBUG - DEBUG - documentation (NXsource.nxdl.xml:/probe): -DEBUG - type of radiation probe (pick one from the enumerated list and spell exactly) +DEBUG - + type of radiation probe (pick one from the enumerated list and spell exactly) + DEBUG - ===== FIELD (//entry/instrument/source/top_up): DEBUG - value: True DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource', 'NX_BOOLEAN'] @@ -1745,7 +1581,9 @@ DEBUG - classes: NXsource.nxdl.xml:/top_up DEBUG - <> DEBUG - documentation (NXsource.nxdl.xml:/top_up): -DEBUG - Is the synchrotron operating in top_up mode? +DEBUG - + Is the synchrotron operating in top_up mode? + DEBUG - ===== FIELD (//entry/instrument/source/type): DEBUG - value: Free Electron Laser DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource', 'NX_CHAR'] @@ -1768,26 +1606,27 @@ DEBUG - -> Ion Source DEBUG - -> UV Plasma Source DEBUG - -> Metal Jet X-ray DEBUG - -> Laser -DEBUG - -> Dye Laser +DEBUG - -> Dye-Laser DEBUG - -> Broadband Tunable Light Source -DEBUG - -> Halogen Lamp +DEBUG - -> Halogen lamp DEBUG - -> LED -DEBUG - -> Mercury Cadmium Telluride Lamp +DEBUG - -> Mercury Cadmium Telluride DEBUG - -> Deuterium Lamp DEBUG - -> Xenon Lamp DEBUG - -> Globar +DEBUG - -> other DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE/type): DEBUG - DEBUG - documentation (NXsource.nxdl.xml:/type): -DEBUG - type of radiation source (pick one from the enumerated list and spell exactly) +DEBUG - + type of radiation source (pick one from the enumerated list and spell exactly) + DEBUG - ===== GROUP (//entry/instrument/source_pump [NXarpes::/NXentry/NXinstrument/NXsource]): DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource'] DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE NXinstrument.nxdl.xml:/SOURCE NXsource.nxdl.xml: -NXcomponent.nxdl.xml: -NXobject.nxdl.xml: DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE): DEBUG - @@ -1795,24 +1634,11 @@ DEBUG - documentation (NXinstrument.nxdl.xml:/SOURCE): DEBUG - DEBUG - documentation (NXsource.nxdl.xml:): DEBUG - - Radiation source emitting a beam. - - Examples include particle sources (electrons, neutrons, protons) or sources for electromagnetic radiation (photons). - This base class can also be used to describe neutron or x-ray storage ring/facilities. - -DEBUG - documentation (NXcomponent.nxdl.xml:): -DEBUG - - Base class for components of an instrument - real ones or simulated ones. + Radiation source emitting a beam. + + Examples include particle sources (electrons, neutrons, protons) or sources for electromagnetic radiation (photons). + This base class can also be used to describe neutron or x-ray storage ring/facilities. -DEBUG - documentation (NXobject.nxdl.xml:): -DEBUG - - This is the base object of NeXus. The groups and fields contained - within this file are allowed to be present in any derived base class. - - If nameType="partial", the placeholders (e.g., FIELDNAME or GROUPNAME) - can be replaced by the name of any object (field or group, - respectively) that exists within the same group. - DEBUG - ===== ATTRS (//entry/instrument/source_pump@NX_class) DEBUG - value: NXsource DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource'] @@ -1820,8 +1646,6 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE NXinstrument.nxdl.xml:/SOURCE NXsource.nxdl.xml: -NXcomponent.nxdl.xml: -NXobject.nxdl.xml: DEBUG - @NX_class [NX_CHAR] DEBUG - DEBUG - ===== FIELD (//entry/instrument/source_pump/bunch_distance): @@ -1831,7 +1655,9 @@ DEBUG - classes: NXsource.nxdl.xml:/bunch_distance DEBUG - <> DEBUG - documentation (NXsource.nxdl.xml:/bunch_distance): -DEBUG - For storage rings, time between bunches +DEBUG - + For storage rings, time between bunches + DEBUG - ===== ATTRS (//entry/instrument/source_pump/bunch_distance@units) DEBUG - value: us DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource', 'NX_FLOAT'] @@ -1845,7 +1671,9 @@ DEBUG - classes: NXsource.nxdl.xml:/bunch_length DEBUG - <> DEBUG - documentation (NXsource.nxdl.xml:/bunch_length): -DEBUG - For storage rings, temporal length of the bunch +DEBUG - + For storage rings, temporal length of the bunch + DEBUG - ===== ATTRS (//entry/instrument/source_pump/bunch_length@units) DEBUG - value: fs DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource', 'NX_FLOAT'] @@ -1879,7 +1707,9 @@ DEBUG - classes: NXsource.nxdl.xml:/frequency DEBUG - <> DEBUG - documentation (NXsource.nxdl.xml:/frequency): -DEBUG - Frequency of pulsed source +DEBUG - + Frequency of pulsed source + DEBUG - ===== ATTRS (//entry/instrument/source_pump/frequency@units) DEBUG - value: Hz DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource', 'NX_FLOAT'] @@ -1896,22 +1726,21 @@ DEBUG - enumeration (NXsource.nxdl.xml:/mode): DEBUG - -> Single Bunch DEBUG - -> Multi Bunch DEBUG - documentation (NXsource.nxdl.xml:/mode): -DEBUG - source operating mode +DEBUG - + source operating mode + DEBUG - ===== FIELD (//entry/instrument/source_pump/name): DEBUG - value: User Laser @ FLASH DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource', 'NX_CHAR'] DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE/name NXsource.nxdl.xml:/name -NXcomponent.nxdl.xml:/name DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE/name): DEBUG - DEBUG - documentation (NXsource.nxdl.xml:/name): -DEBUG - Name of source -DEBUG - documentation (NXcomponent.nxdl.xml:/name): DEBUG - - Name of the component. + Name of source DEBUG - ===== FIELD (//entry/instrument/source_pump/number_of_bunches): DEBUG - value: 400 @@ -1920,7 +1749,9 @@ DEBUG - classes: NXsource.nxdl.xml:/number_of_bunches DEBUG - <> DEBUG - documentation (NXsource.nxdl.xml:/number_of_bunches): -DEBUG - For storage rings, the number of bunches in use. +DEBUG - + For storage rings, the number of bunches in use. + DEBUG - ===== FIELD (//entry/instrument/source_pump/number_of_bursts): DEBUG - value: 1 DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource'] @@ -1948,7 +1779,9 @@ DEBUG - -> proton DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE/probe): DEBUG - DEBUG - documentation (NXsource.nxdl.xml:/probe): -DEBUG - type of radiation probe (pick one from the enumerated list and spell exactly) +DEBUG - + type of radiation probe (pick one from the enumerated list and spell exactly) + DEBUG - ===== FIELD (//entry/instrument/source_pump/rms_jitter): DEBUG - value: 204.68816194453154 DEBUG - classpath: ['NXentry', 'NXinstrument', 'NXsource'] @@ -1981,18 +1814,21 @@ DEBUG - -> Ion Source DEBUG - -> UV Plasma Source DEBUG - -> Metal Jet X-ray DEBUG - -> Laser -DEBUG - -> Dye Laser +DEBUG - -> Dye-Laser DEBUG - -> Broadband Tunable Light Source -DEBUG - -> Halogen Lamp +DEBUG - -> Halogen lamp DEBUG - -> LED -DEBUG - -> Mercury Cadmium Telluride Lamp +DEBUG - -> Mercury Cadmium Telluride DEBUG - -> Deuterium Lamp DEBUG - -> Xenon Lamp DEBUG - -> Globar +DEBUG - -> other DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/INSTRUMENT/SOURCE/type): DEBUG - DEBUG - documentation (NXsource.nxdl.xml:/type): -DEBUG - type of radiation source (pick one from the enumerated list and spell exactly) +DEBUG - + type of radiation source (pick one from the enumerated list and spell exactly) + DEBUG - ===== FIELD (//entry/instrument/spatial_resolution): DEBUG - value: 500 DEBUG - classpath: ['NXentry', 'NXinstrument'] @@ -2020,15 +1856,15 @@ DEBUG - classes: NXentry.nxdl.xml:/run_cycle DEBUG - <> DEBUG - documentation (NXentry.nxdl.xml:/run_cycle): -DEBUG - Such as "2007-3". Some user facilities organize their beam time into run cycles. +DEBUG - + Such as "2007-3". Some user facilities organize their beam time into run cycles. + DEBUG - ===== GROUP (//entry/sample [NXarpes::/NXentry/NXsample]): DEBUG - classpath: ['NXentry', 'NXsample'] DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/SAMPLE NXentry.nxdl.xml:/SAMPLE NXsample.nxdl.xml: -NXcomponent.nxdl.xml: -NXobject.nxdl.xml: DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/SAMPLE): DEBUG - @@ -2036,25 +1872,12 @@ DEBUG - documentation (NXentry.nxdl.xml:/SAMPLE): DEBUG - DEBUG - documentation (NXsample.nxdl.xml:): DEBUG - - Any information on the sample. - - This could include scanned variables that - are associated with one of the data dimensions, e.g. the magnetic field, or - logged data, e.g. monitored temperature vs elapsed time. - -DEBUG - documentation (NXcomponent.nxdl.xml:): -DEBUG - - Base class for components of an instrument - real ones or simulated ones. + Any information on the sample. + + This could include scanned variables that + are associated with one of the data dimensions, e.g. the magnetic field, or + logged data, e.g. monitored temperature vs elapsed time. -DEBUG - documentation (NXobject.nxdl.xml:): -DEBUG - - This is the base object of NeXus. The groups and fields contained - within this file are allowed to be present in any derived base class. - - If nameType="partial", the placeholders (e.g., FIELDNAME or GROUPNAME) - can be replaced by the name of any object (field or group, - respectively) that exists within the same group. - DEBUG - ===== ATTRS (//entry/sample@NX_class) DEBUG - value: NXsample DEBUG - classpath: ['NXentry', 'NXsample'] @@ -2062,8 +1885,6 @@ DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/SAMPLE NXentry.nxdl.xml:/SAMPLE NXsample.nxdl.xml: -NXcomponent.nxdl.xml: -NXobject.nxdl.xml: DEBUG - @NX_class [NX_CHAR] DEBUG - DEBUG - ===== FIELD (//entry/sample/bias): @@ -2107,15 +1928,14 @@ DEBUG - classpath: ['NXentry', 'NXsample', 'NX_CHAR'] DEBUG - classes: NXarpes.nxdl.xml:/ENTRY/SAMPLE/name NXsample.nxdl.xml:/name -NXcomponent.nxdl.xml:/name DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/SAMPLE/name): -DEBUG - Descriptive name of sample +DEBUG - + Descriptive name of sample + DEBUG - documentation (NXsample.nxdl.xml:/name): -DEBUG - Descriptive name of sample -DEBUG - documentation (NXcomponent.nxdl.xml:/name): DEBUG - - Name of the component. + Descriptive name of sample DEBUG - ===== FIELD (//entry/sample/preparation_method): DEBUG - value: in-vacuum cleave @@ -2129,7 +1949,9 @@ DEBUG - classes: NXsample.nxdl.xml:/pressure DEBUG - <> DEBUG - documentation (NXsample.nxdl.xml:/pressure): -DEBUG - Applied pressure +DEBUG - + Applied pressure + DEBUG - ===== ATTRS (//entry/sample/pressure@units) DEBUG - value: mbar DEBUG - classpath: ['NXentry', 'NXsample', 'NX_FLOAT'] @@ -2179,7 +2001,9 @@ DEBUG - classes: NXsample.nxdl.xml:/thickness DEBUG - <> DEBUG - documentation (NXsample.nxdl.xml:/thickness): -DEBUG - sample thickness +DEBUG - + sample thickness + DEBUG - ===== ATTRS (//entry/sample/thickness@units) DEBUG - value: mm DEBUG - classpath: ['NXentry', 'NXsample', 'NX_FLOAT'] @@ -2201,7 +2025,9 @@ DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/start_time): DEBUG - DEBUG - documentation (NXentry.nxdl.xml:/start_time): -DEBUG - Starting time of measurement +DEBUG - + Starting time of measurement + DEBUG - ===== FIELD (//entry/title): DEBUG - value: Excited-state dynamics of WSe2 in the Valence Band and Core-Levels DEBUG - classpath: ['NXentry', 'NX_CHAR'] @@ -2212,7 +2038,9 @@ DEBUG - <> DEBUG - documentation (NXarpes.nxdl.xml:/ENTRY/title): DEBUG - DEBUG - documentation (NXentry.nxdl.xml:/title): -DEBUG - Extended title for entry +DEBUG - + Extended title for entry + DEBUG - ======================== DEBUG - === Default Plotable === DEBUG - ======================== diff --git a/tests/dataconverter/test_helpers.py b/tests/dataconverter/test_helpers.py index 0ef64ea0d..b01ba9e3c 100644 --- a/tests/dataconverter/test_helpers.py +++ b/tests/dataconverter/test_helpers.py @@ -30,18 +30,6 @@ from pynxtools.dataconverter.validation import validate_dict_against -def remove_optional_parent(data_dict: Template): - """Completely removes the optional group from the test Template.""" - internal_dict = Template(data_dict) - del internal_dict["/ENTRY[my_entry]/optional_parent/required_child"] - del internal_dict["/ENTRY[my_entry]/optional_parent/optional_child"] - del internal_dict[ - "/ENTRY[my_entry]/optional_parent/req_group_in_opt_group/DATA[data]" - ] - - return internal_dict - - def alter_dict(data_dict: Template, key: str, value: object): """Helper function to alter a single entry in dict for parametrize.""" if data_dict is not None: @@ -52,83 +40,6 @@ def alter_dict(data_dict: Template, key: str, value: object): return None -def set_to_none_in_dict(data_dict: Optional[Template], key: str, optionality: str): - """Helper function to forcefully set path to 'None'""" - if data_dict is None: - return None - - internal_dict = Template(data_dict) - internal_dict[optionality][key] = None - return internal_dict - - -def set_whole_group_to_none( - data_dict: Optional[Template], key: str, optionality: str -) -> Optional[Template]: - """Set a whole path to None in the dict""" - if data_dict is None: - return None - - internal_dict = Template(data_dict) - for path in data_dict[optionality]: - if path.startswith(key): - internal_dict[optionality][path] = None - return internal_dict - - -def remove_from_dict(data_dict: Template, key: str, optionality: str = "optional"): - """Helper function to remove a key from dict""" - if data_dict is not None and key in data_dict[optionality]: - internal_dict = Template(data_dict) - del internal_dict[optionality][key] - return internal_dict - - return None - - -def listify_template(data_dict: Template): - """Helper function to turn most values in the Template into lists""" - listified_template = Template() - for optionality in ("optional", "recommended", "required", "undocumented"): - for path in data_dict[optionality]: - if path[path.rindex("/") + 1 :] in ( - "@units", - "type", - "definition", - "date_value", - ): - listified_template[optionality][path] = data_dict[optionality][path] - else: - listified_template[optionality][path] = [data_dict[optionality][path]] - return listified_template - - -@pytest.mark.parametrize( - "input_data, expected_output", - [ - ("2.4E-23", 2.4e-23), - ("28", 28), - ("45.98", 45.98), - ("test", "test"), - (["59", "3.00005", "498E-36"], np.array([59.0, 3.00005, 4.98e-34])), - ("23 34 444 5000", np.array([23.0, 34.0, 444.0, 5000.0])), - ("xrd experiment", "xrd experiment"), - (None, None), - ], -) -def test_transform_to_intended_dt(input_data, expected_output): - """Transform to possible numerical method.""" - result = helpers.transform_to_intended_dt(input_data) - - # Use pytest.approx for comparing floating-point numbers - if isinstance(expected_output, np.ndarray): - np.testing.assert_allclose(result, expected_output, rtol=1e-3) - elif isinstance(expected_output, float): - assert result == pytest.approx(expected_output, rel=1e-5) - else: - assert result == expected_output - - @pytest.fixture(name="template") def fixture_template(): """pytest fixture to use the same template in all tests""" @@ -155,6 +66,9 @@ def fixture_filled_test_data(template, tmp_path): ) template.clear() + template[ + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/anamethatRENAMES[anamethatichangetothis]" + ] = 2 template["/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value"] = 2.0 template["/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value/@units"] = "nm" template["/ENTRY[my_entry]/optional_parent/required_child"] = 1 @@ -162,6 +76,8 @@ def fixture_filled_test_data(template, tmp_path): template["/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value"] = True template["/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value"] = 2 template["/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value/@units"] = "eV" + template["/ENTRY[my_entry]/NXODD_name[nxodd_name]/number_value"] = 2 + template["/ENTRY[my_entry]/NXODD_name[nxodd_name]/number_value/@units"] = "eV" template["/ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value"] = np.array( [1, 2, 3], dtype=np.int8 ) @@ -183,378 +99,30 @@ def fixture_filled_test_data(template, tmp_path): return template -TEMPLATE = Template() -TEMPLATE["optional"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value"] = 2.0 # pylint: disable=E1126 -TEMPLATE["optional"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value/@units"] = ( - "nm" # pylint: disable=E1126 -) -TEMPLATE["optional"]["/ENTRY[my_entry]/optional_parent/required_child"] = 1 # pylint: disable=E1126 -TEMPLATE["optional"]["/ENTRY[my_entry]/optional_parent/optional_child"] = 1 # pylint: disable=E1126 -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value"] = True # pylint: disable=E1126 -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value/@units"] = "" -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value"] = 2 # pylint: disable=E1126 -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value/@units"] = "eV" # pylint: disable=E1126 -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value"] = np.array( - [1, 2, 3], # pylint: disable=E1126 - dtype=np.int8, -) # pylint: disable=E1126 -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value/@units"] = ( - "kg" # pylint: disable=E1126 -) -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value"] = ( - "just chars" # pylint: disable=E1126 -) -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value/@units"] = "" -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/bool_value"] = True # pylint: disable=E1126 -TEMPLATE["required"][ - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/bool_value/@units" -] = "" -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/int_value"] = 2 # pylint: disable=E1126 -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/int_value/@units"] = ( - "eV" # pylint: disable=E1126 -) -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/posint_value"] = ( - np.array( - [1, 2, 3], # pylint: disable=E1126 - dtype=np.int8, - ) -) # pylint: disable=E1126 -TEMPLATE["required"][ - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/posint_value/@units" -] = "kg" # pylint: disable=E1126 -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/char_value"] = ( - "just chars" # pylint: disable=E1126 -) -TEMPLATE["required"][ - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/char_value/@units" -] = "" -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/type"] = "2nd type" # pylint: disable=E1126 -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/date_value"] = ( - "2022-01-22T12:14:12.05018+00:00" # pylint: disable=E1126 -) -TEMPLATE["required"][ - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/date_value/@units" -] = "" -TEMPLATE["required"]["/ENTRY[my_entry]/OPTIONAL_group[my_group]/required_field"] = 1 # pylint: disable=E1126 -TEMPLATE["required"]["/ENTRY[my_entry]/definition"] = "NXtest" # pylint: disable=E1126 -TEMPLATE["required"]["/ENTRY[my_entry]/definition/@version"] = "2.4.6" # pylint: disable=E1126 -TEMPLATE["required"]["/ENTRY[my_entry]/program_name"] = "Testing program" # pylint: disable=E1126 -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/type"] = "2nd type" # pylint: disable=E1126 -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value"] = ( - "2022-01-22T12:14:12.05018+00:00" # pylint: disable=E1126 -) -TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value/@units"] = "" -TEMPLATE["optional"]["/ENTRY[my_entry]/OPTIONAL_group[my_group]/optional_field"] = 1 -TEMPLATE["optional"]["/ENTRY[my_entry]/required_group/description"] = ( - "An example description" -) -TEMPLATE["optional"]["/ENTRY[my_entry]/required_group2/description"] = ( - "An example description" -) -TEMPLATE["required"][ - "/ENTRY[my_entry]/optional_parent/req_group_in_opt_group/DATA[data]" -] = 1 -TEMPLATE["lone_groups"] = [ - "/ENTRY[entry]/required_group", - "/ENTRY[entry]/required_group2", - "/ENTRY[entry]/optional_parent/req_group_in_opt_group", -] -TEMPLATE["optional"]["/@default"] = "Some NXroot attribute" - -# "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/in" -# "t_value should be one of: (, , )," -# " as defined in the NXDL as NX_INT." - - -# pylint: disable=too-many-arguments @pytest.mark.parametrize( - "data_dict,error_message", + "input_data, expected_output", [ - pytest.param( - alter_dict( - TEMPLATE, - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value", - "not_a_num", - ), - ( - "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/in" - "t_value should be one of: (, , , ," - " , , , , , " - ", , , , , ), as defined in " - "the NXDL as NX_INT." - ), - id="string-instead-of-int", - ), - pytest.param( - alter_dict( - TEMPLATE, - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value", - "NOT_TRUE_OR_FALSE", - ), - ( - "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value sh" - "ould be one of: (, , , , )," - " as defined in the NXDL as NX_CHAR." - ), - id="int-instead-of-chars", - ), - pytest.param( - alter_dict( - TEMPLATE, "/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value", None - ), - "", - id="empty-optional-field", - ), - pytest.param( - set_to_none_in_dict( - TEMPLATE, - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value", - "required", - ), - ( - "The data entry corresponding to /ENTRY[my_entry]/NXODD_name[nxodd_name]" - "/bool_value is" - " required and hasn't been supplied by the reader." - ), - id="empty-required-field", - ), - pytest.param( - set_to_none_in_dict( - TEMPLATE, - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/bool_value", - "required", - ), - ( - "The data entry corresponding to /ENTRY[my_entry]/" - "NXODD_name[nxodd_two_name]/bool_value is" - " required and hasn't been supplied by the reader." - ), - id="empty-required-field", - ), - pytest.param( - remove_from_dict( - remove_from_dict( - TEMPLATE, - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/bool_value", - "required", - ), - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value", - "required", - ), - ( - "The data entry corresponding to /ENTRY[my_entry]/NXODD_name[nxodd_name]" - "/bool_value is" - " required and hasn't been supplied by the reader." - ), - id="empty-required-field", - ), - pytest.param( - set_whole_group_to_none( - set_whole_group_to_none( - TEMPLATE, - "/ENTRY[my_entry]/NXODD_name", - "required", - ), - "/ENTRY[my_entry]/NXODD_name", - "optional", - ), - ("The required group, /ENTRY[my_entry]/NXODD_name, hasn't been supplied."), - id="all-required-fields-set-to-none", - ), - pytest.param( - alter_dict( - TEMPLATE, - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value", - "2022-01-22T12:14:12.05018+00:00", - ), - "", - id="UTC-with-+00:00", - ), - pytest.param( - alter_dict( - TEMPLATE, - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value", - "2022-01-22T12:14:12.05018Z", - ), - "", - id="UTC-with-Z", - ), - pytest.param( - alter_dict( - TEMPLATE, - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value", - "2022-01-22T12:14:12.05018-00:00", - ), - "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value" - " = 2022-01-22T12:14:12.05018-00:00 should be a timezone aware" - " ISO8601 formatted str. For example, 2022-01-22T12:14:12.05018Z or 2022-01-22" - "T12:14:12.05018+00:00.", - id="UTC-with--00:00", - ), - pytest.param(listify_template(TEMPLATE), "", id="lists"), - pytest.param( - alter_dict( - TEMPLATE, "/ENTRY[my_entry]/NXODD_name[nxodd_name]/type", "Wrong option" - ), - ( - "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/type should " - "be on of the following" - " strings: ['1st type', '2nd type', '3rd type', '4th type']" - ), - id="wrong-enum-choice", - ), - pytest.param( - set_to_none_in_dict( - TEMPLATE, "/ENTRY[my_entry]/optional_parent/required_child", "optional" - ), - ( - "The data entry corresponding to /ENTRY[my_entry]/optional_parent/" - "required_child is required and hasn't been supplied by the reader." - ), - id="atleast-one-required-child-not-provided-optional-parent", - ), - pytest.param( - set_to_none_in_dict( - TEMPLATE, - "/ENTRY[my_entry]/OPTIONAL_group[my_group]/required_field", - "required", - ), - ( - "The data entry corresponding to /ENTRY[my_entry]/" - "OPTIONAL_group[my_group]/required_field " - "is required and hasn't been supplied by the reader." - ), - id="required-field-not-provided-in-variadic-optional-group", - ), - pytest.param( - set_to_none_in_dict( - TEMPLATE, - "/ENTRY[my_entry]/OPTIONAL_group[my_group]/optional_field", - "required", - ), - (""), - id="required-field-provided-in-variadic-optional-group", - ), - pytest.param( - alter_dict( - alter_dict( - TEMPLATE, "/ENTRY[my_entry]/optional_parent/required_child", None - ), - "/ENTRY[my_entry]/optional_parent/optional_child", - None, - ), - (""), - id="no-child-provided-optional-parent", - ), - pytest.param(TEMPLATE, "", id="valid-data-dict"), - pytest.param( - remove_from_dict(TEMPLATE, "/ENTRY[my_entry]/required_group/description"), - "The required group, /ENTRY[my_entry]/required_group, hasn't been supplied.", - id="missing-empty-yet-required-group", - ), - pytest.param( - remove_from_dict(TEMPLATE, "/ENTRY[my_entry]/required_group2/description"), - "The required group, /ENTRY[my_entry]/required_group2, hasn't been supplied.", - id="missing-empty-yet-required-group2", - ), - pytest.param( - alter_dict( - remove_from_dict( - TEMPLATE, "/ENTRY[my_entry]/required_group/description" - ), - "/ENTRY[entry]/required_group", - None, - ), - "The required group, /ENTRY[my_entry]/required_group, hasn't been supplied.", - id="allow-required-and-empty-group", - ), - pytest.param( - remove_from_dict( - TEMPLATE, - "/ENTRY[my_entry]/optional_parent/req_group_in_opt_group/DATA[data]", - "required", - ), - ( - "The required group, /ENTRY[my_entry]/" - "optional_parent/req_group_in_opt_group, " - "hasn't been supplied." - ), - id="req-group-in-opt-parent-removed", - ), - pytest.param( - remove_optional_parent(TEMPLATE), (""), id="opt-group-completely-removed" - ), + ("2.4E-23", 2.4e-23), + ("28", 28), + ("45.98", 45.98), + ("test", "test"), + (["59", "3.00005", "498E-36"], np.array([59.0, 3.00005, 4.98e-34])), + ("23 34 444 5000", np.array([23.0, 34.0, 444.0, 5000.0])), + ("xrd experiment", "xrd experiment"), + (None, None), ], ) -def test_validate_data_dict(caplog, data_dict, error_message, request): - """Unit test for the data validation routine.""" - if request.node.callspec.id in ( - "valid-data-dict", - "lists", - "empty-optional-field", - "UTC-with-+00:00", - "UTC-with-Z", - "no-child-provided-optional-parent", - "int-instead-of-chars", - "link-dict-instead-of-bool", - "opt-group-completely-removed", - "required-field-provided-in-variadic-optional-group", - ): - with caplog.at_level(logging.WARNING): - assert validate_dict_against("NXtest", data_dict)[0] - assert caplog.text == "" - # Missing required fields caught by logger with warning - elif request.node.callspec.id in ( - "empty-required-field", - "allow-required-and-empty-group", - "req-group-in-opt-parent-removed", - "missing-empty-yet-required-group", - "missing-empty-yet-required-group2", - ): - assert "" == caplog.text - captured_logs = caplog.records - assert not validate_dict_against("NXtest", data_dict)[0] - assert any(error_message in rec.message for rec in captured_logs) - else: - with caplog.at_level(logging.WARNING): - assert not validate_dict_against("NXtest", data_dict)[0] +def test_transform_to_intended_dt(input_data, expected_output): + """Transform to possible numerical method.""" + result = helpers.transform_to_intended_dt(input_data) - assert error_message in caplog.text + # Use pytest.approx for comparing floating-point numbers + if isinstance(expected_output, np.ndarray): + np.testing.assert_allclose(result, expected_output, rtol=1e-3) + elif isinstance(expected_output, float): + assert result == pytest.approx(expected_output, rel=1e-5) + else: + assert result == expected_output @pytest.mark.parametrize( diff --git a/tests/dataconverter/test_validation.py b/tests/dataconverter/test_validation.py index 2c946a3a1..6f32d64c8 100644 --- a/tests/dataconverter/test_validation.py +++ b/tests/dataconverter/test_validation.py @@ -17,129 +17,939 @@ # limitations under the License. # import logging -from typing import Any, Dict, List, Tuple, Union +from typing import Optional import numpy as np import pytest +from pynxtools.dataconverter.template import Template from pynxtools.dataconverter.validation import validate_dict_against +from .test_helpers import ( # pylint: disable=unused-import + alter_dict, + fixture_filled_test_data, + fixture_template, +) -def get_data_dict(): - return { - "/ENTRY[my_entry]/optional_parent/required_child": 1, - "/ENTRY[my_entry]/optional_parent/optional_child": 1, - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value_no_attr": 2.0, - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value": 2.0, - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value/@units": "nm", - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value": True, - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value/@units": "", - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value": 2, - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value/@units": "eV", - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value": np.array( - [1, 2, 3], dtype=np.int8 - ), - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value/@units": "kg", - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value": "just chars", - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value/@units": "", - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/type": "2nd type", - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value": "2022-01-22T12:14:12.05018+00:00", - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value/@units": "", - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/bool_value": True, - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/bool_value/@units": "", - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/int_value": 2, - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/int_value/@units": "eV", - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/posint_value": np.array( - [1, 2, 3], dtype=np.int8 - ), - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/posint_value/@units": "kg", - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/char_value": "just chars", - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/char_value/@units": "", - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/type": "2nd type", - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/date_value": "2022-01-22T12:14:12.05018+00:00", - "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/date_value/@units": "", - "/ENTRY[my_entry]/OPTIONAL_group[my_group]/required_field": 1, - "/ENTRY[my_entry]/definition": "NXtest", - "/ENTRY[my_entry]/definition/@version": "2.4.6", - "/ENTRY[my_entry]/program_name": "Testing program", - "/ENTRY[my_entry]/OPTIONAL_group[my_group]/optional_field": 1, - "/ENTRY[my_entry]/required_group/description": "An example description", - "/ENTRY[my_entry]/required_group2/description": "An example description", - "/ENTRY[my_entry]/optional_parent/req_group_in_opt_group/data": 1, - "/@default": "Some NXroot attribute", - } - - -def remove_from_dict(keys: Union[Union[List[str], Tuple[str, ...]], str], data_dict): - if isinstance(keys, (list, tuple)): - for key in keys: - data_dict.pop(key, None) - else: - data_dict.pop(keys) - return data_dict +def set_to_none_in_dict(data_dict: Optional[Template], key: str, optionality: str): + """Helper function to forcefully set path to 'None'""" + if data_dict is None: + return None + internal_dict = Template(data_dict) + internal_dict[optionality][key] = None + return internal_dict -def alter_dict(new_values: Dict[str, Any], data_dict: Dict[str, Any]) -> Dict[str, Any]: - for key, value in new_values.items(): - data_dict[key] = value - return data_dict +def set_whole_group_to_none( + data_dict: Optional[Template], key: str, optionality: str +) -> Optional[Template]: + """Set a whole path to None in the dict""" + if data_dict is None: + return None -@pytest.mark.parametrize( - "data_dict", - [ - pytest.param(get_data_dict(), id="valid-unaltered-data-dict"), - pytest.param( - remove_from_dict( - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value_no_attr", - get_data_dict(), - ), - id="removed-optional-value", - ), - ], + internal_dict = Template(data_dict) + for path in data_dict[optionality]: + if path.startswith(key): + internal_dict[optionality][path] = None + return internal_dict + + +def remove_from_dict(data_dict: Template, key: str, optionality: str = "optional"): + """Helper function to remove a key from dict""" + if data_dict is not None and key in data_dict[optionality]: + internal_dict = Template(data_dict) + del internal_dict[optionality][key] + return internal_dict + + return None + + +def listify_template(data_dict: Template): + """Helper function to turn most values in the Template into lists""" + listified_template = Template() + for optionality in ("optional", "recommended", "required", "undocumented"): + for path in data_dict[optionality]: + if path[path.rindex("/") + 1 :] in ( + "@units", + "type", + "definition", + "date_value", + "@signal", + ) or isinstance(data_dict[optionality][path], list): + listified_template[optionality][path] = data_dict[optionality][path] + else: + listified_template[optionality][path] = [data_dict[optionality][path]] + return listified_template + + +TEMPLATE = Template() +TEMPLATE["optional"][ + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/anamethatRENAMES[anamethatichangetothis]" +] = 2 +TEMPLATE["optional"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value"] = 2.0 # pylint: disable=E1126 +TEMPLATE["optional"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value/@units"] = ( + "nm" # pylint: disable=E1126 +) +TEMPLATE["optional"][ + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/DATA[float_value_no_attr]" +] = (2.0,) +TEMPLATE["optional"]["/ENTRY[my_entry]/optional_parent/required_child"] = 1 # pylint: disable=E1126 +TEMPLATE["optional"]["/ENTRY[my_entry]/optional_parent/optional_child"] = 1 # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value"] = True # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value/@units"] = "" +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value"] = 2 # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value/@units"] = "eV" # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/number_value"] = 2 +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/number_value/@units"] = ( + "eV" +) +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value"] = np.array( + [1, 2, 3], # pylint: disable=E1126 + dtype=np.int8, +) # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value/@units"] = ( + "kg" # pylint: disable=E1126 +) +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value"] = ( + "just chars" # pylint: disable=E1126 +) +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value/@units"] = "" +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/@group_attribute"] = ( + "data" # pylint: disable=E1126 +) +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/@signal"] = "data" +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/DATA[data]"] = 1 # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/bool_value"] = True # pylint: disable=E1126 +TEMPLATE["required"][ + "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/bool_value/@units" +] = "" +TEMPLATE["required"][ + "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/anamethatRENAMES[anamethatichangetothis]" +] = 2 # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/int_value"] = 2 # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/int_value/@units"] = ( + "eV" # pylint: disable=E1126 +) +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/posint_value"] = ( + np.array( + [1, 2, 3], # pylint: disable=E1126 + dtype=np.int8, + ) +) # pylint: disable=E1126 +TEMPLATE["required"][ + "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/posint_value/@units" +] = "kg" # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/char_value"] = ( + "just chars" # pylint: disable=E1126 ) -def test_valid_data_dict(caplog, data_dict): - with caplog.at_level(logging.WARNING): - assert validate_dict_against("NXtest", data_dict)[0] - assert caplog.text == "" +TEMPLATE["required"][ + "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/char_value/@units" +] = "" +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/type"] = "2nd type" # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/type/@array"] = [ + 0, + 1, + 2, +] +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/date_value"] = ( + "2022-01-22T12:14:12.05018+00:00" # pylint: disable=E1126 +) +TEMPLATE["required"][ + "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/date_value/@units" +] = "" +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/@group_attribute"] = ( + "data" # pylint: disable=E1126 +) +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/@signal"] = "data" +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/DATA[data]"] = 1 # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/OPTIONAL_group[my_group]/required_field"] = 1 # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/definition"] = "NXtest" # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/definition/@version"] = "2.4.6" # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/program_name"] = "Testing program" # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/type"] = "2nd type" # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/type/@array"] = [0, 1, 2] +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value"] = ( + "2022-01-22T12:14:12.05018+00:00" # pylint: disable=E1126 +) +TEMPLATE["required"]["/ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value/@units"] = "" +TEMPLATE["optional"]["/ENTRY[my_entry]/OPTIONAL_group[my_group]/optional_field"] = 1 +TEMPLATE["optional"]["/ENTRY[my_entry]/required_group/description"] = ( + "An example description" +) +TEMPLATE["optional"]["/ENTRY[my_entry]/required_group2/description"] = ( + "An example description" +) +TEMPLATE["required"][ + "/ENTRY[my_entry]/optional_parent/req_group_in_opt_group/DATA[data]" +] = 1 +TEMPLATE["lone_groups"] = [ + "/ENTRY[entry]/required_group", + "/ENTRY[entry]/required_group2", + "/ENTRY[entry]/optional_parent/req_group_in_opt_group", +] +TEMPLATE["optional"]["/@default"] = "Some NXroot attribute" +# keys not registered in appdef +TEMPLATE["required"]["/ENTRY[my_entry]/duration"] = 1 # pylint: disable=E1126 +TEMPLATE["required"]["/ENTRY[my_entry]/duration/@units"] = "s" # pylint: disable=E1126 +TEMPLATE["required"][ + "/ENTRY[my_entry]/INSTRUMENT[my_instrument]/SOURCE[my_source]/type" +] = "Ion Source" # pylint: disable=E1126 +# pylint: disable=too-many-arguments @pytest.mark.parametrize( - "data_dict, error_message_1, error_message_2", + "data_dict,error_messages", [ + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/anamethatRENAMES[anamethatichangetothis]", + "not_a_num", + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/anamethatRENAMES[anamethatichangetothis]" + " should be one of the following Python types: (, ), as defined in " + "the NXDL as NX_INT." + ], + id="variadic-field-str-instead-of-int", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value", + "not_a_num", + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/in" + "t_value should be one of the following Python types: (, ), as defined in " + "the NXDL as NX_INT." + ], + id="string-instead-of-int", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value", + "NOT_TRUE_OR_FALSE", + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value should be one of the following Python types: (, ), as defined in the NXDL as NX_BOOLEAN." + ], + id="string-instead-of-bool", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value", + ["1", "2", "3"], + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value should" + " be one of the following Python types: (, ), as defined in the NXDL as NX_INT." + ], + id="list-of-int-str-instead-of-int", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value", + np.array([2.0, 3.0, 4.0], dtype=np.float32), + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value should be" + " one of the following Python types: (, ), as defined in the NXDL as NX_INT." + ], + id="array-of-float-instead-of-int", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value", + [2, 3, 4], + ), + [], + id="list-of-int-instead-of-int", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value", + np.array([2, 3, 4], dtype=np.int32), + ), + [], + id="array-of-int32-instead-of-int", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value", + "2022-01-22T12:14:12.05018-00:00", + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value" + " = 2022-01-22T12:14:12.05018-00:00 should be a timezone aware" + " ISO8601 formatted str. For example, 2022-01-22T12:14:12.05018Z or 2022-01-22" + "T12:14:12.05018+00:00." + ], + id="int-instead-of-date", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value", + 0, + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value should be one of the following Python types: (, ), as defined in the NXDL as NX_FLOAT." + ], + id="int-instead-of-float", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/number_value", + "0", + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/number_value should be one of the following Python types: (, , , ), as defined in the NXDL as NX_NUMBER." + ], + id="str-instead-of-number", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value", + np.array([0.0, 2]), + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value should be one" + " of the following Python types: (, ), as" + " defined in the NXDL as NX_CHAR." + ], + id="wrong-type-ndarray-instead-of-char", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value", + np.array(["x", "2"]), + ), + [], + id="valid-ndarray-instead-of-char", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/int_value", + {"link": "/a-link"}, + ), + [], + id="link-dict-instead-of-int", + ), + pytest.param( + alter_dict( + TEMPLATE, "/ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value", -1 + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value " + "should be a positive int, but is -1." + ], + id="negative-posint", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value", + [-1, 2], + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value " + "should be a positive int, but is [-1, 2]." + ], + id="negative-posint-list", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value", + np.array([-1, 2], dtype=np.int8), + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value should" + " be a positive int, but is [-1 2]." + ], + id="negative-posint-array", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value", + [1, 2], + ), + [], + id="positive-posint-list", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/posint_value", + np.array([1, 2], dtype=np.int8), + ), + [], + id="positive-posint-array", + ), + pytest.param( + alter_dict( + TEMPLATE, "/ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value", 3 + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value should be one of the following Python types:" + " (, )," + " as defined in the NXDL as NX_CHAR." + ], + id="int-instead-of-chars", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value", + np.array(["1", "2", "3"], dtype=np.str_), + ), + [], + id="array-of-chars", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value", + np.array(["1", "2", "3"], dtype=np.bytes_), + ), + [], + id="array-of-bytes-chars", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/char_value", + ["list", "of", "chars"], + ), + [], + id="list-of-string-instead-of-chars", + ), + pytest.param( + alter_dict( + TEMPLATE, "/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value", None + ), + [], + id="empty-optional-field", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value", + np.array([2.0, 3.0, 4.0], dtype=np.float32), + ), + [], + id="array-of-float-instead-of-float", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value", + np.array(["2.0", "3.0"], dtype=np.str_), + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value should be " + "one of the following Python types: (, ), as defined in the NXDL " + "as NX_FLOAT." + ], + id="array-of-str-instead-of-float", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value", + [2], # pylint: disable=E1126 + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value should be " + "one of the following Python types: (, ), as defined in the NXDL " + "as NX_FLOAT." + ], + id="list-of-int-instead-of-float", + ), + pytest.param( + set_to_none_in_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value", + "required", + ), + [ + "The data entry corresponding to /ENTRY[my_entry]/NXODD_name[nxodd_name]" + "/bool_value is" + " required and hasn't been supplied by the reader.", + "There were attributes set for the field /ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value, but the field does not exist.", + ], + id="empty-required-field", + ), + pytest.param( + set_to_none_in_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_two_name]/bool_value", + "required", + ), + [ + "The data entry corresponding to /ENTRY[my_entry]/" + "NXODD_name[nxodd_two_name]/bool_value is" + " required and hasn't been supplied by the reader.", + "There were attributes set for the field /ENTRY[my_entry]/NXODD_name[nxodd_two_name]/bool_value, but the field does not exist.", + ], + id="empty-required-field", + ), pytest.param( remove_from_dict( - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value", get_data_dict() + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value", + "optional", ), - "The attribute /ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value/@units will not be written.", - "There were attributes set for the field /ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value, but the field does not exist.", - id="removed-optional-value-with-attribute-remaining", + [ + "Unit /ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value/@units in dataset without its field /ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value.", + "The attribute /ENTRY[my_entry]/NXODD_name[nxodd_name]/float_value/@units will not be written.", + ], + id="removed-optional-value-with-unit-remaining", ), - ], -) -def test_data_dict_attr_with_no_field( - caplog, data_dict, error_message_1, error_message_2 -): - with caplog.at_level(logging.WARNING): - assert not validate_dict_against("NXtest", data_dict)[0] - assert error_message_1 in caplog.text - assert error_message_2 in caplog.text - - -@pytest.mark.parametrize( - "data_dict, error_message", - [ pytest.param( remove_from_dict( - "/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value", get_data_dict() + remove_from_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value", + "required", + ), + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value/@units", + "required", ), - "The data entry corresponding to /ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value is required and hasn't been supplied by the reader.", + [ + "The data entry corresponding to /ENTRY[my_entry]/NXODD_name[nxodd_name]/bool_value is required and hasn't been supplied by the reader." + ], id="missing-required-value", - ) + ), + pytest.param( + remove_from_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/@group_attribute", + "required", + ), + [ + 'Missing attribute: "/ENTRY[my_entry]/NXODD_name[nxodd_name]/@group_attribute"' + ], + id="missing-required-group-attribute", + ), + pytest.param( + set_whole_group_to_none( + set_whole_group_to_none( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name", + "required", + ), + "/ENTRY[my_entry]/NXODD_name", + "optional", + ), + ["The required group, /ENTRY[my_entry]/NXODD_name, hasn't been supplied."], + id="all-required-fields-set-to-none", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value", + "2022-01-22T12:14:12.05018+00:00", + ), + [], + id="UTC-with-+00:00", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value", + "2022-01-22T12:14:12.05018Z", + ), + [], + id="UTC-with-Z", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value", + "2022-01-22T12:14:12.05018-00:00", + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/date_value" + " = 2022-01-22T12:14:12.05018-00:00 should be a timezone aware" + " ISO8601 formatted str. For example, 2022-01-22T12:14:12.05018Z or 2022-01-22" + "T12:14:12.05018+00:00." + ], + id="UTC-with--00:00", + ), + pytest.param(listify_template(TEMPLATE), "", id="lists"), + pytest.param( + alter_dict( + TEMPLATE, "/ENTRY[my_entry]/NXODD_name[nxodd_name]/type", "Wrong option" + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/type should " + "be one of the following" + ": ['1st type', '2nd type', '3rd type', '4th type']" + ], + id="wrong-enum-choice", + ), + pytest.param( + set_to_none_in_dict( + TEMPLATE, "/ENTRY[my_entry]/optional_parent/required_child", "optional" + ), + [ + "The data entry corresponding to /ENTRY[my_entry]/optional_parent/" + "required_child is required and hasn't been supplied by the reader." + ], + id="atleast-one-required-child-not-provided-optional-parent", + ), + pytest.param( + set_to_none_in_dict( + TEMPLATE, + "/ENTRY[my_entry]/OPTIONAL_group[my_group]/required_field", + "required", + ), + [ + "The data entry corresponding to /ENTRY[my_entry]/" + "OPTIONAL_group[my_group]/required_field " + "is required and hasn't been supplied by the reader." + ], + id="required-field-not-provided-in-variadic-optional-group", + ), + pytest.param( + set_to_none_in_dict( + TEMPLATE, + "/ENTRY[my_entry]/OPTIONAL_group[my_group]/optional_field", + "required", + ), + [], + id="required-field-provided-in-variadic-optional-group", + ), + pytest.param( + alter_dict( + alter_dict( + TEMPLATE, "/ENTRY[my_entry]/optional_parent/required_child", None + ), + "/ENTRY[my_entry]/optional_parent/optional_child", + None, + ), + [], + id="no-child-provided-optional-parent", + ), + pytest.param(TEMPLATE, "", id="valid-data-dict"), + pytest.param( + remove_from_dict(TEMPLATE, "/ENTRY[my_entry]/required_group/description"), + [ + "The required group, /ENTRY[my_entry]/required_group, hasn't been supplied." + ], + id="missing-empty-yet-required-group", + ), + pytest.param( + remove_from_dict(TEMPLATE, "/ENTRY[my_entry]/required_group2/description"), + [ + "The required group, /ENTRY[my_entry]/required_group2, hasn't been supplied." + ], + id="missing-empty-yet-required-group2", + ), + pytest.param( + alter_dict( + remove_from_dict( + TEMPLATE, "/ENTRY[my_entry]/required_group/description" + ), + "/ENTRY[entry]/required_group", + None, + ), + [ + "The required group, /ENTRY[my_entry]/required_group, hasn't been supplied." + ], + id="allow-required-and-empty-group", + ), + pytest.param( + remove_from_dict( + TEMPLATE, + "/ENTRY[my_entry]/optional_parent/req_group_in_opt_group/DATA[data]", + "required", + ), + [ + "The required group, /ENTRY[my_entry]/" + "optional_parent/req_group_in_opt_group, " + "hasn't been supplied." + ], + id="req-group-in-opt-parent-removed", + ), + pytest.param((TEMPLATE), (""), id="opt-group-completely-removed"), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/type/@array", + ["0", 1, 2], + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/type/@array should be one of the following Python types: (, ), as defined in the NXDL as NX_INT.", + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/type/@array should be one of the following: [[0, 1, 2], [2, 3, 4]]", + ], + id="wrong-type-array-in-attribute", + ), + pytest.param( + alter_dict( + TEMPLATE, "/ENTRY[my_entry]/NXODD_name[nxodd_name]/type/@array", [1, 2] + ), + [ + "The value at /ENTRY[my_entry]/NXODD_name[nxodd_name]/type/@array should be one of the following: [[0, 1, 2], [2, 3, 4]]" + ], + id="wrong-value-array-in-attribute", + ), + pytest.param( + remove_from_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/number_value/@units", + "required", + ), + [ + "Field /ENTRY[my_entry]/NXODD_name[nxodd_name]/number_value requires a unit in the unit category NX_ENERGY." + ], + id="missing-unit", + ), + pytest.param( + remove_from_dict( + TEMPLATE, + "/ENTRY[my_entry]/NXODD_name[nxodd_name]/number_value", + "required", + ), + [ + "Unit /ENTRY[my_entry]/NXODD_name[nxodd_name]/number_value/@units in dataset without its field /ENTRY[my_entry]/NXODD_name[nxodd_name]/number_value.", + "The attribute /ENTRY[my_entry]/NXODD_name[nxodd_name]/number_value/@units will not be written.", + ], + id="unit-missing-field", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/required_group/illegal_name", + 1, + ), + [ + "Field /ENTRY[my_entry]/required_group/illegal_name written without documentation." + ], + id="add-undocumented-field", + ), + pytest.param( + alter_dict( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/required_group/author", + "author", + ), + "/ENTRY[my_entry]/required_group/author/@illegal", + "illegal_attribute", + ), + [ + "Attribute /ENTRY[my_entry]/required_group/author/@illegal written without documentation." + ], + id="add-undocumented-attribute", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/INSTRUMENT[my_instrument]/BEAM[my_beam]/@default", + "unknown", + ), + [], + id="group-with-only-attributes", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/INSTRUMENT[my_instrument]/BEAM[my_beam]/@illegal", + "unknown", + ), + [ + "Attribute /ENTRY[my_entry]/INSTRUMENT[my_instrument]/BEAM[my_beam]/@illegal written without documentation." + ], + id="group-with-illegal-attributes", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/optional_parent/required_child/@units", + "s", + ), + [ + "The unit, /ENTRY[my_entry]/optional_parent/required_child/@units = s, is being written but has no documentation." + ], + id="field-with-illegal-unit", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/INSTRUMENT[my_instrument]/ILLEGAL[my_source]/type", + 1, + ), + [ + "Field /ENTRY[my_entry]/INSTRUMENT[my_instrument]/ILLEGAL[my_source]/type written without documentation." + ], + id="bad-namefitting", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/data/test", + 1, + ), + ["Field /ENTRY[my_entry]/data/test written without documentation."], + id="namefitting-of-illegal-named-group", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/USE[user]/name", + "Some name", + ), + ["Field /ENTRY[my_entry]/USE[user]/name written without documentation."], + id="namefitting-of-group-with-typo", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/USE[user]/test", + "Some name", + ), + ["Field /ENTRY[my_entry]/USE[user]/test written without documentation."], + id="namefitting-of-group-with-typo-and-new-field", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/duration", + np.array([2.0, 3.0, 4.0], dtype=np.float32), + ), + [ + "The value at /ENTRY[my_entry]/duration should be" + " one of the following Python types: (, ), as defined in the NXDL as NX_INT." + ], + id="baseclass-wrong-dtype", + ), + pytest.param( + remove_from_dict( + TEMPLATE, + "/ENTRY[my_entry]/duration/@units", + "required", + ), + [ + "Field /ENTRY[my_entry]/duration requires a unit in the unit category NX_TIME." + ], + id="baseclass-missing-unit", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/collection_time/@illegal", + "s", + ), + [ + "There were attributes set for the field /ENTRY[my_entry]/collection_time, but the field does not exist.", + "The attribute /ENTRY[my_entry]/collection_time/@illegal will not be written.", + ], + id="baseclass-attribute-missing-field", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/INSTRUMENT[my_instrument]/SOURCE[my_source]/type", + "Wrong source type", + ), + [ + "The value at /ENTRY[my_entry]/INSTRUMENT[my_instrument]/SOURCE[my_source]/type " + "should be one of the following: ['Spallation Neutron Source', 'Pulsed Reactor Neutron Source', " + "'Reactor Neutron Source', 'Synchrotron X-ray Source', 'Pulsed Muon Source', 'Rotating Anode X-ray', " + "'Fixed Tube X-ray', 'UV Laser', 'Free-Electron Laser', 'Optical Laser', 'Ion Source', 'UV Plasma Source', " + "'Metal Jet X-ray', 'Laser', 'Dye-Laser', 'Broadband Tunable Light Source', 'Halogen lamp', 'LED', " + "'Mercury Cadmium Telluride', 'Deuterium Lamp', 'Xenon Lamp', 'Globar', 'other']" + ], + id="baseclass-wrong-enum", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/INSTRUMENT[my_instrument]/SOURCE[my_source]/illegal_name", + 1, + ), + [ + "Field /ENTRY[my_entry]/INSTRUMENT[my_instrument]/SOURCE[my_source]/illegal_name written without documentation." + ], + id="baseclass-add-undocumented-field", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/INSTRUMENT[my_instrument]/SOURCE[my_source]/type/@illegal", + "illegal_attribute", + ), + [ + "Attribute /ENTRY[my_entry]/INSTRUMENT[my_instrument]/SOURCE[my_source]/type/@illegal written without documentation." + ], + id="baseclass-add-undocumented-attribute", + ), + pytest.param( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/INSTRUMENT[my_instrument]/SOURCE[my_source]/illegal/@units", + "illegal_attribute", + ), + [ + "Unit /ENTRY[my_entry]/INSTRUMENT[my_instrument]/SOURCE[my_source]/illegal/@units " + "in dataset without its field /ENTRY[my_entry]/INSTRUMENT[my_instrument]/SOURCE[my_source]/illegal.", + "The attribute /ENTRY[my_entry]/INSTRUMENT[my_instrument]/SOURCE[my_source]/illegal/@units will not be written.", + ], + id="baseclass-add-unit-of-missing-undocumented-field", + ), + pytest.param( + alter_dict( + alter_dict( + TEMPLATE, + "/ENTRY[my_entry]/required_group/author", + "author", + ), + "/ENTRY[my_entry]/required_group/author/@units", + "s", + ), + [ + "The unit, /ENTRY[my_entry]/required_group/author/@units = s, is being written but has no documentation." + ], + id="baseclass-field-with-illegal-unit", + ), ], ) -def test_validation_shows_warning(caplog, data_dict, error_message): - with caplog.at_level(logging.WARNING): - assert not validate_dict_against("NXtest", data_dict)[0] +def test_validate_data_dict(caplog, data_dict, error_messages, request): + """Unit test for the data validation routine.""" + + def format_error_message(msg: str) -> str: + return msg[msg.rfind("G: ") + 3 :].rstrip("\n") - assert error_message in caplog.text + if not error_messages: + with caplog.at_level(logging.WARNING): + assert validate_dict_against("NXtest", data_dict)[0] + assert caplog.text == "" + else: + if request.node.callspec.id in ( + "field-with-illegal-unit", + "baseclass-field-with-illegal-unit", + ): + with caplog.at_level(logging.INFO): + assert validate_dict_against("NXtest", data_dict)[0] + assert error_messages[0] in caplog.text + else: + with caplog.at_level(logging.WARNING): + assert not validate_dict_against("NXtest", data_dict)[0] + assert len(caplog.records) == len(error_messages) + for expected_message, rec in zip(error_messages, caplog.records): + assert expected_message == format_error_message(rec.message) diff --git a/tests/nexus/test_nexus.py b/tests/nexus/test_nexus.py index f2e341a1c..2069f4dd1 100644 --- a/tests/nexus/test_nexus.py +++ b/tests/nexus/test_nexus.py @@ -297,18 +297,18 @@ def test_get_inherited_nodes(): (_, _, elist) = get_inherited_nodes( nxdl_path="/ENTRY/INSTRUMENT/ENVIRONMENT", elem=elem ) - assert len(elist) == 4 + assert len(elist) == 3 (_, _, elist) = get_inherited_nodes( nxdl_path="/ENTRY/INSTRUMENT/ENVIRONMENT/voltage_controller", elem=elem ) - assert len(elist) == 6 + assert len(elist) == 4 (_, _, elist) = get_inherited_nodes( nxdl_path="/ENTRY/INSTRUMENT/ENVIRONMENT/voltage_controller", nx_name="NXiv_temp", ) - assert len(elist) == 6 + assert len(elist) == 4 def test_c_option(tmp_path): diff --git a/tests/nomad/test_parsing.py b/tests/nomad/test_parsing.py index 8db054bcd..e050448a7 100644 --- a/tests/nomad/test_parsing.py +++ b/tests/nomad/test_parsing.py @@ -57,25 +57,24 @@ def test_nexus_example(): ) # good ENUM - x-ray assert instrument.SOURCE[0].probe__field == "x-ray" - # wrong inherited ENUM - Burst (accepted for open enum) - assert instrument.SOURCE[0].mode__field == "Burst" - # wrong inherited ENUM for extended field - 'Free Electron Laser' (accepted for open enum) - assert instrument.SOURCE[0].type__field == "Free Electron Laser" + # wrong inherited ENUM - Burst + assert instrument.SOURCE[0].mode__field is None + # wrong inherited ENUM for extended field - 'Free Electron Laser' + assert instrument.SOURCE[0].type__field is None data = arpes_obj.ENTRY[0].DATA[0] assert len(data.AXISNAME__field) == 3 # there is still a bug in the variadic name resolution, so skip these # assert data.delays__field is not None # assert data.angles__field.check("1/Å") # assert data.delays__field.check("fs") - # assert data.energies__field is not None - # assert data.energies__field.check("eV") + # but the following still works + assert data.energies__field is not None + assert data.energies__field.check("eV") # manual name resolution assert data.AXISNAME__field["angles__field"] is not None assert data.AXISNAME__max["angles__max"].value == 2.168025463513032 assert (1 * data.AXISNAME__field["angles__field"].unit).check("1/Å") assert (1 * data.AXISNAME__field["delays__field"].unit).check("fs") - assert (1 * data.AXISNAME__field["energies__field"].unit).check("eV") - assert (1 * data.DATA__field["data__field"].unit).check("1") assert data.___axes == "['angles', 'energies', 'delays']"