Skip to content

Commit c7ff0e4

Browse files
committed
rework HDF5 importers for MaxIV
1 parent 6e0318e commit c7ff0e4

File tree

5 files changed

+108
-109
lines changed

5 files changed

+108
-109
lines changed

GSASII/GSASIIdataGUI.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1616,7 +1616,7 @@ def GetDefaultParms(self,rd):
16161616
else:
16171617
rd.instmsg = 'default: '+dI.defaultIparm_lbl[res]
16181618
inst1,inst2 = self.ReadPowderInstprm(dI.defaultIparms[res],bank,rd)
1619-
if rd.instdict.get('wave'):
1619+
if rd.instdict.get('wave') and 'Lam' in inst1:
16201620
inst1['Lam'][0] = rd.instdict.get('wave')
16211621
inst1['Lam'][1] = rd.instdict.get('wave')
16221622
return [inst1,inst2]

GSASII/imports/G2img_HDF5.py

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,17 @@ def Reader(self, filename, ParentFrame=None, **kwarg):
5454
return False
5555
imagenum = kwarg.get('blocknum')
5656
if imagenum is None: imagenum = 1
57+
quick = False
5758
# do we have a image number or a map to the section with the image?
5859
try:
59-
int(imagenum)
60-
# set up an index as to where images are found
61-
self.buffer = kwarg.get('buffer',{})
62-
if not self.buffer.get('imagemap'):
60+
int(imagenum) # test if image # is a tuple
61+
except: # pull the section name and number out from the imagenum value
62+
kwargs = {'name':imagenum[0],'num':imagenum[1]}
63+
quick = True
64+
# set up an index as to where images are found
65+
self.buffer = kwarg.get('buffer',{})
66+
if not quick and not self.buffer.get('imagemap'):
67+
try:
6368
if GSASIIpath.GetConfigValue('debug'): print('Scanning for image map')
6469
self.buffer['imagemap'] = []
6570
self.Comments = self.visit(fp)
@@ -93,9 +98,13 @@ def Reader(self, filename, ParentFrame=None, **kwarg):
9398
self.errors = 'No images selected from file'
9499
fp.close()
95100
return False
101+
except Exception as msg:
102+
print(f'Error mapping file:\n{msg}')
103+
return False
104+
if not quick:
96105
self.buffer['selectedImages'] = self.buffer.get('selectedImages',
97106
list(range(len(self.buffer['imagemap']))))
98-
# get the first selected image
107+
# get the next selected image
99108
while imagenum <= len(self.buffer['imagemap']):
100109
if imagenum-1 in self.buffer['selectedImages']:
101110
del self.buffer['selectedImages'][self.buffer['selectedImages'].index(imagenum-1)]
@@ -107,11 +116,6 @@ def Reader(self, filename, ParentFrame=None, **kwarg):
107116
fp.close()
108117
return False
109118
kwargs = {'imagenum':imagenum}
110-
quick = False
111-
except:
112-
kwargs = {'name':imagenum[0],'num':imagenum[1]}
113-
quick = True
114-
# we have been passed a map to images
115119
self.Data,self.Npix,self.Image = self.readDataset(fp,**kwargs)
116120
if quick:
117121
fp.close()
@@ -153,19 +157,26 @@ def func(name, dset):
153157
if not hasattr(dset,'shape'): return # not array, can't be image
154158
if isinstance(dset, h5py.Dataset):
155159
dims = dset.shape
156-
if len(dims) < 2:
157-
head.append('%s: %s'%(dset.name,str(dset[()][0])))
158-
elif len(dims) == 4:
159-
size = dims[2:]
160-
self.buffer['imagemap'] += [(dset.name,i,size) for i in range(dims[1])]
161-
elif len(dims) == 3:
162-
size = dims[1:]
163-
self.buffer['imagemap'] += [(dset.name,i,size) for i in range(dims[0])]
164-
elif len(dims) == 2:
165-
size = dims
166-
self.buffer['imagemap'] += [(dset.name,None,size)]
167-
else:
168-
print('Skipping entry '+str(dset.name)+'. Shape is '+str(dims))
160+
try:
161+
if len(dims) == 0:
162+
val = dset[()]
163+
if type(val) is bytes: val = val.decode()
164+
head.append(f'{dset.name}: {val}')
165+
elif len(dims) < 2:
166+
head.append(f'{dset.name}: {dset[()][0]}')
167+
elif len(dims) == 4:
168+
size = dims[2:]
169+
self.buffer['imagemap'] += [(dset.name,i,size) for i in range(dims[1])]
170+
elif len(dims) == 3:
171+
size = dims[1:]
172+
self.buffer['imagemap'] += [(dset.name,i,size) for i in range(dims[0])]
173+
elif len(dims) == 2:
174+
size = dims
175+
self.buffer['imagemap'] += [(dset.name,None,size)]
176+
else:
177+
print(f'Skipping entry {dset.name}. Shape is {dims}')
178+
except Exception as msg:
179+
print(f'Skipping entry {dset.name} Error getting shape\n{msg}')
169180
fp.visititems(func)
170181
return head
171182

