def is_NXentry_with_default_NXdata(group, validate=True): """Return True if group is a valid NXentry defining a valid default NXdata. :param group: h5py-like object. :param bool validate: Set this to False if you are sure that the target group is valid NXdata (i.e. :func:`silx.io.nxdata.is_valid_nxdata(target_group)` returns True). Parameter provided for optimisation purposes.""" if not is_group(group): return False if get_attr_as_unicode(group, "NX_class") != "NXentry": return False default_nxdata_name = group.attrs.get("default") if default_nxdata_name is None or default_nxdata_name not in group: return False default_nxdata_group = group.get(default_nxdata_name) if not is_group(default_nxdata_group): return False if not validate: return True else: return is_valid_nxdata(default_nxdata_group)
def get_default(group, validate=True): """Return a :class:`NXdata` object corresponding to the default NXdata group in the group specified as parameter. This function can find the NXdata if the group is already a NXdata, or if it is a NXentry defining a default NXdata, or if it is a NXroot defining such a default valid NXentry. Return None if no valid NXdata could be found. :param group: h5py-like group following the Nexus specification (NXdata, NXentry or NXroot). :param bool validate: Set this to False if you are sure that group is valid NXdata (i.e. :func:`silx.io.nxdata.is_valid_nxdata(group)` returns True). Parameter provided for optimisation purposes. :return: :class:`NXdata` object or None :raise TypeError: if group is not a h5py-like group """ if not is_group(group): raise TypeError("Provided parameter is not a h5py-like group") if is_NXroot_with_default_NXdata(group, validate=validate): default_entry = group[group.attrs["default"]] default_data = default_entry[default_entry.attrs["default"]] elif is_NXentry_with_default_NXdata(group, validate=validate): default_data = group[group.attrs["default"]] elif not validate or is_valid_nxdata(group): default_data = group else: return None return NXdata(default_data, validate=False)
def is_NXroot_with_default_NXdata(group, validate=True): """Return True if group is a valid NXroot defining a default NXentry defining a valid default NXdata. :param group: h5py-like object. :param bool validate: Set this to False if you are sure that the target group is valid NXdata (i.e. :func:`silx.io.nxdata.is_valid_nxdata(target_group)` returns True). Parameter provided for optimisation purposes. """ if not is_group(group): return False # A NXroot is supposed to be at the root of a data file, and @NX_class # is therefore optional. We accept groups that are not located at the root # if they have @NX_class=NXroot (use case: several nexus files archived # in a single HDF5 file) if get_attr_as_unicode(group, "NX_class") != "NXroot" and not is_file(group): return False default_nxentry_name = group.attrs.get("default") if default_nxentry_name is None or default_nxentry_name not in group: return False default_nxentry_group = group.get(default_nxentry_name) return is_NXentry_with_default_NXdata(default_nxentry_group, validate=validate)
def is_NXroot_with_default_NXdata(group, validate=True): """Return True if group is a valid NXroot defining a default NXentry defining a valid default NXdata. .. note:: A NXroot group cannot directly define a default NXdata. If a *@default* argument is present, it must point to a NXentry group. This NXentry must define a valid NXdata for this function to return True. :param group: h5py-like object. :param bool validate: Set this to False if you are sure that the target group is valid NXdata (i.e. :func:`silx.io.nxdata.is_valid_nxdata(target_group)` returns True). Parameter provided for optimisation purposes. """ if not is_group(group): return False # A NXroot is supposed to be at the root of a data file, and @NX_class # is therefore optional. We accept groups that are not located at the root # if they have @NX_class=NXroot (use case: several nexus files archived # in a single HDF5 file) if get_attr_as_unicode(group, "NX_class") != "NXroot" and not is_file(group): return False default_nxentry_name = group.attrs.get("default") if default_nxentry_name is None or default_nxentry_name not in group: return False default_nxentry_group = group.get(default_nxentry_name) return is_NXentry_with_default_NXdata(default_nxentry_group, validate=validate)
def is_group_with_default_NXdata(group, validate=True): """Return True if group defines a valid default NXdata. .. note:: See https://github.com/silx-kit/silx/issues/2215 :param group: h5py-like object. :param bool validate: Set this to skip the NXdata validation, and only check the existence of the group. Parameter provided for optimisation purposes, to avoid double validation if the validation is already performed separately.""" default_nxdata_name = group.attrs.get("default") if default_nxdata_name is None or default_nxdata_name not in group: return False default_nxdata_group = group.get(default_nxdata_name) if not is_group(default_nxdata_group): return False if not validate: return True else: return is_valid_nxdata(default_nxdata_group)
def get_default(group, validate=True): """Return a :class:`NXdata` object corresponding to the default NXdata group in the group specified as parameter. This function can find the NXdata if the group is already a NXdata, or if it is a NXentry defining a default NXdata, or if it is a NXroot defining such a default valid NXentry. Return None if no valid NXdata could be found. :param group: h5py-like group following the Nexus specification (NXdata, NXentry or NXroot). :param bool validate: Set this to False if you are sure that group is valid NXdata (i.e. :func:`silx.io.nxdata.is_valid_nxdata(group)` returns True). Parameter provided for optimisation purposes. :return: :class:`NXdata` object or None :raise TypeError: if group is not a h5py-like group """ if not is_group(group): raise TypeError("Provided parameter is not a h5py-like group") if is_NXroot_with_default_NXdata(group, validate=validate): default_entry = group[group.attrs["default"]] default_data = default_entry[default_entry.attrs["default"]] elif is_group_with_default_NXdata(group, validate=validate): default_data = group[group.attrs["default"]] elif not validate or is_valid_nxdata(group): default_data = group else: return None return NXdata(default_data, validate=False)
def is_NXentry_with_default_NXdata(group, validate=True): """Return True if group is a valid NXentry defining a valid default NXdata. :param group: h5py-like object. :param bool validate: Set this to skip the NXdata validation, and only check the existence of the group. Parameter provided for optimisation purposes, to avoid double validation if the validation is already performed separately.""" if not is_group(group): return False if get_attr_as_unicode(group, "NX_class") != "NXentry": return False return is_group_with_default_NXdata(group, validate)
def _validate(self): """Fill :attr:`issues` with error messages for each error found.""" if not is_group(self.group): raise TypeError("group must be a h5py-like group") if get_attr_as_unicode(self.group, "NX_class") != "NXdata": self.issues.append("Group has no attribute @NX_class='NXdata'") signal_name = get_signal_name(self.group) if signal_name is None: self.issues.append("No @signal attribute on the NXdata group, " "and no dataset with a @signal=1 attr found") # very difficult to do more consistency tests without signal return elif signal_name not in self.group or not is_dataset(self.group[signal_name]): self.issues.append("Cannot find signal dataset '%s'" % signal_name) return auxiliary_signals_names = get_auxiliary_signals_names(self.group) self.issues += validate_auxiliary_signals(self.group, signal_name, auxiliary_signals_names) if "axes" in self.group.attrs: axes_names = get_attr_as_unicode(self.group, "axes") if isinstance(axes_names, (six.text_type, six.binary_type)): axes_names = [axes_names] self.issues += validate_number_of_axes(self.group, signal_name, num_axes=len(axes_names)) # Test consistency of @uncertainties uncertainties_names = get_uncertainties_names(self.group, signal_name) if uncertainties_names is not None: if len(uncertainties_names) != len(axes_names): self.issues.append("@uncertainties does not define the same " + "number of fields than @axes") # Test individual axes is_scatter = True # true if all axes have the same size as the signal signal_size = 1 for dim in self.group[signal_name].shape: signal_size *= dim polynomial_axes_names = [] for i, axis_name in enumerate(axes_names): if axis_name == ".": continue if axis_name not in self.group or not is_dataset(self.group[axis_name]): self.issues.append("Could not find axis dataset '%s'" % axis_name) continue axis_size = 1 for dim in self.group[axis_name].shape: axis_size *= dim if len(self.group[axis_name].shape) != 1: # I don't know how to interpret n-D axes self.issues.append("Axis %s is not 1D" % axis_name) continue else: # for a 1-d axis, fg_idx = self.group[axis_name].attrs.get("first_good", 0) lg_idx = self.group[axis_name].attrs.get("last_good", len(self.group[axis_name]) - 1) axis_len = lg_idx + 1 - fg_idx if axis_len != signal_size: if axis_len not in self.group[signal_name].shape + (1, 2): self.issues.append( "Axis %s number of elements does not " % axis_name + "correspond to the length of any signal dimension," " it does not appear to be a constant or a linear calibration," + " and this does not seem to be a scatter plot.") continue elif axis_len in (1, 2): polynomial_axes_names.append(axis_name) is_scatter = False else: if not is_scatter: self.issues.append( "Axis %s number of elements is equal " % axis_name + "to the length of the signal, but this does not seem" + " to be a scatter (other axes have different sizes)") continue # Test individual uncertainties errors_name = axis_name + "_errors" if errors_name not in self.group and uncertainties_names is not None: errors_name = uncertainties_names[i] if errors_name in self.group and axis_name not in polynomial_axes_names: if self.group[errors_name].shape != self.group[axis_name].shape: self.issues.append( "Errors '%s' does not have the same " % errors_name + "dimensions as axis '%s'." % axis_name) # test dimensions of errors associated with signal if "errors" in self.group and is_dataset(self.group["errors"]): if self.group["errors"].shape != self.group[signal_name].shape: self.issues.append( "Dataset containing standard deviations must " + "have the same dimensions as the signal.")
def _validate(self): """Fill :attr:`issues` with error messages for each error found.""" if not is_group(self.group): raise TypeError("group must be a h5py-like group") if get_attr_as_unicode(self.group, "NX_class") != "NXdata": self.issues.append("Group has no attribute @NX_class='NXdata'") return signal_name = get_signal_name(self.group) if signal_name is None: self.issues.append("No @signal attribute on the NXdata group, " "and no dataset with a @signal=1 attr found") # very difficult to do more consistency tests without signal return elif signal_name not in self.group or not is_dataset( self.group[signal_name]): self.issues.append("Cannot find signal dataset '%s'" % signal_name) return auxiliary_signals_names = get_auxiliary_signals_names(self.group) self.issues += validate_auxiliary_signals(self.group, signal_name, auxiliary_signals_names) if "axes" in self.group.attrs: axes_names = get_attr_as_unicode(self.group, "axes") if isinstance(axes_names, (six.text_type, six.binary_type)): axes_names = [axes_names] self.issues += validate_number_of_axes(self.group, signal_name, num_axes=len(axes_names)) # Test consistency of @uncertainties uncertainties_names = get_uncertainties_names( self.group, signal_name) if uncertainties_names is not None: if len(uncertainties_names) != len(axes_names): if len(uncertainties_names) < len(axes_names): # ignore the field to avoid index error in the axes loop uncertainties_names = None self.issues.append( "@uncertainties does not define the same " + "number of fields than @axes. Field ignored") else: self.issues.append( "@uncertainties does not define the same " + "number of fields than @axes") # Test individual axes is_scatter = True # true if all axes have the same size as the signal signal_size = 1 for dim in self.group[signal_name].shape: signal_size *= dim polynomial_axes_names = [] for i, axis_name in enumerate(axes_names): if axis_name == ".": continue if axis_name not in self.group or not is_dataset( self.group[axis_name]): self.issues.append("Could not find axis dataset '%s'" % axis_name) continue axis_size = 1 for dim in self.group[axis_name].shape: axis_size *= dim if len(self.group[axis_name].shape) != 1: # I don't know how to interpret n-D axes self.issues.append("Axis %s is not 1D" % axis_name) continue else: # for a 1-d axis, fg_idx = self.group[axis_name].attrs.get("first_good", 0) lg_idx = self.group[axis_name].attrs.get( "last_good", len(self.group[axis_name]) - 1) axis_len = lg_idx + 1 - fg_idx if axis_len != signal_size: if axis_len not in self.group[signal_name].shape + (1, 2): self.issues.append( "Axis %s number of elements does not " % axis_name + "correspond to the length of any signal dimension," " it does not appear to be a constant or a linear calibration," + " and this does not seem to be a scatter plot.") continue elif axis_len in (1, 2): polynomial_axes_names.append(axis_name) is_scatter = False else: if not is_scatter: self.issues.append( "Axis %s number of elements is equal " % axis_name + "to the length of the signal, but this does not seem" + " to be a scatter (other axes have different sizes)" ) continue # Test individual uncertainties errors_name = axis_name + "_errors" if errors_name not in self.group and uncertainties_names is not None: errors_name = uncertainties_names[i] if errors_name in self.group and axis_name not in polynomial_axes_names: if self.group[errors_name].shape != self.group[ axis_name].shape: self.issues.append( "Errors '%s' does not have the same " % errors_name + "dimensions as axis '%s'." % axis_name) # test dimensions of errors associated with signal signal_errors = signal_name + "_errors" if "errors" in self.group and is_dataset(self.group["errors"]): errors = "errors" elif signal_errors in self.group and is_dataset( self.group[signal_errors]): errors = signal_errors else: errors = None if errors: if self.group[errors].shape != self.group[signal_name].shape: # In principle just the same size should be enough but # NeXus documentation imposes to have the same shape self.issues.append( "Dataset containing standard deviations must " + "have the same dimensions as the signal.")
def is_valid_nxdata(group): # noqa """Check if a h5py group is a **valid** NX_data group. If the group does not have attribute *@NX_class=NXdata*, this function simply returns *False*. Else, warning messages are logged to troubleshoot malformed NXdata groups prior to returning *False*. :param group: h5py-like group :return: True if this NXdata group is valid. :raise TypeError: if group is not a h5py group, a spech5 group, or a fabioh5 group """ if not is_group(group): raise TypeError("group must be a h5py-like group") if get_attr_as_unicode(group, "NX_class") != "NXdata": return False signal_name = _get_signal_name(group) if signal_name is None: _nxdata_warning( "No @signal attribute on the NXdata group, " "and no dataset with a @signal=1 attr found", group.name) return False if signal_name not in group or not is_dataset(group[signal_name]): _nxdata_warning("Cannot find signal dataset '%s'" % signal_name, group.name) return False auxiliary_signals_names = _get_auxiliary_signals_names(group) if not _are_auxiliary_signals_valid(group, signal_name, auxiliary_signals_names): return False if "axes" in group.attrs: axes_names = get_attr_as_unicode(group, "axes") if isinstance(axes_names, (six.text_type, six.binary_type)): axes_names = [axes_names] if not _has_valid_number_of_axes( group, signal_name, num_axes=len(axes_names)): return False # Test consistency of @uncertainties uncertainties_names = _get_uncertainties_names(group, signal_name) if uncertainties_names is not None: if len(uncertainties_names) != len(axes_names): _nxdata_warning( "@uncertainties does not define the same " + "number of fields than @axes", group.name) return False # Test individual axes is_scatter = True # true if all axes have the same size as the signal signal_size = 1 for dim in group[signal_name].shape: signal_size *= dim polynomial_axes_names = [] for i, axis_name in enumerate(axes_names): if axis_name == ".": continue if axis_name not in group or not is_dataset(group[axis_name]): _nxdata_warning("Could not find axis dataset '%s'" % axis_name, group.name) return False axis_size = 1 for dim in group[axis_name].shape: axis_size *= dim if len(group[axis_name].shape) != 1: # too me, it makes only sense to have a n-D axis if it's total # size is exactly the signal's size (weird n-d scatter) if axis_size != signal_size: _nxdata_warning( "Axis %s is not a 1D dataset" % axis_name + " and its shape does not match the signal's shape", group.name) return False axis_len = axis_size else: # for a 1-d axis, fg_idx = group[axis_name].attrs.get("first_good", 0) lg_idx = group[axis_name].attrs.get("last_good", len(group[axis_name]) - 1) axis_len = lg_idx + 1 - fg_idx if axis_len != signal_size: if axis_len not in group[signal_name].shape + (1, 2): _nxdata_warning( "Axis %s number of elements does not " % axis_name + "correspond to the length of any signal dimension," " it does not appear to be a constant or a linear calibration," + " and this does not seem to be a scatter plot.", group.name) return False elif axis_len in (1, 2): polynomial_axes_names.append(axis_name) is_scatter = False else: if not is_scatter: _nxdata_warning( "Axis %s number of elements is equal " % axis_name + "to the length of the signal, but this does not seem" + " to be a scatter (other axes have different sizes)", group.name) return False # Test individual uncertainties errors_name = axis_name + "_errors" if errors_name not in group and uncertainties_names is not None: errors_name = uncertainties_names[i] if errors_name in group and axis_name not in polynomial_axes_names: if group[errors_name].shape != group[axis_name].shape: _nxdata_warning( "Errors '%s' does not have the same " % errors_name + "dimensions as axis '%s'." % axis_name, group.name) return False # test dimensions of errors associated with signal if "errors" in group and is_dataset(group["errors"]): if group["errors"].shape != group[signal_name].shape: _nxdata_warning( "Dataset containing standard deviations must " + "have the same dimensions as the signal.", group.name) return False return True