Skip to content

Plate-reader exports

Plate readers do not have one file format. Each vendor’s software writes a text, CSV or spreadsheet export meant to be opened in Excel; these are what labs keep and share. OpenReadout reads those exports under one format id, plate (family plate-reader), recognises the dialect from the content (never from the extension alone), and returns every plate read as one long-form table. SoftMax Pro binary documents (.pda of SoftMax Pro 5, .sda/.pda of SoftMax Pro 6/7) are decoded where their layout is validated (section “SoftMax Pro documents” below); anything else in them is refused with exit 6 and a hint to export text.

Derived from public exports (most from allotropy’s MIT-licensed test fixtures) and allotropy 0.1.146, read as prior art and run as a reference reader. See docs/provenance/plate-readers.md.

The table

One table per plate (a plate block, a plate repeat, or a physical plate). One row per value:

column dtype meaning
well uint32 well index, row-major from A1 = 0; the column’s extra.categories lists the names (A1, A2, …, P24, AF48), and export --to csv writes the name
row uint16 plate row, 1-based (A = 1; row 27 = AA, written by BMG as a)
col uint16 plate column, 1-based
read uint16 measurement channel, 1-based; extra.reads[read-1] describes it (label as the file writes it, mode, mode_basis for measured reads (see Read modes below), wavelength_nm/excitation_nm/emission_nm, unit, settings, calculated, origin = measured or calculated, and formula when the export stores the vendor software’s formula: EnVision Calculations:). When a table holds calculated reads, the column’s label lists every read with its origin (1 = LUM:Lum (luminescence, measured); 2 = NormLum (CALCULATED by the vendor software, not measured)), so table output says which values were measured
wavelength_nm float64 the row’s own for spectral scans (the scanned wavelength: excitation, emission or absorbance, as the read’s settings.scanned_wavelength says when the export names it), else the read’s detection wavelength: emission (fluorescence, luminescence) or measurement (absorbance) wavelength; NaN when none
time_s float64 elapsed time of a kinetic cycle; NaN for endpoint reads
value float64 the number as exported; NaN where the cell was not a number (the text is kept in extra.non_numeric)

Rows are ordered by read, time, wavelength, row, column. Reads with calculated: true hold values the vendor software derived (blank correction, ratios, normalizations, curve fits, dilution factors); raw measurements have calculated absent. Values are never rescaled: an absorbance export in OD stays OD.

Table extra: export (dialect id below), manufacturer, instrument {model, serial_number}, software, software_version, protocol, experiment, operator, plate, barcode, plate_type, plate_rows, plate_columns, plate_well_count, declared_well_count (from the plate type), wells_measured, read_type (endpoint, kinetic, spectrum), read_modes, reads, temperature_c, acquired_at (ISO-8601, local time; the exports record no zone), acquired_raw, saved_at (when the export was saved, where that is the only time it states: never a read time; → experiment.acquisition.saved_at), date_order_assumed, time_points, non_numeric_count, non_numeric (first 50: well, read, time, text), decimal_comma_values, layout (sample names and other plate maps, keyed by title then well; SkanIt: one map of sample names, well → name), layout_definitions (SkanIt), plus dialect keys listed below. vendor (info --view full) holds every header key/value pair as written, the container (encoding, delimiter or workbook kind) and verbatim sections (procedure text, protocol sheets, notes, groups, run log).

Geometry: the declared well count of the plate type when the file gives one and every value fits; otherwise the smallest standard plate (6, 12, 24, 48, 96, 384, 1536, 3456 wells) that holds every well seen.

Read modes

Every measured read has a mode (absorbance, fluorescence, luminescence, alpha, unknown) and, in extra.reads[], a mode_basis saying how the mode was established:

mode_basis meaning dialects
stated a field of the export names the mode BMG mode line, Gen5 Read step, SoftMax Pro read mode, SoftMax Pro 6/7 ReadMode, i-control and SparkControl Mode, Magellan Measurement mode, SkanIt step type
detector derived from the detector the export records for the read EnVision MeasInfo
label derived from keywords of the read’s label or technology name EnVision without a detector, Kaleido technology names, Gen5 exports without the procedure and Gen5 .xpt read names, generic matrices, SparkControl groups without a Mode
settings derived from the read’s settings (a measurement wavelength and no excitation or emission: absorbance) i-control and SparkControl groups without a Mode
undetermined nothing names the mode: unknown any

