Skip to content

Commit 3fa501f

Browse files
feat: Bio-Rad Bio-Plex Manager - Add support for statistics dimensions (#1029)
* Migrate Bio-Rad Bio-Plex Manager to map to schema BENCHLING/24/09 * Add support for statistics dimensions
1 parent 07e8c21 commit 3fa501f

6 files changed

Lines changed: 76004 additions & 6771 deletions

File tree

SUPPORTED_INSTRUMENT_SOFTWARE.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ The parsers follow maturation levels of: Recommended, Candidate Release, Working
3131
|Benchling Thermo Fisher Scientific Chromeleon|Recommended|BENCHLING/2023/09
3232
.2+|Liquid Handler|Beckman Coulter Biomek|Recommended|BENCHLING/2024/11
3333
|Beckman Echo Plate Reformat|Recommended|BENCHLING/2024/11
34-
.2+|Multi Analyte Profiling|Bio-Rad Bio-Plex Manager|Recommended|BENCHLING/2024/01
34+
.2+|Multi Analyte Profiling|Bio-Rad Bio-Plex Manager|Recommended|BENCHLING/2024/09
3535
|Luminex xPONENT|Recommended|BENCHLING/2024/09
3636
.14+|Plate Reader|Agilent Gen5|Recommended|REC/2025/03
3737
|Agilent Gen5 Image|Recommended|BENCHLING/2023/09

src/allotropy/parsers/biorad_bioplex_manager/biorad_bioplex_manager_parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from __future__ import annotations
22

3-
from allotropy.allotrope.models.adm.multi_analyte_profiling.benchling._2024._01.multi_analyte_profiling import (
3+
from allotropy.allotrope.models.adm.multi_analyte_profiling.benchling._2024._09.multi_analyte_profiling import (
44
Model,
55
)
6-
from allotropy.allotrope.schema_mappers.adm.multi_analyte_profiling.benchling._2024._01.multi_analyte_profiling import (
6+
from allotropy.allotrope.schema_mappers.adm.multi_analyte_profiling.benchling._2024._09.multi_analyte_profiling import (
77
Data,
88
Mapper,
99
)

src/allotropy/parsers/biorad_bioplex_manager/biorad_bioplex_manager_structure.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@
55
import xml.etree.ElementTree as Et
66

77
from allotropy.allotrope.models.shared.components.plate_reader import SampleRoleType
8-
from allotropy.allotrope.models.shared.definitions.definitions import (
9-
TStatisticDatumRole,
10-
)
11-
from allotropy.allotrope.schema_mappers.adm.multi_analyte_profiling.benchling._2024._01.multi_analyte_profiling import (
8+
from allotropy.allotrope.schema_mappers.adm.multi_analyte_profiling.benchling._2024._09.multi_analyte_profiling import (
129
Analyte,
1310
Error,
1411
Measurement,
1512
MeasurementGroup,
1613
Metadata,
14+
StatisticDimension,
15+
StatisticsDocument,
1716
)
1817
from allotropy.exceptions import get_key_or_error
1918
from allotropy.parsers.biorad_bioplex_manager import constants
19+
from allotropy.parsers.constants import NOT_APPLICABLE
2020
from allotropy.parsers.utils.uuids import random_uuid_str
2121
from allotropy.parsers.utils.values import (
2222
num_to_chars,
@@ -153,15 +153,32 @@ def create_analyte(
153153
) -> Analyte:
154154
# Look up analyte name from sample
155155
assay_bead_identifier = bead_region_xml.attrib["RegionNumber"]
156+
157+
# Create statistics dimensions from available statistics in the XML
158+
statistic_dimensions = []
159+
for statistic_name, statistic_config in constants.STATISTIC_SECTIONS_CONF.items():
160+
statistic_value = get_val_from_xml(bead_region_xml, statistic_name)
161+
statistic_dimensions.append(
162+
StatisticDimension(
163+
value=try_float(statistic_value, f"{statistic_name} statistic"),
164+
unit=statistic_config.unit,
165+
statistic_datum_role=statistic_config.role,
166+
)
167+
)
168+
156169
return Analyte(
157170
identifier=random_uuid_str(),
158171
name=analyte_region_dict[assay_bead_identifier],
159-
value=try_float(get_val_from_xml(bead_region_xml, "Median"), "fluorescence"),
160172
assay_bead_identifier=assay_bead_identifier,
161173
assay_bead_count=try_int(
162174
get_val_from_xml(bead_region_xml, "RegionCount"), "assay_bead_count"
163175
),
164-
statistic_datum_role=TStatisticDatumRole.median_role,
176+
statistics=[
177+
StatisticsDocument(
178+
statistical_feature="fluorescence",
179+
statistic_dimensions=statistic_dimensions,
180+
)
181+
],
165182
)
166183

167184

@@ -213,15 +230,18 @@ def get_well_name(well_attrib: dict[str, str]) -> str:
213230
def create_metadata(
214231
root_xml: Et.Element, system_metadata: SystemMetadata, file_path: str
215232
) -> Metadata:
233+
path = Path(file_path)
216234
return Metadata(
217-
file_name=Path(file_path).name,
235+
file_name=path.name,
218236
unc_path=file_path,
219237
software_name=constants.SOFTWARE_NAME,
220238
software_version=root_xml.attrib["BioPlexManagerVersion"],
221239
equipment_serial_number=system_metadata.serial_number,
222240
firmware_version=system_metadata.controller_version,
223241
product_manufacturer=constants.PRODUCT_MANUFACTURER,
224242
device_type=constants.DEVICE_TYPE,
243+
asm_file_identifier=path.with_suffix(".json").name,
244+
data_system_instance_identifier=NOT_APPLICABLE,
225245
)
226246

227247

src/allotropy/parsers/biorad_bioplex_manager/constants.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
from dataclasses import dataclass
2+
13
from allotropy.allotrope.models.shared.components.plate_reader import SampleRoleType
4+
from allotropy.allotrope.models.shared.definitions.definitions import (
5+
TStatisticDatumRole,
6+
)
27

3-
SOFTWARE_NAME = "Bio-Plex Manager"
8+
SOFTWARE_NAME = "Bio-Plex Manager"
49
PRODUCT_MANUFACTURER = "Bio-Rad"
510
DEVICE_TYPE = "multi analyte profiling analyzer"
611
CONTAINER_TYPE = "well plate"
@@ -27,3 +32,48 @@
2732
"PlateDimensions",
2833
"Wells",
2934
]
35+
36+
37+
@dataclass(frozen=True)
38+
class StatisticSectionConfig:
39+
role: TStatisticDatumRole
40+
unit: str
41+
42+
43+
# Statistics mapping for Bio-Rad BioPlex XML elements
44+
STATISTIC_SECTIONS_CONF = {
45+
"Median": StatisticSectionConfig(
46+
role=TStatisticDatumRole.median_role,
47+
unit="RFU",
48+
),
49+
"Mean": StatisticSectionConfig(
50+
role=TStatisticDatumRole.arithmetic_mean_role,
51+
unit="RFU",
52+
),
53+
"CV": StatisticSectionConfig(
54+
role=TStatisticDatumRole.coefficient_of_variation_role,
55+
unit="(unitless)",
56+
),
57+
"StdDev": StatisticSectionConfig(
58+
role=TStatisticDatumRole.standard_deviation_role,
59+
unit="(unitless)",
60+
),
61+
"TrimmedMean": StatisticSectionConfig(
62+
role=TStatisticDatumRole.trimmed_arithmetic_mean_role,
63+
unit="RFU",
64+
),
65+
"TrimmedStdDev": StatisticSectionConfig(
66+
role=TStatisticDatumRole.trimmed_standard_deviation_role,
67+
unit="(unitless)",
68+
),
69+
# "StdErr": StatisticSectionConfig(
70+
# role=TStatisticDatumRole.standard_error_role,
71+
# unit="RFU",
72+
# ),
73+
# "TrimmedCV": StatisticSectionConfig(
74+
# role=TStatisticDatumRole.trimmed_coefficient_of_variation_role,
75+
# unit="(unitless)",
76+
# ),
77+
# Note: StdErr (standard error) and TrimmedCV (trimmed coefficient of variation)
78+
# are not supported yet
79+
}

tests/parsers/biorad_bioplex_manager/biorad_bioplex_manager_structure_test.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
from allotropy.allotrope.models.shared.definitions.definitions import (
66
TStatisticDatumRole,
77
)
8-
from allotropy.allotrope.schema_mappers.adm.multi_analyte_profiling.benchling._2024._01.multi_analyte_profiling import (
8+
from allotropy.allotrope.schema_mappers.adm.multi_analyte_profiling.benchling._2024._09.multi_analyte_profiling import (
99
Analyte,
1010
Error,
11+
StatisticDimension,
12+
StatisticsDocument,
1113
)
1214
from allotropy.parsers.biorad_bioplex_manager.biorad_bioplex_manager_structure import (
1315
AnalyteMetadata,
@@ -89,13 +91,52 @@ def test_create_analyte_document_data() -> None:
8991
bead_xml,
9092
analyte_region_dict=analyte_region_dict,
9193
)
94+
95+
# Expected statistics from the XML
96+
expected_statistics = [
97+
StatisticsDocument(
98+
statistical_feature="fluorescence",
99+
statistic_dimensions=[
100+
StatisticDimension(
101+
value=992.5,
102+
unit="RFU",
103+
statistic_datum_role=TStatisticDatumRole.median_role,
104+
),
105+
StatisticDimension(
106+
value=983.391296386718750,
107+
unit="RFU",
108+
statistic_datum_role=TStatisticDatumRole.arithmetic_mean_role,
109+
),
110+
StatisticDimension(
111+
value=0.223858922719955440,
112+
unit="(unitless)",
113+
statistic_datum_role=TStatisticDatumRole.coefficient_of_variation_role,
114+
),
115+
StatisticDimension(
116+
value=220.140914916992190,
117+
unit="(unitless)",
118+
statistic_datum_role=TStatisticDatumRole.standard_deviation_role,
119+
),
120+
StatisticDimension(
121+
value=974.904785156250000,
122+
unit="RFU",
123+
statistic_datum_role=TStatisticDatumRole.trimmed_arithmetic_mean_role,
124+
),
125+
StatisticDimension(
126+
value=181.356109619140620,
127+
unit="(unitless)",
128+
statistic_datum_role=TStatisticDatumRole.trimmed_standard_deviation_role,
129+
),
130+
],
131+
)
132+
]
133+
92134
assert analyte_document_data == Analyte(
93135
identifier="dummy_id",
94136
name="Pn4",
95-
value=992.5,
96137
assay_bead_count=46,
97138
assay_bead_identifier="62",
98-
statistic_datum_role=TStatisticDatumRole.median_role,
139+
statistics=expected_statistics,
99140
)
100141

101142

0 commit comments

Comments
 (0)