Coverage for /Users/Newville/Codes/xraylarch/larch/io/nexus_xas.py: 7%

198 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-11-09 10:08 -0600

1import numpy as np 

2import h5py 

3from larch.io import read_xdi 

4from larch.utils.strutils import bytes2str 

5from larch.math.utils import safe_log 

6from larch.utils.physical_constants import (STD_LATTICE_CONSTANTS, 

7 DEG2RAD, PLANCK_HC) 

8NXXAS_URL = 'https://download.nexusformat.org/doc/html/classes/applications/NXxas.html' 

9 

10def parse_mono_reflection(refl): 

11 refl = refl.replace(',', ' ').strip() 

12 if refl.startswith('(') and refl.endswith(')'): 

13 refl = refl[1:-1] 

14 if len(refl) == 3: 

15 return tuple([int(refl[0]), int(refl[1]), int(refl[2])]) 

16 return tuple([int(r) for r in refl.split()]) 

17 

18 

19NXXAS_LINKS = {'element': 'scan/xrayedge/element', 

20 'edge': 'scan/xrayedge/edge', 

21 'energy': 'instrument/monochromator/energy', 

22 'rawdata': 'scan/data', 

23 'column_labels': '/scan/column_labels', 

24 'i0': 'instrument/i0/data'} 

25 

26 

27def xdi2NXxas(xdidata, h5root, name='entry', compress=None): 

28 """add XDI data to an NXxas group in a NeXuS HDF5 file: 

29 

30 Arguments 

31 ----------- 

32 xdidata an XDI data group as read with read_xdi() 

33 h5root hdf5 group to add new NXxas group to 

34 name str, name of group under h5root ['entry'] 

35 compress dict, options for compression of array datasets [None] 

36 

37 

38 Notes 

39 ------ 

40 1 the default compress dict is {'compression': 'gzip', 'compression_opts': 4} 

41 """ 

42 if compress is None: 

43 compress = {'compression': 'gzip', 'compression_opts': 4} 

44 

45 if name in h5root: 

46 base = name[:] 

47 for i in range(1, 100000): 

48 name = f'{base}_{i:02d}' 

49 if name not in h5root: 

50 break 

51 entry_name = name 

52 xas = h5root.create_group(entry_name) 

53 xas.attrs['NX_class'] = 'NXentry' 

54 

55 xdi_scan = xdidata.attrs.get('scan', {}) 

56 xdi_mono = xdidata.attrs.get('mono', {}) 

57 xdi_dets = xdidata.attrs.get('detectpr', {}) 

58 xdi_sample = xdidata.attrs.get('sample', {}) 

59 xdi_bline = xdidata.attrs.get('beamline', {}) 

60 xdi_facil = xdidata.attrs.get('facility', {}) 

61 

62 start_time = xdi_scan.get('start_time', '') 

63 

64 title = xdi_sample.get('name', None) 

65 if title is None: 

66 title = xdidata.filename 

67 title = f'{title} [{xdidata.element} {xdidata.edge} edge]' 

68 

69 xas.create_dataset('title', data=title) 

70 s =xas.create_dataset('start_time', data=start_time) 

71 s.attrs['NX_class'] = 'NX_DATE_TIME' 

72 s = xas.create_dataset('definition', data='NXxas') 

73 s.attrs['URL'] = NXXAS_URL 

74 

75 # instrument 

76 instrument = xas.create_group('instrument') 

77 instrument.attrs['NX_class'] = 'NXinstrument' 

78 

79 # instrument/source 

80 isource = instrument.create_group('source') 

81 isource.attrs['NX_class'] = 'NXsource' 

82 sname = [xdi_facil.get('name', 'unknown facility'), 

83 xdi_facil.get('xray_source', 'unknkown source'), 

84 xdi_bline.get('name', 'unknown beamline')] 

85 isource.create_dataset('name', data=', '.join(sname)) 

86 

87 source_energy = xdi_facil.get('energy', '0 Unknown') 

88 try: 

89 s_en, units = source_energy.split() 

90 source_energy = float(s_en) 

91 except: 

92 source_energy, units = 0, 'Unknown' 

93 s = isource.create_dataset('energy', data=source_energy) 

94 s.attrs['units'] = units 

95 isource.create_dataset('type', data='X-ray Source') 

