A picture wasn't enough. So I built the analysis.
By Michael Baffour Awuah · on turning microscopy into insight you can defend
While writing a paper, I did a lot of microscopy. Phase-contrast image after phase-contrast image, cells over time, conditions side by side. And at some point I hit a wall I didn't expect: the pictures were beautiful, but a picture on its own often couldn't tell the story I was trying to tell. You can see that the infected cells look bigger. Convincing a reader — and a reviewer — that they are bigger, and by how much, is a different job entirely.
So I needed a way to get real insight out of the images — segment them, pull out areas and perimeters and Feret diameters, and turn that beautiful-but-mute picture into a number I could stand behind. That part felt tractable. The part that quietly worried me was the statistics.
Because here's the trap. After segmentation you have thousands of cells in a spreadsheet — hundreds per dish, a few time points, three biological replicates. The temptation is almost irresistible: drop the whole column into a t-test, watch the software report p < 0.0001, and feel bulletproof. It's the most natural thing in the world to do. And it's wrong.
Hundreds of cells from three dishes are not hundreds of independent data points. The p-value that comes out is not the one your experiment earned.
That's pseudoreplication, and it's the half of the job that quietly breaks papers. The cells in one dish shared a culture, an infection event, an imaging session — they're correlated. Counting each as a separate observation inflates your sample size roughly two-hundred-fold and manufactures significance out of differences that may be biologically trivial. The statistical unit is the dish, not the cell — and it took me an embarrassingly long time to really internalise that.
What I wanted was a tool that did the right thing by default — one where the correct analysis was the path of least resistance. So CellMorphR does three things in a fixed order, and the order is the point. It displays every cell — violins, strips, sina plots, ECDFs — so you can see the heterogeneity that makes single-cell data worth collecting. It summarises at the replicate level, collapsing hundreds of cells to one median per dish. And it tests on those summaries, so the p-values reflect the sample size the experiment truly has.
The distribution plots overlay the per-replicate medians on the cell cloud, following the SuperPlots idea — so in one glance you see both the shape of the data and the handful of independent points the inference rests on. And it ships two ways because I work two ways: an R Shiny app for exploring, a Python CLI for pipelines, verified to give identical results on the same data.
I should be honest about how it got built. I'm a scientist who's gotten good at building tools with AI — not a career software engineer. The statistical thinking and the frustration that started the whole thing are mine; the implementation I did in close collaboration with Claude. And weirdly, building it is what made me understand pseudoreplication properly — when you have to decide, in actual code, what a “replicate” is and at what level a test runs, you can't hand-wave anymore.
It isn't finished, and it has gaps I know about. But it runs, both interfaces agree, and it has already kept my own analyses honest. That feels like the right bar for whether something was worth building — and it turns out you can just build the thing your work needs.