Skip to content

Commit

Permalink
Feat: luminex xponent adapter (#255)
Browse files Browse the repository at this point in the history
Add Luminex Xponent support.
  • Loading branch information
slopez-b authored Jan 30, 2024
1 parent d393fea commit 2ab4e76
Show file tree
Hide file tree
Showing 14 changed files with 32,766 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
### Added
- Add Luminex xPONENT Adapter
### Fixed
### Changed
### Deprecated
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# generated by datamodel-codegen:
# filename: multi-analyte-profiling.json
# timestamp: 2024-01-17T20:55:27+00:00
# timestamp: 2024-01-26T15:48:36+00:00

from __future__ import annotations

Expand Down Expand Up @@ -140,7 +140,7 @@ class DeviceControlDocumentItem:
firmware_version: Optional[TStringValue] = None
sample_volume_setting: Optional[TQuantityValueMicroliter] = None
dilution_factor_setting: Optional[TQuantityValueUnitless] = None
detector_gain_settings: Optional[TStringValue] = None
detector_gain_setting: Optional[TStringValue] = None
minimum_assay_bead_count_setting: Optional[TQuantityValueUnitless] = None


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@
"$asm.pattern": "quantity datum",
"$ref": "#/$custom/tQuantityValueUnitless"
},
"detector gain settings": {
"detector gain setting": {
"$asm.property-class": "http://purl.allotrope.org/ontologies/result#AFR_0002250",
"$asm.pattern": "value datum",
"$asm.type": "http://www.w3.org/2001/XMLSchema#string",
Expand Down Expand Up @@ -673,7 +673,6 @@
}
],
"required": [
"multi analyte profiling aggreagate document",
"$asm.manifest"
],
"$defs": {
Expand Down
5 changes: 5 additions & 0 deletions src/allotropy/parser_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
from allotropy.parsers.example_weyland_yutani.example_weyland_yutani_parser import (
ExampleWeylandYutaniParser,
)
from allotropy.parsers.luminex_xponent.luminex_xponent_parser import (
LuminexXponentParser,
)
from allotropy.parsers.moldev_softmax_pro.softmax_pro_parser import SoftmaxproParser
from allotropy.parsers.novabio_flex2.novabio_flex2_parser import NovaBioFlexParser
from allotropy.parsers.perkin_elmer_envision.perkin_elmer_envision_parser import (
Expand All @@ -44,6 +47,7 @@ class Vendor(Enum):
BECKMAN_VI_CELL_XR = "BECKMAN_VI_CELL_XR"
CHEMOMETEC_NUCLEOVIEW = "CHEMOMETEC_NUCLEOVIEW"
EXAMPLE_WEYLAND_YUTANI = "EXAMPLE_WEYLAND_YUTANI"
LUMINEX_XPONENT = "LUMINEX_XPONENT"
MOLDEV_SOFTMAX_PRO = "MOLDEV_SOFTMAX_PRO"
NOVABIO_FLEX2 = "NOVABIO_FLEX2"
PERKIN_ELMER_ENVISION = "PERKIN_ELMER_ENVISION"
Expand All @@ -63,6 +67,7 @@ class Vendor(Enum):
Vendor.BECKMAN_VI_CELL_XR: ViCellXRParser,
Vendor.CHEMOMETEC_NUCLEOVIEW: ChemometecNucleoviewParser,
Vendor.EXAMPLE_WEYLAND_YUTANI: ExampleWeylandYutaniParser,
Vendor.LUMINEX_XPONENT: LuminexXponentParser,
Vendor.MOLDEV_SOFTMAX_PRO: SoftmaxproParser,
Vendor.NOVABIO_FLEX2: NovaBioFlexParser,
Vendor.PERKIN_ELMER_ENVISION: PerkinElmerEnvisionParser,
Expand Down
4 changes: 2 additions & 2 deletions src/allotropy/parsers/lines_reader.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections.abc import Iterator
from io import StringIO
from re import search
from typing import Optional
from typing import Literal, Optional, Union

import chardet
import pandas as pd
Expand Down Expand Up @@ -121,7 +121,7 @@ def pop_csv_block_as_df(
self,
empty_pat: str = EMPTY_STR_PATTERN,
*,
header: Optional[int] = None,
header: Optional[Union[int, Literal["infer"]]] = None,
sep: Optional[str] = ",",
as_str: bool = False,
) -> Optional[pd.DataFrame]:
Expand Down
Empty file.
160 changes: 160 additions & 0 deletions src/allotropy/parsers/luminex_xponent/luminex_xponent_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
from allotropy.allotrope.models.multi_analyte_profiling_benchling_2024_01_multi_analyte_profiling import (
AnalyteAggregateDocument,
AnalyteDocumentItem,
CalibrationAggregateDocument,
CalibrationDocumentItem,
DataSystemDocument,
DeviceControlAggregateDocument,
DeviceControlDocumentItem,
DeviceSystemDocument,
ErrorAggregateDocument,
ErrorDocumentItem,
MeasurementAggregateDocument,
MeasurementDocumentItem,
Model,
MultiAnalyteProfilingAggregateDocument,
MultiAnalyteProfilingDocumentItem,
SampleDocument,
)
from allotropy.allotrope.models.shared.definitions.custom import (
TQuantityValueMicroliter,
TQuantityValueNumber,
TQuantityValueRelativeFluorescenceUnit,
TQuantityValueUnitless,
)
from allotropy.allotrope.models.shared.definitions.definitions import (
TStatisticDatumRole,
)
from allotropy.constants import ASM_CONVERTER_NAME, ASM_CONVERTER_VERSION
from allotropy.named_file_contents import NamedFileContents
from allotropy.parsers.lines_reader import CsvReader, read_to_lines
from allotropy.parsers.luminex_xponent.luminex_xponent_structure import (
Data,
Header,
Measurement,
)
from allotropy.parsers.utils.uuids import random_uuid_str
from allotropy.parsers.vendor_parser import VendorParser

DEFAULT_SOFTWARE_NAME = "xPONENT"
DEFAULT_CONTAINER_TYPE = "well plate"
DEFAULT_DEVICE_TYPE = "multi analyte profiling analyzer"


class LuminexXponentParser(VendorParser):
def to_allotrope(self, named_file_contents: NamedFileContents) -> Model:
lines = read_to_lines(named_file_contents.contents)
reader = CsvReader(lines)
data = Data.create(reader, self.timestamp_parser)
return self._get_model(named_file_contents.original_file_name, data)

def _get_model(self, file_name: str, data: Data) -> Model:
header = data.header
return Model(
field_asm_manifest="http://purl.allotrope.org/manifests/multi-analyte-profiling/BENCHLING/2024/01/multi-analyte-profiling.manifest",
multi_analyte_profiling_aggregate_document=MultiAnalyteProfilingAggregateDocument(
device_system_document=DeviceSystemDocument(
model_number=header.model_number,
equipment_serial_number=header.equipment_serial_number,
calibration_aggregate_document=CalibrationAggregateDocument(
calibration_document=[
CalibrationDocumentItem(
calibration_name=calibration_item.name,
calibration_report=calibration_item.report,
calibration_time=calibration_item.time,
)
for calibration_item in data.calibration_data
]
),
),
data_system_document=DataSystemDocument(
data_system_instance_identifier=header.data_system_instance_identifier,
file_name=file_name,
software_name=DEFAULT_SOFTWARE_NAME,
software_version=header.software_version,
ASM_converter_name=ASM_CONVERTER_NAME,
ASM_converter_version=ASM_CONVERTER_VERSION,
),
multi_analyte_profiling_document=[
MultiAnalyteProfilingDocumentItem(
analyst=header.analyst,
measurement_aggregate_document=MeasurementAggregateDocument(
analytical_method_identifier=header.analytical_method_identifier,
method_version=header.method_version,
experimental_data_identifier=header.experimental_data_identifier,
container_type=DEFAULT_CONTAINER_TYPE,
plate_well_count=TQuantityValueNumber(
value=header.plate_well_count
),
measurement_document=[
self._get_measurement_document_item(
measurement,
header,
data.minimum_bead_count_setting,
)
for measurement in data.measurement_list.measurements
],
),
)
],
),
)

def _get_measurement_document_item(
self,
measurement: Measurement,
header_data: Header,
minimum_bead_count_setting: float,
) -> MeasurementDocumentItem:
error_aggregate_document = None
if measurement.errors:
error_aggregate_document = ErrorAggregateDocument(
error_document=[
ErrorDocumentItem(error=error) for error in measurement.errors
]
)

return MeasurementDocumentItem(
measurement_identifier=random_uuid_str(),
measurement_time=header_data.measurement_time,
sample_document=SampleDocument(
sample_identifier=measurement.sample_identifier,
location_identifier=measurement.location_identifier,
),
device_control_aggregate_document=DeviceControlAggregateDocument(
device_control_document=[
DeviceControlDocumentItem(
device_type=DEFAULT_DEVICE_TYPE,
sample_volume_setting=TQuantityValueMicroliter(
value=header_data.sample_volume_setting
),
dilution_factor_setting=TQuantityValueUnitless(
value=measurement.dilution_factor_setting
),
detector_gain_setting=header_data.detector_gain_setting,
minimum_assay_bead_count_setting=TQuantityValueUnitless(
value=minimum_bead_count_setting
),
)
]
),
assay_bead_count=TQuantityValueNumber(value=measurement.assay_bead_count),
analyte_aggregate_document=AnalyteAggregateDocument(
analyte_document=[
AnalyteDocumentItem(
analyte_identifier=random_uuid_str(),
analyte_name=analyte.analyte_name,
assay_bead_identifier=analyte.assay_bead_identifier,
assay_bead_count=TQuantityValueNumber(
value=analyte.assay_bead_count
),
fluorescence=TQuantityValueRelativeFluorescenceUnit(
value=analyte.fluorescence,
has_statistic_datum_role=TStatisticDatumRole.median_role,
),
)
for analyte in measurement.analytes
]
),
error_aggregate_document=error_aggregate_document,
)
Loading

0 comments on commit 2ab4e76

Please sign in to comment.