<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Automation in Biology Community</title>
    <description>The latest articles on DEV Community by Automation in Biology Community (biologyautomation).</description>
    <link>https://dev.to/biologyautomation</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14290%2Fed6a5f6a-294e-4932-a57c-654aaca786fc.png</url>
      <title>DEV Community: Automation in Biology Community</title>
      <link>https://dev.to/biologyautomation</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/biologyautomation"/>
    <language>en</language>
    <item>
      <title>From Manual Colony Measurements to an Open-Source Robotic Imaging Pipeline</title>
      <dc:creator>RoTSL</dc:creator>
      <pubDate>Sun, 26 Jul 2026 21:34:46 +0000</pubDate>
      <link>https://dev.to/biologyautomation/from-manual-colony-measurements-to-an-open-source-robotic-imaging-pipeline-75k</link>
      <guid>https://dev.to/biologyautomation/from-manual-colony-measurements-to-an-open-source-robotic-imaging-pipeline-75k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;How I replaced a repetitive laboratory workflow with an open-source robot, a computer vision pipeline, and a validated deep learning model.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9e435uxeb4udk54mmjhl.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9e435uxeb4udk54mmjhl.JPG" alt="Automator imaging a Petri dish" width="800" height="1067"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For decades, measuring fungal colony growth has been a surprisingly manual process. A researcher walks to an incubator, removes a stack of Petri dishes, photographs each one under consistent lighting, opens every image in Fiji/ImageJ, draws a region of interest around the colony, records a diameter, and repeats the whole process the following day. Then the next day. Sometimes for two weeks straight. There isn't anything technically difficult about this workflow.&lt;br&gt;
It's just repetitive. The repetition becomes the experiment.&lt;/p&gt;

&lt;p&gt;While working at &lt;a href="https://www.tsl.ac.uk/" rel="noopener noreferrer"&gt;The Sainsbury Laboratory&lt;/a&gt;, I found myself wondering why this process still looked much the same as it did years ago. Cheap single-board computers exist. Affordable motion control hardware exists. Deep learning models capable of segmenting biological images now run comfortably on consumer hardware. Open-source software has reached the point where almost every building block already exists.&lt;br&gt;
What didn't exist was a complete pipeline that connected those pieces together. So I decided to build one.&lt;/p&gt;

&lt;p&gt;Each project can be used independently. Together they automate almost the entire workflow from image acquisition through to quantitative biological measurements.&lt;br&gt;
Instead of moving plates out of the incubator every few hours, the robot performs scheduled imaging automatically. Instead of manually outlining colonies, a lightweight U-Net model segments them. Instead of trusting the model because "it looks right", every predicted diameter is compared against manually reviewed reference measurements.By the end of the project, I had developed three connected open-source projects with separate responsibilities:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Automator&lt;/td&gt;
&lt;td&gt;A Raspberry Pi–controlled robotic platform for scheduled imaging and tracking of QR-labelled Petri dishes.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;metrics-petri&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A production Python package that segments colonies and calculates calibrated growth, morphology, cracking, and texture measurements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;petrimodel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The training and diameter-validation repository used to develop, inspect, and evaluate the SmallUNet model distributed with &lt;code&gt;metrics-petri&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each project can be used independently, but they are designed to form a connected workflow. Automator produces consistently named time-series images; metrics-petri converts those images into calibrated measurements, overlays and charts; and &lt;code&gt;petrimodel&lt;/code&gt; records how the segmentation model was trained and validated.&lt;br&gt;
The present documentation describes Automator as an automated imaging and tracking system, while &lt;code&gt;metrics-petri&lt;/code&gt; describes its scope as growth, morphology, cracking and texture analysis rather than simply “sixteen morphometric measurements.” &lt;/p&gt;

&lt;p&gt;That last part mattered to me.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Automation is useful only if the measurements remain trustworthy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rather than simply reporting segmentation accuracy, I wanted to know whether the numbers biologists actually use in papers still agreed with manual measurements.The validation ended up being one of the most satisfying parts of the project. Across 605 model-versus-manual measurement pairs—121 original images and 484 augmented derivatives—the pipeline produced the following overall agreement statistics:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Measurement pairs&lt;/td&gt;
&lt;td&gt;605&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Original images&lt;/td&gt;
&lt;td&gt;121&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Augmented derivatives&lt;/td&gt;
&lt;td&gt;484&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bias&lt;/td&gt;
&lt;td&gt;0.039 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RMSE&lt;/td&gt;
&lt;td&gt;1.866 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R²&lt;/td&gt;
&lt;td&gt;0.9956&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lin's Concordance Correlation Coefficient&lt;/td&gt;
&lt;td&gt;0.9978&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MAE&lt;/td&gt;
&lt;td&gt;1.184 mm&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These values are reproduced in the current generated analysis report. Bias is defined as model minus manual measurement. &lt;br&gt;
Those aren't segmentation metrics. They're agreement statistics between the automated measurements and a manually reviewed reference workflow, which is ultimately what matters if the software is going to replace manual measurements in practice.&lt;br&gt;
The architecture eventually settled into something surprisingly clean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;+------------------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Petri&lt;/span&gt; &lt;span class="nx"&gt;dishes&lt;/span&gt; &lt;span class="nx"&gt;inside&lt;/span&gt; &lt;span class="nx"&gt;incubator&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------------+-----------------+&lt;/span&gt;
                   &lt;span class="o"&gt;|&lt;/span&gt;
                   &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Automator&lt;/span&gt;                          &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------------+-----------------+&lt;/span&gt;
                   &lt;span class="o"&gt;|&lt;/span&gt;
                   &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;High&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;resolution&lt;/span&gt; &lt;span class="nx"&gt;images&lt;/span&gt;             &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------------+-----------------+&lt;/span&gt;
                   &lt;span class="o"&gt;|&lt;/span&gt;
                   &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;petri&lt;/span&gt;                      &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------------+-----------------+&lt;/span&gt;
                   &lt;span class="o"&gt;|&lt;/span&gt;
                   &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;SmallUNet&lt;/span&gt; &lt;span class="nx"&gt;segmentation&lt;/span&gt;             &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------------+-----------------+&lt;/span&gt;
                   &lt;span class="o"&gt;|&lt;/span&gt;
                   &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="nx"&gt;calibrated&lt;/span&gt; &lt;span class="nx"&gt;morphometric&lt;/span&gt; &lt;span class="nx"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------------+-----------------+&lt;/span&gt;
                   &lt;span class="o"&gt;|&lt;/span&gt;
                   &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Saved&lt;/span&gt; &lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;provenance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;overlays&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;growth&lt;/span&gt; &lt;span class="nx"&gt;charts&lt;/span&gt;                  &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------------+-----------------+&lt;/span&gt;
                   &lt;span class="o"&gt;|&lt;/span&gt;
                   &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;petrimodel&lt;/span&gt; &lt;span class="nx"&gt;training&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------------+-----------------+&lt;/span&gt;
                   &lt;span class="o"&gt;|&lt;/span&gt;
                   &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt; &lt;span class="nx"&gt;provenance&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;agreement&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt;                           &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------------------------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;petrimodel&lt;/code&gt; is not a stage that ordinary &lt;code&gt;metrics-petri&lt;/code&gt; analyses pass through at runtime. It is the companion development and validation repository behind the packaged checkpoint. &lt;/p&gt;

&lt;p&gt;One thing I deliberately avoided was building one enormous application.Large laboratory software has a habit of becoming difficult to maintain because everything depends on everything else. If imaging, analysis, model training and validation all live inside one repository, every change risks breaking something unrelated. Instead, I split the pipeline into three repositories with well-defined responsibilities.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automator knows how to move hardware and capture images.&lt;/li&gt;
&lt;li&gt;metrics-petri knows how to analyse images.&lt;/li&gt;
&lt;li&gt;petrimodel knows how to train and evaluate models.
The only thing they really share is a filename convention and a common understanding that every image contains a standard 90 mm Petri dish.That design decision turned out to simplify far more than I expected.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most computer-vision pipelines require an external calibration object somewhere in the image. In this workflow, the Petri dish itself provides that reference.&lt;br&gt;
The software detects the dish rim and calculates the physical scale from its pixel diameter and the configured outside dish diameter. The default is &lt;code&gt;90 mm&lt;/code&gt;, matching the dishes used to train and validate the current model, but metrics-petri now accepts other sizes through the &lt;code&gt;--dish-size-mm&lt;/code&gt; option.&lt;br&gt;
For a configured dish diameter &lt;code&gt;D&lt;/code&gt; and detected image diameter &lt;code&gt;d&lt;/code&gt;, the calibration is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;millimetres&lt;/span&gt; &lt;span class="nx"&gt;per&lt;/span&gt; &lt;span class="nx"&gt;pixel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;D&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes the need to place a ruler or calibration grid in each photograph while allowing area, perimeter, diameter, displacement and filament-length estimates to be reported in physical units.&lt;br&gt;
The current CLI explicitly supports, for example, &lt;code&gt;--dish-size-mm 60&lt;/code&gt;; &lt;code&gt;90 mm&lt;/code&gt; is the default rather than the only permitted size. &lt;/p&gt;
&lt;h2&gt;
  
  
  The workflow I wanted
