Plate readers, spectrometers and every other bench instrument invent their own export format. Repeated well grids, kinetic dumps, wide tables, long tables, Excel workbooks with a plate grid buried somewhere inside. Most labs answer this with one throwaway script per instrument, and those scripts rot on somebody's laptop.
labparse is a free MIT licensed Python package that takes a different route.
pip install labparse
import labparse
result = labparse.parse("growth.asc")
result.data # tidy pandas DataFrame
result.wide() # one column per well
result.save_csv("tidy.csv")
Or from the shell:
labparse growth.asc --out tidy.csv
labparse kinetic.txt --wide
Recipes, Not Parsing Scripts
A recipe is a small JSON description of a format that a deterministic engine executes. Tecan ASCII exports, Molecular Devices SoftMax Pro and SpectraMax kinetic exports, generic wide and long CSV or TSV, and Excel workbooks with a well header row or a plate grid all ship as bundled recipes.
Recipes are plain JSON. You can read one, edit it, drop new ones into the recipe folder, and share them with your lab. labparse --list-recipes shows everything installed.
Where The AI Actually Sits
This is the part worth stealing even if you never touch a plate reader.
When labparse meets a format it does not know, it shows the raw file to your own model once and the model writes a recipe. The recipe is only accepted if it actually parses your file. A wrong one is rejected and retried, so it cannot quietly produce bad data. Accepted recipes are cached in your home directory, so each format costs at most one call ever and cached formats parse offline.
The parsing engine itself never calls a model. The model proposes, the deterministic engine disposes, and your measurements only ever flow through code you can read.
It looks for a model in this order: the Claude CLI, the Codex CLI, an Anthropic key, then an OpenAI key, where setting a base URL points it at any OpenAI compatible server including a local model. --llm off disables it entirely, and everything with a bundled or cached recipe still works. No model configured means unknown formats give a clear message instead of a wrong guess.
The Output Schema Is Always The Same
Every parse returns the same columns whatever the instrument:
| column | meaning |
|---|---|
| well | normalized well id, A1 to P24 |
| row | plate row letter |
| col | plate column number |
| cycle | reading number in file order, starting at 1 |
| time_s | seconds, NaN when the format has no time |
| value | the measurement |
Extra instrument columns like temperature are kept alongside. Tidy is the shape pandas, R and every statistics tool actually want, and .wide() gives the one column per well layout spreadsheets expect, from the same parse.
Why This Matters More Than It Sounds
The reason instrument data ends up in spreadsheets is that the spreadsheet is where the file lands, not because anyone decided that was a good idea. Ten years after the Genome Biology paper on Excel mangling gene names, that is still the default failure mode across the bench sciences.
Parsing the raw export directly into a tidy table removes the step where numbers get retyped, reformatted and quietly changed. The source of truth stays the file the instrument wrote.
Source, issues and README are on GitHub, and if your instrument's export defeats it, open an issue with a sample file. That is exactly the kind of format the recipe system exists to swallow.
Top comments (0)