Skip to content

Commit 526882e

Browse files
author
Bing Li
committed
implementing nxdict.py
1 parent 70b6551 commit 526882e

8 files changed

+416
-69
lines changed

src/tavi/data/nxdict.py

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
from datetime import datetime
2+
from typing import Optional
3+
4+
5+
def nxsource(spicelogs, instrument_config_params):
6+
source = {
7+
"attrs": {"NX_class": "NXsource", "EX_required": "true"},
8+
"name": {
9+
"attrs": {"type": "NX_CHAR", "EX_required": "true"},
10+
"dataset": "HFIR",
11+
},
12+
"probe": {
13+
"attrs": {"type": "NX_CHAR", "EX_required": "true"},
14+
"dataset": "neutron",
15+
},
16+
}
17+
# Effective distance from sample Distance as seen by radiation from sample.
18+
# This number should be negative to signify that it is upstream of the sample.
19+
# nxsource.attrs["distance"] = -0.0
20+
return source
21+
22+
23+
def nxmono(spicelogs, instrument_config_params):
24+
metadata = spicelogs["attrs"]
25+
26+
try:
27+
mono = {
28+
"attrs": {"NX_class": "NXcrystal", "EX_required": "true"},
29+
"ei": {
30+
"dataset": spicelogs["ei"],
31+
"attrs": {"type": "NX_FLOAT", "EX_required": "true", "units": "meV"},
32+
},
33+
"type": {"dataset": metadata["monochromator"], "attrs": {"type": "NX_CHAR"}},
34+
"sense": {"dataset": metadata["sense"][0], "attrs": {"type": "NX_CHAR"}},
35+
"m1": {
36+
"dataset": spicelogs["m1"],
37+
"attrs": {"type": "NX_FLOAT", "units": "degrees"},
38+
},
39+
"m2": {
40+
"dataset": spicelogs["m2"],
41+
"attrs": {"type": "NX_FLOAT", "units": "degrees"},
42+
},
43+
"mfocus": {"dataset": spicelogs["mfocus"], "attrs": {"type": "NX_FLOAT"}},
44+
"marc": {"dataset": spicelogs["marc"], "attrs": {"type": "NX_FLOAT"}},
45+
"mtrans": {"dataset": spicelogs["mtrans"], "attrs": {"type": "NX_FLOAT"}},
46+
"focal_length": {"dataset": spicelogs["focal_length"], "attrs": {"type": "NX_FLOAT"}},
47+
}
48+
except KeyError:
49+
pass
50+
return mono
51+
52+
53+
def nxcoll(spicelogs, instrument_config_params):
54+
return {}
55+
56+
57+
def nxana(spicelogs, instrument_config_params):
58+
return {}
59+
60+
61+
def nxdet(spicelogs, instrument_config_params):
62+
return {}
63+
64+
65+
def nxmonitor(spicelogs, instrument_config_params):
66+
return {}
67+
68+
69+
def nxsample(spicelogs, sample_config_params):
70+
return {}
71+
72+
73+
def nxslit(spicelogs, instrument_config_params):
74+
return {}
75+
76+
77+
def nxflipper(spicelogs, instrument_config_params):
78+
return {}
79+
80+
81+
def _spicelogs_to_nexus(spicelogs, instrument_config_params, sample_config_params):
82+
metadata = spicelogs["attrs"]
83+
# TODO timezone
84+
start_date_time = "{} {}".format(metadata["date"], metadata["time"])
85+
start_time = datetime.strptime(start_date_time, "%m/%d/%Y %I:%M:%S %p").isoformat()
86+
# if "end_time" in das_logs.attrs: # last scan never finished
87+
end_date_time = metadata["end_time"]
88+
end_time = datetime.strptime(end_date_time, "%m/%d/%Y %I:%M:%S %p").isoformat()
89+
90+
scan_dict = {
91+
"attrs": {
92+
"NX_class": "NXentry",
93+
"EX_required": "true",
94+
},
95+
"SPICElogs": spicelogs,
96+
"definition": {
97+
"attrs": {"EX_required": "true", "type": "NX_CHAR"},
98+
"dataset": "NXtas",
99+
},
100+
"title": {
101+
"attrs": {"EX_required": "true", "type": "NX_CHAR"},
102+
"dataset": metadata["scan_title"],
103+
},
104+
"start_time": {
105+
"attrs": {"EX_required": "true", "type": "NX_DATE_TIME"},
106+
"dataset": start_time,
107+
},
108+
"end_time": {
109+
"attrs": {"type": "NX_DATE_TIME"},
110+
"dataset": end_time,
111+
},
112+
"instrument": {
113+
"attrs": {"EX_required": "true", "NX_class": "NXinstrument"},
114+
"name": {"attrs": {"type": "NX_CHAR"}, "dataset": metadata["instrument"]},
115+
"source": nxsource(spicelogs, instrument_config_params),
116+
"collimator": nxcoll(spicelogs, instrument_config_params),
117+
"monochromator": nxmono(spicelogs, instrument_config_params),
118+
"analyser": nxana(spicelogs, instrument_config_params),
119+
"detector": nxdet(spicelogs, instrument_config_params),
120+
"slit": nxslit(spicelogs, instrument_config_params),
121+
"flipper": nxflipper(spicelogs, instrument_config_params),
122+
},
123+
"monitor": nxmonitor(spicelogs, instrument_config_params),
124+
"sample": nxsample(spicelogs, sample_config_params),
125+
"data": {},
126+
}
127+
return scan_dict
128+
129+
130+
def daslogs_to_nexus_dict(
131+
daslogs: dict,
132+
instrument_config_params: Optional[str],
133+
sample_config_params: Optional[str],
134+
) -> dict:
135+
"""Format DASlogs dict into NeXus dict"""
136+
137+
match (das_key := tuple(daslogs.keys())[0]):
138+
case "SPICElogs":
139+
scan_dict = _spicelogs_to_nexus(
140+
daslogs["SPICElogs"],
141+
instrument_config_params,
142+
sample_config_params,
143+
)
144+
case _:
145+
raise KeyError(f"Unrecogonized DASlogs key {das_key}.")
146+
147+
return scan_dict

