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

1# ruff: noqa: ARG001 

2"""Instrument profile for FEI Titan TEM (642 microscope).""" 

3 

4from __future__ import annotations 

5 

6import logging 

7from typing import TYPE_CHECKING, Any 

8 

9from benedict import benedict 

10 

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) 

17 

18if TYPE_CHECKING: 

19 from nexusLIMS.extractors.base import ExtractionContext 

20 

21_logger = logging.getLogger(__name__) 

22 

23 

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. 

30 

31 The 642 Titan TEM stores extensive microscope parameters in a delimited 

32 string format that needs special parsing. 

33 

34 Parameters 

35 ---------- 

36 metadata 

37 Metadata dictionary with 'nx_meta' key 

38 context 

39 Extraction context (unused but required by profile signature) 

40 

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 ) 

50 

51 # Check if Tecnai metadata exists using benedict's keypaths method 

52 b = benedict(metadata) 

53 keypaths_list = b.keypaths() 

54 

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 

61 

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 

66 

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) 

71 

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 } 

109 

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) 

122 

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) 

128 

129 return metadata 

130 

131 

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. 

138 

139 The 642 TEM indicates diffraction via specific Mode strings. 

140 

141 Parameters 

142 ---------- 

143 metadata 

144 Metadata dictionary with 'nx_meta' key 

145 context 

146 Extraction context (unused but required by profile signature) 

147 

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" 

164 

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" 

176 

177 return metadata 

178 

179 

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""" 

191 

192get_profile_registry().register(fei_titan_tem_642_profile) 

193 

194_logger.debug("Registered FEI Titan TEM (642) instrument profile")