GSASII/imports/G2pwd_HDF5.py

Lines changed: 71 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,18 @@
11
# -*- coding: utf-8 -*-
2-
'''
2+
'''Use to read powder patterns from HDF5 files. At present the only supported
3+
format is a NeXus variant named NXazint1d.
34
'''
45

56
from __future__ import division, print_function
67
import os
7-
import sys
8+
89
try:
910
import h5py
1011
except ImportError:
1112
h5py = None
1213
import numpy as np
1314
from .. import GSASIIobj as G2obj
1415
from .. import GSASIIfiles as G2fil
15-
#from .. import GSASIIpath
16-
17-
# things to do:
18-
# uncertainties
19-
# instr. parms
20-
#instprmList = [('Bank',1.0), ('Lam',0.413263), ('Polariz.',0.99),
21-
# ('SH/L',0.002), ('Type','PXC'), ('U',1.163), ('V',-0.126),
22-
# ('W',0.063), ('X',0.0), ('Y',0.0), ('Z',0.0), ('Zero',0.0)]
23-
# comments
24-
# dataset naming
25-
# sample parameters
26-
#sampleprmList = [('InstrName','APS 1-ID'), ('Temperature', 295.0)]
27-
# 'Scale': [1.0, True], 'Type': 'Debye-Scherrer',
28-
# 'Absorption': [0.0, False], 'DisplaceX': [0.0, False], 'DisplaceY': [0.0, False]# 'Pressure': 0.1, 'Time': 0.0, 'FreePrm1': 0.0,
29-
# 'FreePrm2': 0.0, 'FreePrm3': 0.0, 'Gonio. radius': 200.0, 'Omega': 0.0,
30-
# 'Chi': 0.0, 'Phi': 180.0, 'Azimuth': 0.0,
31-
# 'Materials': [{'Name': 'vacuum', 'VolFrac': 1.0}, {'Name': 'vacuum', 'VolFrac': 0.0}],
32-
# 'Thick': 1.0, 'Contrast': [0.0, 0.0], 'Trans': 1.0, 'SlitLen': 0.0}
33-
3416

3517
class HDF5_Reader(G2obj.ImportPowderData):
3618
'''Routine to read multiple powder patterns from an HDF5 file.
@@ -52,13 +34,14 @@ def __init__(self):
5234
G2fil.ImportErrorMsg(msg,{'HDF5 importer':['h5py','hdf5']})
5335
super(self.__class__,self).__init__( # fancy way to self-reference
5436
extensionlist=('.hdf','.h5'),strictExtension=True,
55-
formatName = 'MAX IV HDF5',longFormatName = 'HDF5 integrated scans')
37+
formatName = 'MAX IV HDF5',longFormatName = 'MaxIV NXazint1d HDF5 integrated scans')
5638
self.scriptable = True
5739
#self.Iparm = {} #only filled for EDS data
5840

5941
def ShowH5Element(self,obj,keylist):
6042
'''Format the contents of an HDF5 entry as a single line. Not used for
61-
reading files, only used in :meth:`HDF5list`
43+
reading files, only used in :meth:`HDF5list` which is here for software
44+
development.
6245
'''
6346
k = '/'.join(keylist)
6447
l = obj.get(k, getlink=True)
@@ -89,16 +72,18 @@ def ShowH5Element(self,obj,keylist):
8972
else:
9073
return f'type is {type(obj[k])}'
9174

92-
def RecurseH5Element(self,obj,prefix=[]):
75+
def RecurseH5Element(self,obj,prefix=[],length=None):
9376
'''Returns a list of entries of all keys in the HDF5 file
9477
(or group) in `obj`. Note that `obj` can be a file object, created by
9578
`h5py.File` or can be a subset `fp['key/subkey']`.
79+
80+
If length is specified, only the entries with arrays of that
81+
length are returned.
9682
9783
The returned list is organized where:
9884
* entry 0 is the top-level keys (/a, /b,...),
9985
* entry 1 has the first level keys (/a/c /a/d, /b/d, /b/e,...)
10086
* ...
101-
Not used for reading files, used only in :meth:`HDF5list`
10287
'''
10388
try:
10489
self.HDF5entries
@@ -109,19 +94,27 @@ def RecurseH5Element(self,obj,prefix=[]):
10994
self.HDF5entries.append([])
11095
for i in obj:
11196
nextprefix = prefix+[i]
112-
self.HDF5entries[depth].append(nextprefix)
113-
# check for link objects
114-
l = obj.get(i, getlink=True)
115-
if isinstance(l, h5py.ExternalLink): continue
97+
if length is None:
98+
self.HDF5entries[depth].append(nextprefix)
11699
try:
117100
typ = str(type(obj[i]))
118101
except:
119102
print(f'**Error** with key {prefix}/{i}')
120103
continue
104+
if length is not None and ".Group'" not in typ:
105+
# get length of this obj[i]
106+
try:
107+
if len(obj[i]) == length:
108+
self.HDF5entries[depth].append(nextprefix)
109+
except TypeError:
110+
continue
111+
# check for link objects
112+
l = obj.get(i, getlink=True)
113+
if isinstance(l, h5py.ExternalLink): continue
121114
if ".Group'" in typ:
122115
#t = f'{prefix}/{i}'
123116
#print(f'\n{nextprefix} contents {(60-len(t))*'='}')
124-
self.RecurseH5Element(obj[i],nextprefix)
117+
self.RecurseH5Element(obj[i],nextprefix,length)
125118
return self.HDF5entries
126119

