← All posts · Michael Baffour Awuah

A tool for microbiologists · Colony-forming-unit assays

Colony counts in, a publication figure out.

Upload your replicate-level CFU data, map the columns, and get a figure with log10 statistics, significance annotations and a Figure QA pass — plus the exact R script that produced it. It runs on your own machine, so unpublished counts never leave it.

Statistics log10(CFU), with intervals and effect sizes  ·  Palette Okabe–Ito, colorblind-safe
Readouts absolute CFU, or survival paired within each culture
Exports PNG · vector PDF/SVG · editable PPTX · every table · a rebuild script — in one archive
Runs locally, on your own data  ·  License MIT · free & open source

Figure 1.
10⁸ 10⁷ 10⁶ 10⁵ 10⁴ ✱✱ 0 h 24 h CFU · mL⁻¹ (log₁₀ scale) vector strain
Figure 1. Strain versus empty-vector control across two timepoints, plotted on a log10 axis with individual replicate points, capped SD error bars, and an adjusted-p annotation. Rendered — and reproducible — by CFU Plot Studio.
7 figure export formats, including editable PPTX
5 variation intervals — SD, SEM, 95% CI, IQR, range
3 correction methods — BH, Holm, Bonferroni
1 reproducible R script attached to every figure
§ 1 — Materials & Methods

The whole CFU-figure loop, in one reproducible app

Every step that turns a table of colony counts into a manuscript figure — mapping, plotting, testing, checking, exporting — happens in a single window, on the same underlying measurements the whole way through.

01

Flexible CSV import with column mapping

Upload replicate-level counts in whatever layout your lab already uses. Map sample/vector, treatment/dose, timepoint, replicate, and CFU columns inside the app — no fixed column order, no reshaping the spreadsheet by hand.

02

Publication-ready bar plots

Grouped, log-scaled bars with individual replicate points on top. Combined multi-sample views faceted by time, a single sample across both timepoints, or a clean single-panel plot for one timepoint.

03

Statistics on log10(CFU)

Welch or Student t-tests on log-transformed counts, or model-based marginal means through emmeans and broom — the right scale for values spanning orders of magnitude. Compare samples, timepoints, treatment-vs-control, or every treatment pair.

04

Multiple-comparison correction

Adjust p-values with Benjamini–Hochberg, Holm, or Bonferroni — or turn correction off for a single planned contrast. The method you chose is recorded so the figure and its numbers never drift apart.

05

Significance annotations

Show comparisons as significance stars or as exact adjusted p/q values above the bars — or hide them for a clean version for a talk. Label color, size, and position are all under your control.

06

Error bars that state their definition

SD, SEM, 95% CI, IQR, or full range — as capped bars, uncapped whiskers, a mean point with whiskers, or crossbar intervals. Sample size travels alongside, so a reader knows how many replicates each bar summarizes.

07

Auto methods caption

The figure captions itself: it names the error-bar type and, when statistics are shown, the test and correction method. The sentence that belongs under the figure is generated for you, and updates as you change settings.

08

Okabe–Ito colorblind-safe palette

One click applies the Okabe–Ito qualitative palette (Wong 2011, Nat. Methods) to sample, timepoint, and single-bar colors — a colorblind-safe default so the figure reads correctly for every reviewer. Every other color stays editable.

09

Many export formats

PNG at your chosen DPI, vector PDF and SVG for downstream editing, an animated GIF of the bars revealing, and PowerPoint — including editable rvg vector slides you can ungroup and restyle in PowerPoint itself.

10

Reproducible R-script export

Download the exact R script that produced the current figure. It is a runnable starting point for recreating the graph outside Shiny — the single most useful thing to keep with a manuscript figure.

11

Figure QA checklist

A pre-export pass flags low replicate counts, hidden replicate points, low DPI, undersized figures, small fonts, crowded axis labels, clipped statistic labels, and inside-legends that may cover data — before a reviewer does.

12

