Skip to content

Commit 90d6ab2

Browse files
chore: Cytiva Unicorn - silence the remaining get_unread warnings and clean up logic a bit (#1056)
1 parent dff8542 commit 90d6ab2

13 files changed

Lines changed: 148 additions & 123 deletions

File tree

src/allotropy/parsers/cytiva_unicorn/reader/unicorn_zip_handler.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ def get_system_data(self) -> StrictXmlElement:
6565
system_data = self.get_zip_from_pattern("SystemData(.zip)?$")
6666
b_stream = system_data.get_file_from_pattern("^Xml$")
6767
raw_content = self.filter_xml_metadata(b_stream).read()
68-
return StrictXmlElement.create_from_bytes(raw_content)
68+
return StrictXmlElement.create_from_bytes(
69+
raw_content, mark_read={"attr:FormatVersion", "attr:UNICORNVersion"}
70+
)
6971

7072
def get_results(self) -> StrictXmlElement:
7173
b_stream = self.get_file_from_pattern("Result.xml$")
@@ -77,12 +79,16 @@ def get_instrument_config_data(self) -> StrictXmlElement:
7779
instrument_config_data = self.get_zip_from_pattern(instrument_regex)
7880
b_stream = instrument_config_data.get_file_from_pattern("^Xml$")
7981
raw_content = self.filter_xml_metadata(b_stream).read()
80-
return StrictXmlElement.create_from_bytes(raw_content)
82+
return StrictXmlElement.create_from_bytes(
83+
raw_content, mark_read={"attr:FormatVersion", "attr:UNICORNVersion"}
84+
)
8185

8286
def get_evaluation_log(self) -> StrictXmlElement:
8387
b_stream = self.get_file_from_pattern("EvaluationLog.xml$")
8488
raw_content = self.filter_xml_metadata(b_stream).read()
85-
return StrictXmlElement.create_from_bytes(raw_content)
89+
return StrictXmlElement.create_from_bytes(
90+
raw_content, mark_read={"attr:FormatVersion", "attr:UNICORNVersion"}
91+
)
8692

8793
def get_chrom_1(self) -> StrictXmlElement:
8894
b_stream = self.get_file_from_pattern("Chrom.1.Xml$")
@@ -93,4 +99,6 @@ def get_column_type_data(self) -> StrictXmlElement:
9399
column_type_data = self.get_zip_from_pattern("ColumnTypeData(.zip)?$")
94100
b_stream = column_type_data.get_file_from_pattern("^Xml$")
95101
raw_content = self.filter_xml_metadata(b_stream).read()
96-
return StrictXmlElement.create_from_bytes(raw_content)
102+
return StrictXmlElement.create_from_bytes(
103+
raw_content, mark_read={"attr:FormatVersion", "attr:UNICORNVersion"}
104+
)

src/allotropy/parsers/cytiva_unicorn/structure/measurement_group.py

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -62,34 +62,32 @@ def create_measurement_groups(
6262
FlowMeasurement.create_or_none(handler, elements, static_docs),
6363
TemperatureMeasurement.create_or_none(handler, elements, static_docs),
6464
]
65+
# Some curves may not be read due not matching any supported measurement type, mark these as read.
66+
for element in elements:
67+
element.mark_read("attr:CurveDataType")
68+
69+
chrom_1.mark_read(
70+
{
71+
"element:TimeUnit",
72+
"element:VolumeUnit",
73+
"element:IsReadonly",
74+
"attr:UNICORNVersion",
75+
}
76+
)
6577

66-
measurement_group = [
78+
return [
6779
MeasurementGroup(
6880
measurements=[measurement for measurement in measurements if measurement],
6981
fractions=create_fractions(event_curves) if event_curves else None,
7082
logs=create_logs(event_curves) if event_curves else None,
71-
measurement_aggregate_custom_info={},
72-
)
73-
]
74-
75-
chrom_1.mark_read({"element:TimeUnit", "element:VolumeUnit", "element:IsReadonly"})
76-
custom_info = chrom_1.get_unread()
77-
78-
if measurement_group[0].measurement_aggregate_custom_info is not None:
79-
custom_info.pop("UNICORNVersion")
80-
custom_info.update(
81-
{
83+
measurement_aggregate_custom_info={
8284
"RunIndex": results.get_sub_text_or_none("RunIndex"),
8385
"RunType": results.get_sub_text_or_none("RunType"),
8486
"Name": results.get_sub_text_or_none("Name"),
85-
}
86-
)
87-
custom_info_sorted = dict(sorted(custom_info.items()))
88-
measurement_group[0].measurement_aggregate_custom_info.update(
89-
custom_info_sorted
87+
**dict(sorted(chrom_1.get_unread().items())),
88+
},
9089
)
91-
92-
return measurement_group
90+
]
9391

9492

9593
def create_fractions(event_curves: StrictXmlElement) -> list[Fraction]:
@@ -142,10 +140,12 @@ def create_logs(event_curves: StrictXmlElement) -> list[Log]:
142140
"Method",
143141
"Manual",
144142
]:
143+
event.get_unread()
145144
continue
146145

