From 1948a475d67a7aa8f982fbf9e92e6e0caf907f76 Mon Sep 17 00:00:00 2001 From: mkuehbach Date: Thu, 18 Jan 2024 15:01:12 +0100 Subject: [PATCH] Added support for y, x, energy, and energy EDS spectra, all content from FHI C. Rohner's example is parsed successfully, next steps: i) add Velox metadata schema version, ii) add microscope metadata, iii) merge PRs --- .../readers/em/subparsers/rsciio_velox.py | 62 +++++++++++++++++++ .../readers/em/utils/rsciio_hyperspy_utils.py | 15 ++++- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/pynxtools/dataconverter/readers/em/subparsers/rsciio_velox.py b/pynxtools/dataconverter/readers/em/subparsers/rsciio_velox.py index 0ae4006f5..005d38c98 100644 --- a/pynxtools/dataconverter/readers/em/subparsers/rsciio_velox.py +++ b/pynxtools/dataconverter/readers/em/subparsers/rsciio_velox.py @@ -105,6 +105,8 @@ def tech_partner_to_nexus_normalization(self, template: dict) -> dict: self.normalize_diff_content(obj, template) # diffraction images elif content_type == "eds_map": self.normalize_eds_map_content(obj, template) # ED(X)S in the TEM + elif content_type == "eds_spc": + self.normalize_eds_spc_content(obj, template) # EDS spectrum/(a) elif content_type == "eels": self.normalize_eels_content(obj, template) # electron energy loss spectroscopy else: # == "n/a" @@ -148,6 +150,7 @@ def content_resolver(self, obj: dict) -> str: # all units indicating we are in real or complex i.e. reciprocal space if meta["General/title"] in ("EDS"): return "eds_spc" + # applies to multiple cases, sum spectrum, spectrum stack etc. for symbol in chemical_symbols[1::]: # an eds_map # TODO::does rosettasciio via hyperspy identify the symbol or is the @@ -297,6 +300,65 @@ def normalize_diff_content(self, obj: dict, template: dict) -> dict: self.id_mgn["event"] += 1 return template + def normalize_eds_spc_content(self, obj: dict, template: dict) -> dict: + """Map relevant EDS spectrum/(a) to NeXus.""" + meta = fd.FlatDict(obj["metadata"], "/") + dims = get_axes_dims(obj["axes"]) + n_dims = None + if dims == [('Energy', 0)]: + n_dims = 1 + elif dims == [('y', 0), ('x', 1), ('X-ray energy', 2)]: + n_dims = 3 + else: + print(f"WARNING eds_spc for {dims} is not implemented!") + return + trg = f"/ENTRY[entry{self.entry_id}]/measurement/event_data_em_set/" \ + f"EVENT_DATA_EM[event_data_em{self.id_mgn['event']}]/" \ + f"SPECTRUM_SET[spectrum_set{self.id_mgn['event_spc']}]" + template[f"{trg}/source"] = meta["General/title"] + template[f"{trg}/PROCESS[process]/source/type"] = "file" + template[f"{trg}/PROCESS[process]/source/path"] = self.file_path + template[f"{trg}/PROCESS[process]/source/checksum"] = self.file_path_sha256 + template[f"{trg}/PROCESS[process]/source/algorithm"] = "SHA256" + template[f"{trg}/PROCESS[process]/detector_identifier"] \ + = f"Check carefully how rsciio/hyperspy knows this {meta['General/title']}!" + trg = f"/ENTRY[entry{self.entry_id}]/measurement/event_data_em_set/" \ + f"EVENT_DATA_EM[event_data_em{self.id_mgn['event']}]/" \ + f"SPECTRUM_SET[spectrum_set{self.id_mgn['event_spc']}]" \ + f"DATA[spectrum_zerod]" + template[f"{trg}/@NX_class"] = "NXdata" # TODO::should be autodecorated + template[f"{trg}/@signal"] = "intensity" + if n_dims == 1: + template[f"{trg}/@axes"] = ["axis_energy"] + template[f"{trg}/@AXISNAME_indices[axis_energy_indices]"] = np.uint32(0) + support, unit = get_named_axis(obj["axes"], "Energy") + template[f"{trg}/AXISNAME[axis_energy]"] \ + = {"compress": support, "strength": 1} + template[f"{trg}/AXISNAME[axis_energy]/@long_name"] \ + = f"Energy ({unit})" + if n_dims == 3: + template[f"{trg}/@axes"] = ["axis_y", "axis_x", "axis_energy"] + template[f"{trg}/@AXISNAME_indices[axis_y_indices]"] = np.uint32(2) + template[f"{trg}/@AXISNAME_indices[axis_x_indices]"] = np.uint32(1) + template[f"{trg}/@AXISNAME_indices[axis_energy_indices]"] = np.uint32(0) + support, unit = get_named_axis(obj["axes"], "y") + template[f"{trg}/AXISNAME[axis_y]"] = {"compress": support, "strength": 1} + template[f"{trg}/AXISNAME[axis_y]/@long_name"] = f"y-axis position ({unit})" + support, unit = get_named_axis(obj["axes"], "x") + template[f"{trg}/AXISNAME[axis_x]"] = {"compress": support, "strength": 1} + template[f"{trg}/AXISNAME[axis_x]/@long_name"] = f"x-axis position ({unit})" + support, unit = get_named_axis(obj["axes"], "X-ray energy") + template[f"{trg}/AXISNAME[axis_energy]"] = {"compress": support, "strength": 1} + template[f"{trg}/AXISNAME[axis_energy]/@long_name"] = f"Energy ({unit})" + # template[f"{trg}/description"] = "" + template[f"{trg}/title"] = f"EDS spectrum {meta['General/title']}" + template[f"{trg}/intensity"] \ + = {"compress": np.asarray(obj["data"]), "strength": 1} + # template[f"{trg}/intensity/@long_name"] = "" + self.id_mgn["event_spc"] += 1 + self.id_mgn["event"] += 1 + return template + def normalize_eds_map_content(self, obj: dict, template: dict) -> dict: """Map relevant EDS map to NeXus.""" meta = fd.FlatDict(obj["metadata"], "/") diff --git a/pynxtools/dataconverter/readers/em/utils/rsciio_hyperspy_utils.py b/pynxtools/dataconverter/readers/em/utils/rsciio_hyperspy_utils.py index a0d9b356e..7bd8f4e0b 100644 --- a/pynxtools/dataconverter/readers/em/utils/rsciio_hyperspy_utils.py +++ b/pynxtools/dataconverter/readers/em/utils/rsciio_hyperspy_utils.py @@ -28,7 +28,9 @@ def get_named_axis(axes_metadata, dim_name): if isinstance(axis, dict): if ("name" in axis): if axis["name"] == dim_name: - reqs = ["index_in_array", "offset", "scale", "size", "units", "navigate"] # "name" + reqs = ["offset", "scale", "size", "units"] + # "index_in_array" and "navigate" are currently not required + # and ignored but might become important for req in reqs: if req not in axis: raise ValueError(f"{req} not in {axis}!") @@ -48,8 +50,15 @@ def get_axes_dims(axes_metadata): if len(axes_metadata) >= 1: for axis in axes_metadata: if isinstance(axis, dict): - if ("name" in axis) and ("index_in_array" in axis): - retval.append((axis["name"], axis["index_in_array"])) + if ("name" in axis): + if "index_in_array" in axis: + retval.append((axis["name"], axis["index_in_array"])) + else: + if len(axes_metadata) == 1: + retval.append((axis["name"], 0)) + else: + raise ValueError(f"get_axes_dims {axes_metadata} " \ + f"is a case not implemented!") # TODO::it seems that hyperspy sorts this by index_in_array return retval