summaryrefslogtreecommitdiff
path: root/src/silx/io/nxdata/_utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/silx/io/nxdata/_utils.py')
-rw-r--r--src/silx/io/nxdata/_utils.py183
1 files changed, 183 insertions, 0 deletions
diff --git a/src/silx/io/nxdata/_utils.py b/src/silx/io/nxdata/_utils.py
new file mode 100644
index 0000000..12318f1
--- /dev/null
+++ b/src/silx/io/nxdata/_utils.py
@@ -0,0 +1,183 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2017-2021 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Utility functions used by NXdata validation and parsing."""
+
+import copy
+import logging
+
+import numpy
+
+from silx.io import is_dataset
+from silx.utils.deprecation import deprecated
+
+
+__authors__ = ["P. Knobel"]
+__license__ = "MIT"
+__date__ = "17/04/2018"
+
+
+nxdata_logger = logging.getLogger("silx.io.nxdata")
+
+
+INTERPDIM = {"scalar": 0,
+ "spectrum": 1,
+ "image": 2,
+ "rgba-image": 3, # "hsla-image": 3, "cmyk-image": 3, # TODO
+ "vertex": 1} # 3D scatter: 1D signal + 3 axes (x, y, z) of same legth
+"""Number of signal dimensions associated to each possible @interpretation
+attribute.
+"""
+
+
+@deprecated(since_version="0.8.0", replacement="get_attr_as_unicode")
+def get_attr_as_string(*args, **kwargs):
+ return get_attr_as_unicode(*args, **kwargs)
+
+
+def get_attr_as_unicode(item, attr_name, default=None):
+ """Return item.attrs[attr_name] as unicode or as a
+ list of unicode.
+
+ Numpy arrays of strings or bytes returned by h5py are converted to
+ lists of unicode.
+
+ :param item: Group or dataset
+ :param attr_name: Attribute name
+ :param default: Value to be returned if attribute is not found.
+ :return: item.attrs[attr_name]
+ """
+ attr = item.attrs.get(attr_name, default)
+
+ if isinstance(attr, bytes):
+ # byte-string
+ return attr.decode("utf-8")
+ elif isinstance(attr, numpy.ndarray) and not attr.shape:
+ if isinstance(attr[()], bytes):
+ # byte string as ndarray scalar
+ return attr[()].decode("utf-8")
+ else:
+ # other scalar, possibly unicode
+ return attr[()]
+ elif isinstance(attr, numpy.ndarray) and len(attr.shape):
+ if hasattr(attr[0], "decode"):
+ # array of byte-strings
+ return [element.decode("utf-8") for element in attr]
+ else:
+ # other array, most likely unicode objects
+ return [element for element in attr]
+ else:
+ return copy.deepcopy(attr)
+
+
+def get_uncertainties_names(group, signal_name):
+ # Test consistency of @uncertainties
+ uncertainties_names = get_attr_as_unicode(group, "uncertainties")
+ if uncertainties_names is None:
+ uncertainties_names = get_attr_as_unicode(group[signal_name], "uncertainties")
+ if isinstance(uncertainties_names, str):
+ uncertainties_names = [uncertainties_names]
+ return uncertainties_names
+
+
+def get_signal_name(group):
+ """Return the name of the (main) signal in a NXdata group.
+ Return None if this info is missing (invalid NXdata).
+
+ """
+ signal_name = get_attr_as_unicode(group, "signal", default=None)
+ if signal_name is None:
+ nxdata_logger.info("NXdata group %s does not define a signal attr. "
+ "Testing legacy specification.", group.name)
+ for key in group:
+ if "signal" in group[key].attrs:
+ signal_name = key
+ signal_attr = group[key].attrs["signal"]
+ if signal_attr in [1, b"1", u"1"]:
+ # This is the main (default) signal
+ break
+ return signal_name
+
+
+def get_auxiliary_signals_names(group):
+ """Return list of auxiliary signals names"""
+ auxiliary_signals_names = get_attr_as_unicode(group, "auxiliary_signals",
+ default=[])
+ if isinstance(auxiliary_signals_names, (str, bytes)):
+ auxiliary_signals_names = [auxiliary_signals_names]
+ return auxiliary_signals_names
+
+
+def validate_auxiliary_signals(group, signal_name, auxiliary_signals_names):
+ """Check data dimensionality and size. Return False if invalid."""
+ issues = []
+ for asn in auxiliary_signals_names:
+ if asn not in group or not is_dataset(group[asn]):
+ issues.append(
+ "Cannot find auxiliary signal dataset '%s'" % asn)
+ elif group[signal_name].shape != group[asn].shape:
+ issues.append("Auxiliary signal dataset '%s' does not" % asn +
+ " have the same shape as the main signal.")
+ return issues
+
+
+def validate_number_of_axes(group, signal_name, num_axes):
+ issues = []
+ ndims = len(group[signal_name].shape)
+ if 1 < ndims < num_axes:
+ # ndim = 1 with several axes could be a scatter
+ issues.append(
+ "More @axes defined than there are " +
+ "signal dimensions: " +
+ "%d axes, %d dimensions." % (num_axes, ndims))
+
+ # case of less axes than dimensions: number of axes must match
+ # dimensionality defined by @interpretation
+ elif ndims > num_axes:
+ interpretation = get_attr_as_unicode(group[signal_name], "interpretation")
+ if interpretation is None:
+ interpretation = get_attr_as_unicode(group, "interpretation")
+ if interpretation is None:
+ issues.append("No @interpretation and not enough" +
+ " @axes defined.")
+
+ elif interpretation not in INTERPDIM:
+ issues.append("Unrecognized @interpretation=" + interpretation +
+ " for data with wrong number of defined @axes.")
+ elif interpretation == "rgba-image":
+ if ndims != 3 or group[signal_name].shape[-1] not in [3, 4]:
+ issues.append(
+ "Inconsistent RGBA Image. Expected 3 dimensions with " +
+ "last one of length 3 or 4. Got ndim=%d " % ndims +
+ "with last dimension of length %d." % group[signal_name].shape[-1])
+ if num_axes != 2:
+ issues.append(
+ "Inconsistent number of axes for RGBA Image. Expected "
+ "3, but got %d." % ndims)
+
+ elif num_axes != INTERPDIM[interpretation]:
+ issues.append(
+ "%d-D signal with @interpretation=%s " % (ndims, interpretation) +
+ "must define %d or %d axes." % (ndims, INTERPDIM[interpretation]))
+ return issues