A derived mode (detector, label, settings) is listed in assurance.inferred with the rule that derived it; --strict returns it only when that rule was confirmed on development files. undetermined is listed in assurance.assumed.

Reading any export

  • Encoding: BOM first (UTF-8, UTF-16 LE/BE), then UTF-16 without a BOM (NUL bytes at every other position), then strict UTF-8, else Windows-1252 (0xB0 → °).
  • Lines: CRLF, LF and bare CR, mixed in one file (bmg-mars-lum-1536).
  • Fields: tab when tabs appear on at least half as many lines as commas, else comma (or semicolon). Comma fields follow CSV quoting; ="…" (Excel formula quoting, EnVision barcodes) is unwrapped.
  • Workbooks (.xlsx, .xlsm, .xls, .xlsb, .ods): read with calamine (pure Rust); every sheet becomes a cell grid, numbers stay numbers.
  • Numbers: plain decimals only (-0.066, 2.1e3). NaN, inf, OVRFLW, MISSED, Range?, Path?, ????? are non-numeric (NaN, text kept). A decimal comma (0,02) in a tab-delimited Gen5 or SoftMax Pro export is read as a decimal and counted (decimal_comma warning), as allotropy does.
  • Plate matrices (grid): a line of consecutive column numbers (1 2 3…, 01 02…, 1.0 in XLSX) after a corner cell (empty, <>, Abs, RLU, Sample, …), followed by lines whose first cell is an increasing row label (A…Z, AA…, or a…f after Z). Rows may be missing (partial plates, EnVision rows A/E/I/M).
  • Dates: M/D/Y (Gen5, EnVision, SoftMax), D/M/Y when the first field exceeds 12 (BMG 29/02/2016), D.M.Y (i-control), Y-M-D, ISO with offset (Kaleido, passed through), AM/PM clocks, a leading ' (i-control). Ambiguous M/D vs D/M is read month first and flagged date_order_assumed.

Dialects

export writer (nominative use) files detected by
gen5 Agilent BioTek Gen5 (Synergy, Cytation, Epoch, ELx) .txt, .xlsx, .xpt (binary, below) Software Version plus two of Procedure Details, Plate Number, Reader Type:, Reader Serial Number: in the first 80 lines; or, for an export without its file header, one of: a Layout line and a row ending in Well ID; Procedure Details/Procedure Summary and a Results line; a Curve Name<TAB>Curve Formula fit table (first 200 lines)
softmax-pro Molecular Devices SoftMax Pro (SpectraMax, FlexStation, FilterMax) .txt first line ##BLOCKS= n
bmg-mars BMG LABTECH MARS (PHERAstar, CLARIOstar, FLUOstar, POLARstar, SPECTROstar) .csv, .txt, workbook first line User: … with Path: or Test run no.:, then Test name:
bmg-smart-control BMG LABTECH SMART Control (VANTAstar, CLARIOstar Plus) .xlsx a Protocol Information sheet and Test ID: lines
envision PerkinElmer / Revvity EnVision Workstation .csv Plate information then Plate,Repeat,Barcode,…
kaleido Revvity Kaleido (EnVision Nexus, EnSight, VICTOR Nivo) .csv first line Results for … / EnSight Results from and Kaleido sections
tecan-i-control Tecan i-control (infinite, Spark), SparkControl .txt, .csv, .xlsx Application: Tecan i-control (or SparkControl) in the first three lines
tecan-magellan Tecan Magellan .xlsx a Well positions column and trailing .mth / Date of measurement lines
skanit Thermo Scientific SkanIt (Varioskan, Multiskan, Fluoroskan, Luminoskan) .xlsx sheets starting Measurement results with a step and Wavelength: line
generic anything else with plate matrices text, workbook at least one matrix of ≥ 2 rows × 3 columns with ≥ 4 numbers; detection likely for text extensions

Gen5

