← All posts · Michael Baffour Awuah

Bacterial colony counting

Colony counts to CFU/mL, without the arithmetic.

Name your samples, type the colonies you counted, and read the CFU/mL straight off the table. One HTML file, no installs, no uploads — and an Excel workbook on the way out that a spreadsheet can actually compute on.

One HTML file Runs offline Excel + CSV export Also inside Dilution Designer
2entry modes — a flat quick table, or full replicates with stats
0dependencies, CDNs, build steps or network requests
32kilobytes — one HTML file you could email to a labmate
2exports: a real Excel workbook, or tidy CSV
Channels

Everything a plating session needs, and nothing it doesn't

The whole tool is one screen. You name samples, type counts, and the numbers appear as you go. The parts worth explaining are the ones that stop a plausible-looking answer from being wrong.

CH 01QUICK

One row per sample

The default mode is a flat table: name, colonies, dilution, volume, CFU/mL. Press Enter in a colonies box and it adds the next row and puts the cursor in it, so a five-sample series types straight down the column without touching the mouse.

CH 02CARRY-DOWN

Dilution and volume inherit

New rows copy the previous row's dilution and plating volume, because in a real session those barely change. You type them once for the first plate and then only the counts.

CH 03REPLICATES

Mean ± SD when you need it

One click switches to the replicates view: several plates per sample, with mean, sample standard deviation and CV% across the countable ones. The quick table stays available for the next session.

CH 04WINDOW

The countable window, flagged

Counts outside 30–300 on a full plate get flagged; switch to spot-assay mode and the window becomes 3–30, since a 5–10 µL spot saturates far sooner. Flagged counts are still included — the tool tells you, it doesn't decide for you.

CH 05ZERO

Detection limits, not fake zeros

A plate with no colonies does not mean zero cells; it means fewer than one colony's worth. An all-zero sample reports < limit computed from its least-diluted plate, instead of averaging a zero into a mean that would then look real.

CH 06TNTC

Too numerous, or just excluded

Mark a plate TNTC when it was uncountable, or Excl to drop a contaminated one. Both leave the stats and carry the reason into the export, so the summary reads "counted 2 of 3" rather than silently shrinking n.

CH 07LADDER

Any dilution factor

Ten-fold is the default, not an assumption. Set the factor to 5 and step 3 means a 1:5 series diluted three times — 125-fold. The superscript label next to each row updates so you can see what you actually plated.

CH 08EXCEL

A workbook, not a CSV renamed

The Excel button writes a genuine .xlsx: two sheets, with numbers stored as numbers. SUM and AVERAGE work on the CFU column immediately, without a text-to-columns detour.

CH 09LOCAL

Nothing leaves the browser

No network requests at all. Your session autosaves to local storage, so a reload or a closed laptop lid doesn't cost you the plate counts, and the file works opened straight off the disk.

CH 10TWINNED

Also a module in Dilution Designer

The same calculator lives inside Dilution Designer as CFU Counts, where an experiment can hold several named runs, feed the lab notebook, and ride along in a workspace backup.

The arithmetic

One formula, and the two numbers people mix up

There is no clever mathematics here. The value of doing it in software is that the same expression runs every time, on every row, with the units kept straight.

CFU/mL  =  colonies × factorstep  ÷  volume plated (mL)
factor
How much you dilute at each step of the series. Ten for a standard ten-fold ladder, five for a 1:5 ladder.
step
Which rung you actually plated. With a factor of ten, step 5 is the 10−5 tube — diluted a hundred thousand fold. Step 0 is neat, undiluted sample.
volume plated
Entered in microlitres because that is what you pipette, divided by 1000 internally because the answer is per millilitre. This is the conversion that gets fumbled at the bench.
Worked example
colonies counted   148
dilution plated   10−6 (factor 10, step 6)
volume plated   100 µL = 0.1 mL

148 × 106  =  1.48 × 108
÷ 0.1 mL  =  1.48 × 109 CFU/mL

