# coding: utf-8
#/*##########################################################################
# Copyright (C) 2016 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.
#
#############################################################################*/
"""This module provides functions to convert a SpecFile into a HDF5 file"""
import h5py
import logging
import re
from .spech5 import SpecH5, SpecH5Group, SpecH5Dataset, \
     SpecH5LinkToGroup, SpecH5LinkToDataset
__authors__ = ["P. Knobel"]
__license__ = "MIT"
__date__ = "30/03/2016"
logger = logging.getLogger(__name__)
# logger.setLevel(logging.DEBUG)
[docs]def write_spec_to_h5(specfile, h5file, h5path='/',
                     mode="a", overwrite_data=False,
                     link_type="hard", create_dataset_args=None):
    """Write content of a SpecFile in a HDF5 file.
    :param specfile: Path of input SpecFile or :class:`SpecH5` instance
    :param h5file: Path of output HDF5 file or HDF5 file handle
    :param h5path: Target path in HDF5 file in which scan groups are created.
        Default is root (``"/"``)
    :param mode: Can be ``"r+"`` (read/write, file must exist),
        ``"w"`` (write, existing file is lost), ``"w-"`` (write, fail
        if exists) or ``"a"`` (read/write if exists, create otherwise).
        This parameter is ignored if ``h5file`` is a file handle.
    :param overwrite_data: If ``True``, existing groups and datasets can be
        overwritten, if ``False`` they are skipped. This parameter is only
        relevant if ``file_mode`` is ``"r+"`` or ``"a"``.
    :param link_type: ``"hard"`` (default) or ``"soft"``
    :param create_dataset_args: Dictionary of args you want to pass to
        ``h5f.create_dataset``. This allows you to specify filters and
        compression parameters. Don't specify ``name`` and ``data``.
        These arguments don't apply to scalar datasets.
    The structure of the spec data in an HDF5 file is described in the
    documentation of :mod:`silx.io.spech5`.
    """
    if not isinstance(specfile, SpecH5):
        sfh5 = SpecH5(specfile)
    else:
        sfh5 = specfile
    if not isinstance(h5file, h5py.File):
        h5f = h5py.File(h5file, mode)
    else:
        h5f = h5file
    if not h5path.endswith("/"):
        h5path += "/"
    if create_dataset_args is None:
        create_dataset_args = {}
    def create_link(link_name, target):
        """Create link
        If member with name ``link_name`` already exists, delete it first or
        ignore link depending on global param ``overwrite_data``.
        :param link_name: Link path
        :param target: Handle for target group or dataset
        """
        if link_name not in h5f:
            logger.debug("Creating link " + link_name + " -> " + target.name)
        elif overwrite_data:
            logger.warn("Overwriting " + link_name + " with link to" +
                        target.name)
            del h5f[link_name]
        else:
            logger.warn(link_name + " already exist. Can't create link to " +
                        target.name)
            return None
        if link_type == "hard":
            h5f[link_name] = target
        elif link_type == "soft":
            h5f[link_name] = h5py.SoftLink(target.name)
        else:
            raise ValueError("link_type  must be 'hard' or 'soft'")
    def append_spec_member_to_h5(spec_h5_name, obj):
        h5_name = h5path + spec_h5_name.lstrip("/")
        if isinstance(obj, SpecH5LinkToGroup) or\
                
isinstance(obj, SpecH5LinkToDataset):
            # links are created at the same time as their targets
            logger.debug("Ignoring link: " + h5_name)
            pass
        elif isinstance(obj, SpecH5Dataset):
            logger.debug("Saving dataset: " + h5_name)
            member_initially_exists = h5_name in h5f
            if overwrite_data and member_initially_exists:
                logger.warn("Overwriting dataset: " + h5_name)
                del h5f[h5_name]
            if overwrite_data or not member_initially_exists:
                # fancy arguments don't apply to scalars (shape==())
                if obj.shape == ():
                    ds = h5f.create_dataset(h5_name, data=obj)
                else:
                    ds = h5f.create_dataset(h5_name, data=obj,
                                            **create_dataset_args)
            else:
                ds = h5f[h5_name]
            # link:
            #  /1.1/measurement/mca_0/data  --> /1.1/instrument/mca_0/data
            if re.match(r".*/([0-9]+\.[0-9]+)/instrument/mca_([0-9]+)/?data$",
                        h5_name):
                link_name = h5_name.replace("instrument", "measurement")
                create_link(link_name, ds)
            # this has to be at the end if we want link creation and
            # dataset creation to remain independent for odd cases
            # where dataset exists but not the link
            if not overwrite_data and member_initially_exists:
                logger.warn("Ignoring existing dataset: " + h5_name)
        elif isinstance(obj, SpecH5Group):
            if h5_name not in h5f:
                logger.debug("Creating group: " + h5_name)
                grp = h5f.create_group(h5_name)
            else:
                grp = h5f[h5_name]
            # link:
            # /1.1/measurement/mca_0/info  --> /1.1/instrument/mca_0/
            if re.match(r".*/([0-9]+\.[0-9]+)/instrument/mca_([0-9]+)/?$",
                        h5_name):
                link_name = h5_name.replace("instrument", "measurement")
                link_name += "/info"
                create_link(link_name, grp)
    sfh5.visititems(append_spec_member_to_h5)
    # Close file if it was opened in this function
    if not isinstance(h5file, h5py.File):
        h5f.close()
 
[docs]def convert(specfile, h5file, mode="w-",
            create_dataset_args=None):
    """Convert a SpecFile into an HDF5 file, write scans into the root (``/``)
     group.
    :param specfile: Path of input SpecFile or :class:`SpecH5` instance
    :param h5file: Path of output HDF5 file or HDF5 file handle
    :param mode: Can be ``"w"`` (write, existing file is
        lost), ``"w-"`` (write, fail if exists). This is ignored
        if ``h5file`` is a file handle.
    :param create_dataset_args: Dictionary of args you want to pass to
        ``h5f.create_dataset``. This allows you to specify filters and
        compression parameters. Don't specify ``name`` and ``data``.
        These arguments don't apply to scalar datasets.
    This is a convenience shortcut to call::
        write_spec_to_h5(specfile, h5file, h5path='/',
                         h5_file_mode="w-", link_type="hard")
    """
    if mode not in ["w", "w-"]:
        raise IOError("File mode must be 'w' or 'w-'. Use write_spec_to_h5" +
                      " to append Spec data to an existing HDF5 file.")
    write_spec_to_h5(specfile, h5file, h5path='/',
                     mode=mode,
                     create_dataset_args=create_dataset_args)