Without the file header. Gen5’s export options can leave out the header block (and with it the procedure), so the text starts at Procedure Details or directly at Layout/Results. Such an export is one plate. Procedure Details, when kept, is read as usual. Without it, the reads are named from the labels Gen5 writes after each matrix row or above each kinetic table (extra.reads_inferred_from_labels), and the label shows the mode: <read>:<ex>,<em> is fluorescence, <read>:Lum luminescence, <read>:<nm> or a bare <nm> title absorbance. Labels Gen5 derives (Blank <read>, [Concentration], Mean V [420]) name no read and stay calculated. Reader, serial number, date and plate type are then unknown, and a note says so. The embedded Layout (Well ID, Conc/Dil) is kept as with the header, so analyze assay finds standards, blanks and samples. On the corpus files made by dropping the header from two real exports, the results are byte-identical to those of the originals (tests/assay.rs cases gen5-linear-headerless, gen5-meanv-4pl-headerless).

Header key<TAB>value lines up to Procedure Details → software_version, Plate Number (table name, plate_number), Date + Time → acquired_at, Reader Type: → instrument.model, Reader Serial Number: → serial_number (n/a/Unknown dropped), Reading Type → reading_type, Experiment File Path: → experiment, Protocol File Path: → protocol. File names yymmdd_hhmmss_<barcode>_….txt give barcode (the pattern allotropy uses).

Procedure: Plate Type (declared well count from its first number); Read steps — a named step (Read<TAB>od then <TAB>Absorbance Endpoint) or unnamed (Read<TAB>Absorbance Endpoint); detail lines Wavelengths: 450, 600, Filter Set n with Excitation: 485/20, Emission: 528/20 and Optics/Mirror/Gain, Start: 300 nm, Stop: 700 nm, Step: 10 nm, Read Speed, Delay, Measurements/Data Point, Read Height, Integration Time, Light Source, Lamp Energy → read settings (read_speed, delay, measurements_per_point, read_height_mm, integration_time, light_source, lamp_energy, gain, optics, mirror, excitation_bandwidth_nm, emission_bandwidth_nm, step); Start Kinetic<TAB>Runtime h:mm:ss …, Interval h:mm:ss, n Reads → kinetic {description, runtime_s, interval_s, reads} and makes every read kinetic.

Gen5’s Excel export places the same matrices one column to the right (row letters in column B, column numbers from C) and writes Time as a time of day and Date as a date; both layouts are read.

Sections after the procedure, separated by blank lines: Actual Temperature: (first → temperature_c, all → actual_temperatures_c); Layout and Results matrices whose header row is <TAB>1<TAB>2…, rows A… with a trailing label column (od:600, DAPI/GFP:360/40,460/40, 460/40, LUM:Lum, Well ID) and continuation lines (blank row label) for further reads of the same row; a data label that names a procedure read (step:spec, or a bare spec for an unnamed step) is a measured read, anything else (NormLum, Max V [600]) is calculated; kinetic tables (Time, T° 600, then well names as columns; empty 0:00:00 padding rows after the last cycle are skipped; temperatures → kinetic_temperatures_c) and spectral tables (Wavelength, then wells), each titled by the line above (600, Spectral_Scan_Test:Spectrum). Several Software Version headers in one file are several plates, one table each.

SoftMax Pro

##BLOCKS= n, then n blocks each ending ~End (block_count_mismatch when different); Note: and Group: blocks are kept verbatim (vendor.sections.notes/groups); the last line Original Filename: …; Date Last Saved: … → header pairs and saved_at (the save time, which follows the read; the export states no read time, so acquired_at and experiment.acquisition.started_at stay empty and a note says so).

Plate: header fields by position (tab-separated): 1 name, 2 export version (1.3; others → export_version info), 3 format (PlateFormat/TimeFormat), 4 read type (Endpoint/Kinetic/Spectrum; Well Scan → unsupported_read_type), 5 read mode (Absorbance/Fluorescence/Luminescence); fluorescence modes have one extra field here (6: bottom read TRUE/FALSE → optics), shifting what follows by one: data type, pre-read, kinetic points, read time, read interval, spectrum start/end/step, number of wavelengths, wavelengths (space-separated), first column, columns, well count; absorbance then first row and rows; fluorescence/luminescence then excitation wavelengths, cutoff, cutoff filters, sweep, reads per well, PMT gain, integration times, first row, rows. Luminescence wavelength 0 means none. Repeated wavelengths (426 426 426) stay separate reads.

