Skip to content

Commit c8db620

Browse files
feat: Perkin elmer envision get unread (#1073)
1 parent b136e14 commit c8db620

18 files changed

Lines changed: 176786 additions & 21134 deletions

src/allotropy/allotrope/schema_mappers/adm/plate_reader/rec/_2024/_06/plate_reader.py

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -236,27 +236,31 @@ def _get_technique_document(
236236
) -> PlateReaderDocumentItem:
237237
plate_reader_doc = PlateReaderDocumentItem(
238238
analyst=measurement_group.analyst,
239-
measurement_aggregate_document=MeasurementAggregateDocument(
240-
analytical_method_identifier=measurement_group.analytical_method_identifier,
241-
experimental_data_identifier=measurement_group.experimental_data_identifier,
242-
experiment_type=measurement_group.experiment_type,
243-
container_type=ContainerType.well_plate.value,
244-
plate_well_count=TQuantityValueNumber(
245-
value=measurement_group.plate_well_count
239+
measurement_aggregate_document=add_custom_information_document(
240+
MeasurementAggregateDocument(
241+
analytical_method_identifier=measurement_group.analytical_method_identifier,
242+
experimental_data_identifier=measurement_group.experimental_data_identifier,
243+
experiment_type=measurement_group.experiment_type,
244+
container_type=ContainerType.well_plate.value,
245+
plate_well_count=TQuantityValueNumber(
246+
value=measurement_group.plate_well_count
247+
),
248+
measurement_time=self.get_date_time(
249+
measurement_group.measurement_time
250+
),
251+
measurement_document=[
252+
self._get_measurement_document(measurement)
253+
for measurement in measurement_group.measurements
254+
],
246255
),
247-
measurement_time=self.get_date_time(measurement_group.measurement_time),
248-
measurement_document=[
249-
self._get_measurement_document(measurement)
250-
for measurement in measurement_group.measurements
251-
],
256+
measurement_group.custom_info,
252257
),
253258
)
254259
custom_info_doc = {
255260
"maximum wavelength signal": quantity_or_none(
256261
TQuantityValueNanometer, measurement_group.maximum_wavelength_signal
257262
)
258263
}
259-
custom_info_doc.update(measurement_group.custom_info or {})
260264
return add_custom_information_document(plate_reader_doc, custom_info_doc)
261265

262266
def _get_measurement_document(
@@ -572,23 +576,26 @@ def _get_calculated_data_aggregate_document(
572576

573577
return CalculatedDataAggregateDocument(
574578
calculated_data_document=[
575-
CalculatedDataDocumentItem(
576-
calculated_data_identifier=calculated_data_item.uuid,
577-
calculated_data_name=calculated_data_item.name,
578-
calculation_description=calculated_data_item.description,
579-
calculated_result=TQuantityValueModel(
580-
value=calculated_data_item.value,
581-
unit=calculated_data_item.unit or Unitless.unit,
582-
),
583-
data_source_aggregate_document=DataSourceAggregateDocument(
584-
data_source_document=[
585-
DataSourceDocumentItem(
586-
data_source_identifier=item.reference.uuid,
587-
data_source_feature=item.feature,
588-
)
589-
for item in calculated_data_item.data_sources
590-
]
579+
add_custom_information_document(
580+
CalculatedDataDocumentItem(
581+
calculated_data_identifier=calculated_data_item.uuid,
582+
calculated_data_name=calculated_data_item.name,
583+
calculation_description=calculated_data_item.description,
584+
calculated_result=TQuantityValueModel(
585+
value=calculated_data_item.value,
586+
unit=calculated_data_item.unit or Unitless.unit,
587+
),
588+
data_source_aggregate_document=DataSourceAggregateDocument(
589+
data_source_document=[
590+
DataSourceDocumentItem(
591+
data_source_identifier=item.reference.uuid,
592+
data_source_feature=item.feature,
593+
)
594+
for item in calculated_data_item.data_sources
595+
]
596+
),
591597
),
598+
calculated_data_item.custom_info,
592599
)
593600
for calculated_data_item in calculated_data_items
594601
]

src/allotropy/parsers/perkin_elmer_envision/perkin_elmer_envision_structure.py

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,16 @@
2222
from enum import Enum
2323
from pathlib import Path
2424
from re import search
25+
from typing import Any
2526

2627
import numpy as np
2728
import pandas as pd
2829

2930
from allotropy.allotrope.models.shared.components.plate_reader import SampleRoleType
31+
from allotropy.allotrope.models.shared.definitions.custom import (
32+
TQuantityValueDegreeCelsius,
33+
TQuantityValuePercent,
34+
)
3035
from allotropy.allotrope.schema_mappers.adm.plate_reader.rec._2024._06.plate_reader import (
3136
Measurement,
3237
MeasurementGroup,
@@ -47,7 +52,9 @@
4752
from 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

5360
class ReadType(Enum):
@@ -104,6 +111,7 @@ def get_plate_number_and_barcode(data: SeriesData) -> tuple[str, str]:
104111
class 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):
288338
class 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:
372424
class 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)
391448
class 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(
714775
def _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(),

src/allotropy/parsers/utils/calculated_data_documents/definition.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from collections.abc import Iterator
44
from dataclasses import dataclass
5+
from typing import Any
56

67

78
@dataclass
@@ -24,6 +25,7 @@ class CalculatedDocument(Referenceable):
2425
unit: str | None = None
2526
description: str | None = None
2627
iterated: bool = False
28+
custom_info: dict[str, Any] | None = None
2729

2830
def iter_struct(self) -> Iterator[CalculatedDocument]:
2931
if self.iterated:

0 commit comments

Comments
 (0)