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
« 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'
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()])
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'}
27def xdi2NXxas(xdidata, h5root, name='entry', compress=None):
28 """add XDI data to an NXxas group in a NeXuS HDF5 file:
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]
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}
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'
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', {})
62 start_time = xdi_scan.get('start_time', '')
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]'
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
75 # instrument
76 instrument = xas.create_group('instrument')
77 instrument.attrs['NX_class'] = 'NXinstrument'
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))
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)
102 # instrument/mono
103 imono = instrument.create_group('monochromator')
104 imono.attrs['NX_class'] = 'NXmonochromator'
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)
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]
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'
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'
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)
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)
169 # instrument/ifluor
170 if hasattr(xdidata, 'ifluor'):
171 idet = instrument.create_group('ifluor')
172 idet.attrs['NX_class'] = 'NXdetector'
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)
183 # instrument/irefer
184 if hasattr(xdidata, 'irefer'):
185 idet = instrument.create_group('irefer')
186 idet.attrs['NX_class'] = 'NXdetector'
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)
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)
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)
209 ncol, nrow = xdidata.data.shape
210 scan.create_dataset('nP', data=nrow)
211 scan.create_dataset('nCol', data=ncol)
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)
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)
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)
230 slinks = {k: v for k,v in NXXAS_LINKS.items()}
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)
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)
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)
250 for dest, source in slinks.items():
251 dat[dest] = h5py.SoftLink(f'/{entry_name}/{source}')
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)
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)
275 def add_xdidata(xdidata, name='entry'):
276 """add Entry/Group for data from an XDI file"""
277 xdi2NXxas(xdidata, self.root, name=name)