PlateFormat: a header row of column numbers per wavelength, groups one empty column apart; each snapshot is rows lines (first line: time or wavelength key, temperature, values); kinetic snapshots repeat with their time. A second header (column numbers after two empty cells) starts the reduced data (a calculated read Reduced). TimeFormat: a header of well names; one line per time point (kinetic), wavelength (spectrum) or the single read, blank lines separating wavelengths; a second well header with an empty temperature column holds the reduced data.

Gen5 experiment files (.xpt)

A compound file (MS-CFB) named .xpt whose Contents stream starts with Gen5ExperimentID; export is gen5, vendor.container.kind gen5-experiment. One table per SUBSETS/<n> plate (plate_number n; name from SUBSETS/<n>/HEADER, Plate 1; plate_time_utc = the plate time stored there, which the Excel export prints in local time). The DATA stream is an MFC archive (Gen5 3.x: zlib-compressed after a size head; 2.x: stored). Each read (CPlateDataSet) → one read, labelled with its name (450, Lum, 485,530), the mode from that name (number = absorbance at that wavelength, ex,em = fluorescence, Lum = luminescence; otherwise unknown_read_mode), its OLE date → acquired_at (local). Values: the record block (reads × rows × columns records of an f64 and flag bytes); wells outside the read region (flag 1…3) are left out; other flags give NaN with flagged (a/b) text and flagged_values. Kinetic blocks (reads > 1) carry per-read times in ms → time_s, kinetic_reads. CTemperatureDataSet → temperature_c (endpoint). CPlateDescr → instrument.model, serial_number, software_version. Blocks without a read header, unreadable streams and other layouts are listed in vendor.sections.refused_reads with read_not_decoded; a file with nothing decoded exits 6. Gen5 protocol files (.prt) hold no data and exit 6. Validated on Gen5 2.08.12, 3.04.17, 3.11.19 and 3.15.15 files from three labs (provenance 2026-09-26).

SoftMax Pro documents (.pda, .sda)

Binary documents, recognised by content: SoftMax Pro 5 by ##BLOCKS= within the first 64 bytes after NUL bytes and a version text ( 5.42.1.0); SoftMax Pro 6/7 by \x0bBinary File + version 1 and ProtocolAndData in the first 512 bytes. export is softmax-pro as for the text export; vendor.container.kind is softmax-pro-5-document or softmax-pro-document. Derivation and validation: docs/provenance/plate-readers.md (2026-09-25).

