CHESSParsers API Reference

CHESSParsers reads instrument export files, such as plate-reader spreadsheets and incubator logs, into LabwareReads and EnvironmentLogs. It is not re-exported by CHESS; load it with using CHESSParsers. See Parsing Instrument Files for worked examples.

The functions most code uses are parse_instrument_file, which auto-detects the file's format, and record_reads!, which records a parsed read onto a CHESSCore labware (it lives in a package extension that loads with CHESSCore). New formats implement the InstrumentFormat interface and are added with register_format!.

CHESSParsers.EnvironmentLog — Type
struct EnvironmentLog
    metadata::Dict{String,Any}
    data::DataFrame
end

The canonical result of parsing one reading kind of a chamber-level environmental log (e.g. a BioSpa incubator session's temperature/O2/CO2/humidity history) – data that has no well to attach to at all, unlike LabwareRead's per-plate-per-channel well measurements. A session logs several reading kinds at once; parse_raw/parse_instrument_file return one EnvironmentLog per kind, mirroring how LabwareRead splits by (plate, channel).

Fields:

  • metadata: everything constant for this one reading kind – at minimum metadata["format"], metadata["read_kind"] (e.g. "Temperature"; informational only, not a registered CHESSCore.ReadKind), plus whatever else the format exposes (unit, setpoint, session start time, a plate slot/barcode mapping table, ...).
  • data: a tidy DataFrame with exactly two columns – time::DateTime, value::Float64.

See also: DataFrame(::EnvironmentLog), environmentlog_to_json.

source
CHESSParsers.InstrumentFormat — Type
abstract type InstrumentFormat end

Dispatch tag for a specific instrument export format (e.g. an Epoch2 plate-reader export, a BioSpa incubator export). Every concrete format is a singleton struct subtyping this and implements two extension-point functions:

  • detect(::Type{T}, path::AbstractString) -> Bool: sniff-test whether path looks like a T export, used by detect_format for auto-detection.
  • parse_raw(::Type{T}, path::AbstractString; kwargs...) -> Vector{LabwareRead}: parse path into the package's canonical LabwareRead representation, one per (plate, channel) found in the file.

Register a new format with register_format! so it participates in both auto-detection and name-based lookup (see parse_instrument_file).

source
CHESSParsers.LabwareRead — Type
struct LabwareRead
    metadata::Dict{String,Any}
    data::DataFrame
end

The canonical, instrument-agnostic result of parsing one channel of one plate from an instrument export file – a channel being one specific measurement configuration (e.g. absorbance at a given wavelength, or fluorescence at a given excitation/emission pair). A single export file commonly contains multiple plates and/or multiple channels per plate; parse_raw/ parse_instrument_file return a Vector{LabwareRead}, one per (plate, channel) combination, rather than trying to force everything into one wide table. This is the seam that keeps the rest of the package (DataFrame/CHESS-object/JSON conversion) generic regardless of the instrument.

Fields:

  • metadata: everything that's constant for this one (plate, channel) – at minimum metadata["format"] (the InstrumentFormat subtype that produced it), metadata["plate"], and metadata["read_kind"] (e.g. "Absorbance"/"Fluorescence", matching an already-registered CHESSCore.ReadKind name), plus whatever else the format exposes (instrument serial, protocol name, export datetime, a channel's wavelength or excitation/emission, ...).
  • data: a tidy DataFrame with exactly three columns – well::String, time::DateTime, value::Float64 – one row per well per timepoint (a single row for an endpoint/non-kinetic read).

See also: DataFrame(::LabwareRead), record_reads!, labwareread_to_json.

source
DataFrames.DataFrame — Method
DataFrame(el::EnvironmentLog) -> DataFrame

Return the time/value table underlying el. Metadata (reading kind, unit, plate mapping, ...) is not included – see el.metadata for that.

source
DataFrames.DataFrame — Method
DataFrame(lr::LabwareRead) -> DataFrame

Return the tidy, long-format measurement table underlying lr. Metadata (instrument, protocol, export datetime, ...) is not included – see lr.metadata for that.

source
CHESSParsers._biospa_plates — Method
_biospa_plates(session) -> DataFrame

Parse <Plates><PlateDetails> into a table with columns id (real barcode), slot, experiment_name, and gen5_plate_number – the mapping a linked Gen5 .xlsx export alone can't always provide (its own sheets are sometimes named by Gen5's internal plate number, not the barcode).

source
CHESSParsers._gen5_channel_meta — Method
_gen5_channel_meta(label_cell) -> Union{Dict{String,Any},Nothing}

Parse a raw data block's trailing marker into channel metadata: read_kind plus either wavelength or excitation/emission. Two shapes are recognized:

  • a "<name>:<label>" string (e.g. "OD600:600", "Fluor:505,536") – label is a bare integer (Absorbance wavelength) or a comma-separated pair (Fluorescence excitation,emission);
  • a bare Real (e.g. 500) – some endpoint exports label each row with just the wavelength and no name/colon at all; since there's no way to spell an excitation/emission pair without a separator, a bare number can only mean a single Absorbance wavelength.

Returns nothing if label_cell doesn't match either shape – the caller's job to skip such a row rather than error, since that's how a computed summary block (not a raw channel) is recognized.

source
CHESSParsers._gen5_reader_type — Method
_gen5_reader_type(path::AbstractString) -> Union{String,Nothing}

