2017-01-07 23:53:12 +01:00
|
|
|
""" npz/hdf5 file based storage;
|
|
|
|
this modules adds the possibility to dump and load objects in files and
|
|
|
|
a more convenient was of accessing the data via the .attributedict thanks
|
|
|
|
to the DataStorage class """
|
2017-01-05 19:22:37 +01:00
|
|
|
import numpy as np
|
|
|
|
import os
|
|
|
|
import h5py
|
|
|
|
import collections
|
|
|
|
|
2017-01-06 15:40:26 +01:00
|
|
|
import logging as log
|
|
|
|
log.basicConfig(level=log.INFO)
|
2017-01-05 19:22:37 +01:00
|
|
|
|
2017-01-07 23:53:12 +01:00
|
|
|
def unwrapArray(a,recursive=True,readH5pyDataset=True):
|
|
|
|
""" This function takes an object (like a dictionary) and recursivively
|
|
|
|
unwraps it solving many issues like the fact that many objects are
|
|
|
|
packaged as 0d array
|
|
|
|
This funciton has also some specific hack for handling h5py limit to
|
|
|
|
handle for example the None object or the numpy unicode ...
|
|
|
|
"""
|
|
|
|
# is h5py dataset convert to array
|
|
|
|
if isinstance(a,h5py.Dataset) and readH5pyDataset: a = a[...]
|
|
|
|
if isinstance(a,h5py.Dataset) and a.shape == (): a = a[...]
|
|
|
|
if isinstance(a,np.ndarray) and a.ndim == 0 : a = a.item()
|
|
|
|
if isinstance(a,np.ndarray) and a.dtype.char == "S": a = a.astype(str)
|
|
|
|
if recursive:
|
|
|
|
if "items" in dir(a): # dict, h5py groups, npz file
|
|
|
|
a = dict(a); # convert to dict, otherwise can't asssign values
|
|
|
|
for key,value in a.items(): a[key] = unwrapArray(value)
|
|
|
|
elif isinstance(a,list):
|
|
|
|
for index in range(len(a)): a[index] = unwrapArray(a[i])
|
|
|
|
else:
|
|
|
|
pass
|
|
|
|
if isinstance(a,dict): a = DataStorage(a)
|
|
|
|
# restore None that cannot be saved in h5py
|
|
|
|
if isinstance(a,str) and a == "NONE_PYTHON_OBJECT": a = None
|
|
|
|
# h5py can't save numpy unicode
|
|
|
|
if isinstance(a,np.ndarray) and a.dtype.char == "S": a = a.astype(str)
|
|
|
|
return a
|
|
|
|
|
2017-01-05 19:22:37 +01:00
|
|
|
def dictToH5Group(d,group):
|
2017-01-07 23:53:12 +01:00
|
|
|
""" helper function that transform (recursive) a dictionary into an
|
|
|
|
hdf group by creating subgroups """
|
2017-01-05 19:22:37 +01:00
|
|
|
for key,value in d.items():
|
2017-01-07 23:53:12 +01:00
|
|
|
if isinstance(value,dict):
|
|
|
|
group.create_group(key)
|
|
|
|
dictToH5Group(value,group[key])
|
|
|
|
else:
|
2017-01-05 19:22:37 +01:00
|
|
|
# h5py can't handle numpy unicode arrays
|
|
|
|
if isinstance(value,np.ndarray) and value.dtype.char == "U":
|
|
|
|
value = np.asarray([vv.encode('ascii') for vv in value])
|
|
|
|
# h5py can't save None
|
|
|
|
if value is None: value="NONE_PYTHON_OBJECT"
|
2017-01-06 15:40:26 +01:00
|
|
|
try:
|
|
|
|
group[key] = value
|
|
|
|
except TypeError:
|
|
|
|
log.error("Can't save %s"%(key))
|
2017-01-05 19:22:37 +01:00
|
|
|
|
|
|
|
def dictToH5(h5,d):
|
2017-01-06 15:40:26 +01:00
|
|
|
""" Save a dictionary into an hdf5 file
|
|
|
|
h5py is not capable of handling dictionaries natively"""
|
2017-01-05 19:22:37 +01:00
|
|
|
h5 = h5py.File(h5,mode="w")
|
|
|
|
# group = h5.create_group("/")
|
|
|
|
dictToH5Group(d,h5["/"])
|
|
|
|
h5.close()
|
|
|
|
|
2017-01-07 23:53:12 +01:00
|
|
|
def h5ToDict(h5,readH5pyDataset=True):
|
2017-01-06 15:40:26 +01:00
|
|
|
""" Read a hdf5 file into a dictionary """
|
2017-01-05 19:22:37 +01:00
|
|
|
with h5py.File(h5,"r") as h:
|
2017-01-07 23:53:12 +01:00
|
|
|
ret = unwrapArray(h,recursive=True,readH5pyDataset=readH5pyDataset)
|
2017-01-05 19:22:37 +01:00
|
|
|
return ret
|
|
|
|
|
|
|
|
def npzToDict(npzFile):
|
|
|
|
with np.load(npzFile) as npz: d = dict(npz)
|
2017-01-07 23:53:12 +01:00
|
|
|
d = unwrapArray(d,recursive=True)
|
2017-01-05 19:22:37 +01:00
|
|
|
return d
|
|
|
|
|
|
|
|
def dictToNpz(npzFile,d): np.savez(npzFile,**d)
|
|
|
|
|
|
|
|
def read(fname):
|
|
|
|
extension = os.path.splitext(fname)[1]
|
2017-01-06 18:06:34 +01:00
|
|
|
log.info("Reading storage file %s"%fname)
|
2017-01-05 19:22:37 +01:00
|
|
|
if extension == ".npz":
|
|
|
|
return npzToDict(fname)
|
|
|
|
elif extension == ".h5":
|
|
|
|
return h5ToDict(fname)
|
|
|
|
else:
|
|
|
|
raise ValueError("Extension must be h5 or npz, it was %s"%extension)
|
|
|
|
|
|
|
|
def save(fname,d):
|
|
|
|
extension = os.path.splitext(fname)[1]
|
2017-01-06 18:06:34 +01:00
|
|
|
log.info("Saving storage file %s"%fname)
|
2017-01-05 19:22:37 +01:00
|
|
|
if extension == ".npz":
|
|
|
|
return dictToNpz(fname,d)
|
|
|
|
elif extension == ".h5":
|
|
|
|
return dictToH5(fname,d)
|
|
|
|
else:
|
|
|
|
raise ValueError("Extension must be h5 or npz")
|
|
|
|
|
2017-01-07 23:53:12 +01:00
|
|
|
class DataStorage(dict):
|
|
|
|
""" Storage for 1d integrated info """
|
|
|
|
def __init__(self,fileOrDict,default_name='pyfai_1d',default_ext='npz'):
|
|
|
|
if isinstance(fileOrDict,dict):
|
|
|
|
self.filename = None
|
|
|
|
d = fileOrDict
|
|
|
|
else:
|
|
|
|
assert isinstance(fileOrDict,str)
|
|
|
|
if os.path.isdir(fileOrDict):
|
|
|
|
fileOrDict = fileOrDict + "/" + default_name + "." + default_ext
|
|
|
|
self.filename = fileOrDict
|
|
|
|
d = read(fileOrDict)
|
|
|
|
|
|
|
|
# allow accessing with .data, .delays, etc.
|
|
|
|
for k,v in d.items(): setattr(self,k,v)
|
|
|
|
|
|
|
|
# allow accessing as proper dict
|
|
|
|
self.update( **dict(d) )
|
|
|
|
|
|
|
|
def __setitem__(self, key, value):
|
|
|
|
setattr(self,key,value)
|
|
|
|
super().__setitem__(key, value)
|
|
|
|
|
|
|
|
def __delitem__(self, key):
|
|
|
|
delattr(self,key)
|
|
|
|
super().__delitem__(key)
|
|
|
|
|
|
|
|
def save(self,fname=None):
|
|
|
|
if fname is None: fname = self.filename
|
|
|
|
assert fname is not None
|
|
|
|
save(fname,dict(self))
|