Full figure geometry control

Exact width, height, and DPI with single-column, double-column, and square presets. Manual y-axis limits, tick spacing, grid lines, plot box, bar and dodge width, point size, alpha, and jitter — set once, reuse everywhere.

13

Named style presets

Save the whole visual style — colors, axes, labels, geometry — as a JSON preset and reload it for the next experiment. Your figures stay consistent across a paper without redialing every slider.

14

Data & analysis products, exported too

Cleaned CSV, summary and QC tables, the statistics and ANOVA tables, plus an analysis manifest recording the mapped columns, plot mode, and options. Everything behind the figure is preserved, not just the picture.

15

Paired survival, not just absolute counts

An induction time-course is really asking how much of each culture survived — CFU at the readout relative to that same flask at baseline. Pairing within the culture does not change the estimate when every replicate has both timepoints; it changes the uncertainty, and on real data with a missing well it can change the sign of the result. The figure carries a no-change reference line, and the axis can be read as log10 ratio, fold change or percent of baseline — the same numbers, relabelled.

16

A data area that stays put

ggplot hands the panel whatever space is left after the legend and labels have taken theirs, so moving a legend to the side can cost 40% of the plotting area. Pin the panel instead and the data area is identical across every figure, however they are labelled — which is what makes panels comparable side by side.

17

Honest about what it dropped

log10 is undefined at zero, so background-subtracted counts at or below it cannot be plotted. The app says how many rows that cost, which replicates lost their partner and can no longer be paired, and names any group that vanished from the figure entirely. The n under each bar is the surviving n, not the number of wells you plated.

18

Move the labels where you want them

Click to place the legend, click a significance star to pick it up and click again to drop it, colour bars individually, and align every piece of text. Nudges are stored against the label rather than the pixel, so they survive re-rendering, filtering, resizing and a preset reload.

19

Download everything, in one archive

One button collects the figure in PNG, PDF, SVG and PowerPoint, the cleaned and plotted data, the summary, statistics, ANOVA and QC tables, a standalone script that rebuilds the figure from embedded data, the preset, the manifest, and a README saying what is inside — and what could not be produced, and why.

20

Safe synthetic template data

A neutral, synthetic example dataset ships with the app and downloads as a formatting template — so you can learn the tool, or file a reproducible bug report, without touching unpublished lab data.

§ 2 — Author's Note

Why I built a CFU plotter

Look — at some point, if you work with E. coli for a living, you have to do something with CFUs or you quietly lose your credibility. So this is partly me keeping mine. That is the honest, slightly-embarrassing origin of this tool, and I would rather say it out loud than pretend it began with a grand vision.

The more serious version is this. A colony-forming-unit assay is one of the simplest things you do at the bench: you dilute, you plate, you incubate, you count. That part is honest, satisfying work. The trouble starts the moment you want to show the data to anyone else.

Because a single CFU experiment is never one number. It is a few strains or vectors, a couple of treatments, an early and a late timepoint, three replicate plates each, and counts that live across five or six orders of magnitude. By the time you want to present it, you have stopped being a microbiologist and become a person shuttling numbers between a spreadsheet, a stats function, and a plotting tool — praying you did not transpose a column somewhere along the way.

I ran a lot of these assays. And every single time, the counting was the easy part; turning the counts into a figure I actually trusted — one I could reproduce a year later — was the part that reliably ate an afternoon.

The counting is easy. Making a figure you trust, and could rebuild a year later, is the part that costs you.

The actual problem

To be fair, none of the individual tools is the villain. Spreadsheets store counts fine. ggplot2 plots beautifully. The problem is the loop you run to assemble the figure by hand, and how fragile it is: copy the counts, compute means, reshape the table, decide between raw and log scale and toggle it twice, run a t-test off to one side, hand-nudge the significance stars, export, discover the DPI is wrong, fix it, re-export — then do it all again for the next panel.

