Cytiva ÄKTA / UNICORN results
ÄKTA protein-purification systems are run by Cytiva UNICORN. UNICORN 3–5 save .res result files; UNICORN 6 and 7 keep results in a database and export them as .zip files. OpenReadout reads both and returns the curves, the logbook, fraction and injection marks, and the method; from 6/7 exports also UNICORN’s peak tables.
Derived from hex dumps and XML of public files (ÄKTAprime, Ettan LC, ÄKTA pure and ÄKTA avant), allotropy’s cytiva_unicorn reader (MIT) read as prior art, and the public MS-NRBF specification; PyCORN (GPL-2.0) and allotropy are run as black boxes for ground truth. Provenance: docs/provenance/cytiva-unicorn.md. Crate: openreadout-fplc.
| format id | files | reads | confidence |
|---|---|---|---|
cytiva-unicorn-res |
.res (UNICORN 3-5 result files; ÄKTAprime, Ettan LC; header text UNICORN 3.10) |
curves, logbook, fraction and injection marks, method text | low (evidence rubric, docs/assurance.md) |
cytiva-unicorn-zip |
.zip written by UNICORN 6/7 Export result |
curves, logbook, fraction and injection marks, UNICORN’s peak tables, system, instrument, column, method | medium (evidence rubric, docs/assurance.md) |
Not read: the UNICORN 6/7 database itself and its backups, .result/.bak files, method files,
report PDFs. A zip that is not a result export (no Result.xml and Chrom.<n>.Xml) is refused
with exit 6 and a hint.
The model (both variants)
A UNICORN result is one chromatography run: curves recorded against time (UV absorbance at up to three wavelengths, conductivity, pH, pressures, temperatures, the %B the pumps deliver, flows), event lists (the logbook of method instructions, fraction marks, injection marks) and, in UNICORN 6/7 exports, peak tables the software integrated.
Traces: one per curve
name: the curve name as the file gives it (UV 1_280,Cond,% Cond,Conc B,System pressure,UV1_215nm,SAPA_215nm). With several chromatograms in one export the names start withChrom.<n>:.- channel 0: the values, named by the curve kind (below), in the file’s unit (
mAU,mS/cm,%,MPa,°C,ml/min,cm/h,CV/h,cm; pH has none). Channel 1volume: the retention volume of each sample in ml, counted from the method start, as stored. sample_rate_hzandstart_s: the curve is sampled at a fixed interval from the method start; sample i is atstart_s + i / sample_rate_hzseconds.tracestatistics give both the time of the maximum (argmax_time_s) and its retention volume (argmax_axis_value, ml).extra.axis={quantity: retention_volume, unit: ml, irregular: true, channel: 1}: the volume is the curve’s abscissa (UNICORN plots against volume).trace --x-range A:Bselects by volume.analyze chromatogramandanalyze peakswork on the time base (minutes).
Trace extra (our vocabulary):
| key | meaning |
|---|---|
kind |
uv, conductivity, conductivity_percent, concentration_b, ph, pressure, temperature, flow, other (from the unit, the name and the UNICORN 7 curve type) |
wavelength_nm |
UV wavelength from the curve name (UV 1_280 → 280; a channel named …_0 is a switched-off monitor channel and has none) |
original |
true for curves the instrument recorded, false for curves the software evaluated afterwards (smoothed, baseline-corrected, cut) |
interval_min, start_min |
sampling interval and time of sample 0 after the method start, minutes |
injection_volume_ml |
the volume of the last injection mark: UNICORN draws retention volumes from it (subtract it to match UNICORN’s axis) |
curve_type |
UNICORN 7’s own curve type (UV, Conduction, pH, Pressure, Temperature, Other) |
curve_number |
UNICORN 7’s curve number (peak tables refer to it) |
isochrone |
time (sampled in time) or volume (an evaluated curve on a volume grid: it has volumes but no times) |
volume_step_ml, volume_start_ml |
the volume grid of an evaluated curve |
column_volume_ml |
the column volume the export records with the curve |
uv_path_length, uv_nominal_path_length, uv_normalized_to_nominal_path |
UV flow-cell path length (cm) and whether values are normalized to the nominal path |
display_decimals |
decimals UNICORN displays |
method_start |
the method start with its UTC offset (UNICORN 7) |
member |
the export member that holds the points |
block, channel_label, time_axis_label, volume_resolution_ml, time_offset_ms |
.res: the block name, its column labels, the volume resolution (the stored integer’s factor) and a sub-sample start offset (see below) |
Tables
Event lists come first, one table each, then the peak tables.
logbook,fractions,injections(and any other event list by its lower-case name): columnstime(min),volume(ml) and the text as a category code:text(the logbook line),fraction(the tube or well label,Waste),injection(the injection number or text). Tableextra.kind=events.- Vendor peak tables (UNICORN 6/7, named as UNICORN names them, e.g.
UV 1_280@01,PEAK (1)): one row per peak, UNICORN’s integration as stored:retention(the maximum),start,end,height,area,percent_of_total_area,percent_of_total_peak_area,width,width_at_half_height,resolution,asymmetry,sigma,start_endpoint_height,end_endpoint_height,average_conductivity, andnamewhen peaks are named. Retentions are in ml (or min when the table’s basis is time) from the injection the table is zeroed at (extra.zero_at_injection); heights in the curve’s unit, areas in unit·ml. Tableextra:kind=vendor_peaks,trace(our curve),retention_basis,curve_number,detected_peaks,total_peak_area,total_area_evaluated_peaks,ratio_peak_area_total_area,max_peaks,zero_at_injection,column_volume_ml,algorithm,technique,created,height_reference(baselineorzero),baseline_curve_numberandbaseline_trace(the evaluated baseline curve the table is measured above, when it names one). - What UNICORN’s peak values mean (validated on the exports of
docs/provenance/cytiva-unicorn.md):height= curve − baseline atretention;area= the trapezoid integral of curve − baseline over retention volume fromstarttoend(ends interpolated);start_endpoint_heightandend_endpoint_height= curve − baseline at the limits;width=end−start. The baseline is the table’sbaseline_trace(UNICORN’s…BASEMevaluated curve on a volume grid); a table without one measures from zero, so its heights equal the curve. A drifting UV signal therefore gives heights below the curve maximum; subtract the baseline trace to reproduce them.
Experiment facts
instrument: vendor Cytiva (ÄKTA), software UNICORN, the model (.res: the system named in
the method dump or strategy notes, e.g. AKTAprime, EttanLC; UNICORN 7: the instrument
configuration, e.g. AKTA pure 25), the UNICORN version (UNICORN 7). method: name (the logbook’s
Method Run … Method: <name>, the method file, or the method description), technique
(size-exclusion CHMO:0001013, affinity CHMO:0001006 or ion-exchange CHMO:0001014 when the method or
column says so, else liquid chromatography CHMO:0001004) and parameters: column,
column_volume (mL), column_article_number, column_bed_height (cm), flow_rate (mL/min),
fraction_volume (mL), uv_wavelength_1…3 (nm), technique (UNICORN’s own word), run_duration
(min), firmware. acquisition: operator (.res: the header user; UNICORN 7: the result’s
creator), start (UNICORN 7: the method start with its offset; .res: derived, see below), end
(.res), duration. sample.id when the method has a Sample_ID variable with a value.
.res (UNICORN 3-5)
Little-endian. Header: 11 47 11 47; u32 at 8 the directory offset; u32 at 16 the file size (a
mismatch is reported as truncation); text at 0x18 (UNICORN 3.10 in both files: a layout version,
not the software version, reported as format_version); u32 Unix times at 0x68 and 0x6c; the user
name at 0x76.
Directory: 344-byte entries until an empty name: bytes 0-5 a type word, 6-301 the NUL-terminated
name, then u32 data size, u32 allocated size, u32 data offset, u32 header size (240 curves, 552
event lists, 0 others). Curve names are <run name>:<run number>_<curve>; an evaluated curve’s
name starts with = and its type word has 03 where recorded curves have 01.
Curve and event blocks start with u16 6, u16 stored columns, u16 1, then 78-byte column
descriptors: u16 78, u16 flags (0x8001 time, 0x8002 volume, 0x4001/0x4000 value), a
40-byte label, a 16-byte unit, u16 storage (0x0104 int32, 0x0308 float64, 0x044c 76-byte
text), float64 factor, float64 second value.
- Curves: three descriptors; (int32 volume, int32 value) pairs follow the header; physical =
stored × factor (a factor of 1/k divides by k so 15328 × 0.001 is 15.328). The time column is not
stored: its factor is the sampling interval in minutes and sample i is at i × interval. Its
second value, when not 0, is read as a start offset in milliseconds (always below one sampling
interval in the files seen;
extra.time_offset_ms; an assumption, see provenance). - Event lists: 180-byte records: float64 time (min), float64 volume (ml), two 76-byte texts (joined), float64 value, int32 flags.
- Text blocks (
CreationNotes,Methods,MethodStrategyNotes,ResultStrategyNotes,Techniques,METHODINFO,Method Signatures) are kept in the vendor tree; binary blocks are listed byinfo --view structure. - Times:
acquisition.ended_atis the header time at 0x6c (UTC; it matched the run end in both files);started_atis that time minus the longest curve’s duration (the logbook’sMethod Runtext is local time without a zone and is kept in the vendor tree asmethod_run); the time at 0x68 is kept asheader_time_0x68(it matched the start in one file and not in the other).
UNICORN 6/7 result export (.zip)
A zip whose members are XML documents and nested zips (members may sit in a folder or carry
Copy of prefixes and .zip suffixes when a user re-packed the export: names are matched on
their base name without them):
Result.xml: result name, system name, creator and times, run information (base64 text: used columns, instrument server version, run start), and the method’s variable values (ResultSearchCriteriakeyword/value/unit triples; all in the vendor tree, the common ones as method parameters).Chrom.<n>.Xml: per curve its name,CurveDataType,AmplitudeUnit,IsoChroneType,DistanceBetweenPointsandDistanceToStartPoint(minutes for time curves, ml for volume-grid curves),MethodStartTimeand its UTC offset,IsOriginalData,ColumnVolume,CurveUVInfo, and the member holding the points; event curves (Fraction,Injection,Logbook, …) withEventTime(min),EventVolume(ml),EventText; peak tables.- Nested zips (
SystemData,InstrumentConfigurationData,ColumnTypeData,MethodData,MethodDocumentationData,StrategyData, …) holdXmlDataType(System.String) andXml: a .NET binary-serialized string (MS-NRBF: a 17-byte serialization header, a string record with a 7-bit length prefix, a message end). A damaged or non-serialized metadata document is a warning, never fatal. - Curve points (
Chrom.<n>_<curve>_True): a nested zip (ZIP64 local headers, padded with zeros after the end record) withCoordinateData.VolumesandCoordinateData.Amplitudes, each an MS-NRBF array of 32-bit floats (record 15, primitive type 11) and a…DataTypemember (System.Single[]). The declared array length must fill the member exactly; a member cut short or holding bare floats is refused (the curve is left out,checkreports it). An evaluated curve on a volume grid may store no volumes: they arevolume_start_ml + i × volume_step_ml. - Time of sample i =
DistanceToStartPoint + i × DistanceBetweenPoints(validated against every event of six exports, see provenance). A curve with a zero or missing interval keeps its volumes and has no times (a warning).
Manifest.xml lists members with CRC codes that do not match the nested zips; they are not used.
Every member read is checked against its zip CRC-32.
Validation
cargo test -p openreadout-corpus-tests --features corpus (tests/fplc_oracle/mod.rs): every
curve PyCORN returns matches ours at 128 sampled positions (exactly for UNICORN 7 floats; within
1e-9 for .res), allotropy’s data cubes agree (it converts conductivity to S/m), fraction and
injection marks match, the 22 vendor peaks of the two exports with peak tables (one measured from
zero, one above a baseline curve) have their height (curve − baseline at the retention) and area
(∫ curve − baseline) within 0.5 % of UNICORN’s and their width equal to end − start, and damaged
curve members are refused.
PyCORN and allotropy both read UNICORN 7 floats from byte 47 to 48 bytes before a member’s end, so
they drop the first five and last eleven samples of every curve; the record layout accounts for
every byte, and the event (time, volume) pairs fit our sample indexing, not theirs. PyCORN applies
fixed scales to .res pH and pressure (÷10, ÷100) where the ÄKTAprime file declares 0.01 and
0.001; its method dump’s 300 kPa pressure limit, which PyCORN’s values would exceed for the whole
run, shows the declared factor is right.
Vocabulary (public API of openreadout-fplc)
| identifier | meaning |
|---|---|
UnicornResReader |
reader of UNICORN 3-5 .res result files |
UnicornZipReader |
reader of UNICORN 6/7 result exports (.zip) |
UNICORN_RES_FORMAT_ID |
cytiva-unicorn-res |
UNICORN_ZIP_FORMAT_ID |
cytiva-unicorn-zip |
FplcDataset |
the dataset both readers return |
How this reader was derived, file by file: provenance log.