127120

@@ -158,7 +151,6 @@ def ContentsValidator(self, filename):
158151
'''Test if valid by seeing if the HDF5 library recognizes the file.
159152
Then get file type (currently MAX IV NeXus/NXazint[12]d only)
160153
'''
161-
#from .. import GSASIIpath
162154
try:
163155
fp = h5py.File(filename, 'r')
164156
if 'entry' in fp: # NeXus
@@ -168,9 +160,6 @@ def ContentsValidator(self, filename):
168160
# MAX IV NXazint1d file
169161
if fp['/entry/definition'][()].decode() == 'NXazint1d':
170162
return True
171-
# MAX IV NXazint1d file
172-
#if fp['/entry/definition'][()].decode() == 'NXazint2d':
173-
# return True
174163
except IOError:
175164
return False
176165
finally:
@@ -220,11 +209,13 @@ def readNXazint1d(self, filename, fpbuffer={}):
220209
see https://nxazint-hdf5-nexus-3229ecbd09ba8a773fbbd8beb72cace6216dfd5063e1.gitlab-pages.esrf.fr/classes/contributed_definitions/NXazint1d.html
221210
'''
222211
#self.instmsg = 'HDF file'
212+
self.comments = []
223213
doread = False # has the file already been read into a buffer?
224-
arrays = ('entry/data/radial_axis','entry/data/I')
214+
arrays = ('entry/data/radial_axis','entry/data/I','entry/data/I_errors')
225215
floats = ('entry/instrument/monochromator/wavelength',
226216
'entry/reduction/input/polarization_factor')
227-
strings = ('entry/instrument/source/name','entry/reduction/input/unit')
217+
strings = ('entry/instrument/name','entry/reduction/input/unit',
218+
'entry/sample/name','entry/instrument/source/name')
228219
for i in arrays+floats+strings:
229220
if i not in fpbuffer:
230221
doread = True
@@ -234,72 +225,68 @@ def readNXazint1d(self, filename, fpbuffer={}):
234225
fp = h5py.File(filename, 'r')
235226
for i in arrays:
236227
fpbuffer[i] = np.array(fp.get(i))
228+
self.numbanks = len(fpbuffer['entry/data/I']) # number of scans
237229
for i in floats:
238230
fpbuffer[i] = float(fp[i][()])
239231
for i in strings:
240-
fpbuffer[i] = fp[i][()].decode()
232+
try:
233+
fpbuffer[i] = fp[i][()].decode()
234+
self.comments.append(f'{i}={fpbuffer[i]}')
235+
except:
236+
fpbuffer[i] = None
241237
if fpbuffer['entry/reduction/input/unit'] != '2th':
242238
print('NXazint1d HDF5 file has units',fpbuffer['entry/reduction/input/unit'])
243239
self.errors = 'NXazint1d only can be read with 2th units'
244240
return False
241+
# save arrays that are potentially tracking the parametric conditions
242+
# e.g. variables with the same length as the humber of datasets
243+
paramItems = self.RecurseH5Element(fp,length=self.numbanks)
244+
fpbuffer['ParamTrackingVars'] = {}
245+
for i in paramItems:
246+
for j in i:
247+
key = '/'.join(j)
248+
if key in arrays: continue
249+
obj = fp.get(key)
250+
if obj is None: continue
251+
if len(obj[()].shape) != 1: continue
252+
# are all values the same? If so, put them into the comments
253+
# for the first histogram. If they are changing, note that and
254+
# later they will be put into every histogram.
255+
if all(obj[0] == obj):
256+
self.comments.append(f'{key.split("/")[-1]}={obj[0]}')
257+
else:
258+
fpbuffer['ParamTrackingVars'][key] = np.array(obj[()])
245259
if self.selections is None or len(self.selections) == 0:
246260
self.blknum = 0
247261
else:
248262
self.blknum = min(self.selections)
249263
except IOError:
250-
print ('cannot open file '+ filename)
264+
print (f'Can not open or read file {filename}')
251265
return False
252266
finally:
253267
fp.close()
254-
self.numbanks=len(fpbuffer['entry/data/I'])
255-
# # get overriding sample & instrument parameters
256-
# fpbuffer['sampleprm'] = {}
257-
# samplefile = os.path.splitext(filename)[0] + '.samprm'
258-
# if os.path.exists(samplefile):
259-
# fp = open(samplefile,'r')
260-
# S = fp.readline()
261-
# while S:
262-
# if not S.strip().startswith('#'):
263-
# [item,val] = S[:-1].split(':')
264-
# fpbuffer['sampleprm'][item.strip("'")] = eval(val)
265-
# S = fp.readline()
266-
# fp.close()
267-
# fpbuffer['instprm'] = {}
268-
# instfile = os.path.splitext(filename)[0] + '.instprm'
269-
# if os.path.exists(instfile):
270-
# self.instmsg = 'HDF and .instprm files'
271-
# fp = open(instfile,'r')
272-
# S = fp.readline()
273-
# while S:
274-
# if not S.strip().startswith('#'):
275-
# [item,val] = S[:-1].split(':')
276-
# fpbuffer['instprm'][item.strip("'")] = eval(val)
277-
# S = fp.readline()
278-
# fp.close()
279-
# now transfer information into current histogram
280-
#self.pwdparms['Instrument Parameters'] = [
281-
# {'Type': ['PXC', 'PXC', False]},
282-
# {}]
283-
# inst = {}
284-
# inst.update(instprmList)
285-
# inst.update(fpbuffer['instprm'])
286-
# for key,val in inst.items():
287-
# self.pwdparms['Instrument Parameters'][0][key] = [val,val,False]
288-
# samp = {}
289-
# samp.update(sampleprmList)
290-
# samp.update(fpbuffer['sampleprm'])
291-
# for key,val in samp.items():
292-
# self.Sample[key] = val
293268
x = fpbuffer['entry/data/radial_axis']
294269
y = fpbuffer['entry/data/I'][self.blknum]
295-
w = np.nan_to_num(1/y) # this is not correct
296-
#self.pwdparms['Instrument Parameters'][0]['Azimuth'] = [90-eta,90-eta,False]
297-
#self.pwdparms['Instrument Parameters'][0]['Bank'] = [self.blknum,self.blknum,False]
298-
# self.Sample['Gonio. radius'] = float(S.split('=')[1])
299-
# self.Sample['Omega'] = float(S.split('=')[1])
300-
# self.Sample['Chi'] = float(S.split('=')[1])
301-
#self.Sample['Phi'] = Omega = fpbuffer['Omegas'][self.blknum]
270+
try:
271+
esd = fpbuffer['entry/data/I_errors'][self.blknum]
272+
w = np.where(esd==0,0,np.nan_to_num(1/esd**2))
273+
except:
274+
w = np.nan_to_num(1/y) # best we can do, alas
302275
self.powderdata = [x,y,w,np.zeros_like(x),np.zeros_like(x),np.zeros_like(x)]
276+
# add parametric var as a comment
277+
for key,arr in fpbuffer['ParamTrackingVars'].items():
278+
val = arr[self.blknum]
279+
self.comments.append(f'{key.split("/")[-1]}={val}')
280+
if 'temperature' in key:
281+
self.Sample['Temperature'] = val # in K already
282+
elif 'time' in key:
283+
self.Sample['Time'] = val # should be seconds
284+
elif 'chi' in key:
285+
self.Sample['Chi'] = val # not sure if correct mapping
286+
elif 'phi' in key:
287+
self.Sample['Phi'] = val
288+
elif 'omega' in key:
289+
self.Sample['Omega'] = val
303290
#self.comments = comments[selblk]
304291
self.powderentry[0] = filename
305292
#self.powderentry[1] = Pos # position offset (never used, I hope)

GSASII/imports/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
from . import G2pwd_CIF
2626
from . import G2pwd_FP
2727
from . import G2pwd_GPX
28-
from . import G2pwd_MIDAS
2928
from . import G2pwd_HDF5
29+
from . import G2pwd_MIDAS
3030
from . import G2pwd_Panalytical
3131
from . import G2pwd_csv
3232
from . import G2pwd_fxye

GSASII/imports/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ py.install_sources([
2626
'G2pwd_CIF.py',
2727
'G2pwd_FP.py',
2828
'G2pwd_GPX.py',
29+
'G2pwd_HDF5.py',
2930
'G2pwd_MIDAS.py',
3031
'G2pwd_Panalytical.py',
3132
'G2pwd_csv.py',

0 commit comments

Comments
 (0)