src/tavi/data/nxentry.py

+51-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1+
from typing import Optional
2+
13
import h5py
24

5+
from tavi.data.spice_reader import spice_data_reader
6+
37

48
class NexusEntry(dict):
59
"""Read and write NeXus data files.
@@ -58,14 +62,14 @@ def _write_recursively(items, nexus_entry):
5862
dv = value["dataset"]
5963
if isinstance(dv, str):
6064
dv = dv.encode("utf-8")
61-
ds = nexus_entry.create_dataset(
62-
name=key,
63-
data=dv,
64-
maxshape=None,
65-
)
65+
if key in nexus_entry:
66+
ds = nexus_entry[key]
67+
ds[...] = dv
68+
else:
69+
ds = nexus_entry.create_dataset(name=key, data=dv, maxshape=None)
6670
NexusEntry._write_recursively(value, ds)
6771
else:
68-
grp = nexus_entry.create_group(key + "/")
72+
grp = nexus_entry.require_group(key + "/")
6973
NexusEntry._write_recursively(value, grp)
7074

7175
@staticmethod
@@ -93,42 +97,72 @@ def _read_recursively(nexus_entry, items=None):
9397

9498
return items
9599

100+
@staticmethod
101+
def _dict_to_nexus_entry(nexus_dict):
102+
"""convert nested dict to instances of the NexusEntry class"""
103+
nexus_entries = {}
104+
for scan_num, scan_content in nexus_dict.items():
105+
content_list = []
106+
for key, val in scan_content.items():
107+
content_list.append((key, val))
108+
nexus_entries.update({scan_num: NexusEntry(content_list)})
109+
return nexus_entries
110+
111+
# TODO read in instrument and sample configuratio json files
96112
@classmethod
97-
def from_spice(cls, path_to_spice_folder: str) -> dict:
113+
def from_spice(
114+
cls,
115+
path_to_spice_folder: str,
116+
scan_num: Optional[int] = None,
117+
path_to_instrument_json: Optional[str] = None,
118+
path_to_sample_json: Optional[str] = None,
119+
) -> dict:
98120
"""return a NexusEntry instance from loading a SPICE file
99121
100122
Args:
101123
path_to_spice_folder (str): path to a SPICE folder
124+
scan_num (int): read all scans in folder if not None
125+
path_to_instrument_json: Optional[str] = None,
126+
path_to_sample_json: Optional[str] = None,
102127
"""
128+
# TODO validate path
103129

104-
nexus_dict = {}
105-
return cls([(key, val) for key, val in nexus_dict.items()])
130+
nexus_dict = spice_data_reader(
131+
path_to_spice_folder,
132+
scan_num,
133+
path_to_instrument_json,
134+
path_to_sample_json,
135+
)
136+
137+
return NexusEntry._dict_to_nexus_entry(nexus_dict)
106138

107139
@classmethod
108-
def from_nexus(cls, path_to_nexus: str) -> dict:
140+
def from_nexus(cls, path_to_nexus: str, scan_num: Optional[int] = None) -> dict:
109141
"""return a NexusEntry instance from loading a NeXus file
110142
111143
Args:
112144
path_to_nexus (str): path to a NeXus file with the extension .h5
145+
scan_num (int): read all scans in file if not None
113146
"""
114147
with h5py.File(path_to_nexus, "r") as nexus_file:
115-
nexus_dict = NexusEntry._read_recursively(nexus_file)
116-
return cls([(key, val) for key, val in nexus_dict.items()])
148+
if scan_num is None: # read all scans in file
149+
nexus_dict = NexusEntry._read_recursively(nexus_file)
150+
else: # read one scan only
151+
key = f"scan{scan_num:04}"
152+
nexus_dict = {key: NexusEntry._read_recursively(nexus_file[key])}
153+
154+
return NexusEntry._dict_to_nexus_entry(nexus_dict)
117155

118-
# TODO
119156
def to_nexus(self, path_to_nexus: str, name="scan") -> None:
120157
"""write a NexueEntry instance to a NeXus file
121158
122159
Args:
123160
path_to_nexus (str): path to a NeXus file with the extention .h5
124161
"""
125162
with h5py.File(path_to_nexus, "a") as nexus_file:
126-
# TODO what if a group alreay exsit??
127-
pass
128-
scan_grp = nexus_file.create_group(name + "/")
163+
scan_grp = nexus_file.require_group(name + "/")
129164
NexusEntry._write_recursively(self, scan_grp)
130165

131-
# TODO need to catch errors when multiple IPTS are loaded in a tavi file
132166
def get(self, key, ATTRS=False, default=None):
133167
"""
134168
Return dataset spicified by key regardless of the hierarchy.

src/tavi/data/scan_old/spice_to_nexus.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def _nexus_mono(nxmono, das_logs, instrument_config_params=None):
216216
nxmono["ei"].attrs["units"] = "meV"
217217

218218
nxmono.create_dataset(name="type", data=das_logs.attrs["monochromator"], maxshape=None)
219-
nxmono.attrs["type"] = "NX_CHAR"
219+
nxmono["type"].attrs["type"] = "NX_CHAR"
220220

221221
nxmono.create_dataset(name="m1", data=das_logs["m1"], maxshape=None)
222222
nxmono["m1"].attrs["type"] = "NX_FLOAT"

0 commit comments

Comments
 (0)