SoftMax Pro 5 (.pda). Big-endian objects named CS…. One table per plate section (CSPlateSection; name Plate#1) that holds data. Settings (CSPlateData): read type, read mode, reads, wavelength, run time, interval, temperature set point. Per read (CSPlateDescriptor): the temperature (temperature_c = first, temperatures_c for kinetic runs); the time stamp (seconds since 1904, local) → acquired_at; the number of reads completed → reads_completed (a stopped kinetic run keeps its planned kinetic_points; unmade reads are left out and run_stopped_early says so). One CSSite per well (A1 … H12) with the values of every read. time_s = read index × read_interval_s (time_basis: the document stores no per-read time; the export prints the same times). Instrument model and firmware from the string before CSMorphPlateTable (SPECTRAmax M2e → instrument.model, ROM v2.1.35 20May09 → firmware); section_id; temperature_set_point_c when temperature control was on; run_time_s.

The template (groups → samples → wells) becomes layout: Sample (well → sample name), SoftMax group (well → group), Role (blank for wells of the group named Blank, SoftMax Pro’s plate blank; plate_blank_group notes that SoftMax Pro subtracts its mean in reduced and exported values while the table holds raw values). All groups with units and columns are kept in vendor.sections.template.

Decoded: read mode 1 (absorbance), endpoint or kinetic, one wavelength, 96 wells on 12 columns. Refused and listed in vendor.sections.refused_plates with the plate_not_decoded warning: other read types (spectrum, well scan), modes, several wavelengths, other plate sizes, cuvette sets (CSCuvetteSection). A plate section with no data is noted, not refused.

SoftMax Pro 6/7 (.sda, .pda). A little-endian tree of named entries with sizes (types: string, f64, i32, byte array, object, date/time, f32, bool, and 2- and 4-byte codes). One table per SerializablePlateSectionData section with data: name → table name; reader model (DeviceInfo first line, SpectraMax M3 → instrument.model); ReadType, ReadMode (0 fluorescence, 1 absorbance, 2 luminescence), the wavelength entry (Wavelength; ExcitationWavelength, EmissionWavelength, CutoffFilter → cutoff_nm), Microplate name → plate_type, plate size; IsReadFromBottom → optics (fluorescence), FlashesPerRead → reads_per_well; report strings device_info, temperature_info, read_details (Start Read : 1:24 PM 9/1/2021 → acquired_at, month first when ambiguous); the read’s temperature → temperature_c; its rows × columns values (row-major). reader_settings_class names the settings class. vendor.sections.section_classes lists every section (PlateSectionData, GroupSectionData, NoteSectionData, GraphSectionData). A non-zero per-well flag byte (all zero in the corpus) raises well_flags_set. Objects cut short by an unknown value type are counted (document_truncated).

Decoded: endpoint (ReadType 0), modes 0/1/2, one wavelength, one read. Refused (plate_not_decoded): kinetic, spectrum and well-scan sections, several wavelengths or reads. Not read: group sections (templates, reduced values) and graph sections.

BMG MARS and SMART Control

Key: value cells in the first lines → header (User → operator, Path → instrument.model from the folder after BMG\, Test name/Test Name → protocol, Date+Time → acquired_at, ID1 → plate name and barcode, ID2/ID3/Test run no./Test ID → id2/id3/test_run/test_id). A line naming only a mode (Absorbance, Fluorescence (FI), Luminescence) gives the read mode. Matrices titled Raw Data (<filters>) (after an optional 4. section number) are measured reads: (450) absorbance wavelength, (580/620) or (480-14/520-30) excitation/emission with bandwidths, (No filter) none. Titles containing Layout, Content, Concentration or Dilution are layouts (extra.layout); other matrices (Blank corrected based on Raw Data (…)) are calculated reads. SMART Control: the Microplate … sheet holds the matrices; Protocol Information key/value lines → vendor.sections.protocol, Microplate name → plate_type.

Table view. MARS can also write one line per well instead of plate matrices (extra.export_view = table): a title line Well (or Well Row, Well Col), Content, then one column per value under a column title, and one line per well (A01, Sample X1, values). Columns with one title form one read: Raw Data (…) is measured, any other title (Blank corrected based on Raw Data (…), Average over replicates based on Raw Data (…)) is a calculated read with the optics of the raw data it names. Content → extra.layout.Content. The header pairs may stand one per line (User: USER, Path: …, Test ID: …, Test Name: …), and the mode line may carry a remark in a second field (Absorbance spectrum;;;Absorbance values are displayed as OD). Spectral scans: a second title line ;Wavelength [nm];260;261;… gives every column’s wavelength; the read type is spectrum, every value’s wavelength_nm is its column’s, and the read’s settings.scanned_wavelength says what was scanned: absorbance (Abs Spectrum), excitation (Ex Spectrum: the table’s wavelength is the excitation wavelength) or emission (Em Spectrum). Units: OD (absorbance), RFU. A second title line of any other kind (Time [s], cycles) is not decoded (table_axis_not_decoded warning, no values): no development file has one.

EnVision and Kaleido

EnVision: per plate, Plate information (a header line and a value line: Plate, Repeat (a repeat > 1 is its own table), Barcode (="…"), Measured height, Chamber temperature at start → temperature_c, humidity and ambient temperature → measured_height_mm, chamber_temperature_start_c, chamber_temperature_end_c, humidity_start_percent, ambient_temperature_start_c, Measurement date → acquired_at), Background information, then Results for <label>(<n>) - channel <k> [window w] (<unit>) (measured read; unit from the last parentheses) or Calculated results: … (calculated), each followed by a lettered matrix or a bare matrix of numbers (unlabelled_matrix). After the plates: Basic assay information (Assay Started → acquired_at, Protocol Name → protocol, Serial#), Protocol information / Plate type (Number of the wells in the plate → declared well count, Name of the plate type), Labels: (the read label; Exc. filter, Ems. filter, 2nd ems. filter, Number of flashes → flashes), Filters: (Description … CWL=450nm BW=10nm → wavelengths and bandwidth_nm), Instrument: (Serial number, Nickname), Exported with EnVision Workstation version … → software_version. Channel 2 uses the second emission filter.

Where the plate information stands. The export’s Auto export parameters: section says Place plate information at … Beginning of plate (the usual layout above) or End of plate: then each plate’s data come first and its Plate information follows them (the first line of such a file can be Calculated results: … or a bare matrix). Data lines belong to the plate information before them, or after them for End of plate. Basic assay information and Protocol information can stand before the plates too; lines inside those sections (and Labels:, Platemap:, Calculations:, Auto export parameters:, …) never hold plate data.

Untitled matrices. Some export formats write a read’s values with no Results for title, no row letters and no column numbers, right after its plate information: a bare matrix at column 0, or lines that all start with an empty field and have the same number of fields, where rows and columns not read stay as empty fields. Such a block is read only when its size (lines × fields) is a standard plate (8 × 12, 16 × 24, 32 × 48, …); its read is the Label of its plate information.

Read modes. Each measured read’s mode comes, in this order, from: the detector its measurement information records (MeasInfo/Measinfo of the plate or background information, matched by label: De=USLum, De=Lum luminescence; De=…Alpha… alpha; Ex=Top/Btm with Em=N/A absorbance; Ex and Em both Top/Btm fluorescence) — mode_basis detector; else keywords of the read’s label (ABS, Absorbance, A450, OD absorbance; LUM… luminescence; Alpha… alpha; FI, FP, HTRF, TRF, FRET, LANCE, Fluor… fluorescence) — mode_basis label; else unknown (unknown_read_mode). Labels are names the user can edit, so a label without a keyword is not guessed (before 2026-09-26 it defaulted to fluorescence). When detector and label disagree, the detector’s mode is used and read_mode_conflict is reported. Labels: entries are <label>,,,,<id> or Label name,,,,<label>.

Kaleido: Results for <technology> + Barcode: …, PlateRepeat: n, WellRepeat: n + matrix; Key:,,value lines (Software version → software_version, Measurement Started → acquired_at, Instrument Serial Number, Protocol Name, Barcode, Plate Type Name, Number of rows/columns); Measurements: lines (Tech, Excitation wavelength [nm], Emission wavelength [nm], Number of flashes) → vendor.sections.measurements and the read’s wavelengths.

Tecan i-control and Magellan

i-control: Application: Tecan i-control<TAB…>Tecan i-control , 1.9.17.0 (→ software_version), Device: … → instrument.model, Serial number: …, Date:, Time: (with a leading '), System, User → operator, Plate → plate_type, Plate-ID (Stacker) → barcode. One read per Label: <name> section: Mode (Absorbance, Fluorescence Top Reading, Luminescence) and key … value … unit settings (Measurement Wavelength → wavelength_nm, Excitation/Emission Wavelength, bandwidths, Gain, Number of Flashes → flashes, Integration Time → integration_time_us, Lag Time → lag_time_us, Settle Time → settle_time_ms, Part of Plate → part_of_plate, Kinetic duration, Interval Time → interval), Start Time:, Temperature: 23.1 °C, then a <> matrix, or for Kinetic Measurement a list: Cycle Nr., Time [s] (→ time_s), Temp. [°C] (→ kinetic_temperatures_c), one line per well.

Exports without Label: lines (vendors/tecan_csv.rs): the comma-delimited i-control 1.11 CSV (Application: Tecan i-control,,,,"Tecan i-control , 1.11.1.0", key and value in columns 1 and 5) and the SparkControl CSV (Method name: … then Application: SparkControl,,,,V2.3 → software_version 2.3; Method name → protocol). Reads are Mode groups of contiguous lines (Mode,,,,Absorbance / Wavelength,,,,595,nm; SparkControl Mode,Absorbance / Name,OD600 / Measurement wavelength,,,,600,nm; a Mode,Kinetic group holds kinetic settings and is not a read). A unit in the third field is folded into the key as in the tab export (settle_time_ms, integration_time_us or integration_time_ms, lag_time_us, z_position_um; …_nm keys drop nm), a gain’s Manual/Optimal becomes gain_mode, any other unit stays as <key>_unit; other keys: attenuation, kinetic_cycles, mirror, excitation, emission, z_position_mode. Tables start at Cycle Nr. or <>, with an optional title line above (OD600:600, LUMI:Lum, OD600): the table belongs to the read whose Name equals the title’s part before :, else to the next unused read in order; the channel label is that part (OD600), the full title is settings.section_title. Three table layouts: i-control 1.11 kinetic, one line per cycle (Cycle Nr.,Time [s],Temp. [°C],A1,A2,…; temperatures → kinetic_temperatures_c); SparkControl kinetic, one line per well after Cycle Nr./Time [s]/Temp. [°C] lines (as the tab export); SparkControl endpoint, <>,Value,Time [ms] then A1,0.0921,0 (the per-well read time is not kept). Temperature,,,,28.5,°C lines → temperature_c (all of them in extra.temperatures_c), the first Start Time → extra.start_time. The dialect id and software stay tecan-i-control / i-control for SparkControl files (the header’s Application value says SparkControl).

Magellan: a list with a Well positions column, an optional Plate column and one value column per label (Raw data, Label1, …), optional 0s time and 24.8 °C temperature lines above the wells; after the list, metadata lines: Date of measurement: …/Time of measurement: …, method (.mth, name and path), workspace (.wsp), wavelength lines, user, device (→ instrument.model), Instrument serial number: …, per-measurement blocks (Measurement n:, Measurement mode, Measurement wavelength, Number of flashes, Plate definition file → plate name, Unit), Meas. temperature: <label>: 24.8 °C (per read settings.temperature_c), or the compact Magellan Pro form (Plate Description: [id] …, Range: A1:B2, Label: …). All lines are kept in vendor.sections.metadata_lines.

SkanIt

One sheet per protocol step starting Measurement results, the session file, the run time (→ acquired_at), the step name (row 5: Absorbance 1, Luminescence 1, Blank Subtraction 1, Standard Curve 1, Dilution Factor 1), Wavelength: 450 nm (or excitation/emission lines), then per plate its name (Plate 1, Blank plate, Sample Plate: one table per plate name) and a matrix whose corner names the quantity (Abs, RLU, Blank subtracted, Value, Dilution factor), followed by a Sample matrix (→ extra.layout). Titles are taken from column A only (side tables share the lines). Steps named after a detection technology (absorbance, fluorescence, luminescence, photometric) are measured reads, labelled <step> (<corner>); every other step is calculated. The Layout definitions sheet (per row: sample names, then the group, then a standard’s concentration with its unit or an unknown’s 1:N dilution) → extra.layout_definitions (Sample, Group, Conc/Dil: title → well → text), which openreadout analyze assay uses as the plate layout (plate-analysis.md). General information (Report generated with SW version → software_version), Session information (Session name → experiment, Execution time), Instrument information (Name → instrument.model, Serial number), Protocol parameters and Run log sheets → header pairs and vendor.sections.

check

Performed: exporter recognised, every matrix cell parsed or recorded, geometry against the declared plate type, grid completeness per read and time point, unknown read modes, duplicates. Finding codes: no_plate_data, missing_section, well_count_mismatch (values outside the declared plate) (errors); non_numeric_value, decimal_comma, well_count_mismatch (matrix smaller than the declared plate), duplicate_value, unknown_read_mode, read_mode_conflict (an EnVision read whose detector and label name different modes), table_axis_not_decoded (a BMG table view with a second header line other than wavelengths), no_read_step, unsupported_read_type, block_count_mismatch (warnings); partial_plate, nonstandard_plate, date_order_assumed, export_version (info).

ASM output (export --to asm)

Allotrope Simple Model plate-reader JSON declaring the manifest http://purl.allotrope.org/manifests/plate-reader/REC/2025/03/plate-reader.manifest (ASM_MANIFEST), shaped like allotropy’s output for the same exports:

  • plate reader aggregate document: plate reader document[] — one per plate and well, each a measurement aggregate document with measurement time (the exports record no zone: +00:00 is stated, and the report notes it), plate well count, container type = well plate, analytical method identifier (protocol), experimental data identifier (experiment), analyst (operator), and measurement document[]: one per measured read and value (endpoint: absorbance/fluorescence/luminescence with the schema’s units mAU/RFU/RLU; kinetic: … profile data cube over elapsed time; spectrum: … spectrum data cube over wavelength), each with measurement identifier, sample document (sample identifier from a well-id layout or <plate> <well>, location identifier, well plate identifier), device control aggregate document (device type = plate reader, detection type, detector wavelength setting, excitation wavelength setting, number of averages, detector gain setting, detector distance setting (plate reader), detector carriage speed setting, scan position setting (plate reader)) and compartment temperature. A non-numeric cell becomes an error aggregate document (error = the cell text) with -0.0 as the point value (allotropy’s convention) or, in a cube, the point is left out and the error names it (absorbance at 300 nm).
  • calculated data aggregate document: one calculated data document per calculated value, citing the well’s first measurement as its data source.
  • device system document (device identifier, model number, equipment serial number, product manufacturer), data system document (ASM file identifier, data system instance identifier, file name, UNC path, ASM converter name = openreadout, ASM converter version, software name, software version).

The file is written to a temporary name, parsed back, its documents, measurements, values and the digest of (well, value) pairs compared with the source, then renamed (verified). The report (AsmExportReport) counts documents, measurements, values, errors and calculated values.

The ASM schema is licensed CC BY-NC 4.0 / CC BY-ND 4.0 and is not copied into this repository; oracle/asm_validate.py downloads it at a pinned commit and validates with jsonschema (plus a stricter pass against the single detector schema each measurement names, because the plate-reader schema’s anyOf/oneOf of detector schemas accepts documents that match none of them strictly).

Validation (2026-09-22)

  • Tables vs allotropy 0.1.146 (oracle/gen.py → plate, corpus harness check_plate): per detection mode, the measured values as sorted (row, col, value) triples — count, distinct wells, xxh3-128 — plus detection wavelengths and instrument model / serial / software version where both report one. 25 files: 24 match exactly; envision-lum-384 matches in counts only (allotropy numbers the rows A/E/I/M of a 384-well plate A–D). The two Tecan i-control files (no allotropy parser) are checked against an independent reader in oracle/plate.py. The three Tecan CSV files without Label: lines (tecan-icontrol-csv-kinetic-wellr, tecan-sparkcontrol-csv-kinetic-flopr, tecan-sparkcontrol-csv-endpoint-flopr; 2026-09-24) are checked against tecan_csv_summary in oracle/plate.py, an independent pandas reading of the CSV text (not a vendor-independent measurement): all three match exactly (23,040, 13,200 and 952 values; model and software version).
  • ASM: all 25 of our documents validate against the ASM schema, including the strict per-detector pass. Against allotropy’s documents (oracle/asm_compare.py): 3,417 measurement documents matched by well, measure and ordinal; of their value leaves 6,832 are identical and 2 differ (a Gen5 spectrum point that is OVRFLW: allotropy writes -0.0, we leave the point out). Other field-level differences are conventions: well names for 1536-well rows 27–32 (AE1 vs BMG’s e1), BMG SMART Control well names (A01), sample identifiers (<plate> <well> vs <plate>_<well>), detection type capitalisation, device type (plate reader vs allotropy’s absorbance detector for BMG), identifiers (EnVision protocol name vs protocol id, Magellan method file vs path), allotropy fixed defaults (EnSight for Kaleido), and the many vendor keys allotropy copies into custom information documents (we keep them in vendor).

Vocabulary (every public identifier in openreadout-plate must appear here)

identifier meaning
PlateReader, PlateDataset, FORMAT_ID, open the format reader, an opened export (core Dataset), the id plate, opening a file
TABLE_COLUMNS the long-form table columns well,row,col,read,wavelength_nm,time_s,value
export_asm, ASM_MANIFEST write an export as ASM plate-reader JSON; the manifest IRI declared
AsmExportReport, input, output, format, manifest, documents, measurements, values, errors, calculated, bytes_written, verified, notes report of export --to asm: paths, asm, manifest, counts of plate/well documents, measurement documents, numeric values, error documents and calculated values, size, read-back verification, notes

How this reader was derived, file by file: provenance log.