2222from enum import Enum
2323from pathlib import Path
2424from re import search
25+ from typing import Any
2526
2627import numpy as np
2728import pandas as pd
2829
2930from allotropy .allotrope .models .shared .components .plate_reader import SampleRoleType
31+ from allotropy .allotrope .models .shared .definitions .custom import (
32+ TQuantityValueDegreeCelsius ,
33+ TQuantityValuePercent ,
34+ )
3035from allotropy .allotrope .schema_mappers .adm .plate_reader .rec ._2024 ._06 .plate_reader import (
3136 Measurement ,
3237 MeasurementGroup ,
4752from allotropy .parsers .utils .values import (
4853 assert_not_none ,
4954 num_to_chars ,
55+ quantity_or_none ,
5056)
57+ from allotropy .parsers .utils .warnings_tools import suppress_unused_keys_warning
5158
5259
5360class ReadType (Enum ):
@@ -104,6 +111,7 @@ def get_plate_number_and_barcode(data: SeriesData) -> tuple[str, str]:
104111class CalculatedPlateInfo (PlateInfo ):
105112 formula : str
106113 name : str
114+ custom_info : dict [str , Any ] | None = None
107115
108116 @staticmethod
109117 def create (data : SeriesData ) -> CalculatedPlateInfo :
@@ -126,6 +134,7 @@ def create(data: SeriesData) -> CalculatedPlateInfo:
126134 ),
127135 formula = formula ,
128136 name = name .strip (),
137+ custom_info = data .get_unread (),
129138 )
130139
131140
@@ -134,6 +143,9 @@ class ResultPlateInfo(PlateInfo):
134143 label : str
135144 measinfo : str
136145 emission_filter_id : str
146+ sample_custom_info : dict [str , Any ]
147+ device_control_custom_info : dict [str , Any ]
148+ custom_info : dict [str , Any ]
137149
138150 @staticmethod
139151 def create (data : SeriesData ) -> ResultPlateInfo | None :
@@ -159,6 +171,42 @@ def create(data: SeriesData) -> ResultPlateInfo | None:
159171 search ("De=(...)" , measinfo ),
160172 msg = f"Unable to find emission filter ID for Plate { barcode } ." ,
161173 ).group (1 ),
174+ device_control_custom_info = {
175+ "Ambient temperature at start" : quantity_or_none (
176+ TQuantityValueDegreeCelsius ,
177+ data .get (float , "Ambient temperature at start" ),
178+ ),
179+ "Ambient temperature at end" : quantity_or_none (
180+ TQuantityValueDegreeCelsius ,
181+ data .get (float , "Ambient temperature at end" ),
182+ ),
183+ "Chamber temperature at end" : quantity_or_none (
184+ TQuantityValueDegreeCelsius ,
185+ data .get (float , "Chamber temperature at end" ),
186+ ),
187+ "Humidity at start" : quantity_or_none (
188+ TQuantityValuePercent , data .get (float , "Humidity at start" )
189+ ),
190+ "Humidity at end" : quantity_or_none (
191+ TQuantityValuePercent , data .get (float , "Humidity at end" )
192+ ),
193+ "Kinetics" : data .get (float , "Kinetics" ),
194+ "ScanX" : data .get (float , "ScanX" ),
195+ "ScanY" : data .get (float , "ScanY" ),
196+ "Inside temperature at start" : quantity_or_none (
197+ TQuantityValueDegreeCelsius ,
198+ data .get (float , "Inside temperature at start" ),
199+ ),
200+ "Inside temperature at end" : quantity_or_none (
201+ TQuantityValueDegreeCelsius ,
202+ data .get (float , "Inside temperature at end" ),
203+ ),
204+ },
205+ sample_custom_info = {
206+ "group identifier" : data .get (float , "Group" ),
207+ "Repeat" : data .get (float , "Repeat" ),
208+ },
209+ custom_info = data .get_unread (),
162210 )
163211
164212 def match (self , background_info : BackgroundInfo ) -> bool :
@@ -176,13 +224,15 @@ class BackgroundInfo:
176224 plate_num : str
177225 label : str
178226 measinfo : str
227+ custom_info : dict [str , Any ]
179228
180229 @staticmethod
181230 def create (data : SeriesData ) -> BackgroundInfo :
182231 return BackgroundInfo (
183232 plate_num = data [str , "Plate" ],
184233 label = data [str , "Label" ],
185234 measinfo = data [str , "MeasInfo" ],
235+ custom_info = data .get_unread (),
186236 )
187237
188238
@@ -288,6 +338,7 @@ class ResultPlate(Plate):
288338class CalculatedPlate (Plate ):
289339 plate_info : CalculatedPlateInfo
290340 results : list [CalculatedResult ]
341+ custom_info : dict [str , Any ] | None = None
291342
292343 def get_source_results (self , plate_list : PlateList ) -> Iterator [list [Result ]]:
293344 if not self .background_infos :
@@ -327,6 +378,7 @@ def create_plate(reader: CsvReader) -> ResultPlate | CalculatedPlate:
327378 plate_info = CalculatedPlateInfo .create (series ),
328379 background_infos = create_background_infos (reader ),
329380 results = create_calculated_results (reader ),
381+ custom_info = series .get_unread (),
330382 )
331383
332384
@@ -372,6 +424,7 @@ def get_measurement_time(self) -> str:
372424class BasicAssayInfo :
373425 protocol_id : str | None
374426 assay_id : str | None
427+ custom_info : dict [str , Any ]
375428
376429 @staticmethod
377430 def create (reader : CsvReader ) -> BasicAssayInfo :
@@ -384,12 +437,17 @@ def create(reader: CsvReader) -> BasicAssayInfo:
384437 data_frame .columns .astype (str ).str .replace (":" , "" ).str .strip ()
385438 )
386439 data = df_to_series_data (data_frame )
387- return BasicAssayInfo (data .get (str , "Protocol ID" ), data .get (str , "Assay ID" ))
440+ return BasicAssayInfo (
441+ data .get (str , "Protocol ID" ),
442+ data .get (str , "Assay ID" ),
443+ data .get_unread (skip = {"Serial#" }),
444+ )
388445
389446
390447@dataclass (frozen = True )
391448class PlateType :
392449 number_of_wells : float
450+ custom_info : dict [str , Any ]
393451
394452 @staticmethod
395453 def create (reader : CsvReader ) -> PlateType :
@@ -399,7 +457,8 @@ def create(reader: CsvReader) -> PlateType:
399457 )
400458 data = df_to_series_data (data_frame .T )
401459 return PlateType (
402- number_of_wells = data [float , "Number of the wells in the plate" ]
460+ number_of_wells = data [float , "Number of the wells in the plate" ],
461+ custom_info = data .get_unread (),
403462 )
404463
405464
@@ -510,6 +569,7 @@ class Filter:
510569 bandwidth : float | None = None
511570
512571 @staticmethod
572+ @suppress_unused_keys_warning
513573 def create (reader : CsvReader ) -> Filter | None :
514574 if not reader .current_line_exists () or reader .match (
515575 "(^Mirror modules)|(^Instrument:)|(^Aperture:)"
@@ -569,6 +629,7 @@ class Labels:
569629 detector_gain_setting : str | None = None
570630
571631 @staticmethod
632+ @suppress_unused_keys_warning
572633 def create (reader : CsvReader ) -> Labels :
573634 reader .drop_until_inclusive ("^Labels" )
574635 data_frame = assert_not_none (
@@ -676,7 +737,7 @@ class Data:
676737 software : Software
677738 plate_list : PlateList
678739 basic_assay_info : BasicAssayInfo
679- number_of_wells : float
740+ plate_type_info : PlateType
680741 plate_maps : dict [str , PlateMap ]
681742 labels : Labels
682743 instrument : Instrument
@@ -686,7 +747,7 @@ def create(reader: CsvReader) -> Data:
686747 return Data (
687748 plate_list = PlateList .create (reader ),
688749 basic_assay_info = BasicAssayInfo .create (reader ),
689- number_of_wells = PlateType .create (reader ). number_of_wells ,
750+ plate_type_info = PlateType .create (reader ),
690751 plate_maps = create_plate_maps (reader ),
691752 labels = Labels .create (reader ),
692753 instrument = Instrument .create (reader ),
@@ -714,14 +775,23 @@ def create_metadata(
714775def _create_measurement (
715776 plate_info : ResultPlateInfo ,
716777 result : Result ,
717- plate_maps : dict [str , PlateMap ],
718- labels : Labels ,
778+ data : Data ,
719779 read_type : ReadType ,
720780) -> Measurement :
721781 plate_barcode = plate_info .barcode
722782 well_location = f"{ result .col } { result .row } "
723- ex_filter = labels .excitation_filter
724- em_filter = labels .get_emission_filter (plate_info .emission_filter_id )
783+ ex_filter = data .labels .excitation_filter
784+ em_filter = data .labels .get_emission_filter (plate_info .emission_filter_id )
785+ sample_custom_info = {
786+ ** plate_info .sample_custom_info ,
787+ "Name of the plate type" : data .plate_type_info .custom_info .pop (
788+ "Name of the plate type" , None
789+ ),
790+ }
791+ device_control_custom_info = {
792+ ** plate_info .device_control_custom_info ,
793+ ** data .plate_type_info .custom_info ,
794+ }
725795 return Measurement (
726796 type_ = read_type .measurement_type ,
727797 device_type = read_type .device_type ,
@@ -731,21 +801,24 @@ def _create_measurement(
731801 location_identifier = well_location ,
732802 sample_role_type = (
733803 p_map .get_sample_role_type (result .col , result .row )
734- if (p_map := plate_maps .get (plate_info .number ))
804+ if (p_map := data . plate_maps .get (plate_info .number ))
735805 else None
736806 ),
737807 compartment_temperature = plate_info .chamber_temperature_at_start ,
738808 absorbance = result .value if read_type is ReadType .ABSORBANCE else None ,
739809 fluorescence = result .value if read_type is ReadType .FLUORESCENCE else None ,
740810 luminescence = result .value if read_type is ReadType .LUMINESCENCE else None ,
741811 detector_distance_setting = plate_info .measured_height ,
742- number_of_averages = labels .number_of_flashes ,
743- detector_gain_setting = labels .detector_gain_setting ,
744- scan_position_setting = labels .scan_position_setting ,
812+ number_of_averages = data . labels .number_of_flashes ,
813+ detector_gain_setting = data . labels .detector_gain_setting ,
814+ scan_position_setting = data . labels .scan_position_setting ,
745815 detector_wavelength_setting = em_filter .wavelength if em_filter else None ,
746816 detector_bandwidth_setting = em_filter .bandwidth if em_filter else None ,
747817 excitation_wavelength_setting = ex_filter .wavelength if ex_filter else None ,
748818 excitation_bandwidth_setting = ex_filter .bandwidth if ex_filter else None ,
819+ device_control_custom_info = device_control_custom_info ,
820+ sample_custom_info = sample_custom_info ,
821+ measurement_custom_info = plate_info .custom_info ,
749822 )
750823
751824
@@ -757,8 +830,7 @@ def create_measurement_groups(data: Data) -> list[MeasurementGroup]:
757830 measurement = _create_measurement (
758831 plate .plate_info ,
759832 result ,
760- data .plate_maps ,
761- data .labels ,
833+ data ,
762834 read_type ,
763835 )
764836 well_loc_measurements [
@@ -769,10 +841,11 @@ def create_measurement_groups(data: Data) -> list[MeasurementGroup]:
769841 return [
770842 MeasurementGroup (
771843 measurement_time = measurement_time ,
772- plate_well_count = data .number_of_wells ,
844+ plate_well_count = data .plate_type_info . number_of_wells ,
773845 analytical_method_identifier = data .basic_assay_info .protocol_id ,
774846 experimental_data_identifier = data .basic_assay_info .assay_id ,
775847 measurements = well_loc_measurements [well_location ],
848+ custom_info = data .basic_assay_info .custom_info ,
776849 )
777850 for well_location in sorted (
778851 well_loc_measurements .keys (),
0 commit comments