&lt;/h2&gt;

&lt;p&gt;Before writing the controller, I wrote down what an ideal experiment should look like. A researcher would prepare and label the Petri dishes, scan the workspace, select the detected plates and configure each plate’s name, imaging interval and end time through the Automator interface.&lt;br&gt;
The robot would then execute the scheduled run: move to each selected plate, enter the capture position, acquire and save the image, reset safely and repeat when the next capture became due. The web interface would provide live status, normal pause and resume controls, image previews and methods for retrieving the captured data.&lt;/p&gt;

&lt;p&gt;A segmentation model isolates each colony. Sixteen morphometric measurements are calculated automatically. Time-series plots appear. Overlay images allow every prediction to be inspected visually.The measurements are exported as CSV and JSON files ready for statistical analysis.&lt;br&gt;
That was the goal.&lt;br&gt;
The rest of this article explains how I built each part of that pipeline, what worked, what didn't, and why I made some design decisions that might initially seem a little unusual.&lt;/p&gt;
&lt;h2&gt;
  
  
  Building Automator
&lt;/h2&gt;

&lt;p&gt;The robot came first. That might sound backwards. Most image analysis projects begin with a dataset and a neural network, then worry about how new images will arrive later.&lt;br&gt;
I had the opposite problem.&lt;/p&gt;

&lt;p&gt;I knew I could always train a better segmentation model in the future. What I couldn't buy was consistent image acquisition. Every experiment depended on somebody remembering to photograph plates at the same time every day, under roughly the same lighting, from roughly the same height, while trying not to disturb colonies growing inside the incubator. "Roughly" isn't something computer vision likes. If images are captured under changing illumination, with different camera heights or slightly different viewing angles, the segmentation model ends up learning those inconsistencies instead of the biology.So before thinking about neural networks, I wanted a robot that could produce the same photograph every single time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why not buy a commercial system?
&lt;/h2&gt;

