Skip to content

Commit 4e59b4e

Browse files
feat: Luminex xPONENT - migrate to BENCHLING/2024/09 schema (#1007)
Co-authored-by: Nathan Stender <nathan.stender@benchling.com>
1 parent 2e746ca commit 4e59b4e

19 files changed

Lines changed: 8653 additions & 107191 deletions

SUPPORTED_INSTRUMENT_SOFTWARE.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ The parsers follow maturation levels of: Recommended, Candidate Release, Working
3232
.2+|Liquid Handler|Beckman Coulter Biomek|Recommended|BENCHLING/2024/11
3333
|Beckman Echo Plate Reformat|Recommended|BENCHLING/2024/11
3434
.2+|Multi Analyte Profiling|Bio-Rad Bio-Plex Manager|Recommended|BENCHLING/2024/01
35-
|Luminex xPONENT|Recommended|BENCHLING/2024/01
35+
|Luminex xPONENT|Recommended|BENCHLING/2024/09
3636
.14+|Plate Reader|Agilent Gen5|Recommended|REC/2024/06
3737
|Agilent Gen5 Image|Recommended|BENCHLING/2023/09
3838
|BMG Labtech SMART Control|Recommended|REC/2024/06
@@ -63,4 +63,4 @@ The parsers follow maturation levels of: Recommended, Candidate Release, Working
6363
.3+|qPCR|AppBio QuantStudio RT-PCR|Recommended|REC/2024/09
6464
|AppBio QuantStudio Design & Analysis|Recommended|REC/2024/09
6565
|Bio-Rad CFX Maestro|Recommended|REC/2024/09
66-
|===
66+
|===

src/allotropy/allotrope/models/shared/definitions/definitions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ class TStatisticDatumRole(Enum):
8484
frequency_of_grandparent_role = "frequency of grandparent role"
8585
frequency_of_total_role = "frequency of total role"
8686
sum_of_squares_role = "sum of squares role"
87+
mode_value_role = "mode value role"
88+
trimmed_mode_value_role = "trimmed mode value role"
89+
trimmed_count_role = "trimmed count role"
90+
trimmed_standard_deviation_role = "trimmed standard deviation role"
91+
trimmed_arithmetic_mean_role = "trimmed arithmetic mean role"
8792

8893

8994
class InvalidJsonFloat(Enum):

src/allotropy/allotrope/schema_mappers/adm/multi_analyte_profiling/benchling/_2024/_09/multi_analyte_profiling.py

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
from allotropy.allotrope.models.adm.multi_analyte_profiling.benchling._2024._09.multi_analyte_profiling import (
44
AnalyteAggregateDocument,
55
AnalyteDocumentItem,
6+
CalculatedDataAggregateDocument,
7+
CalculatedDataDocumentItem,
68
CalibrationAggregateDocument,
79
CalibrationDocumentItem,
10+
DataSourceAggregateDocument,
11+
DataSourceDocumentItem,
812
DataSystemDocument,
913
DeviceControlAggregateDocument,
1014
DeviceControlDocumentItem,
@@ -17,6 +21,11 @@
1721
MultiAnalyteProfilingAggregateDocument,
1822
MultiAnalyteProfilingDocumentItem,
1923
SampleDocument,
24+
StatisticDimensionAggregateDocument,
25+
StatisticDimensionDocumentItem,
26+
StatisticsAggregateDocument,
27+
StatisticsDocumentItem,
28+
TQuantityValueModel,
2029
)
2130
from allotropy.allotrope.models.shared.components.plate_reader import SampleRoleType
2231
from allotropy.allotrope.models.shared.definitions.custom import (
@@ -28,19 +37,37 @@
2837
from allotropy.allotrope.models.shared.definitions.definitions import (
2938
TStatisticDatumRole,
3039
)
40+
from allotropy.allotrope.models.shared.definitions.units import Unitless
3141
from allotropy.allotrope.schema_mappers.schema_mapper import SchemaMapper
3242
from allotropy.constants import ASM_CONVERTER_VERSION
43+
from allotropy.parsers.utils.calculated_data_documents.definition import (
44+
CalculatedDocument,
45+
)
3346
from allotropy.parsers.utils.values import quantity_or_none
3447

3548

49+
@dataclass(frozen=True)
50+
class StatisticDimension:
51+
value: float
52+
unit: str
53+
statistic_datum_role: TStatisticDatumRole
54+
55+
56+
@dataclass(frozen=True)
57+
class StatisticsDocument:
58+
statistical_feature: str
59+
statistic_dimensions: list[StatisticDimension]
60+
61+
3662
@dataclass(frozen=True)
3763
class Analyte:
3864
identifier: str
3965
name: str
40-
value: float
4166
assay_bead_identifier: str
4267
assay_bead_count: float
43-
statistic_datum_role: TStatisticDatumRole | None
68+
statistics: list[StatisticsDocument] | None = None
69+
statistic_datum_role: TStatisticDatumRole | None = None
70+
fluorescence: float | None = None
4471

4572

4673
@dataclass(frozen=True)
@@ -115,6 +142,7 @@ class Metadata:
115142
class Data:
116143
metadata: Metadata
117144
measurement_groups: list[MeasurementGroup]
145+
calculated_data: list[CalculatedDocument] | None = None
118146

119147

120148
class Mapper(SchemaMapper[Data, Model]):
@@ -146,6 +174,9 @@ def map_model(self, data: Data) -> Model:
146174
self._get_technique_document(measurement_group, data.metadata)
147175
for measurement_group in data.measurement_groups
148176
],
177+
calculated_data_aggregate_document=self._get_calculated_data_aggregate_document(
178+
data.calculated_data
179+
),
149180
),
150181
field_asm_manifest=self.MANIFEST,
151182
)
@@ -208,9 +239,33 @@ def _get_measurement_document(
208239
),
209240
fluorescence=quantity_or_none(
210241
TQuantityValueRelativeFluorescenceUnit,
211-
analyte.value,
242+
analyte.fluorescence,
212243
has_statistic_datum_role=analyte.statistic_datum_role,
213244
),
245+
statistics_aggregate_document=(
246+
StatisticsAggregateDocument(
247+
statistics_document=[
248+
StatisticsDocumentItem(
249+
statistical_feature=statistic.statistical_feature,
250+
statistic_dimension_aggregate_document=StatisticDimensionAggregateDocument(
251+
statistic_dimension_document=[
252+
StatisticDimensionDocumentItem(
253+
statistical_value=TQuantityValueModel(
254+
value=dimension.value,
255+
unit=dimension.unit,
256+
has_statistic_datum_role=dimension.statistic_datum_role,
257+
),
258+
)
259+
for dimension in statistic.statistic_dimensions
260+
]
261+
),
262+
)
263+
for statistic in analyte.statistics
264+
]
265+
)
266+
if analyte.statistics
267+
else None
268+
),
214269
)
215270
for analyte in measurement.analytes
216271
]
@@ -261,3 +316,33 @@ def _get_error_aggregate_document(
261316
for error in errors
262317
]
263318
)
319+
320+
def _get_calculated_data_aggregate_document(
321+
self, calculated_data_items: list[CalculatedDocument] | None
322+
) -> CalculatedDataAggregateDocument | None:
323+
if not calculated_data_items:
324+
return None
325+
326+
return CalculatedDataAggregateDocument(
327+
calculated_data_document=[
328+
CalculatedDataDocumentItem(
329+
calculated_data_identifier=calculated_data_item.uuid,
330+
calculated_data_name=calculated_data_item.name,
331+
calculation_description=calculated_data_item.description,
332+
calculated_result=TQuantityValueModel(
333+
value=calculated_data_item.value,
334+
unit=calculated_data_item.unit or Unitless.unit,
335+
),
336+
data_source_aggregate_document=DataSourceAggregateDocument(
337+
data_source_document=[
338+
DataSourceDocumentItem(
339+
data_source_identifier=item.reference.uuid,
340+
data_source_feature=item.feature,
341+
)
342+
for item in calculated_data_item.data_sources
343+
]
344+
),
345+
)
346+
for calculated_data_item in calculated_data_items
347+
]
348+
)