147146
event_text = event.get_sub_text_or_none("EventText")
148147
if event_text is None:
148+
event.get_unread()
149149
continue
150150

151151
event_subtype = event.get_attr_or_none("EventSubType")

src/allotropy/parsers/cytiva_unicorn/structure/measurements/absorbance.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,16 @@ def create_or_none(
5757
elements: list[StrictXmlElement],
5858
static_docs: StaticDocs,
5959
) -> UnicornMeasurement:
60+
element = assert_not_none(
61+
cls.filter_curve_or_none(elements, cls.get_curve_regex()),
62+
"Unable to find curve data for absorbance measurement.",
63+
)
6064
measurement = cls.get_measurement(
6165
static_docs=static_docs,
6266
chromatogram_data_cube=assert_not_none(
6367
cls.get_data_cube_or_none(
6468
handler,
65-
cls.filter_curve_or_none(elements, cls.get_curve_regex()),
69+
element,
6670
DataCubeComponent(
6771
type_=FieldComponentDatatype.float,
6872
concept="absorbance",
@@ -75,13 +79,13 @@ def create_or_none(
7579
DeviceControlDoc(
7680
device_type=DEVICE_TYPE,
7781
start_time=static_docs.start_time,
78-
device_control_custom_info={},
82+
device_control_custom_info=cls.get_device_control_custom_info(
83+
element
84+
),
7985
)
8086
],
8187
peaks=cls.get_peaks(handler),
82-
)
83-
cls.add_custom_info(
84-
measurement, cls.filter_curve_or_none(elements, cls.get_curve_regex())
88+
processed_data_custom_info=cls.get_processed_data_custom_info(element),
8589
)
8690
return measurement
8791

@@ -154,6 +158,7 @@ def get_peaks_custom_info(
154158
@classmethod
155159
def get_peaks(cls, handler: UnicornZipHandler) -> list[Peak]:
156160
chrom_1 = handler.get_chrom_1()
161+
chrom_1.get_unread() # chrom_1 data is read in create_measurement_groups
157162
peaks = chrom_1.recursive_find_or_none(["PeakTables", "PeakTable", "Peaks"])
158163
output = []
159164
for idx, peak in enumerate(peaks.findall("Peak") if peaks else [], start=1):

src/allotropy/parsers/cytiva_unicorn/structure/measurements/concentration.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def create_or_none(
2828
elements: list[StrictXmlElement],
2929
static_docs: StaticDocs,
3030
) -> UnicornMeasurement | None:
31+
element = cls.filter_curve_or_none(elements, r"^Conc B$")
3132
measurement = cls.get_measurement(
3233
static_docs=static_docs,
3334
device_control_docs=[
@@ -36,19 +37,19 @@ def create_or_none(
3637
start_time=static_docs.start_time,
3738
solvent_conc_data_cube=cls.get_data_cube_or_none(
3839
handler,
39-
cls.filter_curve_or_none(elements, r"^Conc B$"),
40+
element,
4041
DataCubeComponent(
4142
type_=FieldComponentDatatype.float,
4243
concept="solvent concentration",
4344
unit="%",
4445
),
4546
),
46-
device_control_custom_info={},
47+
device_control_custom_info=cls.get_device_control_custom_info(
48+
element
49+
),
4750
),
4851
],
49-
)
50-
cls.add_custom_info(
51-
measurement, cls.filter_curve_or_none(elements, r"^Conc B$")
52+
processed_data_custom_info=cls.get_processed_data_custom_info(element),
5253
)
5354
return measurement if cls.is_valid(cls.get_data_cubes(measurement)) else None
5455

src/allotropy/parsers/cytiva_unicorn/structure/measurements/conductivity.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ def create_or_none(
3131
elements: list[StrictXmlElement],
3232
static_docs: StaticDocs,
3333
) -> UnicornMeasurement | None:
34+
element = cls.filter_curve_or_none(elements, r"^Cond$")
3435
measurement = cls.get_measurement(
3536
static_docs=static_docs,
3637
chromatogram_data_cube=cls.get_data_cube_or_none(
3738
handler,
38-
cls.filter_curve_or_none(elements, r"^Cond$"),
39+
element,
3940
DataCubeComponent(
4041
type_=FieldComponentDatatype.float,
4142
concept="electric conductivity",
@@ -56,11 +57,13 @@ def create_or_none(
5657
DeviceControlDoc(
5758
device_type=DEVICE_TYPE,
5859
start_time=static_docs.start_time,
59-
device_control_custom_info={},
60+
device_control_custom_info=cls.get_device_control_custom_info(
61+
element
62+
),
6063
)
6164
],
65+
processed_data_custom_info=cls.get_processed_data_custom_info(element),
6266
)
63-
cls.add_custom_info(measurement, cls.filter_curve_or_none(elements, r"^Cond$"))
6467
return measurement if cls.is_valid(cls.get_data_cubes(measurement)) else None
6568

6669
@classmethod

src/allotropy/parsers/cytiva_unicorn/structure/measurements/flow.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ def create_device_control(
3636
start_time=start_time,
3737
sample_flow_data_cube=sample_flow_data_cube,
3838
system_flow_data_cube=system_flow_data_cube,
39-
device_control_custom_info={},
4039
)
4140

4241
@classmethod

src/allotropy/parsers/cytiva_unicorn/structure/measurements/generic.py

Lines changed: 54 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
from re import search
4+
from typing import Any
45

56
from allotropy.allotrope.schema_mappers.adm.liquid_chromatography.benchling._2023._09.liquid_chromatography import (
67
DeviceControlDoc,
@@ -28,64 +29,58 @@
2829

2930
class UnicornMeasurement(Measurement):
3031
@classmethod
31-
def add_custom_info(
32-
cls, measurement: Measurement, base_element: StrictXmlElement | None
33-
) -> None:
34-
if base_element:
35-
device_control_custom_info = {
36-
"ColumnVolume": base_element.get_sub_text_or_none("ColumnVolume"),
37-
"ScanInterval": base_element.get_sub_text_or_none("ScanInterval"),
38-
}
32+
def get_device_control_custom_info(
33+
cls, base_element: StrictXmlElement | None
34+
) -> dict[str, Any]:
35+
if not base_element:
36+
return {}
37+
return {
38+
"ColumnVolume": base_element.get_sub_text_or_none("ColumnVolume"),
39+
"ScanInterval": base_element.get_sub_text_or_none("ScanInterval"),
40+
}
3941

40-
processed_data_custom_info = {
41-
"AmplitudePrecision": base_element.get_sub_text_or_none(
42-
"AmplitudePrecision"
43-
),
44-
"AmplitudeUnit": base_element.get_sub_text_or_none("AmplitudeUnit"),
45-
"ChromatogramID": base_element.get_sub_text_or_none("ChromatogramID"),
46-
"ChromatogramName": base_element.get_sub_text_or_none(
47-
"ChromatogramName"
48-
),
49-
"ChromatogramStartTime": base_element.get_sub_text_or_none(
50-
"ChromatogramStartTime"
51-
),
52-
"ChromatogramStartTimeUtcOffsetMinutes": base_element.get_sub_text_or_none(
53-
"ChromatogramStartTimeUtcOffsetMinutes"
54-
),
55-
"ColumnVolumeUnitName": base_element.get_sub_text_or_none(
56-
"ColumnVolumeUnitName"
57-
),
58-
"CurveDataType": base_element.get_sub_text_or_none("CurveDataType"),
59-
"CurveNumber": base_element.get_sub_text_or_none("CurveNumber"),
60-
"DistanceBetweenPoints": base_element.get_sub_text_or_none(
61-
"DistanceBetweenPoints"
62-
),
63-
"DistancetoStartPoints": base_element.get_sub_text_or_none(
64-
"DistancetoStartPoints"
65-
),
66-
"IsExternal": base_element.get_sub_text_or_none("IsExternal"),
67-
"IsOriginalData": base_element.get_sub_text_or_none("IsOriginalData"),
68-
"IsReadOnly": base_element.get_sub_text_or_none("IsReadOnly"),
69-
"IsoChroneType": base_element.get_sub_text_or_none("IsoChroneType"),
70-
"MethodStartTime": base_element.get_sub_text_or_none("MethodStartTime"),
71-
"MethodStartTimeUtcOffsetMinutes": base_element.get_sub_text_or_none(
72-
"MethodStartTimeUtcOffsetMinutes"
73-
),
74-
"TimeUnit": base_element.get_sub_text_or_none("TimeUnit"),
75-
"VolumeUnit": base_element.get_sub_text_or_none("VolumeUnit"),
76-
}
77-
if (
78-
measurement.device_control_docs[0].device_control_custom_info
79-
is not None
80-
):
81-
measurement.device_control_docs[0].device_control_custom_info.update(
82-
device_control_custom_info
83-
)
84-
if measurement.processed_data_custom_info is not None:
85-
processed_data_custom_info.update(base_element.get_unread())
86-
measurement.processed_data_custom_info.update(
87-
processed_data_custom_info
88-
)
42+
@classmethod
43+
def get_processed_data_custom_info(
44+
cls, base_element: StrictXmlElement | None
45+
) -> dict[str, Any]:
46+
if not base_element:
47+
return {}
48+
return {
49+
"AmplitudePrecision": base_element.get_sub_text_or_none(
50+
"AmplitudePrecision"
51+
),
52+
"AmplitudeUnit": base_element.get_sub_text_or_none("AmplitudeUnit"),
53+
"ChromatogramID": base_element.get_sub_text_or_none("ChromatogramID"),
54+
"ChromatogramName": base_element.get_sub_text_or_none("ChromatogramName"),
55+
"ChromatogramStartTime": base_element.get_sub_text_or_none(
56+
"ChromatogramStartTime"
57+
),
58+
"ChromatogramStartTimeUtcOffsetMinutes": base_element.get_sub_text_or_none(
59+
"ChromatogramStartTimeUtcOffsetMinutes"
60+
),
61+
"ColumnVolumeUnitName": base_element.get_sub_text_or_none(
62+
"ColumnVolumeUnitName"
63+
),
64+
"CurveDataType": base_element.get_attr_or_none("CurveDataType"),
65+
"CurveNumber": base_element.get_sub_text_or_none("CurveNumber"),
66+
"DistanceBetweenPoints": base_element.get_sub_text_or_none(
67+
"DistanceBetweenPoints"
68+
),
69+
"DistancetoStartPoints": base_element.get_sub_text_or_none(
70+
"DistancetoStartPoints"
71+
),
72+
"IsExternal": base_element.get_sub_text_or_none("IsExternal"),
73+
"IsOriginalData": base_element.get_sub_text_or_none("IsOriginalData"),
74+
"IsReadOnly": base_element.get_sub_text_or_none("IsReadOnly"),
75+
"IsoChroneType": base_element.get_sub_text_or_none("IsoChroneType"),
76+
"MethodStartTime": base_element.get_sub_text_or_none("MethodStartTime"),
77+
"MethodStartTimeUtcOffsetMinutes": base_element.get_sub_text_or_none(
78+
"MethodStartTimeUtcOffsetMinutes"
79+
),
80+
"TimeUnit": base_element.get_sub_text_or_none("TimeUnit"),
81+
"VolumeUnit": base_element.get_sub_text_or_none("VolumeUnit"),
82+
**base_element.get_unread(),
83+
}
8984

9085
@classmethod
9186
def filter_curve_or_none(
@@ -141,6 +136,7 @@ def get_measurement(
141136
processed_data_chromatogram_data_cube: DataCube | None = None,
142137
derived_column_pressure_data_cube: DataCube | None = None,
143138
peaks: list[Peak] | None = None,
139+
processed_data_custom_info: dict[str, Any] | None = None,
144140
) -> UnicornMeasurement:
145141
return UnicornMeasurement(
146142
measurement_identifier=random_uuid_str(),
@@ -166,7 +162,7 @@ def get_measurement(
166162
"sample_volume_3": static_docs.sample_volume_3,
167163
},
168164
peaks=peaks,
169-
processed_data_custom_info={},
165+
processed_data_custom_info=processed_data_custom_info,
170166
)
171167

172168
@classmethod

src/allotropy/parsers/cytiva_unicorn/structure/measurements/ph.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ def create_or_none(
2828
elements: list[StrictXmlElement],
2929
static_docs: StaticDocs,
3030
) -> UnicornMeasurement | None:
31+
element = cls.filter_curve_or_none(elements, r"^pH$")
3132
measurement = cls.get_measurement(
3233
static_docs=static_docs,
3334
chromatogram_data_cube=cls.get_data_cube_or_none(
3435
handler,
35-
cls.filter_curve_or_none(elements, r"^pH$"),
36+
element,
3637
DataCubeComponent(
3738
type_=FieldComponentDatatype.float,
3839
concept="pH",
@@ -43,11 +44,13 @@ def create_or_none(
4344
DeviceControlDoc(
4445
device_type=DEVICE_TYPE,
4546
start_time=static_docs.start_time,
46-
device_control_custom_info={},
47+
device_control_custom_info=cls.get_device_control_custom_info(
48+
element
49+
),
4750
)
4851
],
52+
processed_data_custom_info=cls.get_processed_data_custom_info(element),
4953
)
50-
cls.add_custom_info(measurement, cls.filter_curve_or_none(elements, r"^pH$"))
5154
return measurement if cls.is_valid(cls.get_data_cubes(measurement)) else None
5255

5356
@classmethod

0 commit comments

Comments
 (0)