&lt;p&gt;Commercial colony imaging systems certainly exist. They're also expensive, closed source, and usually tied to proprietary hardware or consumables. I wasn't trying to compete with those systems. I wanted something that another research group could reproduce using readily available parts, a 3D printer, and open-source software.That decision shaped almost every engineering choice that followed. The entire machine is built from off-the-shelf components.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Controller&lt;/td&gt;
&lt;td&gt;Raspberry Pi 4B (8 GB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion controller&lt;/td&gt;
&lt;td&gt;MKS Robin Nano V3.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion firmware&lt;/td&gt;
&lt;td&gt;Klipper&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API server&lt;/td&gt;
&lt;td&gt;Moonraker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High-resolution camera&lt;/td&gt;
&lt;td&gt;Raspberry Pi Camera Module 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detection camera&lt;/td&gt;
&lt;td&gt;USB webcam&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Motion&lt;/td&gt;
&lt;td&gt;Three NEMA17 stepper motors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lid handling&lt;/td&gt;
&lt;td&gt;MG995 servo + vacuum suction cup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lighting&lt;/td&gt;
&lt;td&gt;5 V LED strip&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chassis&lt;/td&gt;
&lt;td&gt;PETG printed components + aluminium extrusion&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these components are exotic. In fact, several came from the desktop 3D printing ecosystem.&lt;strong&gt;That wasn't accidental.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Reusing the 3D printing ecosystem
&lt;/h2&gt;

&lt;p&gt;Building reliable motion systems from scratch is difficult.&lt;br&gt;
Fortunately, thousands of people have already solved exactly that problem.Modern desktop 3D printers routinely achieve sub-millimetre positioning accuracy over hundreds of thousands of movements. Instead of reinventing that work, I borrowed it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automator uses Klipper as its motion firmware.&lt;/li&gt;
&lt;li&gt;Klipper normally controls printers.&lt;/li&gt;
&lt;li&gt;Here it controls a laboratory robot.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;+---------+&lt;/span&gt;     &lt;span class="o"&gt;+-------+&lt;/span&gt;     &lt;span class="o"&gt;+------------+&lt;/span&gt;     &lt;span class="o"&gt;+---------+&lt;/span&gt;     &lt;span class="o"&gt;+-------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Browser&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Flask&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Moonraker&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Klipper&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;STM32&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+---------+&lt;/span&gt;     &lt;span class="o"&gt;+-------+&lt;/span&gt;     &lt;span class="o"&gt;+------------+&lt;/span&gt;     &lt;span class="o"&gt;+---------+&lt;/span&gt;     &lt;span class="o"&gt;+---+---+&lt;/span&gt;
                                                                     &lt;span class="o"&gt;|&lt;/span&gt;
                 &lt;span class="o"&gt;+-------------------+-------------------+-------------+-------------------+-------------------+&lt;/span&gt;
                 &lt;span class="o"&gt;|&lt;/span&gt;                   &lt;span class="o"&gt;|&lt;/span&gt;                   &lt;span class="o"&gt;|&lt;/span&gt;                                 &lt;span class="o"&gt;|&lt;/span&gt;                   &lt;span class="o"&gt;|&lt;/span&gt;
                 &lt;span class="nx"&gt;v&lt;/span&gt;                   &lt;span class="nx"&gt;v&lt;/span&gt;                   &lt;span class="nx"&gt;v&lt;/span&gt;                                 &lt;span class="nx"&gt;v&lt;/span&gt;                   &lt;span class="nx"&gt;v&lt;/span&gt;
           &lt;span class="o"&gt;+-----------+&lt;/span&gt;       &lt;span class="o"&gt;+-----------+&lt;/span&gt;       &lt;span class="o"&gt;+-----------+&lt;/span&gt;                     &lt;span class="o"&gt;+---------+&lt;/span&gt;       &lt;span class="o"&gt;+----------+&lt;/span&gt;
           &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;X&lt;/span&gt; &lt;span class="nx"&gt;Motor&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Y&lt;/span&gt; &lt;span class="nx"&gt;Motor&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Z&lt;/span&gt; &lt;span class="nx"&gt;Motor&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt;                     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Servo&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Vacuum&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt;
           &lt;span class="o"&gt;+-----------+&lt;/span&gt;       &lt;span class="o"&gt;+-----------+&lt;/span&gt;       &lt;span class="o"&gt;+-----------+&lt;/span&gt;                     &lt;span class="o"&gt;+---------+&lt;/span&gt;       &lt;span class="o"&gt;+----------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Klipper handles everything timing sensitive.&lt;/p&gt;

&lt;p&gt;The Raspberry Pi doesn't generate step pulses directly. Instead it sends high-level movement commands to Moonraker, which forwards compiled motion plans to the STM32 microcontroller. That separation has a few advantages.&lt;br&gt;
&lt;strong&gt;First&lt;/strong&gt;, the Raspberry Pi remains free to handle cameras, QR decoding, scheduling and the web interface without worrying about real-time motor control.&lt;br&gt;
&lt;strong&gt;Second&lt;/strong&gt;, every mechanical parameter lives inside a version-controlled configuration file. Changing acceleration or travel limits doesn't require recompiling firmware.&lt;br&gt;
&lt;strong&gt;It requires editing a text file.&lt;/strong&gt; For a research project where hardware evolves constantly, that's a much more pleasant workflow.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two cameras, two jobs
&lt;/h2&gt;

&lt;p&gt;Early prototypes used a single camera. That quickly became awkward. The camera needed two completely different viewpoints.&lt;br&gt;
One job required a wide field of view for finding Petri dishes and reading QR codes. &lt;br&gt;
The other needed a close-up, distortion-free image suitable for quantitative measurements. &lt;br&gt;
Trying to satisfy both with one camera meant compromising both.&lt;br&gt;
Eventually I split the responsibilities.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;+----------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;USB&lt;/span&gt; &lt;span class="nx"&gt;Camera&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+-------+--------+&lt;/span&gt;
        &lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="o"&gt;+--------------------+--------------------+&lt;/span&gt;
        &lt;span class="o"&gt;|&lt;/span&gt;                    &lt;span class="o"&gt;|&lt;/span&gt;                    &lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="nx"&gt;v&lt;/span&gt;                    &lt;span class="nx"&gt;v&lt;/span&gt;                    &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+-----------------+&lt;/span&gt;   &lt;span class="o"&gt;+-----------------+&lt;/span&gt;   &lt;span class="o"&gt;+-------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Plate&lt;/span&gt; &lt;span class="nx"&gt;detection&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;QR&lt;/span&gt; &lt;span class="nx"&gt;decoding&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Workspace&lt;/span&gt; &lt;span class="nx"&gt;preview&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+-----------------+&lt;/span&gt;   &lt;span class="o"&gt;+-----------------+&lt;/span&gt;   &lt;span class="o"&gt;+-------------------+&lt;/span&gt;


&lt;span class="o"&gt;+----------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Pi&lt;/span&gt; &lt;span class="nx"&gt;Camera&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+-------+--------+&lt;/span&gt;
        &lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;High&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;resolution&lt;/span&gt; &lt;span class="nx"&gt;stills&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+-----------+------------+&lt;/span&gt;
            &lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+----------------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Colony&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;petri&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+----------------------------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The USB webcam remains active almost continuously.It watches the workspace, detects plates, reads QR codes and provides the live preview shown in the browser.The Pi Camera Module 3 stays idle until the robot reaches imaging position.Only then does it switch on the LED lighting, capture the still image and immediately switch everything off again.Besides reducing power consumption, this also keeps colonies in darkness between imaging cycles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Opening a Petri dish without human hands
&lt;/h2&gt;

&lt;p&gt;This was probably the most mechanically challenging part of the project. Photographing colonies through closed lids isn't ideal. Condensation creates reflections. Plastic scratches confuse segmentation. Lighting becomes inconsistent. Removing the lid solves all of those problems. Unfortunately, removing the lid usually requires a person. I wanted the robot to do it instead. After experimenting with several gripper designs, I settled on a vacuum suction cup.&lt;br&gt;
The imaging sequence became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;+------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Move&lt;/span&gt; &lt;span class="nx"&gt;above&lt;/span&gt; &lt;span class="nx"&gt;plate&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+--------+---------+&lt;/span&gt;
         &lt;span class="o"&gt;|&lt;/span&gt;
         &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Lower&lt;/span&gt; &lt;span class="nx"&gt;Z&lt;/span&gt; &lt;span class="nx"&gt;axis&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+--------+---------+&lt;/span&gt;
         &lt;span class="o"&gt;|&lt;/span&gt;
         &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Enable&lt;/span&gt; &lt;span class="nx"&gt;vacuum&lt;/span&gt;    &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+--------+---------+&lt;/span&gt;
         &lt;span class="o"&gt;|&lt;/span&gt;
         &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Lift&lt;/span&gt; &lt;span class="nx"&gt;lid&lt;/span&gt;         &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+--------+---------+&lt;/span&gt;
         &lt;span class="o"&gt;|&lt;/span&gt;
         &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Rotate&lt;/span&gt; &lt;span class="nx"&gt;lid&lt;/span&gt; &lt;span class="nx"&gt;aside&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+--------+---------+&lt;/span&gt;
         &lt;span class="o"&gt;|&lt;/span&gt;
         &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Capture&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;    &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+--------+---------+&lt;/span&gt;
         &lt;span class="o"&gt;|&lt;/span&gt;
         &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Replace&lt;/span&gt; &lt;span class="nx"&gt;lid&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+--------+---------+&lt;/span&gt;
         &lt;span class="o"&gt;|&lt;/span&gt;
         &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Release&lt;/span&gt; &lt;span class="nx"&gt;vacuum&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+--------+---------+&lt;/span&gt;
         &lt;span class="o"&gt;|&lt;/span&gt;
         &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Return&lt;/span&gt; &lt;span class="nx"&gt;home&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It sounds simple. It took far longer than I expected.&lt;br&gt;
The vacuum had to hold firmly enough to lift the lid without deforming it. The servo needed enough torque to rotate the lid reliably. Travel heights had to avoid collisions with neighbouring dishes. Even something as mundane as tubing length affected repeatability because longer tubes increased the time needed to establish vacuum pressure. Those details rarely appear in papers, but they're where most of the engineering time went.&lt;/p&gt;
&lt;h2&gt;
  
  
  QR codes as metadata
&lt;/h2&gt;

&lt;p&gt;I wanted every image to explain itself. Instead of creating spreadsheets that later needed matching against photographs, each Petri dish carries a QR code before the experiment even begins.&lt;/p&gt;

&lt;p&gt;The encoded string contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;experiment date&lt;/li&gt;
&lt;li&gt;strain&lt;/li&gt;
&lt;li&gt;growth medium&lt;/li&gt;
&lt;li&gt;treatment&lt;/li&gt;
&lt;li&gt;viewing orientation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once scanned, that metadata becomes part of every captured filename.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;YYYYMMDD_P001_D03_MAGNAPORTHE_PCBM_CONTROL_TOP.JPG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks like a filename. It's actually an like an API between repositories. &lt;code&gt;metrics-petri&lt;/code&gt; can recover experiment information without requiring another database. That meant the robot didn't need to know anything about image analysis. It simply writes well-structured filenames.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety first
&lt;/h2&gt;

&lt;p&gt;Laboratory robots shouldn't assume software always behaves correctly. Automator therefore includes several hardware safety features.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A latching emergency stop physically disconnects power.&lt;/li&gt;
&lt;li&gt;Door sensors pause imaging if the enclosure opens.&lt;/li&gt;
&lt;li&gt;Axis end stops protect against over-travel.&lt;/li&gt;
&lt;li&gt;Motion can only resume after the enclosure is closed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important point is that these protections exist independently of Python. If the Raspberry Pi crashes, the emergency stop still works. Software safety is useful. Hardware safety is essential. Anyone reproducing it remains responsible for electrical protection, mechanical guarding, external emergency-stop hardware, risk assessment and compliance with local laboratory regulations.&lt;/p&gt;

&lt;h2&gt;
  
  
  A browser interface instead of a conventional desktop application
&lt;/h2&gt;

&lt;p&gt;I decided early not to build a conventional desktop GUI. The Raspberry Pi already hosted the controller, camera services and imaging schedule, so Automator exposes its operational interface through a browser.&lt;br&gt;
The current system supports two normal access modes. A user can connect a MacBook directly over Ethernet and open the interface at &lt;br&gt;
&lt;code&gt;http://192.168.50.2:8080/&lt;/code&gt;       or &lt;code&gt;http://automator.local:8080/&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Alternatively, a monitor, keyboard and mouse can be attached directly to the Raspberry Pi, which boots into a full-screen local kiosk.&lt;br&gt;
The interface guides users through workspace scanning, plate detection, per-plate configuration, QR-label generation, scheduled imaging, status monitoring and image retrieval. The two access methods should not be used simultaneously because doing so can introduce latency.&lt;br&gt;
Captured images can be viewed or retrieved through the interface or exported to USB. The current storage policy retains images for 21 days and automatically keeps the data directory below 5 GiB.(These access and retention rules are now explicitly documented.)&lt;/p&gt;
&lt;h2&gt;
  
  
  The first image
&lt;/h2&gt;

&lt;p&gt;The first successful automated image wasn't particularly exciting. It looked almost identical to a photograph I could have taken myself. That was exactly what I wanted. The point wasn't to produce a spectacular image. The point was to produce the same image every single time. Once I had consistent image acquisition, everything else became easier.&lt;/p&gt;
&lt;h2&gt;
  
  
  From Images to Measurements: Building &lt;code&gt;metrics-petri&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Once I had a robot capable of producing consistent images, the next question was obvious. Now what? A folder containing hundreds of JPEGs isn't especially useful on its own. Someone still has to extract biological measurements from every image.&lt;br&gt;
That usually meant opening Fiji/ImageJ, calibrating the image, outlining the colony, measuring its area or diameter, saving the results, and repeating the process for every photograph.&lt;br&gt;
&lt;strong&gt;That was exactly the bottleneck I wanted to remove.&lt;/strong&gt; Rather than writing another GUI application that automated mouse clicks, I wanted something that behaved like any other Python tool.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install it.&lt;/li&gt;
&lt;li&gt;Point it at a folder.&lt;/li&gt;
&lt;li&gt;Receive measurements.
That became &lt;strong&gt;&lt;code&gt;metrics-petri&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike Automator, which is tightly coupled to hardware, metrics-petri is intentionally hardware agnostic. It doesn't care where the images came from. They might have been captured using Automator. They might have been photographed by hand.As long as the image contains a Petri dish, the pipeline is the same.&lt;/p&gt;


&lt;h2&gt;
  
  
  Installing the package
&lt;/h2&gt;

&lt;p&gt;I wanted installation to be almost trivial.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;metrics-petri
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command installs the complete analysis pipeline, including the trained segmentation model. One design decision I'm particularly happy with was bundling the production checkpoint directly inside the Python wheel.Many computer vision packages require downloading a model after installation. That sounds reasonable until somebody tries to run the software on a machine without internet access or discovers the download link has disappeared two years later.&lt;/p&gt;

&lt;p&gt;Instead, the wheel already contains the production checkpoint. The package simply works after installation. If users prefer another checkpoint, metrics-petri can also load one supplied through an environment variable, a command-line argument, or download one automatically from Hugging Face as a fallback.&lt;br&gt;
The search order looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UNET_MODEL
      │
      ▼
Local model file
      │
      ▼
Bundled checkpoint
      │
      ▼
Hugging Face
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives flexibility without making the default installation fragile.&lt;/p&gt;




&lt;h2&gt;
  
  
  One command
&lt;/h2&gt;

&lt;p&gt;Most users only need a single command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;metrics-petri analyse ./experiment_images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The software scans the folder, processes every image, and writes a ZIP archive containing&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSV results&lt;/li&gt;
&lt;li&gt;JSON results&lt;/li&gt;
&lt;li&gt;overlay images&lt;/li&gt;
&lt;li&gt;growth charts&lt;/li&gt;
&lt;li&gt;metadata&lt;/li&gt;
&lt;li&gt;publication-ready figures
There isn't a complicated project structure. There aren't dozens of configuration files. The defaults are designed for the imaging workflow produced by Automator.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Five stages
&lt;/h2&gt;

&lt;p&gt;Internally, the analysis pipeline is surprisingly simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;+-------------+&lt;/span&gt;     &lt;span class="o"&gt;+-------------+&lt;/span&gt;     &lt;span class="o"&gt;+------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Input&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Detect&lt;/span&gt; &lt;span class="nx"&gt;dish&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Calibrate&lt;/span&gt; &lt;span class="nx"&gt;mm&lt;/span&gt; &lt;span class="nx"&gt;per&lt;/span&gt; &lt;span class="nx"&gt;pixel&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+-------------+&lt;/span&gt;     &lt;span class="o"&gt;+-------------+&lt;/span&gt;     &lt;span class="o"&gt;+-----------+------------+&lt;/span&gt;
                                                    &lt;span class="o"&gt;|&lt;/span&gt;
                                                    &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+-------------+&lt;/span&gt;     &lt;span class="o"&gt;+--------------------+&lt;/span&gt;     &lt;span class="o"&gt;+-----------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;ZIP&lt;/span&gt; &lt;span class="nx"&gt;archive&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Charts&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;CSV&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;SmallUNet&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+-------------+&lt;/span&gt;     &lt;span class="o"&gt;+--------------------+&lt;/span&gt;     &lt;span class="o"&gt;+-----+-----+&lt;/span&gt;
                                                    &lt;span class="o"&gt;|&lt;/span&gt;
                                                    &lt;span class="nx"&gt;v&lt;/span&gt;
                                      &lt;span class="o"&gt;+------------------------+&lt;/span&gt;
                                      &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Binary&lt;/span&gt; &lt;span class="nx"&gt;colony&lt;/span&gt; &lt;span class="nx"&gt;mask&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt;
                                      &lt;span class="o"&gt;+-----------+------------+&lt;/span&gt;
                                                  &lt;span class="o"&gt;|&lt;/span&gt;
                                                  &lt;span class="nx"&gt;v&lt;/span&gt;
                                      &lt;span class="o"&gt;+---------------------------+&lt;/span&gt;
                                      &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Morphometric&lt;/span&gt; &lt;span class="nx"&gt;measurements&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
                                      &lt;span class="o"&gt;+---------------------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every image passes through exactly these five stages. Each stage exists for a reason.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 1 — Finding the Petri dish
&lt;/h2&gt;

&lt;p&gt;Before measuring anything, the software needs to understand scale.Computer vision models naturally measure everything in pixels. Biologists rarely care about pixels. They care about millimetres.The solution was surprisingly elegant.Every experiment already uses identical 90 mm Petri dishes.Instead of placing a ruler beside every colony, the software detects the dish itself using a circular Hough transform.&lt;/p&gt;

&lt;p&gt;Once the dish diameter is known,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mm per pixel = 90 mm/ detected dish diameter

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every downstream measurement inherits that calibration automatically.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Area.&lt;/li&gt;
&lt;li&gt;Perimeter.&lt;/li&gt;
&lt;li&gt;Diameter.&lt;/li&gt;
&lt;li&gt;Hyphal length.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything ends up expressed in physical units rather than image coordinates.That means measurements remain comparable even if different cameras produce different image resolutions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 2 — Preparing the image
&lt;/h2&gt;

&lt;p&gt;The segmentation model expects a fixed input size. Every photograph therefore passes through a preprocessing stage before inference.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="nx"&gt;Raw&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;

       &lt;span class="err"&gt;↓&lt;/span&gt;

   &lt;span class="nx"&gt;Detect&lt;/span&gt; &lt;span class="nx"&gt;dish&lt;/span&gt;

       &lt;span class="err"&gt;↓&lt;/span&gt;

     &lt;span class="nx"&gt;Resize&lt;/span&gt;

       &lt;span class="err"&gt;↓&lt;/span&gt;

   &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt;

       &lt;span class="err"&gt;↓&lt;/span&gt;

   &lt;span class="nx"&gt;SmallUNet&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Resizing dramatically reduces computation while keeping enough spatial information for accurate segmentation.Once inference completes, the predicted mask is mapped back onto the original image resolution before measurements are computed.That means users get accurate measurements without sacrificing image detail.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 3 — Segmenting the colony
&lt;/h2&gt;

&lt;p&gt;The heart of the package is a deliberately lightweight U-Net variant called &lt;strong&gt;SmallUNet&lt;/strong&gt;. There are now hundreds of segmentation architectures available.Many achieve slightly better benchmark scores than U-Net. Most are also considerably larger. I wasn't interested in chasing another decimal place of IoU if it meant shipping a 400 MB model.&lt;/p&gt;

&lt;p&gt;Instead I wanted something that&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installs quickly&lt;/li&gt;
&lt;li&gt;runs on ordinary laptops&lt;/li&gt;
&lt;li&gt;works on Apple Silicon&lt;/li&gt;
&lt;li&gt;works on CPUs&lt;/li&gt;
&lt;li&gt;is easy to redistribute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SmallUNet ended up being around &lt;strong&gt;23 MB&lt;/strong&gt;, small enough to bundle directly inside the package.The architecture follows the familiar encoder-decoder pattern.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;
   &lt;span class="nx"&gt;Image&lt;/span&gt;

     &lt;span class="err"&gt;↓&lt;/span&gt;

  &lt;span class="nx"&gt;Encoder&lt;/span&gt;

     &lt;span class="err"&gt;↓&lt;/span&gt;

 &lt;span class="nx"&gt;Bottleneck&lt;/span&gt;

     &lt;span class="err"&gt;↓&lt;/span&gt;

  &lt;span class="nx"&gt;Decoder&lt;/span&gt;

     &lt;span class="err"&gt;↓&lt;/span&gt;

  &lt;span class="nx"&gt;Sigmoid&lt;/span&gt;

     &lt;span class="err"&gt;↓&lt;/span&gt;

 &lt;span class="nx"&gt;Binary&lt;/span&gt; &lt;span class="nx"&gt;mask&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing particularly revolutionary.Sometimes the best architecture is the one people already understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stage 4 — Measuring biology instead of pixels
&lt;/h2&gt;

&lt;p&gt;Once segmentation is complete, the interesting work begins. Rather than returning only colony area, metrics-petri calculates sixteen morphometric measurements.These were chosen because they capture different aspects of fungal growth.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Unit&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Area&lt;/td&gt;
&lt;td&gt;mm²&lt;/td&gt;
&lt;td&gt;Colony size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diameter&lt;/td&gt;
&lt;td&gt;mm&lt;/td&gt;
&lt;td&gt;Equivalent circular diameter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Perimeter&lt;/td&gt;
&lt;td&gt;mm&lt;/td&gt;
&lt;td&gt;Colony perimeter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eccentricity&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Shape elongation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edge roughness&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Boundary irregularity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Centre displacement&lt;/td&gt;
&lt;td&gt;mm&lt;/td&gt;
&lt;td&gt;Offset from dish centre&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Texture standard deviation&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Intensity variation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entropy&lt;/td&gt;
&lt;td&gt;bits&lt;/td&gt;
&lt;td&gt;Image complexity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crack area&lt;/td&gt;
&lt;td&gt;mm²&lt;/td&gt;
&lt;td&gt;Physical crack size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crack coverage&lt;/td&gt;
&lt;td&gt;%&lt;/td&gt;
&lt;td&gt;Crack percentage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crack count&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Number of crack regions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frangi hyphal length&lt;/td&gt;
&lt;td&gt;mm&lt;/td&gt;
&lt;td&gt;Filament estimate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Meijering hyphal length&lt;/td&gt;
&lt;td&gt;mm&lt;/td&gt;
&lt;td&gt;Alternative estimate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hybrid hyphal length&lt;/td&gt;
&lt;td&gt;mm&lt;/td&gt;
&lt;td&gt;Combined estimate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Relative growth rate&lt;/td&gt;
&lt;td&gt;day⁻¹&lt;/td&gt;
&lt;td&gt;Requires metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Absolute growth rate&lt;/td&gt;
&lt;td&gt;mm²/day&lt;/td&gt;
&lt;td&gt;Requires metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Some of these are familiar. Area and diameter appear in almost every fungal growth paper. Others are less common. Edge roughness, for example, captures how irregular colony boundaries become during development. Crack coverage quantifies visible fissures rather than simply reporting that they exist.&lt;br&gt;
Hyphal length estimates use vessel enhancement filters originally developed for medical imaging. Collectively they describe colony morphology far better than a single diameter measurement ever could.&lt;/p&gt;


&lt;h2&gt;
  
  
  Seeing what the model saw
&lt;/h2&gt;

&lt;p&gt;One thing I dislike about many AI tools is that they quietly produce numbers without showing how they arrived there. &lt;code&gt;metrics-petri&lt;/code&gt; deliberately produces overlay images.&lt;/p&gt;

&lt;p&gt;Each output includes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the original photograph&lt;/li&gt;
&lt;li&gt;detected dish boundary&lt;/li&gt;
&lt;li&gt;predicted colony mask
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Raw&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;Detected&lt;/span&gt; &lt;span class="nx"&gt;dish&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;Predicted&lt;/span&gt; &lt;span class="nx"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Overlay&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If something looks wrong, users can inspect the overlay immediately instead of discovering problems weeks later during statistical analysis. Transparency was far more valuable to me than squeezing a little more processing speed from the pipeline.&lt;/p&gt;


&lt;h2&gt;
  
  
  Metadata without spreadsheets
&lt;/h2&gt;

&lt;p&gt;Growth measurements become much more useful once time enters the picture.Instead of manually constructing spreadsheets, the package includes a metadata builder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;metrics-petri-metadata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application generates a simple metadata file describing&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;experiment&lt;/li&gt;
&lt;li&gt;user&lt;/li&gt;
&lt;li&gt;dates&lt;/li&gt;
&lt;li&gt;imaging days&lt;/li&gt;
&lt;li&gt;day codes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those dates are available, the package automatically computes relative and absolute growth rates. Because Automator already embeds day codes into filenames, the two projects fit together naturally. &lt;br&gt;
No database.&lt;br&gt;
No proprietary file format.&lt;br&gt;
Just sensible filenames.&lt;/p&gt;


&lt;h2&gt;
  
  
  More than a command-line tool
&lt;/h2&gt;

&lt;p&gt;Although most users will probably interact with the command line, metrics-petri also ships with several supporting utilities.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;metrics-petri&lt;/td&gt;
&lt;td&gt;Complete analysis pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;metrics-petri-metadata&lt;/td&gt;
&lt;td&gt;Metadata editor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;metrics-petri-crop&lt;/td&gt;
&lt;td&gt;Crop individual dishes from larger photographs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;doctor&lt;/td&gt;
&lt;td&gt;Environment diagnostics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The doctor command turned out to be especially useful during development. Rather than letting users discover missing dependencies halfway through an experiment, it checks Python, NumPy, Torch, available hardware acceleration, and model availability before analysis begins.A small feature. A surprisingly large reduction in support emails.&lt;/p&gt;


&lt;h2&gt;
  
  
  Packaging for reproducibility
&lt;/h2&gt;

&lt;p&gt;One lesson I learned during this project is that machine learning software becomes much easier to reproduce when models travel with the code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repositories disappear.&lt;/li&gt;
&lt;li&gt;Download links break.&lt;/li&gt;
&lt;li&gt;Cloud storage changes.&lt;/li&gt;
&lt;li&gt;Package indexes remain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By shipping the trained checkpoint inside the package, the version installed from PyPI remains tied to exactly the model it was validated with. Years from now someone should still be able to install the same version and reproduce the same measurements without hunting for archived checkpoints. That was worth the extra megabytes. Of course, none of this matters unless the measurements actually agree with human measurements. Building the analysis pipeline solved the automation problem.It didn't answer the much more important question.&lt;br&gt;
&lt;strong&gt;Can those measurements be trusted?&lt;/strong&gt;&lt;br&gt;
That question became the motivation for the third project: &lt;strong&gt;&lt;code&gt;petrimodel&lt;/code&gt;&lt;/strong&gt;, where the segmentation model was trained, evaluated and compared against a manually reviewed reference pipeline.&lt;/p&gt;
&lt;h2&gt;
  
  
  Training the Model: Why &lt;code&gt;petrimodel&lt;/code&gt; Exists
&lt;/h2&gt;

&lt;p&gt;By the time Automator and metrics-petri were working together, I could automatically photograph colonies and calculate measurements from them.That still left one uncomfortable question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I know those measurements are actually correct?&lt;/strong&gt;&lt;br&gt;
Deep learning papers often stop after reporting segmentation metrics such as Dice score or Intersection over Union.Those metrics are useful for comparing neural networks.They are much less useful for convincing a biologist that a reported colony diameter is trustworthy.&lt;/p&gt;

&lt;p&gt;Nobody publishes a paper saying,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Our Dice score was excellent."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They publish biological measurements.So I wanted to validate the measurements themselves rather than simply the segmentation masks.That decision eventually became the third project in the pipeline: &lt;strong&gt;&lt;code&gt;petrimodel&lt;/code&gt;&lt;/strong&gt;. Unlike the other two repositories, petrimodel isn't intended for everyday users. It's the engineering and research repository behind the production model. It contains everything needed to reproduce the model development process, including&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the annotated dataset&lt;/li&gt;
&lt;li&gt;training scripts&lt;/li&gt;
&lt;li&gt;augmentation pipeline&lt;/li&gt;
&lt;li&gt;checkpoint selection&lt;/li&gt;
&lt;li&gt;validation software&lt;/li&gt;
&lt;li&gt;agreement statistics&lt;/li&gt;
&lt;li&gt;manual annotation tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, it answers the question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Where did the model inside metrics-petri actually come from?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Building a dataset
&lt;/h2&gt;

&lt;p&gt;Computer vision projects live or die by their datasets.The model can only learn from what it sees. For this project I assembled a dataset of fungal colony images captured during laboratory experiments.&lt;br&gt;
The complete training dataset contains:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dataset component&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original images&lt;/td&gt;
&lt;td&gt;121&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Augmented images&lt;/td&gt;
&lt;td&gt;484&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total image/mask pairs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;605&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every image has a corresponding LabelMe polygon annotation describing the visible colony.One detail I deliberately mention is that these are &lt;strong&gt;rough annotations&lt;/strong&gt;. That wording wasn't accidental. The initial polygons are starting points. Later stages of the validation process include manual review and correction.Rather than pretending every annotation is perfect, I wanted the workflow itself to acknowledge that human judgement still matters.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why SmallUNet?
&lt;/h2&gt;

&lt;p&gt;Machine learning evolves incredibly quickly.Every few months another segmentation architecture appears claiming slightly higher benchmark scores. For this project I wasn't interested in chasing leaderboards. I cared about something much simpler.&lt;br&gt;
Could another researcher install the package, run it on an ordinary laptop, and obtain reliable measurements?&lt;/p&gt;

&lt;p&gt;That meant the model needed to be&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lightweight&lt;/li&gt;
&lt;li&gt;reproducible&lt;/li&gt;
&lt;li&gt;portable&lt;/li&gt;
&lt;li&gt;easy to distribute&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SmallUNet turned out to be a good balance. It follows the familiar encoder-decoder structure introduced by U-Net while remaining compact enough to ship inside the Python package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;+-------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="nx"&gt;RGB&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------+------------+&lt;/span&gt;
             &lt;span class="o"&gt;|&lt;/span&gt;
             &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+-------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Encoder&lt;/span&gt;                 &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt; &lt;span class="nx"&gt;channels&lt;/span&gt;             &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------+------------+&lt;/span&gt;
             &lt;span class="o"&gt;|&lt;/span&gt;
             &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+-------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Encoder&lt;/span&gt;                 &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="nx"&gt;channels&lt;/span&gt;             &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------+------------+&lt;/span&gt;
             &lt;span class="o"&gt;|&lt;/span&gt;
             &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+-------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Encoder&lt;/span&gt;                 &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="nx"&gt;channels&lt;/span&gt;             &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------+------------+&lt;/span&gt;
             &lt;span class="o"&gt;|&lt;/span&gt;
             &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+-------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Bottleneck&lt;/span&gt;              &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;128&lt;/span&gt; &lt;span class="nx"&gt;channels&lt;/span&gt;            &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------+------------+&lt;/span&gt;
             &lt;span class="o"&gt;|&lt;/span&gt;
             &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+-------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Decoder&lt;/span&gt;                 &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------+------------+&lt;/span&gt;
             &lt;span class="o"&gt;|&lt;/span&gt;
             &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+-------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Skip&lt;/span&gt; &lt;span class="nx"&gt;connections&lt;/span&gt;        &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------+------------+&lt;/span&gt;
             &lt;span class="o"&gt;|&lt;/span&gt;
             &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+-------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="nx"&gt;convolution&lt;/span&gt;       &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+------------+------------+&lt;/span&gt;
             &lt;span class="o"&gt;|&lt;/span&gt;
             &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+-------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Binary&lt;/span&gt; &lt;span class="nx"&gt;colony&lt;/span&gt; &lt;span class="nx"&gt;mask&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+-------------------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's nothing especially fashionable about the architecture. That was part of the appeal. Researchers already understand U-Net. Documentation becomes easier. Maintenance becomes easier.&lt;br&gt;
Future contributors spend their time improving the biology instead of deciphering an exotic network architecture.&lt;/p&gt;


&lt;h2&gt;
  
  
  Training wasn't just optimisation
&lt;/h2&gt;

&lt;p&gt;The objective wasn't simply to minimise segmentation error. Ultimately the model exists to measure colonies. That led me to experiment with a composite loss function rather than relying on binary cross entropy alone.&lt;/p&gt;

&lt;p&gt;Thw report combines three terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Binary Cross Entropy&lt;/li&gt;
&lt;li&gt;Dice loss&lt;/li&gt;
&lt;li&gt;Area consistency loss&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conceptually it looks like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Loss&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Binary&lt;/span&gt; &lt;span class="nx"&gt;Cross&lt;/span&gt; &lt;span class="nx"&gt;Entropy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;Dice&lt;/span&gt; &lt;span class="nx"&gt;Loss&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;λ&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="nx"&gt;Area&lt;/span&gt; &lt;span class="nx"&gt;Loss&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The area component deserves particular attention.Two masks can achieve similar Dice scores while producing noticeably different colony areas. Since downstream biological measurements depend directly on area, encouraging area consistency during training made practical sense.&lt;/p&gt;




&lt;h2&gt;
  
  
  Searching for the best checkpoint
&lt;/h2&gt;

&lt;p&gt;Rather than choosing the first acceptable model, I trained several versions using different weights for the area-consistency term.&lt;br&gt;
The report/repo evaluates four values.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area weight (λ)&lt;/th&gt;
&lt;th&gt;Outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;td&gt;Candidate checkpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.3&lt;/td&gt;
&lt;td&gt;Candidate checkpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.5&lt;/td&gt;
&lt;td&gt;Candidate checkpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;0.7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Selected production checkpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;According to the reported results, the λ = 0.7 model produced the strongest validation performance and therefore became the checkpoint distributed with metrics-petri. That checkpoint is the one users install automatically from PyPI. An important consequence of this approach is reproducibility. When somebody installs version 3.0.0 of the package, they're using exactly the same checkpoint evaluated in the repo.&lt;/p&gt;


&lt;h2&gt;
  
  
  Measuring biology instead of segmentation
&lt;/h2&gt;

&lt;p&gt;This is where the project began to differ from many computer vision papers. Instead of asking,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How well does the predicted mask overlap the annotation?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I started asking,&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"If a biologist measured this colony manually, would they reach the same conclusion?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those are related questions. They're not identical.&lt;/p&gt;

&lt;p&gt;A segmentation mask can look visually convincing while still producing biased measurements. Likewise, a mask that differs slightly along the boundary may have almost no practical effect on colony diameter.Because the end goal was biological measurement, validation also needed to happen in biological units.&lt;/p&gt;


&lt;h2&gt;
  
  
  Building a manual reference workflow
&lt;/h2&gt;

&lt;p&gt;To create that reference, I built a separate desktop application specifically for reviewing segmentation outputs.&lt;/p&gt;

&lt;p&gt;The interface displays&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the original image&lt;/li&gt;
&lt;li&gt;the automatically generated mask&lt;/li&gt;
&lt;li&gt;detected dish calibration&lt;/li&gt;
&lt;li&gt;editable polygons&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of accepting the model output blindly, every colony can be inspected and corrected manually. That corrected polygon becomes the reference measurement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;+-----------+&lt;/span&gt;     &lt;span class="o"&gt;+------------------+&lt;/span&gt;     &lt;span class="o"&gt;+---------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Raw&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt; &lt;span class="nx"&gt;prediction&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Manual&lt;/span&gt; &lt;span class="nx"&gt;review&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+-----------+&lt;/span&gt;     &lt;span class="o"&gt;+------------------+&lt;/span&gt;     &lt;span class="o"&gt;+-------+-------+&lt;/span&gt;
                                                   &lt;span class="o"&gt;|&lt;/span&gt;
                                                   &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+--------------------+&lt;/span&gt;     &lt;span class="o"&gt;+----------------------+&lt;/span&gt;     &lt;span class="o"&gt;+-------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Agreement&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Diameter&lt;/span&gt; &lt;span class="nx"&gt;calculation&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Reference&lt;/span&gt; &lt;span class="nx"&gt;polygon&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+--------------------+&lt;/span&gt;     &lt;span class="o"&gt;+----------------------+&lt;/span&gt;     &lt;span class="o"&gt;+-------------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The software also supports multipart polygons. That turned out to matter more often than I expected because some colonies develop separated lobes or visible cracking during growth.A single closed polygon isn't always sufficient.&lt;/p&gt;




&lt;h2&gt;
  
  
  Calibration matters
&lt;/h2&gt;

&lt;p&gt;One design decision appears repeatedly throughout the report. Everything assumes a 90 mm Petri dish. At first glance that might seem unnecessarily restrictive.&lt;/p&gt;

&lt;p&gt;Why not support every possible dish size?&lt;br&gt;
The answer is consistency.&lt;/p&gt;

&lt;p&gt;Because every experiment uses the same physical standard, calibration becomes automatic.The detected dish diameter immediately provides&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;millimetres per pixel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;No rulers.&lt;/li&gt;
&lt;li&gt;No calibration grids.&lt;/li&gt;
&lt;li&gt;No additional workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every image calibrates itself. That same assumption appears in&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automator&lt;/li&gt;
&lt;li&gt;metrics-petri&lt;/li&gt;
&lt;li&gt;petrimodel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping that convention consistent across all three repositories removed an entire category of user error.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cross-checking with Fiji/ImageJ
&lt;/h2&gt;

&lt;p&gt;I didn't want the validation to depend on only one measurement workflow. The report therefore includes an independent comparison using Fiji/ImageJ. Twenty-five representative images were measured manually in Fiji after calibrating the Petri dish diameter to 90 mm. Equivalent colony diameters calculated from Fiji were then compared against measurements generated by the validation pipeline. This comparison isn't intended to prove Fiji is wrong or the model is right. It's a sanity check.&lt;br&gt;
If both workflows agree closely, confidence in the overall measurement process increases.&lt;/p&gt;


&lt;h2&gt;
  
  
  Agreement instead of accuracy
&lt;/h2&gt;

&lt;p&gt;One thing I learned while reading the statistical literature is that agreement matters more than correlation.Two methods can correlate almost perfectly while consistently disagreeing by several millimetres. That's why it reports several complementary statistics.&lt;br&gt;
Among them are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bias&lt;/li&gt;
&lt;li&gt;mean absolute error&lt;/li&gt;
&lt;li&gt;root mean squared error&lt;/li&gt;
&lt;li&gt;Bland–Altman limits of agreement&lt;/li&gt;
&lt;li&gt;Lin's Concordance Correlation Coefficient&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together they answer a more useful question.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Can the automated workflow replace the manual one?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Rather than&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Do these two sets of numbers increase together?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction became one of the central ideas behind the project.Ultimately the purpose of automation isn't producing beautiful segmentation masks. It's producing measurements that researchers can trust. In the next section I'll look at the validation results in detail, explain what those statistics actually mean, and discuss the engineering lessons I took away after building the complete imaging pipeline from robot to reproducible biological measurements.&lt;/p&gt;
&lt;h2&gt;
  
  
  Do the Numbers Hold Up? Validating an Automated Measurement Pipeline
&lt;/h2&gt;

&lt;p&gt;Building a robot is satisfying.&lt;br&gt;
Training a segmentation model is satisfying.&lt;/p&gt;

&lt;p&gt;Neither of those guarantees the final measurements are scientifically useful.&lt;/p&gt;

&lt;p&gt;That became the central question behind the validation work. Could an entirely automated workflow produce measurements that were interchangeable with a careful manual workflow?&lt;/p&gt;

&lt;p&gt;Notice the wording.&lt;/p&gt;

&lt;p&gt;Not &lt;em&gt;similar&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Not &lt;em&gt;highly correlated&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interchangeable.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That distinction shaped almost every decision in the validation study. Instead of stopping at machine learning metrics like Dice score or IoU, I wanted to evaluate what researchers actually report in papers: physical measurements. If someone measured the same colony by hand, would they reach the same biological conclusion?&lt;/p&gt;


&lt;h2&gt;
  
  
  A Three-Part Validation Strategy
&lt;/h2&gt;

&lt;p&gt;Rather than relying on a single comparison, I designed the validation as three complementary studies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;+----------------------+&lt;/span&gt;     &lt;span class="o"&gt;+-----------------------+&lt;/span&gt;     &lt;span class="o"&gt;+------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Fiji&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;ImageJ&lt;/span&gt;        &lt;span class="o"&gt;|&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Manual&lt;/span&gt; &lt;span class="nx"&gt;polygon&lt;/span&gt; &lt;span class="nx"&gt;review&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt; &lt;span class="nx"&gt;prediction&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+----------+-----------+&lt;/span&gt;     &lt;span class="o"&gt;+-----------+-----------+&lt;/span&gt;     &lt;span class="o"&gt;+---------+--------+&lt;/span&gt;
           &lt;span class="o"&gt;|&lt;/span&gt;                             &lt;span class="o"&gt;|&lt;/span&gt;                           &lt;span class="o"&gt;|&lt;/span&gt;
           &lt;span class="o"&gt;+-----------------------------+---------------------------+&lt;/span&gt;
                                         &lt;span class="o"&gt;|&lt;/span&gt;
                                         &lt;span class="nx"&gt;v&lt;/span&gt;
                              &lt;span class="o"&gt;+----------------------+&lt;/span&gt;
                              &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Agreement&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt;
                              &lt;span class="o"&gt;+----------+-----------+&lt;/span&gt;
                                         &lt;span class="o"&gt;|&lt;/span&gt;
              &lt;span class="o"&gt;+--------------------------+--------------------------+&lt;/span&gt;
              &lt;span class="o"&gt;|&lt;/span&gt;                          &lt;span class="o"&gt;|&lt;/span&gt;                          &lt;span class="o"&gt;|&lt;/span&gt;
              &lt;span class="nx"&gt;v&lt;/span&gt;                          &lt;span class="nx"&gt;v&lt;/span&gt;                          &lt;span class="nx"&gt;v&lt;/span&gt;
        &lt;span class="o"&gt;+-----------+&lt;/span&gt;              &lt;span class="o"&gt;+-----------+&lt;/span&gt;              &lt;span class="o"&gt;+-------------+&lt;/span&gt;
        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Bias&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;              &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;RMSE&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;              &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Lin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s CCC   |
        +-----------+              +-----------+              +-------------+
                                         |
                                         v
                               +-------------------+
                               | Bland-Altman      |
                               +-------------------+
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each comparison answers a different question.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Comparison&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fiji vs Manual&lt;/td&gt;
&lt;td&gt;Is the manual measurement pipeline internally consistent?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model vs Manual&lt;/td&gt;
&lt;td&gt;Does the neural network reproduce manual measurements?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Group and per-image analysis&lt;/td&gt;
&lt;td&gt;Does performance remain stable across experiments?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Looking at only one comparison would have left important questions unanswered.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Correlation Isn't Enough
&lt;/h2&gt;

&lt;p&gt;It's surprisingly easy to obtain a very high correlation coefficient. Imagine every automated measurement is exactly &lt;strong&gt;2 mm larger&lt;/strong&gt; than the manual measurement.Correlation would still be almost perfect. From a biological perspective, the software would clearly be wrong.&lt;br&gt;
That is why the report follows measurement-validation literature rather than relying solely on machine learning benchmarks. The goal is to determine whether two methods can reasonably replace one another. Correlation alone cannot answer that.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Statistics I Chose
&lt;/h2&gt;

&lt;p&gt;The report exhibits several complementary statistics.Each captures a different aspect of measurement quality.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Statistic&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bias&lt;/td&gt;
&lt;td&gt;Average difference between methods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MAE&lt;/td&gt;
&lt;td&gt;Typical absolute error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RMSE&lt;/td&gt;
&lt;td&gt;Larger errors receive greater weight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bland–Altman analysis&lt;/td&gt;
&lt;td&gt;Detects systematic disagreement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lin's Concordance Correlation Coefficient&lt;/td&gt;
&lt;td&gt;Combines precision and accuracy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R²&lt;/td&gt;
&lt;td&gt;Overall agreement trend&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No single number tells the whole story.Together they provide a much clearer picture of method agreement.&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 1 — Fiji vs Manual Measurements
&lt;/h2&gt;

&lt;p&gt;Before trusting the model, I wanted to know whether two independent manual workflows already agreed with one another. For this comparison, a representative subset of &lt;strong&gt;25 images&lt;/strong&gt; was measured using Fiji/ImageJ.Each image was calibrated manually using the known &lt;strong&gt;90 mm&lt;/strong&gt; Petri dish diameter before colony measurements were extracted.Those measurements were then compared against the measurements produced by the polygon-review workflow developed for petrimodel.&lt;/p&gt;

&lt;p&gt;The agreement was reassuring.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Images&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mean bias&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;−0.036 mm&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mean absolute error&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.333 mm&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RMSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.422 mm&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The average difference between the two manual workflows was only a few hundredths of a millimetre.That doesn't mean the measurements were identical.It means they differed by an amount small enough to establish confidence in the manual reference pipeline itself. Without that step, later comparisons against the neural network would have rested on much weaker foundations.&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 2 — The Full Validation
&lt;/h2&gt;

&lt;p&gt;Once the manual workflow had been established, I compared the production segmentation model against manually reviewed measurements across the full dataset. This comparison involved &lt;strong&gt;605 image pairs&lt;/strong&gt;.That's considerably larger than many proof-of-concept studies and provides a much better indication of how the software behaves under realistic laboratory conditions. The overall agreement statistics reported in the report are shown below.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Images&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;605&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mean bias&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.039 mm&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RMSE&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1.866 mm&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R²&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.9956&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lin's CCC&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.9978&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those numbers were particularly encouraging because they measure agreement in physical units rather than segmentation overlap. From the perspective of a biologist, colony diameter is far more meaningful than pixel-level IoU.&lt;/p&gt;


&lt;h2&gt;
  
  
  Visualising Agreement
&lt;/h2&gt;

&lt;p&gt;One of my favourite ways to examine measurement agreement is the Bland–Altman plot. Unlike a scatter plot, it makes disagreement immediately obvious.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;+-----------------+&lt;/span&gt;     &lt;span class="o"&gt;+------------------+&lt;/span&gt;     &lt;span class="o"&gt;+------------+&lt;/span&gt;     &lt;span class="o"&gt;+---------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Manual&lt;/span&gt; &lt;span class="nx"&gt;diameter&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Average&lt;/span&gt; &lt;span class="nx"&gt;diameter&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Difference&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Bias&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+-----------------+&lt;/span&gt;     &lt;span class="o"&gt;+------------------+&lt;/span&gt;     &lt;span class="o"&gt;+------------+&lt;/span&gt;     &lt;span class="o"&gt;+---------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of asking&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Do larger colonies remain larger?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;it asks&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How far apart are these measurements?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reported limits of agreement were approximately&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="err"&gt;−&lt;/span&gt;&lt;span class="mf"&gt;3.62&lt;/span&gt; &lt;span class="nx"&gt;mm&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mf"&gt;3.70&lt;/span&gt; &lt;span class="nx"&gt;mm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Importantly, the average bias remained close to zero. That suggests the automated measurements do not consistently overestimate or underestimate colony size.&lt;/p&gt;




&lt;h2&gt;
  
  
  Looking Beyond Averages
&lt;/h2&gt;

&lt;p&gt;Summary statistics are useful. They can also hide interesting behaviour. For that reason the report also examines agreement&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;across experimental groups&lt;/li&gt;
&lt;li&gt;across individual images&lt;/li&gt;
&lt;li&gt;across the complete dataset&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helps identify whether particular growth conditions or colony morphologies systematically challenge the segmentation model. One of the reassuring observations was that the agreement remained stable across these different levels of analysis rather than depending on only a handful of easy examples.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Validation Was Harder Than Training
&lt;/h2&gt;

&lt;p&gt;Training the model was relatively straightforward.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collect images.&lt;/li&gt;
&lt;li&gt;Annotate colonies.
-Optimise the network.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Validation required much more thought.The difficult part wasn't calculating statistics. It was deciding which statistics actually answered the scientific question.Many computer vision papers stop after reporting Dice score because that's the standard benchmark. My end users aren't comparing segmentation architectures.&lt;br&gt;
They're measuring fungal growth.&lt;br&gt;
That changed the evaluation criteria completely.I found myself reading clinical measurement-validation literature more often than machine learning papers. Concepts like Bland–Altman analysis and Lin's Concordance Correlation Coefficient became much more relevant than another percentage point of IoU. Looking back, I think that shift in perspective improved the project more than changing the neural network architecture ever could.&lt;/p&gt;


&lt;h2&gt;
  
  
  Engineering Lessons
&lt;/h2&gt;

&lt;p&gt;Working on all three repositories simultaneously taught me several lessons that I probably wouldn't have appreciated if I had focused only on software.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Hardware consistency simplifies software
&lt;/h2&gt;

&lt;p&gt;The more repeatable image acquisition became, the simpler the segmentation problem became.Many computer vision challenges originate from inconsistent data rather than inadequate models. Automating the imaging process reduced that variability before the neural network ever saw an image.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. Constraints can be useful
&lt;/h2&gt;

&lt;p&gt;Supporting only &lt;strong&gt;90 mm&lt;/strong&gt; Petri dishes initially felt restrictive.In practice it simplified calibration, reduced configuration, and made measurements directly comparable across experiments.Sometimes deliberately limiting flexibility produces a much better user experience.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Reproducibility begins with packaging
&lt;/h2&gt;

&lt;p&gt;Machine learning projects often publish code while leaving trained models scattered across cloud storage providers. Bundling the production checkpoint directly inside the Python package ensures that every installation corresponds to a validated model.That small packaging decision makes the software much easier to reproduce years later.&lt;/p&gt;


&lt;h2&gt;
  
  
  4. Transparency matters
&lt;/h2&gt;

&lt;p&gt;I deliberately chose to generate overlay images alongside numerical outputs. Researchers should be able to inspect what the software measured rather than treating it as a black box.&lt;br&gt;
If a segmentation looks wrong, it should be obvious immediately.Trust grows much more easily when users can see the evidence.&lt;/p&gt;


&lt;h2&gt;
  
  
  Looking Ahead
&lt;/h2&gt;

&lt;p&gt;Although I'm pleased with where the pipeline ended up, there are still plenty of directions worth exploring. Some ideas are already obvious.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Supporting additional Petri dish sizes without sacrificing automatic calibration.&lt;/li&gt;
&lt;li&gt;Running segmentation directly during image acquisition.&lt;/li&gt;
&lt;li&gt;Extending the analysis beyond fungal colonies to bacterial or plant pathology experiments.&lt;/li&gt;
&lt;li&gt;Exploring newer lightweight segmentation architectures as they mature.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important point is that the overall architecture doesn't need to change.The robot captures consistent images.The analysis package extracts calibrated measurements. The training repository improves the model.Each component can evolve independently while preserving the workflow. That modularity was intentional from the beginning, and after living with the system for a while, I think it turned out to be one of the best design decisions in the entire project.&lt;/p&gt;


&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Looking back, what I'm happiest with isn't the motion system, the segmentation model, or even the validation statistics.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's that the entire workflow is open.&lt;/li&gt;
&lt;li&gt;Anyone can inspect the hardware.&lt;/li&gt;
&lt;li&gt;Anyone can read the code.&lt;/li&gt;
&lt;li&gt;Anyone can reproduce the measurements.
&lt;strong&gt;Scientific software shouldn't be a black box.&lt;/strong&gt;
If another laboratory can take these repositories, adapt them to their own experiments, improve them, and contribute those improvements back to the community, then the project has achieved something much more valuable than simply automating my own workflow. That's exactly what open source should enable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;All three projects are open source and can be used independently, although they are designed to work together.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Project&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Automator&lt;/td&gt;
&lt;td&gt;Robotic imaging platform&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/rotsl/Automator" rel="noopener noreferrer"&gt;https://github.com/rotsl/Automator&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation&lt;/td&gt;
&lt;td&gt;Complete hardware and software documentation&lt;/td&gt;
&lt;td&gt;&lt;a href="https://rotsl.github.io/Automator/" rel="noopener noreferrer"&gt;https://rotsl.github.io/Automator/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;metrics-petri&lt;/td&gt;
&lt;td&gt;Colony segmentation and morphometric analysis&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/rotsl/metrics-petri" rel="noopener noreferrer"&gt;https://github.com/rotsl/metrics-petri&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PyPI&lt;/td&gt;
&lt;td&gt;Installable analysis package&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pypi.org/project/metrics-petri/" rel="noopener noreferrer"&gt;https://pypi.org/project/metrics-petri/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;petrimodel repo&lt;/td&gt;
&lt;td&gt;Training data and validation resources&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/rotsl/petrimodel" rel="noopener noreferrer"&gt;https://github.com/rotsl/petrimodel&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Installing the Software
&lt;/h2&gt;

&lt;p&gt;Installing the analysis pipeline is intentionally simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv

&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate

pip &lt;span class="nb"&gt;install &lt;/span&gt;metrics-petri
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the installation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;metrics-petri doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical output looks similar to&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Python ✔️

Torch ✔️

NumPy ✔️

Model ✔️

Accelerator: Apple MPS

Ready.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Running an Analysis
&lt;/h2&gt;

&lt;p&gt;Analysing a folder of images requires only a single command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;metrics-petri analyse ./experiment
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outputs are organised into&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;analysis.zip
└── analysis/
    ├── analysis_full.csv
    ├── analysis_full.json
    ├── metadata.csv
    ├── overlays/
    └── charts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The overlays are particularly useful because they make every prediction inspectable.Instead of blindly trusting numerical outputs, I can immediately verify whether the segmentation behaved as expected.&lt;/p&gt;




&lt;h2&gt;
  
  
  Working with Metadata
&lt;/h2&gt;

&lt;p&gt;For longitudinal experiments I generate metadata before analysis.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;metrics-petri-metadata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;image_metadata.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once dates are available, metrics-petri automatically calculates&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Relative Growth Rate&lt;/li&gt;
&lt;li&gt;Absolute Growth Rate
without any additional scripting.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Example Python Usage
&lt;/h2&gt;

&lt;p&gt;Although the command line is the primary interface, the package is designed to fit naturally into Python workflows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;metrics_petri&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;analyse_folder&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;analyse_folder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

    &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;images&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;analysis.zip&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes it straightforward to integrate the package into larger data-processing pipelines.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reproducing the Workflow
&lt;/h2&gt;

&lt;p&gt;The complete workflow can be summarised in just a few steps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="o"&gt;+----------------------+&lt;/span&gt;     &lt;span class="o"&gt;+------------------+&lt;/span&gt;     &lt;span class="o"&gt;+----------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Prepare&lt;/span&gt; &lt;span class="nx"&gt;Petri&lt;/span&gt; &lt;span class="nx"&gt;dishes&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Attach&lt;/span&gt; &lt;span class="nx"&gt;QR&lt;/span&gt; &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Load&lt;/span&gt; &lt;span class="nx"&gt;Automator&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+----------------------+&lt;/span&gt;     &lt;span class="o"&gt;+------------------+&lt;/span&gt;     &lt;span class="o"&gt;+--------+-------+&lt;/span&gt;
                                                               &lt;span class="o"&gt;|&lt;/span&gt;
                                                               &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+-------------------+&lt;/span&gt;     &lt;span class="o"&gt;+-----------------+&lt;/span&gt;     &lt;span class="o"&gt;+-------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;CSV&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;metrics&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;petri&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Automatic&lt;/span&gt; &lt;span class="nx"&gt;imaging&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+---------+---------+&lt;/span&gt;     &lt;span class="o"&gt;+-----------------+&lt;/span&gt;     &lt;span class="o"&gt;+-------------------+&lt;/span&gt;
          &lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+---------------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;Python&lt;/span&gt; &lt;span class="nx"&gt;statistical&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+----------------+----------------+&lt;/span&gt;
                 &lt;span class="o"&gt;|&lt;/span&gt;
                 &lt;span class="nx"&gt;v&lt;/span&gt;
&lt;span class="o"&gt;+-------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Publication&lt;/span&gt;             &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+-------------------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important point is that the only manual step is preparing the biological experiment itself.&lt;/p&gt;

&lt;p&gt;Everything after imaging can be reproduced automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why I Released Everything
&lt;/h2&gt;

&lt;p&gt;One decision I made early was that the software, hardware, and training pipeline should all be open. Many laboratory automation projects publish a paper while leaving the implementation inaccessible. Others release code without datasets. Some release datasets without trained models. I wanted anyone reading the report to be able to reproduce the complete workflow.&lt;/p&gt;

&lt;p&gt;That meant releasing&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hardware designs&lt;/li&gt;
&lt;li&gt;controller software&lt;/li&gt;
&lt;li&gt;Python package&lt;/li&gt;
&lt;li&gt;training pipeline&lt;/li&gt;
&lt;li&gt;datasets&lt;/li&gt;
&lt;li&gt;validation methodology&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Separating these into three repositories also made long-term maintenance much easier. Someone interested only in image analysis doesn't need to clone a robotics project. Someone building laboratory hardware doesn't need to understand neural network training. Each repository has a single responsibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;No engineering project is ever really finished. Looking back, there are several things I'd like to explore in future versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional hardware
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;interchangeable dish holders&lt;/li&gt;
&lt;li&gt;automatic focus&lt;/li&gt;
&lt;li&gt;environmental sensors&lt;/li&gt;
&lt;li&gt;barcode support&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Computer vision
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;instance segmentation&lt;/li&gt;
&lt;li&gt;uncertainty estimation&lt;/li&gt;
&lt;li&gt;confidence maps&lt;/li&gt;
&lt;li&gt;real-time inference&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Analysis
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;additional fungal phenotypes&lt;/li&gt;
&lt;li&gt;multispectral imaging&lt;/li&gt;
&lt;li&gt;bacterial colony support&lt;/li&gt;
&lt;li&gt;richer temporal modelling&lt;/li&gt;
&lt;li&gt;moving from macroscopy to microscopy&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The important thing is that none of those ideas require rewriting the architecture.Each repository can evolve independently.That modularity was one of the original goals, and I think it has proven worthwhile.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The most rewarding part wasn't getting a robot to move accurately or training another segmentation network. It was seeing three independent open-source projects fit together into a workflow that feels coherent. Each project solves a different problem. Together they remove a surprising amount of repetitive laboratory work while keeping every stage transparent and reproducible.I don't expect this pipeline to be the final answer for automated fungal phenotyping. I hope it becomes a foundation that other researchers can build upon. If someone replaces the robot, swaps in a different segmentation model, or adapts the analysis pipeline for another organism, then the architecture has done exactly what I hoped it would do. Open science isn't just about making code available. It's about making the entire engineering process understandable, reproducible, and useful to the next person.If this work saves another researcher a few hours every week, or inspires someone to build a better version, then publishing it openly has already been worthwhile.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/rotsl/Automator" rel="noopener noreferrer"&gt;Automator&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://rotsl.github.io/Automator/" rel="noopener noreferrer"&gt;Automator Documentation&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/rotsl/metrics-petri" rel="noopener noreferrer"&gt;metrics-petri&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://pypi.org/project/metrics-petri/" rel="noopener noreferrer"&gt;metrics-petri PyPI Package&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/rotsl/petrimodel" rel="noopener noreferrer"&gt;petrimodel Validation Repository&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How to cite
&lt;/h2&gt;

&lt;p&gt;If you use this software, hardware designs, or derivative works in academic publications, preprints, theses, posters, or internal reports, please cite relevant repository details of which can be found below.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://doi.org/10.5281/zenodo.21609180" rel="noopener noreferrer"&gt;(&lt;code&gt;Automator&lt;/code&gt;)10.5281/zenodo.21609180&lt;/a&gt;&lt;br&gt;
&lt;a href="https://doi.org/10.5281/zenodo.21628852" rel="noopener noreferrer"&gt;(&lt;code&gt;metrics-petri&lt;/code&gt;)10.5281/zenodo.21628852&lt;/a&gt;&lt;br&gt;
&lt;a href="https://doi.org/10.5281/zenodo.21628765" rel="noopener noreferrer"&gt;(&lt;code&gt;petri-model&lt;/code&gt;)10.5281/zenodo.21628765&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>raspberrypi</category>
    </item>
  </channel>
</rss>