src/allotropy/allotrope/schemas/adm/multi-analyte-profiling/BENCHLING/2024/09/CHANGE_NOTES.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ Changes:
1515
* Reasoning: These should be included in REC/2024/12 core schema
1616
* Proposal: Add "method version" to measurement aggregate document
1717

18-
* Add "coefficient of variation role", "trimmed mode role", "trimmed count role", "trimmed standard deviation role", "trimmed arithmetic mean role" to tStatisiticDatumRole class
18+
* Add "coefficient of variation role", "trimmed mode value role", "trimmed count role", "trimmed standard deviation role", "trimmed arithmetic mean role" to tStatisiticDatumRole class
1919
* Reasoning: Support of additional statistical terms that are exported from multi-analyte-profilers
20-
* Proposal: Add "coefficient of variation role", "trimmed mode role", "trimmed count role", "trimmed standard deviation role", "trimmed arithmetic mean role" to tStatisiticDatumRole
20+
* Proposal: Add "coefficient of variation role", "trimmed mode value role", "trimmed count role", "trimmed standard deviation role", "trimmed arithmetic mean role" to tStatisiticDatumRole

src/allotropy/allotrope/schemas/adm/multi-analyte-profiling/BENCHLING/2024/09/multi-analyte-profiling.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2081,7 +2081,7 @@
20812081
"variance role",
20822082
"mode value role",
20832083
"maximum value role",
2084-
"trimmed mode role",
2084+
"trimmed mode value role",
20852085
"trimmed count role",
20862086
"trimmed standard deviation role",
20872087
"trimmed arithmetic mean role",