96 isource.create_dataset('probe', data='X-ray') 

97 for key, val in xdi_facil.items(): 

98 isource.create_dataset(f'facility_{key}', data=val) 

99 for key, val in xdi_bline.items(): 

100 isource.create_dataset(f'beamline_{key}', data=val) 

101 

102 # instrument/mono 

103 imono = instrument.create_group('monochromator') 

104 imono.attrs['NX_class'] = 'NXmonochromator' 

105 

106 # instrument/mono/crystal 

107 imonoxtal = imono.create_group('crystal') 

108 imonoxtal.attrs['NX_class'] = 'NXcrystal' 

109 mono_name = xdi_mono.get('name', 'Si (111)') 

110 try: 

111 mono_chem, mono_refl = mono_name.split() 

112 except: 

113 mono_chem, mono_refl = 'Si', '111' 

114 mono_chem = mono_chem.title() 

115 mono_refl = parse_mono_reflection(mono_refl) 

116 mono_dspacing = xdi_mono.get('d_spacing', None) 

117 if mono_dspacing is None: 

118 mono_dspacing = 0.0 

119 if mono_chem in STD_LATTICE_CONSTANTS: 

120 latt_c = STD_LATTICE_CONSTANTS[mono_chem] 

121 hkl2 = mono_refl[0]**2 + mono_refl[1]**2 + mono_refl[2]**2 

122 mono_dspacing = latt_c / np.sqrt(hkl2) 

123 else: 

124 mono_dspacing = float(mono_dspacing) 

125 

126 if not hasattr(xdidata, 'energy') and hasattr(xdidata, 'angle'): 

127 omega = PLANCK_HC/(2*mono_dspacing) 

128 xdidata.energy = omega/np.sin(xdidata.angle*DEG2RAD) 

129 en_units = 'eV' 

130 else: 

131 ien = xdidata.array_labels.index('energy') 

132 en_units = 'eV' 

133 if ien > -1: 

134 en_units = xdidata.array_units[ien] 

135 

136 s = imono.create_dataset('energy', data=xdidata.energy, **compress) 

137 s.attrs['units'] = en_units 

138 if hasattr(xdidata, 'angle'): 

139 s = imono.create_dataset('angle', data=xdidata.angle, **compress) 

140 s.attrs['units'] = 'degrees' 

141 

142 

143 imonoxtal.create_dataset('chemical_formula', data=mono_chem) 

144 imonoxtal.create_dataset('reflection', data=mono_refl) 

145 s = imonoxtal.create_dataset('d_spacing', data=mono_dspacing) 

146 s.attrs['units'] = 'Angstroms' 

147 

148 # instrument/i0 

149 idet = instrument.create_group('i0') 

150 idet.attrs['NX_class'] = 'NXdetector' 

151 idet.create_dataset('data', data=xdidata.i0, **compress) 

152 desc = xdi_dets.get('i0', None) 

153 if desc is None: 

154 desc = xdi_dets.get('monitor', None) 

155 if desc is not None: 

156 idet.create_dataset('description', data=desc) 

157 

158 # instrument/itrans 

159 if hasattr(xdidata, 'itrans'): 

160 idet = instrument.create_group('itrans') 

161 idet.attrs['NX_class'] = 'NXdetector' 

162 idet.create_dataset('data', data=xdidata.itrans, **compress) 

163 desc = xdi_dets.get('itrans', None) 

164 if desc is None: 

165 desc = xdi_dets.get('i1', None) 

166 if desc is not None: 

167 idet.create_dataset('description', data=desc) 

168 

169 # instrument/ifluor 

170 if hasattr(xdidata, 'ifluor'): 

171 idet = instrument.create_group('ifluor') 

172 idet.attrs['NX_class'] = 'NXdetector' 

173 

174 idet.create_dataset('data', data=xdidata.ifluor, **compress) 

175 desc = xdi_dets.get('ifluor', None) 

176 if desc is None: 

177 desc = xdi_dets.get('if', None) 

178 if desc is not None: 

179 idet.create_dataset('description', data=desc) 

180 mode = xdi_dets.get('fluor_mode', 'Unknown') 

181 idet.create_dataset('mode', data=mode) 

182 

183 # instrument/irefer 

