Coverage for nexusLIMS/extractors/plugins/profiles/fei_titan_tem_642.py: 100%
54 statements
« prev ^ index » next coverage.py v7.11.3, created at 2026-03-24 05:23 +0000
« prev ^ index » next coverage.py v7.11.3, created at 2026-03-24 05:23 +0000
1# ruff: noqa: ARG001
2"""Instrument profile for FEI Titan TEM (642 microscope)."""
4from __future__ import annotations
6import logging
7from typing import TYPE_CHECKING, Any
9from benedict import benedict
11from nexusLIMS.extractors.base import InstrumentProfile
12from nexusLIMS.extractors.profiles import get_profile_registry
13from nexusLIMS.utils.dicts import (
14 set_nested_dict_value,
15 try_getting_dict_value,
16)
18if TYPE_CHECKING:
19 from nexusLIMS.extractors.base import ExtractionContext
21_logger = logging.getLogger(__name__)
24def parse_tecnai_metadata(
25 metadata: dict[str, Any],
26 context: ExtractionContext,
27) -> dict[str, Any]:
28 """
29 Parse Tecnai-specific metadata from ImageTags.Tecnai.Microscope_Info.
31 The 642 Titan TEM stores extensive microscope parameters in a delimited
32 string format that needs special parsing.
34 Parameters
35 ----------
36 metadata
37 Metadata dictionary with 'nx_meta' key
38 context
39 Extraction context (unused but required by profile signature)
41 Returns
42 -------
43 dict
44 Modified metadata dictionary with parsed Tecnai metadata
45 """
46 # Import the processing function from the DM3 extractor
47 from nexusLIMS.extractors.plugins.digital_micrograph import ( # noqa: PLC0415
48 process_tecnai_microscope_info,
49 )
51 # Check if Tecnai metadata exists using benedict's keypaths method
52 b = benedict(metadata)
53 keypaths_list = b.keypaths()
55 # Find the keypath that ends with "Tecnai"
56 path_to_tecnai = None
57 for keypath in keypaths_list:
58 if keypath.endswith(".Tecnai") or keypath == "Tecnai":
59 path_to_tecnai = keypath.split(".")
60 break
62 if path_to_tecnai is None:
63 # For whatever reason, the expected Tecnai Tag is not present,
64 # so return to prevent errors below
65 return metadata
67 tecnai_value = b[".".join(path_to_tecnai)]
68 microscope_info = tecnai_value["Microscope Info"]
69 tecnai_value["Microscope Info"] = process_tecnai_microscope_info(microscope_info)
70 set_nested_dict_value(metadata, path_to_tecnai, tecnai_value)
72 # Map Tecnai metadata fields to nx_meta fields
73 term_mapping = {
74 "Gun_Name": "Gun Name",
75 "Extractor_Voltage": "Extractor Voltage (V)",
76 "Camera_Length": "Camera Length (m)",
77 "Gun_Lens_No": "Gun Lens #",
78 "Emission_Current": "Emission Current (μA)",
79 "Spot": "Spot",
80 "Mode": "Tecnai Mode",
81 "Defocus": "Defocus",
82 "C2_Strength": "C2 Lens Strength (%)",
83 "C3_Strength": "C3 Lens Strength (%)",
84 "Obj_Strength": "Objective Lens Strength (%)",
85 "Dif_Strength": "Diffraction Lens Strength (%)",
86 "Microscope_Name": "Tecnai Microscope Name",
87 "User": "Tecnai User",
88 "Image_Shift_x": "Image Shift X (μm)",
89 "Image_Shift_y": "Image Shift Y (μm)",
90 "Stage_Position_x": ["Stage Position", "X (μm)"],
91 "Stage_Position_y": ["Stage Position", "Y (μm)"],
92 "Stage_Position_z": ["Stage Position", "Z (μm)"],
93 "Stage_Position_theta": ["Stage Position", "θ (°)"],
94 "Stage_Position_phi": ["Stage Position", "φ (°)"],
95 "C1_Aperture": "C1 Aperture (μm)",
96 "C2_Aperture": "C2 Aperture (μm)",
97 "Obj_Aperture": "Objective Aperture (μm)",
98 "SA_Aperture": "Selected Area Aperture (μm)",
99 ("Filter_Settings", "Mode"): ["Tecnai Filter", "Mode"],
100 ("Filter_Settings", "Dispersion"): ["Tecnai Filter", "Dispersion (eV/channel)"],
101 ("Filter_Settings", "Aperture"): ["Tecnai Filter", "Aperture (mm)"],
102 ("Filter_Settings", "Prism_Shift"): ["Tecnai Filter", "Prism Shift (eV)"],
103 ("Filter_Settings", "Drift_Tube"): ["Tecnai Filter", "Drift Tube (eV)"],
104 ("Filter_Settings", "Total_Energy_Loss"): [
105 "Tecnai Filter",
106 "Total Energy Loss (eV)",
107 ],
108 }
110 for in_term, out_term in term_mapping.items():
111 base = [*list(path_to_tecnai), "Microscope Info"]
112 if isinstance(in_term, str):
113 in_term = [in_term] # noqa: PLW2901
114 elif isinstance(in_term, tuple):
115 in_term = list(in_term) # noqa: PLW2901
116 if isinstance(out_term, str):
117 out_term = [out_term] # noqa: PLW2901
118 val = try_getting_dict_value(metadata, base + in_term)
119 # only add the value to this list if we found it
120 if val is not None and val not in ["DO NOT EDIT", "DO NOT ENTER"]:
121 set_nested_dict_value(metadata, ["nx_meta", *out_term], val)
123 # Parse specimen info
124 path = [*list(path_to_tecnai), "Specimen Info"]
125 val = try_getting_dict_value(metadata, path)
126 if val is not None and val != "Specimen information is not available yet":
127 set_nested_dict_value(metadata, ["nx_meta", "Specimen"], val)
129 return metadata
132def detect_diffraction_mode(
133 metadata: dict[str, Any],
134 context: ExtractionContext,
135) -> dict[str, Any]:
136 """
137 Detect diffraction patterns by Mode or Operation Mode values.
139 The 642 TEM indicates diffraction via specific Mode strings.
141 Parameters
142 ----------
143 metadata
144 Metadata dictionary with 'nx_meta' key
145 context
146 Extraction context (unused but required by profile signature)
148 Returns
149 -------
150 dict
151 Modified metadata dictionary with updated dataset type if applicable
152 """
153 # Check Tecnai Mode
154 if (
155 "Tecnai Mode" in metadata["nx_meta"]
156 and metadata["nx_meta"]["Tecnai Mode"] == "STEM nP SA Zoom Diffraction"
157 ):
158 _logger.info(
159 'Detected file as Diffraction type based on "Tecnai '
160 'Mode" == "STEM nP SA Zoom Diffraction"',
161 )
162 metadata["nx_meta"]["DatasetType"] = "Diffraction"
163 metadata["nx_meta"]["Data Type"] = "STEM_Diffraction"
165 # Check Operation Mode
166 elif (
167 "Operation Mode" in metadata["nx_meta"]
168 and metadata["nx_meta"]["Operation Mode"] == "DIFFRACTION"
169 ):
170 _logger.info(
171 'Detected file as Diffraction type based on "Operation '
172 'Mode" == "DIFFRACTION"',
173 )
174 metadata["nx_meta"]["DatasetType"] = "Diffraction"
175 metadata["nx_meta"]["Data Type"] = "TEM_Diffraction"
177 return metadata
180# Register the profile
181fei_titan_tem_642_profile = InstrumentProfile(
182 instrument_id="FEI-Titan-TEM",
183 parsers={
184 "tecnai_metadata": parse_tecnai_metadata,
185 "diffraction_detection": detect_diffraction_mode,
186 },
187 transformations={},
188 extension_fields={},
189)
190"""An instrument profile for the FEI Titan TEM"""
192get_profile_registry().register(fei_titan_tem_642_profile)
194_logger.debug("Registered FEI Titan TEM (642) instrument profile")