@@ -80,22 +80,66 @@ def create_metadata(
8080 )
8181 general_info_data = ThermoSkanItMetadata ._get_general_info_data (general_info_df )
8282 path = Path (file_path )
83+
84+ device_identifier = str (instrument_info_data .pop ("device_identifier" ))
85+ model_number = str (instrument_info_data .pop ("model_number" ))
86+
87+ equipment_serial_number = None
88+ if "equipment_serial_number" in instrument_info_data :
89+ equipment_serial_number = str (
90+ instrument_info_data .pop ("equipment_serial_number" )
91+ )
92+
93+ software_name = None
94+ if general_info_data .get ("software_name" ) is not None :
95+ software_name = str (general_info_data .pop ("software_name" ))
96+ else :
97+ general_info_data .pop ("software_name" , None )
98+
99+ software_version = None
100+ if general_info_data .get ("software_version" ) is not None :
101+ software_version = str (general_info_data .pop ("software_version" ))
102+ else :
103+ general_info_data .pop ("software_version" , None )
104+
105+ # Merge custom_info dicts, ensuring we only unpack dict types
106+ instrument_custom_info = instrument_info_data .get ("custom_info" , {})
107+ device_control_custom_info_raw = instrument_info_data .get (
108+ "device_control_custom_info"
109+ )
110+ general_custom_info = general_info_data .get ("custom_info" , {})
111+
112+ custom_info : dict [str , Any ] = {}
113+ if isinstance (instrument_custom_info , dict ):
114+ custom_info .update (instrument_custom_info )
115+ if isinstance (general_custom_info , dict ):
116+ custom_info .update (general_custom_info )
117+
118+ # Ensure device_control_custom_info is the correct type
119+ device_control_custom_info = (
120+ device_control_custom_info_raw
121+ if isinstance (device_control_custom_info_raw , dict )
122+ else None
123+ )
124+
83125 return Metadata (
84126 asm_file_identifier = path .with_suffix (".json" ).name ,
85127 data_system_instance_id = NOT_APPLICABLE ,
86128 file_name = path .name ,
87129 unc_path = file_path ,
88- device_identifier = instrument_info_data ["device_identifier" ],
89- model_number = instrument_info_data ["model_number" ],
90- equipment_serial_number = instrument_info_data .get ("equipment_serial_number" ),
91- software_name = general_info_data ["software_name" ],
92- software_version = general_info_data ["software_version" ],
130+ device_identifier = device_identifier ,
131+ model_number = model_number ,
132+ equipment_serial_number = equipment_serial_number ,
133+ software_name = software_name ,
134+ software_version = software_version ,
135+ metadata_custom_info = custom_info ,
136+ device_control_custom_info = device_control_custom_info ,
93137 )
94138
95139 @staticmethod
96140 def _get_general_info_data (
97141 general_info_df : pd .DataFrame | None ,
98- ) -> dict [str , str | None ]:
142+ ) -> dict [str , str | None | dict [ str , str ] ]:
99143 if general_info_df is None :
100144 return dict .fromkeys (GENERAL_INFO_KEYS , None )
101145
@@ -106,15 +150,21 @@ def _get_general_info_data(
106150 software_name , software_version_txt = software_info .split ("," , 1 )
107151 match = re .search (r"\d+(?:\.\d+)*" , software_version_txt )
108152 software_version = match .group () if match else None
153+ unread_data = general_info_data .get_unread (skip = {"Filter" , "Position" })
154+ # Filter out "nan" keys from custom info and ensure string values
155+ custom_info = {
156+ k : str (v ) for k , v in unread_data .items () if k != "nan" and v is not None
157+ }
109158 return {
110159 "software_name" : software_name ,
111160 "software_version" : software_version ,
161+ "custom_info" : custom_info ,
112162 }
113163
114164 @staticmethod
115165 def _get_instrument_data (
116166 instrument_info_df : pd .DataFrame | None ,
117- ) -> dict [str , str ]:
167+ ) -> dict [str , str | None | dict [ str , str ] | dict [ str , float | str | None ] ]:
118168 if instrument_info_df is None :
119169 return {
120170 "device_identifier" : NOT_APPLICABLE ,
@@ -139,11 +189,45 @@ def _get_instrument_data(
139189 "model_number" : ("Name" , NOT_APPLICABLE ),
140190 "equipment_serial_number" : ("Serial number" , None ),
141191 }
142- return {
192+ # Get the raw data first
193+ raw_data = {
143194 key : value
144195 for key , (label , default ) in lookups .items ()
145196 if (value := instrument_info_data .get (str , label , default )) is not None
146197 }
198+ device_control_custom_info = instrument_info_data .get_custom_keys (
199+ {
200+ "Optical response compensation" ,
201+ "Plate adapter number" ,
202+ "Plate adapter name" ,
203+ }
204+ )
205+ unread_data = instrument_info_data .get_unread (
206+ skip = {
207+ "Wavelength" ,
208+ "Incubator" ,
209+ "Date and time of definition" ,
210+ "Top optics" ,
211+ "Bottom optics" ,
212+ "Dispenser 1" ,
213+ "Dispenser 2" ,
214+ "Module's name" ,
215+ "Module's serial number" ,
216+ "Instrument modules" ,
217+ "Gas control" ,
218+ "Bandwidth" ,
219+ "Position" ,
220+ "Filter" ,
221+ }
222+ )
223+ custom_info = {
224+ k : str (v ) for k , v in unread_data .items () if k != "nan" and v is not None
225+ }
226+ return {
227+ ** raw_data ,
228+ "custom_info" : custom_info ,
229+ "device_control_custom_info" : device_control_custom_info ,
230+ }
147231
148232
149233@dataclass
@@ -158,6 +242,7 @@ def create(
158242 well_plate_identifier : str | None ,
159243 error_documents : list [ErrorDocument ] | None = None ,
160244 experimental_data_identifier : str | None = None ,
245+ device_control_custom_info : dict [str , Any ] | None = None ,
161246 ) -> Measurement :
162247 measurement_type_str = MEASUREMENT_TYPES [type_ ]
163248 error_docs = error_documents or []
@@ -184,6 +269,7 @@ def create(
184269 else None ,
185270 experimental_data_identifier = experimental_data_identifier ,
186271 error_document = error_docs if error_docs else None ,
272+ device_control_custom_info = device_control_custom_info ,
187273 )
188274
189275 @staticmethod
@@ -202,14 +288,19 @@ def create(
202288 sheet_df : pd .DataFrame ,
203289 type_ : MeasurementType ,
204290 session_info_df : pd .DataFrame | None ,
291+ device_control_custom_info : dict [str , Any ] | None = None ,
205292 ) -> list [MeasurementGroup ]:
206293 plates = ThermoSkanItMeasurementGroups .identify_data_and_sample_dfs (sheet_df )
207294
208295 session_name = exec_time = None
296+ custom_info = {}
209297 if session_info_df is not None :
210298 session_info_data = df_to_series_data (parse_header_row (session_info_df .T ))
211299 session_name = session_info_data .get (str , "Session name" )
212300 exec_time = session_info_data .get (str , "Execution time" )
301+ unread_data = session_info_data .get_unread (skip = {"Executed with" })
302+ # Filter out "nan" keys from custom info
303+ custom_info = {k : v for k , v in unread_data .items () if k != "nan" }
213304
214305 if not exec_time :
215306 exec_time = sheet_df .iloc [1 ].iloc [0 ]
@@ -273,6 +364,7 @@ def create(
273364 experimental_data_identifier = session_name .replace (".skax" , "" )
274365 if session_name
275366 else None ,
367+ device_control_custom_info = device_control_custom_info ,
276368 )
277369
278370 if well_key not in well_measurements :
@@ -288,6 +380,7 @@ def create(
288380 measurements = measurements ,
289381 plate_well_count = plate_well_counts [plate_id ],
290382 measurement_time = exec_time ,
383+ custom_info = custom_info ,
291384 )
292385 )
293386
@@ -458,10 +551,15 @@ def create(sheet_data: dict[str, pd.DataFrame], file_path: str) -> Data:
458551 general_info_df = clean_data .get ("General information" ),
459552 file_path = file_path ,
460553 )
554+
555+ # Extract device control custom info from metadata
556+ device_control_custom_info = metadata .device_control_custom_info
557+
461558 measurement_groups = ThermoSkanItMeasurementGroups .create (
462559 sheet_df = measurement_df ,
463560 session_info_df = clean_data .get ("Session information" ),
464561 type_ = _type ,
562+ device_control_custom_info = device_control_custom_info ,
465563 )
466564
467565 return Data (
0 commit comments