Return the value of the first real plate sheet's "Reader Type:" field (e.g. "Epoch 2", "CytationC10"), or nothing if path isn't a readable Gen5 export. Used by every Gen5-family format's detect.

source
CHESSParsers.detect — Function
detect(::Type{T}, path::AbstractString) where T<:InstrumentFormat -> Bool

Return true if the file at path looks like a T export. Every concrete InstrumentFormat must implement this method; there is no fallback.

source
CHESSParsers.parse_instrument_file — Method
parse_instrument_file(path::AbstractString; format=nothing, kwargs...) -> Vector{LabwareRead}

Parse the instrument export file at path into one LabwareRead per (plate, channel) found in it.

format selects which InstrumentFormat to use:

  • nothing (default): auto-detect via detect_format, erroring if no registered format matches.
  • a String: looked up by name in format_registry.
  • an InstrumentFormat subtype: used directly, skipping detection.

Any kwargs are forwarded to the concrete format's parse_raw method.

source
CHESSParsers.parse_raw — Function
parse_raw(::Type{T}, path::AbstractString; kwargs...) where T<:InstrumentFormat -> Vector

Parse the file at path, known to be a T export, into a vector of result objects – one LabwareRead per (plate, channel) for the well-shaped Gen5 formats, or one EnvironmentLog per reading kind for chamber-level formats like BioSpaFormat that have no per-well data at all. Every concrete InstrumentFormat must implement this method; there is no fallback.

source
CHESSParsers.parse_raw — Method
parse_raw(::Type{BioSpaFormat}, path::AbstractString; kwargs...) -> Vector{EnvironmentLog}

Parse a BioSpa .SES session file into one EnvironmentLog per reading kind (Temperature/O2/CO2/Humidity), each sharing the same time vector and the session's plate slot/barcode mapping (metadata["plates"]).

source
CHESSParsers.parse_raw — Method
parse_raw(::Type{Take3TrioFormat}, path::AbstractString; kwargs...) -> Vector{LabwareRead}

Parse a Take3 Trio nucleic-acid quant .xlsx export into one LabwareRead per (sheet, channel): Absorbance@260/280/320 for the "Blank Read" sheet (real well IDs), and Absorbance@260/280 plus Concentration (ng/µL) for each "Sample Read #N" sheet (Gen5 SPL# sample tokens, not real wells – mapping a sample back to its physical source plate/well is the caller's responsibility, not something this file records).

source
CHESSParsers.record_reads! — Function
record_reads!(labware::CHESSCore.Labware, lr::LabwareRead; well_map=identity, instrument=nothing, layout=nothing)
record_reads!(labware::CHESSCore.Labware, lrs::Vector{LabwareRead}; kwargs...)

Record every row of lr.data (or every LabwareRead in lrs, e.g. the result of parse_instrument_file) onto labware as a CHESSCore.Read, via CHESSCore.record_read!. lr.data has well, time, and value columns; lr.metadata["read_kind"] (constant for the whole LabwareRead) must already be a registered ReadKind (see CHESSCore.read_kinds, populated by e.g. CHESSLabConstants).

well_map converts a row's well value to the well name used in labware (identity by default, e.g. pass a function if the instrument's well-naming convention differs from labware's). instrument is forwarded to record_read! for capability checking.

layout, if given, is a CHESSExperiments.Experiment's :layout metadata DataFrame (one row per well, with a well column and a per-well metadata::Dict{Symbol,Any} column). The rest of lr.metadata (everything but "read_kind", which is already consumed structurally as a ReadKind) is merged into the matching row's metadata, namespaced under "chessparsers." – otherwise this would be silently dropped on the way into CHESSCore since Read has nowhere to carry free-form metadata; a layout row's metadata is the intended carrier for it. Wells with no matching row in layout are recorded as before, without attaching metadata anywhere.

A design's layout can span multiple plates, and well names (e.g. "A1") repeat across them – when layout has a labware column, rows are matched on (labware, well) (labware compared against CHESSCore.name(labware), this call's own labware argument), not well alone, so metadata never lands on the wrong plate's row. Layouts without a labware column fall back to matching on well only.

Returns labware.

Provided by the CHESSCore package extension (using CHESSCore activates it) – CHESSParsers itself has no dependency on CHESSCore, so this function has no methods until then. Recording also needs CHESSCore.read_kinds to already contain whatever lr.metadata["read_kind"] names, which is the caller's responsibility (typically via using CHESSLabConstants or another package that registers the relevant ReadKinds) – CHESSParsers doesn't register any itself.

source
CHESSParsers.register_format! — Method
register_format!(T::Type{<:InstrumentFormat}; name::AbstractString, override::Bool=false)

Register T in the global format_registry under name, making it discoverable via format_registry["name"] and usable by name in parse_instrument_file.

Errors if name is already registered unless override=true (in which case the previous entry is replaced and a warning is emitted) – this guards against two independently-authored instrument packages accidentally colliding on the same name.

Third-party instrument packages should call this once per InstrumentFormat they define, typically at module top-level, right after using CHESSParsers.

Examples

julia> struct ExFormat <: InstrumentFormat end

julia> CHESSParsers.detect(::Type{ExFormat}, path) = endswith(path, ".ex");

julia> register_format!(ExFormat; name="ex_format");

julia> haskey(format_registry, "ex_format")
true
source