184 if hasattr(xdidata, 'irefer'): 

185 idet = instrument.create_group('irefer') 

186 idet.attrs['NX_class'] = 'NXdetector' 

187 

188 idet.create_dataset('data', data=xdidata.irefer, **compress) 

189 desc = xdi_dets.get('irefer', None) 

190 if desc is None: 

191 desc = xdi_dets.get('ir', None) 

192 if desc is not None: 

193 idet.create_dataset('description', data=desc) 

194 refmode = xdi_dets.get('refer_mode', 'Unknown') 

195 idet.create_dataset('mode', data=mode) 

196 

197 # sample 

198 sample = xas.create_group('sample') 

199 sample.attrs['NX_class'] = 'NXsample' 

200 for key, val in xdi_sample.items(): 

201 sample.create_dataset(key, data=val) 

202 

203 # scan 

204 scan = xas.create_group('scan') 

205 scan.attrs['NX_class'] = 'NXscan' 

206 for key, val in xdi_scan.items(): 

207 scan.create_dataset(key, data=val) 

208 

209 ncol, nrow = xdidata.data.shape 

210 scan.create_dataset('nP', data=nrow) 

211 scan.create_dataset('nCol', data=ncol) 

212 

213 xedge = scan.create_group('xrayedge') 

214 xedge.attrs['NX_class'] = 'NXxrayedge' 

215 xedge.create_dataset('element', data=xdidata.element) 

216 xedge.create_dataset('edge', data=xdidata.edge) 

217 

218 scan.create_dataset('scan_mode', data=xdi_scan.get('mode', 'Unknown')) 

219 scan.create_dataset('data', data=xdidata.data.transpose(), **compress) 

220 scan.create_dataset('column_labels', data=xdidata.array_labels) 

221 

222 # data arrays: mostly links to the data above 

223 dat = xas.create_group('data') 

224 dat.attrs['NX_class'] = 'NXdata' 

225 mode = 'Transmission' 

226 if not hasattr(xdidata, 'itrans') and hasattr(xdidata, 'ifluor'): 

227 mode = 'Fluorescence' 

228 dat.create_dataset('mode', data=mode) 

229 

230 slinks = {k: v for k,v in NXXAS_LINKS.items()} 

231 

232 if hasattr(xdidata, 'itrans'): 

233 slinks['itrans'] = 'instrument/itrans/data' 

234 mutrans = -safe_log(xdidata.itrans/xdidata.i0) 

235 dat.create_dataset('mutrans', data=mutrans, **compress) 

236 

237 if hasattr(xdidata, 'ifluor'): 

238 slink['ifluor'] = 'instrument/ifluor/data' 

239 mufluor = xdidata.ifluor/xdidata.i0 

240 dat.create_dataset('mufluor', data=mufluor, **compress) 

241 

242 if hasattr(xdidata, 'irefer'): 

243 slink['irefer'] = 'instrument/irefer/data' 

244 if refmode.startswith('Fluo'): 

245 muref = xdidata.irefer/xdidata.i0 

246 else: 

247 muref = -safe_log(xdidata.irefer/xdidata.itrans) 

248 dat.create_dataset('murefer', data=murefer, **compress) 

249 

250 for dest, source in slinks.items(): 

251 dat[dest] = h5py.SoftLink(f'/{entry_name}/{source}') 

252 

253class NXxasFile(object): 

254 """ 

255 NeXuS NXxas data file 

256 """ 

257 def __init__(self, filename): 

258 self.filename = filename 

259 self.xas_groups = None 

260 if filename is not None: 

261 self.read(filename) 

262 

263 def read(self, filename): 

264 self.root = h5py.File(filename, 'r') 

265 self.xas_groups = [] 

266 for key, grp in self.root.items(): 

267 attrs = grp.attrs.items() 

268 if grp.attrs.get('NX_class', None) != 'NXentry': 

269 continue 

270 defn = grp.get('definition', None) 

271 if isinstance(defn, h5py.Dataset): 

272 if bytes2str(defn[()]) == 'NXxas': 

273 self.xas_groups.append(key) 

274 

275 def add_xdidata(xdidata, name='entry'): 

276 """add Entry/Group for data from an XDI file""" 

277 xdi2NXxas(xdidata, self.root, name=name)