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
56from __future__ import division , print_function
67import os
7- import sys
8+
89try :
910 import h5py
1011except ImportError :
1112 h5py = None
1213import numpy as np
1314from .. import GSASIIobj as G2obj
1415from .. 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
3517class 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)
0 commit comments