diff options
author | Picca Frédéric-Emmanuel <picca@debian.org> | 2017-10-07 07:59:01 +0200 |
---|---|---|
committer | Picca Frédéric-Emmanuel <picca@debian.org> | 2017-10-07 07:59:01 +0200 |
commit | bfa4dba15485b4192f8bbe13345e9658c97ecf76 (patch) | |
tree | fb9c6e5860881fbde902f7cbdbd41dc4a3a9fb5d /silx/io/fabioh5.py | |
parent | f7bdc2acff3c13a6d632c28c4569690ab106eed7 (diff) |
New upstream version 0.6.0+dfsg
Diffstat (limited to 'silx/io/fabioh5.py')
-rw-r--r-- | silx/io/fabioh5.py | 569 |
1 files changed, 65 insertions, 504 deletions
diff --git a/silx/io/fabioh5.py b/silx/io/fabioh5.py index 092ac0c..3dfba96 100644 --- a/silx/io/fabioh5.py +++ b/silx/io/fabioh5.py @@ -28,472 +28,43 @@ .. note:: This module has a dependency on the `h5py <http://www.h5py.org/>`_ and `fabio <https://github.com/silx-kit/fabio>`_ libraries, - which are not a mandatory dependencies for `silx`. You might need - to install it if you don't already have it. + which are not mandatory dependencies for `silx`. + """ import collections -import numpy -import numbers +import datetime import logging -from silx.third_party import six - -_logger = logging.getLogger(__name__) - -try: - import fabio -except ImportError as e: - _logger.error("Module %s requires fabio", __name__) - raise e - -try: - import h5py -except ImportError as e: - _logger.error("Module %s requires h5py", __name__) - raise e - - -class Node(object): - """Main class for all fabioh5 classes. Help to manage a tree.""" - - def __init__(self, name, parent=None): - self.__parent = parent - self.__basename = name - - @property - def h5py_class(self): - """Returns the h5py classes which is mimicked by this class. It can be - one of `h5py.File, h5py.Group` or `h5py.Dataset` - - :rtype: Class - """ - raise NotImplementedError() - - @property - def parent(self): - """Returns the parent of the node. - - :rtype: Node - """ - return self.__parent - - @property - def file(self): - """Returns the file node of this node. - - :rtype: Node - """ - node = self - while node.__parent is not None: - node = node.__parent - if isinstance(node, File): - return node - else: - return None - - def _set_parent(self, parent): - """Set the parent of this node. - - It do not update the parent object. - - :param Node parent: New parent for this node - """ - self.__parent = parent - - @property - def attrs(self): - """Returns HDF5 attributes of this node. - - :rtype: dict - """ - return {} - - @property - def name(self): - """Returns the HDF5 name of this node. - """ - if self.__parent is None: - return "/" - if self.__parent.name == "/": - return "/" + self.basename - return self.__parent.name + "/" + self.basename - - @property - def basename(self): - """Returns the HDF5 basename of this node. - """ - return self.__basename - - -class Dataset(Node): - """Class which handle a numpy data as a mimic of a h5py.Dataset. - """ - - def __init__(self, name, data, parent=None, attrs=None): - self.__data = data - Node.__init__(self, name, parent) - if attrs is None: - self.__attrs = {} - else: - self.__attrs = attrs - - def _set_data(self, data): - """Set the data exposed by the dataset. - - It have to be called only one time before the data is used. It should - not be edited after use. - - :param numpy.ndarray data: Data associated to the dataset - """ - self.__data = data - - def _get_data(self): - """Returns the exposed data - - :rtype: numpy.ndarray - """ - return self.__data - - @property - def attrs(self): - """Returns HDF5 attributes of this node. - - :rtype: dict - """ - return self.__attrs - - @property - def h5py_class(self): - """Returns the h5py classes which is mimicked by this class. It can be - one of `h5py.File, h5py.Group` or `h5py.Dataset` - - :rtype: Class - """ - return h5py.Dataset - - @property - def dtype(self): - """Returns the numpy datatype exposed by this dataset. - - :rtype: numpy.dtype - """ - return self._get_data().dtype - - @property - def shape(self): - """Returns the shape of the data exposed by this dataset. - - :rtype: tuple - """ - if isinstance(self._get_data(), numpy.ndarray): - return self._get_data().shape - else: - return tuple() - - @property - def size(self): - """Returns the size of the data exposed by this dataset. - - :rtype: int - """ - if isinstance(self._get_data(), numpy.ndarray): - return self._get_data().size - else: - # It is returned as float64 1.0 by h5py - return numpy.float64(1.0) - - def __len__(self): - """Returns the size of the data exposed by this dataset. - - :rtype: int - """ - if isinstance(self._get_data(), numpy.ndarray): - return len(self._get_data()) - else: - # It is returned as float64 1.0 by h5py - raise TypeError("Attempt to take len() of scalar dataset") - - def __getitem__(self, item): - """Returns the slice of the data exposed by this dataset. - - :rtype: numpy.ndarray - """ - if not isinstance(self._get_data(), numpy.ndarray): - if item == Ellipsis: - return numpy.array(self._get_data()) - elif item == tuple(): - return self._get_data() - else: - raise ValueError("Scalar can only be reached with an ellipsis or an empty tuple") - return self._get_data().__getitem__(item) - - def __str__(self): - basename = self.name.split("/")[-1] - return '<FabIO dataset "%s": shape %s, type "%s">' % \ - (basename, self.shape, self.dtype.str) - - def __getslice__(self, i, j): - """Returns the slice of the data exposed by this dataset. - - Deprecated but still in use for python 2.7 - - :rtype: numpy.ndarray - """ - return self.__getitem__(slice(i, j, None)) - - @property - def value(self): - """Returns the data exposed by this dataset. - - Deprecated by h5py. It is prefered to use indexing `[()]`. - - :rtype: numpy.ndarray - """ - return self._get_data() - - @property - def compression(self): - """Returns compression as provided by `h5py.Dataset`. - - There is no compression.""" - return None - - @property - def compression_opts(self): - """Returns compression options as provided by `h5py.Dataset`. - - There is no compression.""" - return None - - @property - def chunks(self): - """Returns chunks as provided by `h5py.Dataset`. - - There is no chunks.""" - return None - - -class LazyLoadableDataset(Dataset): - """Abstract dataset which provide a lazy loading of the data. - - The class have to be inherited and the :meth:`_create_data` have to be - implemented to return the numpy data exposed by the dataset. This factory - is only called ones, when the data is needed. - """ - - def __init__(self, name, parent=None, attrs=None): - super(LazyLoadableDataset, self).__init__(name, None, parent, attrs=attrs) - self.__is_initialized = False - - def _create_data(self): - """ - Factory to create the data exposed by the dataset when it is needed. - - It have to be implemented to work. - - :rtype: numpy.ndarray - """ - raise NotImplementedError() - - def _get_data(self): - """Returns the data exposed by the dataset. - - Overwrite Dataset method :meth:`_get_data` to implement the lazy - loading feature. - - :rtype: numpy.ndarray - """ - if not self.__is_initialized: - data = self._create_data() - self._set_data(data) - self.__is_initialized = True - return super(LazyLoadableDataset, self)._get_data() - - -class Group(Node): - """Class which mimic a `h5py.Group`.""" - - def __init__(self, name, parent=None, attrs=None): - Node.__init__(self, name, parent) - self.__items = collections.OrderedDict() - if attrs is None: - attrs = {} - self.__attrs = attrs - - def _get_items(self): - """Returns the child items as a name-node dictionary. - - :rtype: dict - """ - return self.__items - - def add_node(self, node): - """Add a child to this group. - - :param Node node: Child to add to this group - """ - self._get_items()[node.basename] = node - node._set_parent(self) - - @property - def h5py_class(self): - """Returns the h5py classes which is mimicked by this class. - - It returns `h5py.Group` - - :rtype: Class - """ - return h5py.Group - - @property - def attrs(self): - """Returns HDF5 attributes of this node. - - :rtype: dict - """ - return self.__attrs - - def items(self): - """Returns items iterator containing name-node mapping. - - :rtype: iterator - """ - return self._get_items().items() - - def get(self, name, default=None, getclass=False, getlink=False): - """ Retrieve an item or other information. - - If getlink only is true, the returned value is always HardLink - cause this implementation do not use links. Like the original - implementation. - - :param str name: name of the item - :param object default: default value returned if the name is not found - :param bool getclass: if true, the returned object is the class of the object found - :param bool getlink: if true, links object are returned instead of the target - :return: An object, else None - :rtype: object - """ - if name not in self._get_items(): - return default - - if getlink: - node = h5py.HardLink() - else: - node = self._get_items()[name] - - if getclass: - obj = node.h5py_class - else: - obj = node - return obj - - def __len__(self): - """Returns the number of child contained in this group. - - :rtype: int - """ - return len(self._get_items()) - - def __iter__(self): - """Iterate over member names""" - for x in self._get_items().__iter__(): - yield x - - def __getitem__(self, name): - """Return a child from is name. - - :param name str: name of a member or a path throug members using '/' - separator. A '/' as a prefix access to the root item of the tree. - :rtype: Node - """ - - if name is None or name == "": - raise ValueError("No name") - - if "/" not in name: - return self._get_items()[name] - - if name.startswith("/"): - root = self - while root.parent is not None: - root = root.parent - if name == "/": - return root - return root[name[1:]] - - path = name.split("/") - result = self - for item_name in path: - if not isinstance(result, Group): - raise KeyError("Unable to open object (Component not found)") - result = result._get_items()[item_name] - - return result - - def __contains__(self, name): - """Returns true is a name is an existing child of this group. - - :rtype: bool - """ - return name in self._get_items() - - def keys(self): - return self._get_items().keys() - - -class LazyLoadableGroup(Group): - """Abstract group which provide a lazy loading of the child. - - The class have to be inherited and the :meth:`_create_child` have to be - implemented to add (:meth:`_add_node`) all child. This factory - is only called ones, when child are needed. - """ - - def __init__(self, name, parent=None, attrs=None): - Group.__init__(self, name, parent, attrs) - self.__is_initialized = False - - def _get_items(self): - """Returns internal structure which contains child. - - It overwrite method :meth:`_get_items` to implement the lazy - loading feature. +import numbers - :rtype: dict - """ - if not self.__is_initialized: - self.__is_initialized = True - self._create_child() - return Group._get_items(self) +import fabio +import numpy - def _create_child(self): - """ - Factory to create the child contained by the group when it is needed. +from . import commonh5 +from silx.third_party import six +from silx import version as silx_version - It have to be implemented to work. - """ - raise NotImplementedError() +_logger = logging.getLogger(__name__) -class FrameData(LazyLoadableDataset): +class FrameData(commonh5.LazyLoadableDataset): """Expose a cube of image from a Fabio file using `FabioReader` as cache.""" def __init__(self, name, fabio_reader, parent=None): attrs = {"interpretation": "image"} - LazyLoadableDataset.__init__(self, name, parent, attrs=attrs) + commonh5.LazyLoadableDataset.__init__(self, name, parent, attrs=attrs) self.__fabio_reader = fabio_reader def _create_data(self): return self.__fabio_reader.get_data() -class RawHeaderData(LazyLoadableDataset): +class RawHeaderData(commonh5.LazyLoadableDataset): """Lazy loadable raw header""" def __init__(self, name, fabio_file, parent=None): - LazyLoadableDataset.__init__(self, name, parent) + commonh5.LazyLoadableDataset.__init__(self, name, parent) self.__fabio_file = fabio_file def _create_data(self): @@ -510,18 +81,18 @@ class RawHeaderData(LazyLoadableDataset): for key, value in header.items(): data.append("%s: %s" % (str(key), str(value))) - headers.append(u"\n".join(data)) + headers.append(u"\n".join(data).encode("utf-8")) # create the header list - return numpy.array(headers) + return numpy.array(headers, dtype=numpy.string_) -class MetadataGroup(LazyLoadableGroup): +class MetadataGroup(commonh5.LazyLoadableGroup): """Abstract class for groups containing a reference to a fabio image. """ def __init__(self, name, metadata_reader, kind, parent=None, attrs=None): - LazyLoadableGroup.__init__(self, name, parent, attrs) + commonh5.LazyLoadableGroup.__init__(self, name, parent, attrs) self.__metadata_reader = metadata_reader self.__kind = kind @@ -529,7 +100,7 @@ class MetadataGroup(LazyLoadableGroup): keys = self.__metadata_reader.get_keys(self.__kind) for name in keys: data = self.__metadata_reader.get_value(self.__kind, name) - dataset = Dataset(name, data) + dataset = commonh5.Dataset(name, data) self.add_node(dataset) @property @@ -537,14 +108,14 @@ class MetadataGroup(LazyLoadableGroup): return self.__metadata_reader -class DetectorGroup(LazyLoadableGroup): +class DetectorGroup(commonh5.LazyLoadableGroup): """Define the detector group (sub group of instrument) using Fabio data. """ def __init__(self, name, fabio_reader, parent=None, attrs=None): if attrs is None: attrs = {"NX_class": "NXdetector"} - LazyLoadableGroup.__init__(self, name, parent, attrs) + commonh5.LazyLoadableGroup.__init__(self, name, parent, attrs) self.__fabio_reader = fabio_reader def _create_child(self): @@ -558,57 +129,56 @@ class DetectorGroup(LazyLoadableGroup): self.add_node(others) -class ImageGroup(LazyLoadableGroup): +class ImageGroup(commonh5.LazyLoadableGroup): """Define the image group (sub group of measurement) using Fabio data. """ def __init__(self, name, fabio_reader, parent=None, attrs=None): - LazyLoadableGroup.__init__(self, name, parent, attrs) + commonh5.LazyLoadableGroup.__init__(self, name, parent, attrs) self.__fabio_reader = fabio_reader def _create_child(self): - data = FrameData("data", self.__fabio_reader) + basepath = self.parent.parent.name + data = commonh5.SoftLink("data", path=basepath + "/instrument/detector_0/data") self.add_node(data) - - # TODO detector should be a real soft-link - detector = DetectorGroup("info", self.__fabio_reader) + detector = commonh5.SoftLink("info", path=basepath + "/instrument/detector_0") self.add_node(detector) -class SampleGroup(LazyLoadableGroup): +class SampleGroup(commonh5.LazyLoadableGroup): """Define the image group (sub group of measurement) using Fabio data. """ def __init__(self, name, fabio_reader, parent=None): attrs = {"NXclass": "NXsample"} - LazyLoadableGroup.__init__(self, name, parent, attrs) + commonh5.LazyLoadableGroup.__init__(self, name, parent, attrs) self.__fabio_reader = fabio_reader def _create_child(self): if self.__fabio_reader.has_ub_matrix(): scalar = {"interpretation": "scalar"} data = self.__fabio_reader.get_unit_cell_abc() - data = Dataset("unit_cell_abc", data, attrs=scalar) + data = commonh5.Dataset("unit_cell_abc", data, attrs=scalar) self.add_node(data) unit_cell_data = numpy.zeros((1, 6), numpy.float32) unit_cell_data[0, :3] = data data = self.__fabio_reader.get_unit_cell_alphabetagamma() - data = Dataset("unit_cell_alphabetagamma", data, attrs=scalar) + data = commonh5.Dataset("unit_cell_alphabetagamma", data, attrs=scalar) self.add_node(data) unit_cell_data[0, 3:] = data - data = Dataset("unit_cell", unit_cell_data, attrs=scalar) + data = commonh5.Dataset("unit_cell", unit_cell_data, attrs=scalar) self.add_node(data) data = self.__fabio_reader.get_ub_matrix() - data = Dataset("ub_matrix", data, attrs=scalar) + data = commonh5.Dataset("ub_matrix", data, attrs=scalar) self.add_node(data) -class MeasurementGroup(LazyLoadableGroup): +class MeasurementGroup(commonh5.LazyLoadableGroup): """Define the measurement group for fabio file. """ def __init__(self, name, fabio_reader, parent=None, attrs=None): - LazyLoadableGroup.__init__(self, name, parent, attrs) + commonh5.LazyLoadableGroup.__init__(self, name, parent, attrs) self.__fabio_reader = fabio_reader def _create_child(self): @@ -628,7 +198,7 @@ class MeasurementGroup(LazyLoadableGroup): # add all counters for name in keys: data = self.__fabio_reader.get_value(FabioReader.COUNTER, name) - dataset = Dataset(name, data) + dataset = commonh5.Dataset(name, data) self.add_node(dataset) @@ -667,22 +237,29 @@ class FabioReader(object): image = self.__fabio_file.getframe(frame).data images.append(image) + # returns the data without extra dim in case of single frame + if len(images) == 1: + return images[0] + # get the max size - max_shape = [0, 0] + max_dim = max([i.ndim for i in images]) + max_shape = [0] * max_dim for image in images: - if image.shape[0] > max_shape[0]: - max_shape[0] = image.shape[0] - if image.shape[1] > max_shape[1]: - max_shape[1] = image.shape[1] + for dim in range(image.ndim): + if image.shape[dim] > max_shape[dim]: + max_shape[dim] = image.shape[dim] max_shape = tuple(max_shape) # fix smallest images for index, image in enumerate(images): if image.shape == max_shape: continue - right_image = numpy.zeros(max_shape) - right_image[0:image.shape[0], 0:image.shape[1]] = image - images[index] = right_image + location = [slice(0, i) for i in image.shape] + while len(location) < max_dim: + location.append(0) + normalized_image = numpy.zeros(max_shape, dtype=image.dtype) + normalized_image[location] = image + images[index] = normalized_image # create a cube return numpy.array(images) @@ -1058,7 +635,7 @@ class EdfFabioReader(FabioReader): return self.__ub_matrix -class File(Group): +class File(commonh5.File): """Class which handle a fabio image as a mimick of a h5py.File. """ @@ -1071,7 +648,12 @@ class File(Group): self.__must_be_closed = True elif fabio_image is not None: self.__fabio_image = fabio_image - Group.__init__(self, name="", parent=None, attrs={"NX_class": "NXroot"}) + file_name = self.__fabio_image.filename + attrs = {"NX_class": "NXroot", + "file_time": datetime.datetime.now().isoformat(), + "file_name": file_name, + "creator": "silx %s" % silx_version} + commonh5.File.__init__(self, name=file_name, attrs=attrs) self.__fabio_reader = self.create_fabio_reader(self.__fabio_image) scan = self.create_scan_group(self.__fabio_image, self.__fabio_reader) self.add_node(scan) @@ -1081,13 +663,13 @@ class File(Group): :param FabioImage fabio_image: A Fabio image :param FabioReader fabio_reader: A reader for the Fabio image - :rtype: Group + :rtype: commonh5.Group """ - scan = Group("scan_0", attrs={"NX_class": "NXentry"}) - instrument = Group("instrument", attrs={"NX_class": "NXinstrument"}) + scan = commonh5.Group("scan_0", attrs={"NX_class": "NXentry"}) + instrument = commonh5.Group("instrument", attrs={"NX_class": "NXinstrument"}) measurement = MeasurementGroup("measurement", fabio_reader, attrs={"NX_class": "NXcollection"}) - file_ = Group("file", attrs={"NX_class": "NXcollection"}) + file_ = commonh5.Group("file", attrs={"NX_class": "NXcollection"}) positioners = MetadataGroup("positioners", fabio_reader, FabioReader.POSITIONER, attrs={"NX_class": "NXpositioner"}) raw_header = RawHeaderData("scan_header", fabio_image, self) detector = DetectorGroup("detector_0", fabio_reader) @@ -1115,29 +697,6 @@ class File(Group): metadata = FabioReader(fabio_file) return metadata - @property - def h5py_class(self): - return h5py.File - - @property - def filename(self): - return self.__fabio_image.filename - - def __enter__(self): - return self - - def __exit__(self, type, value, tb): # pylint: disable=W0622 - """Called at the end of a `with` statement. - - It will close the internal FabioImage only if the FabioImage was - created by the class itself. The reference to the FabioImage is anyway - broken. - """ - if self.__must_be_closed: - self.close() - else: - self.__fabio_image = None - def close(self): """Close the object, and free up associated resources. @@ -1146,6 +705,8 @@ class File(Group): After calling this method, attempts to use the object may fail. """ - # It looks like there is no close on FabioImage - # self.__fabio_image.close() + if self.__must_be_closed: + # It looks like there is no close on FabioImage + # self.__fabio_image.close() + pass self.__fabio_image = None |