Machine learning · June to October 2026

Colony Counter

An Android app that counts bacterial colonies from plate photos, offline. Two small YOLO models do the counting, in about three quarters of a second a plate on my phone.

to count a plate on my phone
0.74 s
average error in colonies per plate, against 25.7 for the threshold method
16.6
test plates counted the same on phone and laptop
16 of 16

This app counts bacterial colonies on membrane filtration plates from a photo. I developed it in collaboration with Moushumi Hazra, a postdoctoral researcher at the University of Nebraska-Lincoln. Under a blue lamp the colonies glow cyan against blue agar. Each plate also has a printed counting grid and a label written in black marker. The count has to ignore both.

You photograph a plate or share photos in from elsewhere, check the count, correct it if needed and export results per plate or per sample. The app works out CFU from the dilution. It all runs on the phone with no connection.

  • The home screen with five plates to review across two projectsHome
  • A project with seven plates and results per sample in CFU per 100 mLA project
  • Editing a project: membrane filtration chosen as the method, 250 mL filtered, undilutedMethod
  • Settings, including the light theme and the counting model, which runs on the phone offlineSettings
The app in its light theme. Set the method and dilution once, and each sample's result comes out in CFU per 100 mL.

Thresholds first

My first version used classical computer vision in OpenCV. It corrected for uneven lighting, picked out bright spots and split touching colonies with a watershed. On the first 16 plates it looked fine.

The next batch of 199 plates broke it. On 80 of them, the mask meant to hide the marker label spread over most of the dish, and bright rings at the dish edge turned into colonies. I got the floods down from 80 plates to 8. Faint colonies on dark sediment plates never came out, though. Any threshold low enough to catch them also caught the grid.

Two models

In September 2026 I rebuilt it. Version 1 uses two YOLO26 models. The first finds the dish in the photo. The second counts colonies on a crop of the dish at 1024 pixels, so a colony 16 pixels wide in the photo is still 16 pixels wide when the model sees it. My July model looked at the whole photo at 640 pixels, which shrank colonies to about 6.

Both models run on the phone with ONNX Runtime. The app crops each photo with exactly the same integer arithmetic as the Python code. A test on real plates checks that the phone and my laptop agree. All 16 test plates gave the laptop’s count to the colony. The release build counted those 16 plates in 11.8 seconds on my Pixel 9 Pro XL, which is 0.74 seconds a plate with model loading included.

Results

I froze a test set of 16 plates before training. Neither detector saw them during tuning.

Detector Mean absolute error (colonies per plate) Plates within 5 colonies or 10%
Version 1 model 16.6 50%
OpenCV detector 25.7 31%

Sixteen plates is a small test. The 95% bootstrap interval for the model’s error runs from 6.7 to 29.9 colonies. A difference under about 5 colonies wouldn’t mean much.

What it gets wrong

Faint colonies on dark sediment discs. On one test plate with 115 labelled colonies the model found 17. Part of that is my labels: on some sediment plates every faint speck is marked, and on others none are. For now the app flags plates like this for a manual check. Across all plates it undercounts by about 11 colonies.

Next

Once I have answers I’ll relabel the affected plates and finish correcting the ones the model pre-labelled. Then I’ll retrain and score the new model on the same 16 test plates, so the numbers compare with these.

More work

2026R packages

FounderForge

A Shiny app that turns real marker data into an AlphaSimR founder population without writing R. Load a VCF, HapMap, numeric or PLINK file, check it, filter it and export what newMapPop() needs.

Tools: R · Shiny · golem · AlphaSimR · vcfR · data.table · ggplot2

2026Apps

Plotpin

An Android app for finding a plant again. I built it to walk back to bagged sorghum plants in a crossing block. Now it works for anyone who marks points in a field.

Tools: Kotlin · Jetpack Compose · Room · Paparazzi

2026Reverse engineering

FF Mod Studio

Tools for modding Freedom Fighters, a game from 2003. There's an editor for its textures and models, plus a Blender addon that sends edits back into the game.

Tools: C++17 · Dear ImGui · miniz · HLSL · Python · Blender

2026R packages

ggWizard

A Shiny wizard for making ggplot2 charts from a CSV or Excel file without writing code. You check and clean the data, pick one of eight chart types and style it, then export it with the R script behind it.

Tools: R · Shiny · golem · ggplot2 · plotly · readxl · DT

2026R packages

Linkmapper

A Shiny app for building genetic linkage maps and mapping QTL without writing code. It began as the final-year project of two colleagues and me at KNUST. Now it's an R package, with a hosted copy that runs in the browser.

Tools: R · Shiny · golem · onemap · R/qtl · ggiraph

2026R packages

Clusnalyst

A Shiny app for cluster analysis of marker, trait and sequence data. My colleagues and I built the first version at KNUST in 2022. In 2026 I rewrote it as an R package with a live demo.

Tools: R · Shiny · golem · ape · cluster · msa · ggtree · ggiraph

2026Codebreaking

The Voynich manuscript

A book about 600 years old, written in a script nobody can read. Here's what's known about it and why no codebreaker has cracked it, with a note on my own work.

Tools: Python · NumPy · SciPy