The same expression, fed plaque counts instead of colonies, is what Dilution Designer uses for phage titers. One implementation, two assays — a second copy would eventually drift from the first.
Why this exists

The counting was never the slow part

Plating is patient work. You do the serial dilutions, you plate, you wait overnight, and the next morning you sit down with a marker and a plate and count. That part I don't mind — it's the closest thing microbiology has to meditation.

What I minded was the twenty minutes afterwards. Colonies times the dilution, divided by the volume plated, once per plate, for however many samples the experiment had. Then the same numbers again into a spreadsheet so they'd survive past the notebook page. It is arithmetic a calculator can do and a tired person can get wrong, and the failure mode is quiet: a number that is off by a factor of ten still looks like a perfectly reasonable bacterial density.

The specific thing I kept getting wrong was the volume. You pipette in microlitres and you report per millilitre, so every single row carries a divide-by-a-thousand that has nothing to do with the biology. Miss it once and the result is wrong by three orders of magnitude — and it will not look wrong.

An error of 10× in a CFU count doesn't look like an error. It looks like a result.

So this is a deliberately small tool. Type the counts, read the answer. The interesting decisions were all about refusing to produce a confident-looking number when the data doesn't support one: a plate with no colonies reports a detection limit rather than a zero; counts outside the countable window get flagged rather than quietly averaged; an uncountable plate can be marked as such and the summary then admits it counted two of three replicates instead of pretending n was two all along.

It deliberately stops there. It does not plot, and it does not do statistics beyond mean, SD and CV. That work already has a home in CFU Plot Studio, which takes replicate-level counts and produces publication-ready figures with log10 statistics and significance annotations. This tool is the step before: the bench arithmetic that turns a morning of counting into the tidy table those figures are built from. The Excel export is shaped to be that table.

I'll be straight about how it got built, as I have been about the others: the science and the design judgements are mine — what to compute, when to refuse to compute it, why a zero is a detection limit and not a data point. The engineering I did alongside Claude. I'm a scientist who has gotten good at building tools with AI, not a career software engineer, and I would rather say that plainly than imply a dependency-free spreadsheet writer materialised on its own. The next section is about a bug that came out of exactly that collaboration, and what it taught me.

Post-mortem

The column header that said millilitres

While checking a screenshot of the finished quick table, I noticed the volume column was labelled VOL (ML). The values under it were microlitres. In a tool people dose from, that is a thousand-fold error sitting in the interface.

What rendered
VOL (ML)
100

Reads as 100 millilitres. Nobody plates 100 mL. But a hurried reader doesn't parse the header — they read the number and trust the label.

What it should say
VOL (µL)
100

Reads as 100 microlitres, which is what the maths actually divides by. Same markup, one CSS rule different.

The mechanism

The table headers are styled text-transform: uppercase. The micro sign, U+00B5, has an uppercase mapping in Unicode: Greek capital Mu, U+039C. In most fonts that glyph is drawn identically to a Latin capital M. So the browser dutifully rendered µL as ML, and the label became a different unit — not a typo, a correct-by-the-spec case conversion that happens to change scientific meaning by three orders of magnitude.

Signal chain

From a stack of plates to a workbook

There is nothing to install and no account. Open the page and start typing.

Scope

What it deliberately doesn't do

A small tool stays useful by staying small. These are the edges, so you know before you open it.

No plots

No charts, no error-bar figures. Take the export to CFU Plot Studio, which is built for exactly that.

No significance testing

Mean, SD and CV% only. No t-tests, no ANOVA, no log10 modelling — deliberately, because a bench calculator is the wrong place to pick a statistical test.

One experiment at a time

The standalone page holds a single working session. For a registry of named experiments with notebook capture, use the CFU Counts module in Dilution Designer.

No sync

Data lives in one browser on one machine, which is the price of never uploading anything. Export if you want it somewhere else.

Free · offline · one file

Count the plates. Let something else do the dividing.

Free, open source, and it runs entirely in your own browser. Nothing to install, nothing uploaded.