66import logging
77from pathlib import Path
88import re
9+ from typing import Any
910
1011import pandas as pd
1112
@@ -235,6 +236,7 @@ class MeasurementInfo:
235236 measurement_time : str
236237 protocol_signature : str
237238 measurement_signature : str
239+ custom_info : dict [str , Any ]
238240
239241 @staticmethod
240242 def create (
@@ -253,14 +255,15 @@ def create(
253255 reader .lines_as_df (lines , index_col = 0 , names = range (max_num_cols )),
254256 msg = f"Unable to parser data for { section_name } section." ,
255257 ).T .dropna (how = "all" )
256- df .columns = df .columns .astype (str ).str .strip (":" )
258+ df .columns = df .columns .astype (str ).str .lower (). str . strip (":" )
257259 data = df_to_series_data (df , index = 0 )
258260
259261 return MeasurementInfo (
260- instrument_serial_number = data [str , "Instrument Serial Number" ],
261- measurement_time = data [str , "Measurement Started" ],
262- protocol_signature = data [str , "Protocol Signature" ],
263- measurement_signature = data [str , "Measurement Signature" ],
262+ instrument_serial_number = data [str , "instrument serial number" ],
263+ measurement_time = data [str , "measurement started" ],
264+ protocol_signature = data [str , "protocol signature" ],
265+ measurement_signature = data [str , "measurement signature" ],
266+ custom_info = data .get_unread (skip = {"software version" , "barcode" , "nan" }),
264267 )
265268
266269
@@ -350,15 +353,21 @@ def create(
350353class Measurements :
351354 channels : list [Channel ]
352355 number_of_averages : float | None
356+ detector_distance_luminiscence : float | None
353357 detector_distance : float | None
354358 scan_position : ScanPositionSettingPlateReader | None
355359 emission_wavelength : float | None
356360 excitation_wavelength : float | None
357361 focus_height : float | None
362+ device_control_custom_info : dict [str , Any ]
363+ custom_info : dict [str , Any ] | None = None
358364
359365 @staticmethod
360366 def create (
361- reader : CsvReader , section_title : str , next_section_title : str
367+ reader : CsvReader ,
368+ section_title : str ,
369+ next_section_title : str ,
370+ measurement_info : MeasurementInfo ,
362371 ) -> Measurements :
363372 assert_not_none (
364373 reader .drop_until_inclusive (f"^{ section_title } " ),
@@ -382,7 +391,8 @@ def create(
382391 return Measurements (
383392 channels = Measurements .create_channels (data ),
384393 number_of_averages = data .get (float , "number of flashes" ),
385- detector_distance = data .get (
394+ detector_distance = data .get (float , "measurement height [mm]" ),
395+ detector_distance_luminiscence = data .get (
386396 float , "distance between plate and detector [mm]"
387397 ),
388398 scan_position = (
@@ -400,21 +410,47 @@ def create(
400410 else try_float_or_none (excitation_wavelength .removesuffix ("nm" ))
401411 ),
402412 focus_height = data .get (float , "focus height [µm]" ),
413+ device_control_custom_info = {
414+ "number of flashes integrated" : data .get (
415+ float , "number of flashes integrated"
416+ ),
417+ "flash power" : data .get (float , "flash power" ),
418+ "measurement mode setting" : data .get (str , "measurement mode" ),
419+ "start plate repeat each [s]" : measurement_info .custom_info .get (
420+ "start plate repeat each [s]" , None
421+ ),
422+ "number of plate repeats" : measurement_info .custom_info .get (
423+ "number of plate repeats" , None
424+ ),
425+ "sequence executed by" : measurement_info .custom_info .get (
426+ "sequence executed by" , None
427+ ),
428+ "filter set" : data .get (str , "filter set" ),
429+ "generate bright field" : data .get (bool , "generate bright field" ),
430+ "digital phase contrast" : data .get (bool , "digital phase contrast" ),
431+ "generate dpc" : data .get (bool , "generate dpc" ),
432+ },
433+ custom_info = data .get_unread (
434+ skip = {
435+ "tech" ,
436+ "operation" ,
437+ "nan" ,
438+ }
439+ ),
403440 )
404441
405442 @staticmethod
406443 def create_channels (data : SeriesData ) -> list [Channel ]:
407444 # Get all channel keys
408- values = {
409- key : data .series .get (key )
410- for key in [
411- "channel" ,
412- "excitation wavelength [nm]" ,
413- "excitation power [%]" ,
414- "exposure time [ms]" ,
415- "additional focus offset [mm]" ,
416- ]
417- }
445+ channel_keys = [
446+ "channel" ,
447+ "excitation wavelength [nm]" ,
448+ "excitation power [%]" ,
449+ "exposure time [ms]" ,
450+ "additional focus offset [mm]" ,
451+ ]
452+ values = {key : data .series .get (key ) for key in channel_keys }
453+ data .mark_read (set (channel_keys ))
418454 if values ["channel" ] is None :
419455 return []
420456
@@ -479,6 +515,7 @@ def create_metadata(data: Data, file_path: str) -> Metadata:
479515 model_number = constants .MODEL_NUMBER ,
480516 product_manufacturer = constants .PRODUCT_MANUFACTURER ,
481517 equipment_serial_number = data .measurement_info .instrument_serial_number ,
518+ custom_info = data .measurement_info .custom_info ,
482519 )
483520
484521
@@ -513,7 +550,9 @@ def _create_measurement(data: Data, well_position: str, well_value: str) -> Meas
513550 well_plate_identifier = data .results .barcode ,
514551 sample_role_type = data .platemap .get_sample_role_type (well_position ),
515552 number_of_averages = data .measurements .number_of_averages ,
516- detector_distance_setting = data .measurements .detector_distance ,
553+ detector_distance_setting = data .measurements .detector_distance_luminiscence
554+ if experiment_type is ExperimentType .LUMINESCENCE
555+ else data .measurements .detector_distance ,
517556 scan_position_setting = data .measurements .scan_position ,
518557 detector_wavelength_setting = detector_wavelength_setting ,
519558 excitation_wavelength_setting = data .measurements .excitation_wavelength ,
@@ -526,12 +565,23 @@ def _create_measurement(data: Data, well_position: str, well_value: str) -> Meas
526565 luminescence = measurement_value
527566 if experiment_type is ExperimentType .LUMINESCENCE
528567 else None ,
568+ device_control_custom_info = data .measurements .device_control_custom_info ,
569+ custom_info = data .measurements .custom_info ,
529570 )
530571
531572
532573def _create_optical_measurement (
533574 data : Data , well_position : str , channel : Channel
534575) -> Measurement :
576+ device_control_custom_info = {
577+ ** data .measurements .device_control_custom_info ,
578+ "additional focus offset [mm]" : channel .additional_focus_offset ,
579+ }
580+ if channel .name != "BRIGHTFIELD" :
581+ to_remove = ["generate bright field" , "digital phase contrast" , "generate dpc" ]
582+ for key in to_remove :
583+ device_control_custom_info .pop (key , None )
584+
535585 return Measurement (
536586 type_ = data .background_info .experiment_type .measurement_type ,
537587 identifier = random_uuid_str (),
@@ -550,6 +600,8 @@ def _create_optical_measurement(
550600 illumination_setting_unit = "%" ,
551601 transmitted_light_setting = channel .transmitted_light ,
552602 fluorescent_tag_setting = channel .fluorescent_tag ,
603+ device_control_custom_info = device_control_custom_info ,
604+ custom_info = data .measurements .custom_info ,
553605 )
554606
555607
@@ -566,6 +618,16 @@ def _create_measurements(
566618
567619
568620def create_measurement_groups (data : Data ) -> list [MeasurementGroup ]:
621+ protocol_owner = data .measurement_info .custom_info .pop ("protocol owner" , None )
622+ measurement_finished = data .measurement_info .custom_info .pop (
623+ "measurement finished" , None
624+ )
625+ protocol_name = data .measurement_info .custom_info .pop ("protocol name" , None )
626+
627+ data .measurement_info .custom_info .pop ("sequence executed by" , None )
628+ data .measurement_info .custom_info .pop ("number of plate repeats" , None )
629+ data .measurement_info .custom_info .pop ("start plate repeat each [s]" , None )
630+
569631 return [
570632 MeasurementGroup (
571633 measurement_time = data .measurement_info .measurement_time ,
@@ -596,6 +658,11 @@ def create_measurement_groups(data: Data) -> list[MeasurementGroup]:
596658 ]
597659 if data .background_info .experiment_type is ExperimentType .OPTICAL_IMAGING
598660 else None ,
661+ custom_info = {
662+ "protocol owner" : protocol_owner ,
663+ "measurement finished" : measurement_finished ,
664+ "protocol name" : protocol_name ,
665+ },
599666 )
600667 for well_position , well_value in data .iter_wells ()
601668 ]
0 commit comments