src/allotropy/parsers/luminex_xponent/constants.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
from dataclasses import dataclass
2+
3+
from allotropy.allotrope.models.shared.definitions.definitions import (
4+
TStatisticDatumRole,
5+
)
6+
17
DEFAULT_SOFTWARE_NAME = "xPONENT"
28
DEFAULT_CONTAINER_TYPE = "well plate"
39
DEFAULT_DEVICE_TYPE = "multi analyte profiling analyzer"
@@ -8,4 +14,58 @@
814
MINIMUM_CALIBRATION_LINE_COLS = 2
915
EXPECTED_CALIBRATION_RESULT_LEN = 2
1016
EXPECTED_HEADER_COLUMNS = 7
11-
REQUIRED_SECTIONS = ["Median", "Count", "Units", "Dilution Factor"]
17+
REQUIRED_SECTIONS = ["Count", "Units", "Dilution Factor"]
18+
19+
20+
@dataclass(frozen=True)
21+
class StatisticSectionConfig:
22+
role: TStatisticDatumRole
23+
unit: str
24+
25+
26+
STATISTIC_SECTIONS_CONF = {
27+
"Median": StatisticSectionConfig(
28+
role=TStatisticDatumRole.median_role,
29+
unit="RFU",
30+
),
31+
"Mean": StatisticSectionConfig(
32+
role=TStatisticDatumRole.arithmetic_mean_role,
33+
unit="RFU",
34+
),
35+
"%CV": StatisticSectionConfig(
36+
role=TStatisticDatumRole.coefficient_of_variation_role,
37+
unit="(unitless)",
38+
),
39+
"Standard Deviation": StatisticSectionConfig(
40+
role=TStatisticDatumRole.standard_deviation_role,
41+
unit="(unitless)",
42+
),
43+
"Peak": StatisticSectionConfig(
44+
role=TStatisticDatumRole.mode_value_role,
45+
unit="RFU",
46+
),
47+
"Trimmed Peak": StatisticSectionConfig(
48+
role=TStatisticDatumRole.trimmed_mode_value_role,
49+
unit="RFU",
50+
),
51+
"Trimmed Count": StatisticSectionConfig(
52+
role=TStatisticDatumRole.trimmed_count_role,
53+
unit="RFU",
54+
),
55+
"Trimmed Standard Deviation": StatisticSectionConfig(
56+
role=TStatisticDatumRole.trimmed_standard_deviation_role,
57+
unit="(unitless)",
58+
),
59+
"Trimmed Mean": StatisticSectionConfig(
60+
role=TStatisticDatumRole.trimmed_arithmetic_mean_role,
61+
unit="RFU",
62+
),
63+
}
64+
65+
CALCULATED_DATA_SECTIONS: dict[str, str] = {
66+
"Net MFI": "RFU",
67+
"Avg Net MFI": "RFU",
68+
"Test Result": "(unitless)",
69+
"% Recovery": "(unitless)",
70+
"%CV of Replicates": "(unitless)",
71+
}

src/allotropy/parsers/luminex_xponent/luminex_xponent_parser.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from allotropy.allotrope.models.adm.multi_analyte_profiling.benchling._2024._01.multi_analyte_profiling import (
1+
from allotropy.allotrope.models.adm.multi_analyte_profiling.benchling._2024._09.multi_analyte_profiling import (
22
Model,
33
)
4-
from allotropy.allotrope.schema_mappers.adm.multi_analyte_profiling.benchling._2024._01.multi_analyte_profiling import (
4+
from allotropy.allotrope.schema_mappers.adm.multi_analyte_profiling.benchling._2024._09.multi_analyte_profiling import (
55
Data as MapperData,
66
Mapper,
77
)
@@ -31,5 +31,5 @@ def create_data(self, named_file_contents: NamedFileContents) -> MapperData:
3131
create_metadata(
3232
data.header, data.calibrations, named_file_contents.original_file_path
3333
),
34-
create_measurement_groups(data.measurement_list.measurements, data.header),
34+
*create_measurement_groups(data.measurement_list.measurements, data.header),
3535
)

0 commit comments

Comments
 (0)