docs: 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