r"""
    Restricted save of data using hdf5. Can only save ndarrays and dicts of
    ndarrays.

    Args:
        fpath (str):
        data (ndarray):
        compression (str):
            DEFLATE/GZIP - standard
            LZF  - fast
            SHUFFLE - compression ratio
            FLETCHER32 - error detection
            Scale-offset - integer / float scaling and truncation
            SZIP - fast and patented

    CommandLine:
        python -m utool.util_io --test-save_hdf5

    References:
        http://docs.h5py.org/en/latest/quick.html
        http://docs.h5py.org/en/latest/mpi.html

    Example:
        >>> # DISABLE_DOCTEST
        >>> from utool.util_io import *  # NOQA
        >>> import numpy as np
        >>> import utool as ut
        >>> rng = np.random.RandomState(0)
        >>> data = (rng.rand(100000, 128) * 255).astype(np.uint8).copy()
        >>> verbose = True
        >>> fpath = ut.unixjoin(ut.ensure_app_resource_dir('utool'), 'myfile.hdf5')
        >>> compression = 'lzf'
        >>> ut.delete(fpath)
        >>> save_hdf5(fpath, data, verbose, compression)
        >>> data2 = load_hdf5(fpath, verbose)
        >>> assert data is not data2
        >>> assert np.all(data == data2)
        >>> assert ut.delete(fpath)

    Example:
        >>> # DISABLE_DOCTEST
        >>> from utool.util_io import *  # NOQA
        >>> import numpy as np
        >>> import utool as ut
        >>> rng = np.random.RandomState(0)
        >>> data = {'name': 'foobar', 'x': [1, 2, 3], 'y': np.array([3, 2, 1])}
        >>> ut.exec_funckw(save_hdf5, globals())
        >>> fpath = ut.unixjoin(ut.ensure_app_resource_dir('utool'), 'myfile2.hdf5')
        >>> ut.delete(fpath)
        >>> save_hdf5(fpath, data, verbose, compression)
        >>> data2 = load_hdf5(fpath, verbose)
        >>> assert data is not data2
        >>> assert all([np.all(data[key] == data2[key]) for key in data.keys()])
        >>> assert ut.delete(fpath)

    Timeit:
        >>> # DISABLE_DOCTEST
        >>> # cPkl / numpy seems to be faster with this initial implementation
        >>> import utool as ut
        >>> data = (rng.rand(1000000, 128) * 255).astype(np.uint8).copy()
        >>> print(ut.get_object_size_str(data))
        >>> del data
        >>> setup = ut.codeblock(
        >>>     '''
                import numpy as np
                import utool as ut
                rng = np.random.RandomState(0)
                fpath = ut.unixjoin(ut.ensure_app_resource_dir('utool'), 'io_test_data')
                data = (rng.rand(1000000, 128) * 255).astype(np.uint8).copy()
                #print(ut.get_object_size_str(data))
                ''')
        >>> # Test load time
        >>> stmt_list1 = ut.codeblock(
        >>>     '''
                ut.save_hdf5(fpath + '.hdf5', data, verbose=False, compression='gzip')
                ut.save_hdf5(fpath + '.hdf5', data, verbose=False, compression='lzf')
                ut.save_cPkl(fpath + '.cPkl', data, verbose=False)
                ut.save_numpy(fpath + '.npy', data, verbose=False)
                ut.save_pytables(fpath + '.tables', data, verbose=False)
                ''').split('\n')
        >>> ut.util_dev.timeit_compare(stmt_list1, setup, int(10))
        >>> # Test save time
        >>> stmt_list2 = ut.codeblock(
        >>>     '''
                ut.load_hdf5(fpath + '.hdf5', verbose=False)
                ut.load_cPkl(fpath + '.cPkl', verbose=False)
                ut.load_numpy(fpath + '.npy', verbose=False)
                ut.load_pytables(fpath + '.tables', verbose=False)
                ''').split('\n')
        >>> ut.util_dev.timeit_compare(stmt_list2, setup, int(10))
        >>> print('finished timeing')
        +----------------
        | TIMEIT COMPARE
        +----------------
        | iterations = 10
        | Input:
        |     | num | stmt
        |     |   0 | u"ut.save_hdf5(fpath + '.hdf5', data, verbose=False, compression='gzip')"
        |     |   1 | u"ut.save_hdf5(fpath + '.hdf5', data, verbose=False, compression='lzf')"
        |     |   2 | u"ut.save_cPkl(fpath + '.cPkl', data, verbose=False)"
        |     |   3 | u"ut.save_numpy(fpath + '.npy', data, verbose=False)"
        |     |   4 | u"ut.save_pytables(fpath + '.tables', data, verbose=False)"
        ...
        | Output:
        |    * PASSED: each statement produced the same result
        |    | num | total time | per loop | stmt
        |    |   0 |    0.03 ks |   3.15 s | ut.save_hdf5(fpath + '.hdf5', data, verbose=False, compression='gzip')
        |    |   1 |    0.01 ks |   1.25 s | ut.save_hdf5(fpath + '.hdf5', data, verbose=False, compression='lzf')
        |    |   2 |     5.30 s |   0.53 s | ut.save_cPkl(fpath + '.cPkl', data, verbose=False)
        |    |   3 |     4.97 s |   0.50 s | ut.save_numpy(fpath + '.npy', data, verbose=False)
        |    |   4 |     9.23 s |   0.92 s | ut.save_pytables(fpath + '.tables', data, verbose=False)
        L_________________
        +----------------
        | TIMEIT COMPARE
        +----------------
        | iterations = 10
        | Input:
        |     | num | stmt
        |     |   0 | u"ut.load_hdf5(fpath + '.hdf5', verbose=False)"
        |     |   1 | u"ut.load_cPkl(fpath + '.cPkl', verbose=False)"
        |     |   2 | u"ut.load_numpy(fpath + '.npy', verbose=False)"
        |     |   3 | u"ut.load_pytables(fpath + '.tables', verbose=False)"
        ...
        | Output:
        |    * PASSED: each statement produced the same result
        |    | num | total time | per loop | stmt
        |    |   0 |     2.39 s |   0.24 s | ut.load_hdf5(fpath + '.hdf5', verbose=False)
        |    |   1 |     0.39 s |   0.04 s | ut.load_cPkl(fpath + '.cPkl', verbose=False)
        |    |   2 |     0.19 s |   0.02 s | ut.load_numpy(fpath + '.npy', verbose=False)
        |    |   3 |     0.33 s |   0.03 s | ut.load_pytables(fpath + '.tables', verbose=False)
        L_________________

    Ignore:
        %timeit save_hdf5(fpath, data, verbose=False, compression='gzip')
        %timeit save_hdf5(fpath, data, verbose=False, compression='lzf')
        %timeit save_cPkl(fpath + '.cPkl', data, verbose=False)
        %timeit save_pytables(fpath + '.tables', data, verbose=False)
        1 loops, best of 3: 258 ms per loop
        10 loops, best of 3: 111 ms per loop
        10 loops, best of 3: 53.1 ms per loop
        10 loops, best of 3: 96.5 ms per loop

        save_hdf5(fpath, data, verbose=False, compression='gzip')
        %timeit load_hdf5(fpath, verbose=False)
        save_hdf5(fpath, data, verbose=False, compression='lzf')
        %timeit load_hdf5(fpath, verbose=False)
        %timeit load_cPkl(fpath + '.cPkl', verbose=False)
        %timeit load_pytables(fpath + '.tables', verbose=False)
        100 loops, best of 3: 19.4 ms per loop
        100 loops, best of 3: 14.4 ms per loop
        100 loops, best of 3: 3.92 ms per loop
        100 loops, best of 3: 6.22 ms per loop

    Notes:
        pip install mpi4py

def save_hdf5(fpath, data, verbose=None, compression='lzf'):
    r"""
    Restricted save of data using hdf5. Can only save ndarrays and dicts of
    ndarrays.

    Args:
        fpath (str):
        data (ndarray):
        compression (str):
            DEFLATE/GZIP - standard
            LZF  - fast
            SHUFFLE - compression ratio
            FLETCHER32 - error detection
            Scale-offset - integer / float scaling and truncation
            SZIP - fast and patented

    CommandLine:
        python -m utool.util_io --test-save_hdf5

    References:
        http://docs.h5py.org/en/latest/quick.html
        http://docs.h5py.org/en/latest/mpi.html

    Example:
        >>> # DISABLE_DOCTEST
        >>> from utool.util_io import *  # NOQA
        >>> import numpy as np
        >>> import utool as ut
        >>> rng = np.random.RandomState(0)
        >>> data = (rng.rand(100000, 128) * 255).astype(np.uint8).copy()
        >>> verbose = True
        >>> fpath = ut.unixjoin(ut.ensure_app_resource_dir('utool'), 'myfile.hdf5')
        >>> compression = 'lzf'
        >>> ut.delete(fpath)
        >>> save_hdf5(fpath, data, verbose, compression)
        >>> data2 = load_hdf5(fpath, verbose)
        >>> assert data is not data2
        >>> assert np.all(data == data2)
        >>> assert ut.delete(fpath)

    Example:
        >>> # DISABLE_DOCTEST
        >>> from utool.util_io import *  # NOQA
        >>> import numpy as np
        >>> import utool as ut
        >>> rng = np.random.RandomState(0)
        >>> data = {'name': 'foobar', 'x': [1, 2, 3], 'y': np.array([3, 2, 1])}
        >>> ut.exec_funckw(save_hdf5, globals())
        >>> fpath = ut.unixjoin(ut.ensure_app_resource_dir('utool'), 'myfile2.hdf5')
        >>> ut.delete(fpath)
        >>> save_hdf5(fpath, data, verbose, compression)
        >>> data2 = load_hdf5(fpath, verbose)
        >>> assert data is not data2
        >>> assert all([np.all(data[key] == data2[key]) for key in data.keys()])
        >>> assert ut.delete(fpath)

    Timeit:
        >>> # DISABLE_DOCTEST
        >>> # cPkl / numpy seems to be faster with this initial implementation
        >>> import utool as ut
        >>> data = (rng.rand(1000000, 128) * 255).astype(np.uint8).copy()
        >>> print(ut.get_object_size_str(data))
        >>> del data
        >>> setup = ut.codeblock(
        >>>     '''
                import numpy as np
                import utool as ut
                rng = np.random.RandomState(0)
                fpath = ut.unixjoin(ut.ensure_app_resource_dir('utool'), 'io_test_data')
                data = (rng.rand(1000000, 128) * 255).astype(np.uint8).copy()
                #print(ut.get_object_size_str(data))
                ''')
        >>> # Test load time
        >>> stmt_list1 = ut.codeblock(
        >>>     '''
                ut.save_hdf5(fpath + '.hdf5', data, verbose=False, compression='gzip')
                ut.save_hdf5(fpath + '.hdf5', data, verbose=False, compression='lzf')
                ut.save_cPkl(fpath + '.cPkl', data, verbose=False)
                ut.save_numpy(fpath + '.npy', data, verbose=False)
                ut.save_pytables(fpath + '.tables', data, verbose=False)
                ''').split('\n')
        >>> ut.util_dev.timeit_compare(stmt_list1, setup, int(10))
        >>> # Test save time
        >>> stmt_list2 = ut.codeblock(
        >>>     '''
                ut.load_hdf5(fpath + '.hdf5', verbose=False)
                ut.load_cPkl(fpath + '.cPkl', verbose=False)
                ut.load_numpy(fpath + '.npy', verbose=False)
                ut.load_pytables(fpath + '.tables', verbose=False)
                ''').split('\n')
        >>> ut.util_dev.timeit_compare(stmt_list2, setup, int(10))
        >>> print('finished timeing')
        +----------------
        | TIMEIT COMPARE
        +----------------
        | iterations = 10
        | Input:
        |     | num | stmt
        |     |   0 | u"ut.save_hdf5(fpath + '.hdf5', data, verbose=False, compression='gzip')"
        |     |   1 | u"ut.save_hdf5(fpath + '.hdf5', data, verbose=False, compression='lzf')"
        |     |   2 | u"ut.save_cPkl(fpath + '.cPkl', data, verbose=False)"
        |     |   3 | u"ut.save_numpy(fpath + '.npy', data, verbose=False)"
        |     |   4 | u"ut.save_pytables(fpath + '.tables', data, verbose=False)"
        ...
        | Output:
        |    * PASSED: each statement produced the same result
        |    | num | total time | per loop | stmt
        |    |   0 |    0.03 ks |   3.15 s | ut.save_hdf5(fpath + '.hdf5', data, verbose=False, compression='gzip')
        |    |   1 |    0.01 ks |   1.25 s | ut.save_hdf5(fpath + '.hdf5', data, verbose=False, compression='lzf')
        |    |   2 |     5.30 s |   0.53 s | ut.save_cPkl(fpath + '.cPkl', data, verbose=False)
        |    |   3 |     4.97 s |   0.50 s | ut.save_numpy(fpath + '.npy', data, verbose=False)
        |    |   4 |     9.23 s |   0.92 s | ut.save_pytables(fpath + '.tables', data, verbose=False)
        L_________________
        +----------------
        | TIMEIT COMPARE
        +----------------
        | iterations = 10
        | Input:
        |     | num | stmt
        |     |   0 | u"ut.load_hdf5(fpath + '.hdf5', verbose=False)"
        |     |   1 | u"ut.load_cPkl(fpath + '.cPkl', verbose=False)"
        |     |   2 | u"ut.load_numpy(fpath + '.npy', verbose=False)"
        |     |   3 | u"ut.load_pytables(fpath + '.tables', verbose=False)"
        ...
        | Output:
        |    * PASSED: each statement produced the same result
        |    | num | total time | per loop | stmt
        |    |   0 |     2.39 s |   0.24 s | ut.load_hdf5(fpath + '.hdf5', verbose=False)
        |    |   1 |     0.39 s |   0.04 s | ut.load_cPkl(fpath + '.cPkl', verbose=False)
        |    |   2 |     0.19 s |   0.02 s | ut.load_numpy(fpath + '.npy', verbose=False)
        |    |   3 |     0.33 s |   0.03 s | ut.load_pytables(fpath + '.tables', verbose=False)
        L_________________

    Ignore:
        %timeit save_hdf5(fpath, data, verbose=False, compression='gzip')
        %timeit save_hdf5(fpath, data, verbose=False, compression='lzf')
        %timeit save_cPkl(fpath + '.cPkl', data, verbose=False)
        %timeit save_pytables(fpath + '.tables', data, verbose=False)
        1 loops, best of 3: 258 ms per loop
        10 loops, best of 3: 111 ms per loop
        10 loops, best of 3: 53.1 ms per loop
        10 loops, best of 3: 96.5 ms per loop

        save_hdf5(fpath, data, verbose=False, compression='gzip')
        %timeit load_hdf5(fpath, verbose=False)
        save_hdf5(fpath, data, verbose=False, compression='lzf')
        %timeit load_hdf5(fpath, verbose=False)
        %timeit load_cPkl(fpath + '.cPkl', verbose=False)
        %timeit load_pytables(fpath + '.tables', verbose=False)
        100 loops, best of 3: 19.4 ms per loop
        100 loops, best of 3: 14.4 ms per loop
        100 loops, best of 3: 3.92 ms per loop
        100 loops, best of 3: 6.22 ms per loop

    Notes:
        pip install mpi4py
    """
    import h5py

    verbose = _rectify_verb_write(verbose)
    if verbose:
        print('[util_io] * save_hdf5(%r, data)' % (util_path.tail(fpath),))
    if verbose > 1:
        if isinstance(data, dict):
            print('[util_io] ... shapes=%r' % ([val.shape for val in data.values()],))
        else:
            print('[util_io] ... shape=%r' % (data.shape,))

    chunks = True  # True enables auto-chunking
    fname = basename(fpath)

    # check for parallel hdf5
    #have_mpi = h5py.h5.get_config().mpi
    #if have_mpi:
    #    import mpi4py
    #    h5kw = dict(driver='mpio', comm=mpi4py.MPI.COMM_WORLD)
    #    # cant use compression with mpi
    #    #ValueError: Unable to create dataset (Parallel i/o does not support filters yet)
    #else:
    h5kw = {}

    if isinstance(data, dict):
        array_data = {key: val for key, val in data.items()
                      if isinstance(val, (list, np.ndarray))}
        attr_data = {key: val for key, val in data.items() if key not in array_data}

        #assert all([
        #    isinstance(vals, np.ndarray)
        #    for vals in six.itervalues(data)
        #]), ('can only save dicts as ndarrays')
        # file_ = h5py.File(fpath, 'w', **h5kw)
        with h5py.File(fpath, mode='w', **h5kw) as file_:
            grp = file_.create_group(fname)
            for key, val in six.iteritems(array_data):
                val = np.asarray(val)
                dset = grp.create_dataset(
                    key, val.shape,  val.dtype, chunks=chunks,
                    compression=compression)
                dset[...] = val
            for key, val in six.iteritems(attr_data):
                grp.attrs[key] = val
    else:
        assert isinstance(data, np.ndarray)
        shape = data.shape
        dtype = data.dtype
        #if verbose or (verbose is None and __PRINT_WRITES__):
        #    print('[util_io] * save_hdf5(%r, data)' % (util_path.tail(fpath),))
        # file_ = h5py.File(fpath, 'w', **h5kw)
        with h5py.File(fpath, mode='w', **h5kw) as file_:
            #file_.create_dataset(
            #    fname, shape,  dtype, chunks=chunks, compression=compression,
            #    data=data)
            dset = file_.create_dataset(
                fname, shape,  dtype, chunks=chunks, compression=compression)
            dset[...] = data