10
10
from tavi .data .spice_reader import _create_spicelogs , read_spice_datafile
11
11
12
12
13
+ def _find_val (val , grp , prefix = "" ):
14
+ """Find value in hdf5 groups"""
15
+ for obj_name , obj in grp .items ():
16
+ if obj_name in ("SPICElogs" , "data" ):
17
+ continue
18
+ else :
19
+ path = f"{ prefix } /{ obj_name } "
20
+ if val == obj_name :
21
+ return path
22
+ # test for group (go down)
23
+ elif isinstance (obj , h5py .Group ):
24
+ gpath = _find_val (val , obj , path )
25
+ if gpath :
26
+ return gpath
27
+
28
+
29
+ def _recast_type (ds , dtype ):
30
+ if type (ds ) is str :
31
+ if "," in ds : # vector
32
+ if dtype == "NX_FLOAT" :
33
+ dataset = np .array ([float (d ) for d in ds .split ("," )])
34
+ else :
35
+ dataset = np .array ([int (d ) for d in ds .split ("," )])
36
+ elif ds .replace ("." , "" ).isnumeric (): # numebrs only
37
+ if dtype == "NX_FLOAT" :
38
+ dataset = float (ds )
39
+ else :
40
+ dataset = int (ds )
41
+ else : # expect np.ndarray
42
+ if dtype == "NX_FLOAT" :
43
+ dataset = np .array ([float (d ) for d in ds ])
44
+ else :
45
+ dataset = np .array ([int (d ) for d in ds ])
46
+ return dataset
47
+
48
+
13
49
class NXdataset (dict ):
14
50
"""Dataset in a format consistent with NeXus, containg attrs and dataset"""
15
51
@@ -24,10 +60,10 @@ def __init__(self, ds, **kwargs):
24
60
match kwargs :
25
61
case {"type" : "NX_CHAR" }:
26
62
dataset = str (ds )
27
- case {"type" : "NX_INT" }:
28
- dataset = np . array ([ int ( d ) for d in ds ])
29
- case {"type" : "NX_FLOAT " }:
30
- dataset = np . array ([ float ( d ) for d in ds ] )
63
+ case {"type" : "NX_INT" } | { "type" : "NX_FLOAT" } :
64
+ dataset = _recast_type ( ds , kwargs [ "type" ])
65
+ case {"type" : "NX_DATE_TIME " }:
66
+ dataset = datetime . strptime ( ds , "%m/%d/%Y %I:%M:%S %p" ). isoformat ( )
31
67
case _:
32
68
dataset = ds
33
69
@@ -64,13 +100,36 @@ def add_dataset(self, key: str, ds: NXdataset):
64
100
else :
65
101
self .update ({key : ds })
66
102
103
+ def add_attribute (self , key : str , attr ):
104
+ if not attr : # ignore if empty
105
+ pass
106
+ else :
107
+ self ["attrs" ].update ({key : attr })
108
+
67
109
110
+ def _formatted_spicelogs (spicelogs : dict ) -> NXentry :
111
+ """Format SPICE logs into NeXus dict"""
112
+ formatted_spicelogs = NXentry (NX_class = "NXcollection" , EX_required = "false" )
113
+ metadata = spicelogs .pop ("metadata" )
114
+ for attr_key , attr_entry in metadata .items ():
115
+ formatted_spicelogs .add_attribute (attr_key , attr_entry )
116
+
117
+ for entry_key , entry_data in spicelogs .items ():
118
+ formatted_spicelogs .add_dataset (key = entry_key , ds = NXdataset (ds = entry_data ))
119
+ return formatted_spicelogs
120
+
121
+
122
+ # TODO json support
68
123
def spice_scan_to_nxdict (
69
124
path_to_scan_file : str ,
70
125
path_to_instrument_json : Optional [str ] = None ,
71
126
path_to_sample_json : Optional [str ] = None ,
72
127
) -> NXentry :
73
- """Format SPICE data in a nested dictionary format"""
128
+ """Format SPICE data in a nested dictionary format
129
+
130
+ Note:
131
+ json files can overwrite the parameters in SPICE
132
+ """
74
133
75
134
# parse instruemnt and sample json files
76
135
instrument_config_params = None
@@ -92,17 +151,20 @@ def spice_scan_to_nxdict(
92
151
spicelogs = _create_spicelogs (path_to_scan_file )
93
152
metadata = spicelogs ["metadata" ]
94
153
154
+ # ---------------------------------------- source ----------------------------------------
155
+
95
156
nxsource = NXentry (
96
157
name = NXdataset (ds = "HFIR" , type = "NX_CHAR" , EX_required = "true" ),
97
158
probe = NXdataset (ds = "neutron" , type = "NX_CHAR" , EX_required = "true" ),
98
159
NX_class = "NXsource" ,
99
160
EX_required = "true" ,
100
161
)
162
+ # ------------------------------------- monochromator ----------------------------------------
101
163
102
164
nxmono = NXentry (
103
165
ei = NXdataset (ds = spicelogs .get ("ei" ), type = "NX_FLOAT" , EX_required = "true" , units = "meV" ),
104
166
type = NXdataset (ds = metadata .get ("monochromator" ), type = "NX_CHAR" ),
105
- sense = NXdataset (ds = metadata . get ["sense" ][0 ], type = "NX_CHAR" ),
167
+ sense = NXdataset (ds = metadata ["sense" ][0 ], type = "NX_CHAR" ),
106
168
m1 = NXdataset (ds = spicelogs .get ("m1" ), type = "NX_FLOAT" , units = "degrees" ),
107
169
m2 = NXdataset (ds = spicelogs .get ("m2" ), type = "NX_FLOAT" , units = "degrees" ),
108
170
NX_class = "NXcrystal" ,
@@ -113,6 +175,8 @@ def spice_scan_to_nxdict(
113
175
nxmono .add_dataset ("mtrans" , NXdataset (ds = spicelogs .get ("mtrans" ), type = "NX_FLOAT" ))
114
176
nxmono .add_dataset ("focal_length" , NXdataset (ds = spicelogs .get ("focal_length" ), type = "NX_FLOAT" ))
115
177
178
+ # ------------------------------------- analyzer ----------------------------------------
179
+
116
180
nxana = NXentry (
117
181
ef = NXdataset (ds = spicelogs .get ("ef" ), type = "NX_FLOAT" , EX_required = "true" , units = "meV" ),
118
182
type = NXdataset (ds = metadata .get ("analyzer" ), type = "NX_CHAR" ),
@@ -123,25 +187,60 @@ def spice_scan_to_nxdict(
123
187
NX_class = "NXcrystal" ,
124
188
EX_required = "true" ,
125
189
)
126
- for i in range (8 ):
190
+ for i in range (8 ): # CG4C horizontal focusing
127
191
nxana .add_dataset (key = f"qm{ i + 1 } " , ds = NXdataset (ds = spicelogs .get (f"qm{ i + 1 } " ), type = "NX_FLOAT" ))
128
192
nxana .add_dataset (key = f"xm{ i + 1 } " , ds = NXdataset (ds = spicelogs .get (f"xm{ i + 1 } " ), type = "NX_FLOAT" ))
129
193
194
+ # ------------------------------------- detector ----------------------------------------
195
+
130
196
nxdet = NXentry (
131
197
data = NXdataset (ds = spicelogs .get ("detector" ), type = "NX_INT" , EX_required = "true" , units = "counts" ),
132
198
NX_class = "NXdetector" ,
133
199
EX_required = "true" ,
134
200
)
135
201
202
+ # ------------------------------------- collimators ----------------------------------------
203
+
204
+ div_x = [float (v ) for v in list (metadata ["collimation" ].split ("-" ))]
205
+ nxcoll = NXentry (
206
+ type = NXdataset (ds = "Soller" , type = "NX_CHAR" ),
207
+ NX_class = "NXcollimator" ,
208
+ )
209
+ nxcoll .add_dataset (key = "divergence_x" , ds = NXdataset (ds = div_x , type = "NX_ANGLE" , units = "minutes of arc" ))
210
+
211
+ # ------------------------------------- slits ----------------------------------------
212
+
213
+ nxslits = NXentry (NX_class = "NXslit" )
214
+ slits_str1 = tuple ([f"b{ idx } { loc } " for idx in ("a" , "b" ) for loc in ("t" , "b" , "l" , "r" )])
215
+ slits_str2 = tuple ([f"slit{ idx } _{ loc } " for idx in ("a" , "b" ) for loc in ("lf" , "rt" , "tp" , "bt" )])
216
+ slits_str3 = tuple ([f"slit_{ idx } _{ loc } " for idx in ("pre" ,) for loc in ("lf" , "rt" , "tp" , "bt" )])
217
+ slits_str = (slits_str1 , slits_str2 , slits_str3 )
218
+ for slit_str in slits_str :
219
+ for st in slit_str :
220
+ nxslits .add_dataset (key = st , ds = NXdataset (ds = spicelogs .get (st ), type = "NX_FLOAT" , units = "cm" ))
221
+ # ------------------------------------- flipper ----------------------------------------
222
+
223
+ nxflipper = NXentry (NX_class = "NXflipper" )
224
+ nxflipper .add_dataset (key = "fguide" , ds = NXdataset (ds = spicelogs .get ("fguide" ), type = "NX_FLOAT" ))
225
+ nxflipper .add_dataset (key = "hguide" , ds = NXdataset (ds = spicelogs .get ("hguide" ), type = "NX_FLOAT" ))
226
+ nxflipper .add_dataset (key = "vguide" , ds = NXdataset (ds = spicelogs .get ("vguide" ), type = "NX_FLOAT" ))
227
+
228
+ # ---------------------------------------- instrument ---------------------------------------------
229
+
136
230
nxinstrument = NXentry (
137
231
source = nxsource ,
138
232
monochromator = nxmono ,
233
+ collimator = nxcoll ,
139
234
analyzer = nxana ,
140
235
detector = nxdet ,
236
+ slits = nxslits ,
237
+ flipper = nxflipper ,
141
238
name = NXdataset (ds = metadata .get ("instrument" ), type = "NX_CHAR" ),
142
239
NX_class = "NXinstrument" ,
143
240
EX_required = "true" ,
144
241
)
242
+ # ---------------------------------------- monitor ---------------------------------------------
243
+
145
244
preset_type = metadata .get ("preset_type" )
146
245
if preset_type == "normal" :
147
246
preset_channel = metadata .get ("preset_channel" )
@@ -156,6 +255,7 @@ def spice_scan_to_nxdict(
156
255
NX_class = "NXmonitor" ,
157
256
EX_required = "true" ,
158
257
)
258
+
159
259
# TODO polarized exp at HB1
160
260
elif preset_type == "countfile" :
161
261
print ("Polarization data, not yet supported." )
@@ -164,34 +264,79 @@ def spice_scan_to_nxdict(
164
264
print (f"Unrecogonized preset type { preset_type } ." )
165
265
nxmonitor = NXentry (NX_class = "NXmonitor" , EX_required = "true" )
166
266
167
- # ------------------------------------------------------------------
168
- nxsample = NXentry (NX_class = "NXsample" , EX_required = "true" )
267
+ # ---------------------------------------- sample ---------------------------------------------
268
+
269
+ nxsample = NXentry (
270
+ name = NXdataset (ds = metadata .get ("samplename" ), type = "NX_CHAR" , EX_required = "true" ),
271
+ type = NXdataset (ds = metadata .get ("sampletype" ), type = "NX_CHAR" , EX_required = "true" ),
272
+ unit_cell = NXdataset (ds = metadata .get ("latticeconstants" ), type = "NX_FLOAT" , EX_required = "true" ),
273
+ qh = NXdataset (ds = spicelogs .get ("h" ), type = "NX_FLOAT" , EX_required = "true" ),
274
+ qk = NXdataset (ds = spicelogs .get ("k" ), type = "NX_FLOAT" , EX_required = "true" ),
275
+ ql = NXdataset (ds = spicelogs .get ("l" ), type = "NX_FLOAT" , EX_required = "true" ),
276
+ en = NXdataset (ds = spicelogs .get ("e" ), type = "NX_FLOAT" , EX_required = "true" , units = "meV" ),
277
+ q = NXdataset (ds = spicelogs .get ("q" ), type = "NX_FLOAT" ),
278
+ sense = NXdataset (ds = metadata ["sense" ][1 ], type = "NX_CHAR" ),
279
+ NX_class = "NXsample" ,
280
+ EX_required = "true" ,
281
+ )
282
+ nxsample .add_dataset (key = "Pt." , ds = NXdataset (ds = spicelogs .get ("Pt." ), type = "NX_INT" ))
283
+
284
+ # Motor angles
285
+ nxsample .add_dataset (key = "s1" , ds = NXdataset (ds = spicelogs .get ("s1" ), type = "NX_FLOAT" , units = "degrees" ))
286
+ nxsample .add_dataset (key = "s2" , ds = NXdataset (ds = spicelogs .get ("s2" ), type = "NX_FLOAT" , units = "degrees" ))
287
+ nxsample .add_dataset (key = "sgu" , ds = NXdataset (ds = spicelogs .get ("sgu" ), type = "NX_FLOAT" , units = "degrees" ))
288
+ nxsample .add_dataset (key = "sgl" , ds = NXdataset (ds = spicelogs .get ("sgl" ), type = "NX_FLOAT" , units = "degrees" ))
289
+ nxsample .add_dataset (key = "stu" , ds = NXdataset (ds = spicelogs .get ("stu" ), type = "NX_FLOAT" , units = "degrees" ))
290
+ nxsample .add_dataset (key = "stl" , ds = NXdataset (ds = spicelogs .get ("stl" ), type = "NX_FLOAT" , units = "degrees" ))
291
+
292
+ # UB info
293
+ nxsample .add_dataset (
294
+ key = "orientation_matrix" ,
295
+ ds = NXdataset (ds = metadata .get ("ubmatrix" ), type = "NX_FLOAT" , EX_required = "true" , units = "NX_DIMENSIONLESS" ),
296
+ )
297
+ nxsample .add_dataset (key = "ub_conf" , ds = NXdataset (ds = metadata ["ubconf" ].split ("." )[0 ], type = "NX_CHAR" ))
298
+ nxsample .add_dataset (key = "plane_normal" , ds = NXdataset (ds = metadata .get ("plane_normal" ), type = "NX_FLOAT" ))
299
+
300
+ # ---------------------------------------- sample environment ---------------------------------------------
169
301
170
302
# TODO all sample environment variable names needed!
171
303
temperatue_str = (
172
304
("temp" , "temp_a" , "temp_2" , "coldtip" , "tsample" , "sample" )
173
305
+ ("vti" , "dr_tsample" , "dr_temp" )
174
306
+ ("lt" , "ht" , "sorb_temp" , "sorb" , "sample_ht" )
175
307
)
308
+ for te in temperatue_str :
309
+ nxsample .add_dataset (key = te , ds = NXdataset (ds = spicelogs .get (te ), type = "NX_FLOAT" , units = "Kelvin" ))
176
310
177
311
field_str = ("persistent_field" , "mag_i" )
312
+ for fi in field_str :
313
+ nxsample .add_dataset (key = fi , ds = NXdataset (ds = spicelogs .get (fi ), type = "NX_FLOAT" , units = "Tesla" ))
314
+ # ---------------------------------------- data ---------------------------------------------
315
+ nexus_keywork_conversion_dict = {"h" : "qh" , "k" : "qk" , "l" : "ql" , "e" : "en" }
316
+ def_x = metadata .get ("def_x" )
317
+ def_y = metadata .get ("def_y" )
318
+ if def_x in nexus_keywork_conversion_dict :
319
+ def_x = nexus_keywork_conversion_dict [def_x ]
320
+
321
+ nxdata = NXentry (NX_class = "NXdata" , EX_required = "true" , signal = def_y , axes = def_x )
322
+ # ---------------------------------------- scan ---------------------------------------------
178
323
179
324
# TODO timezone
180
325
start_date_time = "{} {}" .format (metadata .get ("date" ), metadata .get ("time" ))
181
- start_time = datetime .strptime (start_date_time , "%m/%d/%Y %I:%M:%S %p" ).isoformat ()
182
326
# TODO what is last scan never finished?
183
327
# if "end_time" in das_logs.attrs:
184
328
end_date_time = metadata .get ("end_time" )
185
- end_time = datetime .strptime (end_date_time , "%m/%d/%Y %I:%M:%S %p" ).isoformat ()
186
329
187
330
nxscan = NXentry (
188
- SPICElogs = spicelogs ,
331
+ SPICElogs = _formatted_spicelogs (spicelogs ),
332
+ data = nxdata ,
189
333
definition = NXdataset (ds = "NXtas" , type = "NX_CHAR" , EX_required = "true" ),
190
334
title = NXdataset (ds = metadata .get ("scan_title" ), type = "NX_CHAR" , EX_required = "true" ),
191
- start_time = NXdataset (ds = start_time , type = "NX_DATE_TIME" , EX_required = "true" ),
192
- end_time = NXdataset (ds = end_time , type = "NX_DATE_TIME" ),
335
+ start_time = NXdataset (ds = start_date_time , type = "NX_DATE_TIME" , EX_required = "true" ),
336
+ end_time = NXdataset (ds = end_date_time , type = "NX_DATE_TIME" ),
193
337
instrument = nxinstrument ,
194
338
monitor = nxmonitor ,
339
+ sample = nxsample ,
195
340
NX_class = "NXentry" ,
196
341
EX_required = "true" ,
197
342
)
0 commit comments