CFU data has one extra trap. Because the counts are log-distributed, the statistics really ought to run on log10(CFU), not the raw numbers — and that is easy to get subtly wrong when the test lives in a different window from the plot. The number under the figure and the bars in the figure should never be able to disagree. By hand, they can.

So I built the loop into one app

I wanted a way to quantify and present this data in the cleanest, most insightful, most story-telling way I could manage — and to have it stay reproducible. So CFU Plot Studio does the whole loop in one place: you map your columns, it plots the bars with the replicate points on top, runs the statistics on log10(CFU) with the correction you choose, places the annotations, checks the figure for the boring submission-killers, and exports — including the exact R script that made it. The graph, the numbers, and the recipe stay attached to each other. That last part is the whole point.

One honest footnote: I build these tools with an AI partner. I am a microbiologist, not a career software engineer — the decisions about what a trustworthy CFU figure is are mine, and Claude did a lot of the implementation heavy-lifting. That collaboration is a big part of why a single frustrated afternoon could turn into something I can actually share.

§ 3 — Protocol

From a CSV to a finished figure

The app follows the shape of a real lab workflow — map, plot, test, check, export. Start with the built-in synthetic dataset if you want to learn it without touching your own data.

1

Upload or load data

Drop in a replicate-level CSV, one row per plate, or load the neutral synthetic template. Data stays at replicate level, so the plot and the statistics come from the same measurements.

2

Map your columns

Tell the app which columns mean sample/vector, treatment/dose, timepoint, replicate, and CFU count. Any naming scheme works — Group, Strain, Vector, Construct, whatever your sheet already uses.

3

Choose scope and variation

Pick the plot mode — combined and faceted by time, one sample across time, or a single timepoint — and how spread is shown: SD, SEM, 95% CI, IQR, or range, with replicate points on top.

4

Run the statistics

Select the comparison and a test on log10(CFU), then a correction: BH, Holm, Bonferroni, or none. Show results as stars or adjusted values — the methods caption updates to match.

5

Style and run Figure QA

Set axis limits, ticks, colors, and figure size — or apply the Okabe–Ito palette in one click. Then run the Figure QA checklist to catch low DPI, small fonts, and clipped labels before export.

6

Export and archive

Download the figure as PNG, PDF, SVG, GIF, or PowerPoint, and keep the statistics table, the plot preset, and the reproducible R script beside it — so the figure can always be rebuilt.

§ 4 — Getting it

Running it on your own machine

There is no hosted version, on purpose. It is an R Shiny app that runs locally, so unpublished counts stay on your computer and nothing is uploaded anywhere.

1

Install R

Windows and macOS builds are at cran.r-project.org. That is the only thing you install by hand — the launcher fetches the R packages itself, into a private folder beside the app rather than your system library.

2

Download the app

Grab the ZIP and unzip it anywhere, or git clone https://github.com/mbaffour/cfu-plot-studio.git if you would rather pull updates later.

3

Start it

On Windows, double-click Run CFU Plot Studio.bat. It finds R, installs whatever is missing, picks a free port and opens your browser; close the console window to stop it. On macOS or Linux, run Rscript run_app.R from the app folder. The first start takes a few minutes while packages install; after that it is seconds.

4

Try it on the synthetic data first

Click Load dummy example data, or Download dummy/template CSV to see the expected layout. It is neutral synthetic data, so you can learn the tool — or file a reproducible bug report — without touching unpublished results.

5

Check it against your own file

Rscript tests/test_end_to_end.R path/to/your.csv runs the whole pipeline against your data and reports what it found — how many rows survived import, how many replicates paired, which groups could not be tested. Five more suites cover the figure geometry, the survival readout, every statistic, the export bundle and CSV column detection.

§ 5 — Availability

Your figure and the script that made it, together.

Free, open-source, and reproducible. Bring a CSV of colony counts; leave with a figure you can trust and cite — and the R that rebuilds it.

“Built with passion for science and discovery. You can just build things.”