Tutorial 5: Working with Detected Grid Plates#
In Tutorial 1, you saw that most arrayed colony workflows use GridImage: an Image with row-and-column plate layout. This tutorial picks up after detection, when that grid layout becomes biologically useful.
Once colonies have been detected, a GridImage can assign each colony to a well, extract individual wells as subimages, count colonies per grid section, and show the detected objects together with the grid overlay.
What you will learn:
Apply a detection pipeline to a
GridImageVisualize detected colonies with grid boundaries
Query per-colony row, column, and section assignments
Extract a single well as a subimage
Count colonies per grid section
Imports#
[1]:
import phenotypic as pht
from phenotypic.data import load_yeast_plate
from phenotypic.enhance import GaussianBlur, EnhanceLocalContrast
from phenotypic.detect import OtsuDetector
Load the Grid Plate#
load_yeast_plate() returns the same 2-by-4 GridImage introduced in Tutorial 1. Here we keep the setup brief because the focus is what you can do after colonies have been detected.
[2]:
plate = load_yeast_plate()
print(f"Type: {type(plate).__name__}")
print(f"Rows: {plate.grid.nrows}")
print(f"Columns: {plate.grid.ncols}")
Type: GridImage
Rows: 2
Columns: 4
Detect Colonies First#
Grid assignment needs detected objects. To keep this notebook runnable on its own, run a compact enhance-and-detect pipeline like the ones used in the earlier detection tutorials.
[3]:
pipeline = pht.ImagePipeline(
ops=[GaussianBlur(sigma=2.0), EnhanceLocalContrast(clip_limit=0.01), OtsuDetector()]
)
plate = pipeline.apply(plate)
print(f"Detected colonies: {plate.num_objects}")
Detected colonies: 7
Visualize the Grid Overlay#
After detection, the overlay view shows the segmented colonies and the grid boundaries at the same time. This is the quickest sanity check that colonies are being assigned to the expected wells.
[4]:
plate.dash(overlay=True, show_grid=True)
The dashed lines show the grid boundaries. Each rectangular region is one grid section, corresponding to one well on the physical plate.
Query Grid Assignments#
The .grid.info() method returns a DataFrame with one row per detected colony, including its grid position: row, column, and flattened section number.
[5]:
info = plate.grid.info()
info.head(10)
[5]:
| Metadata_FileSuffix | Metadata_BitDepth | Metadata_ImageType | Metadata_ImageName | Object_Label | Bbox_CenterRR | Bbox_CenterCC | Bbox_MinRR | Bbox_MinCC | Bbox_MaxRR | Bbox_MaxCC | Bbox_IntensityWeightedCenterRR | Bbox_IntensityWeightedCenterCC | Bbox_DistWeightedCenterRR | Bbox_DistWeightedCenterCC | Grid_RowNum | Grid_ColNum | Grid_RowMajorIdx | Grid_ColMajorIdx | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | .png | 8 | GridImage | RhodotorulaYeastCenterCrop | 1 | 189.076930 | 190.595854 | 102 | 107 | 278 | 277 | 188.628120 | 190.642286 | 189.115912 | 190.522931 | 0 | 0 | 0 | 0 |
| 1 | .png | 8 | GridImage | RhodotorulaYeastCenterCrop | 2 | 180.905600 | 1003.162298 | 107 | 930 | 258 | 1077 | 180.521263 | 1003.831491 | 180.830685 | 1003.171207 | 0 | 2 | 2 | 4 |
| 2 | .png | 8 | GridImage | RhodotorulaYeastCenterCrop | 3 | 179.499034 | 1409.025799 | 108 | 1339 | 252 | 1481 | 179.111878 | 1409.794201 | 179.506605 | 1409.024055 | 0 | 3 | 3 | 6 |
| 3 | .png | 8 | GridImage | RhodotorulaYeastCenterCrop | 4 | 184.490509 | 599.195288 | 118 | 535 | 250 | 664 | 184.258571 | 599.483198 | 184.551175 | 599.125678 | 0 | 1 | 1 | 2 |
| 4 | .png | 8 | GridImage | RhodotorulaYeastCenterCrop | 5 | 587.079492 | 1410.152127 | 510 | 1337 | 665 | 1487 | 586.998752 | 1410.776659 | 587.048721 | 1410.124326 | 1 | 3 | 7 | 7 |
| 5 | .png | 8 | GridImage | RhodotorulaYeastCenterCrop | 6 | 588.952239 | 1001.870964 | 514 | 928 | 664 | 1078 | 589.007286 | 1002.402431 | 588.972481 | 1001.858232 | 1 | 2 | 6 | 5 |
| 6 | .png | 8 | GridImage | RhodotorulaYeastCenterCrop | 7 | 588.916248 | 599.684815 | 520 | 534 | 658 | 665 | 588.832607 | 600.071547 | 588.901346 | 599.664213 | 1 | 1 | 5 | 3 |
Key columns:
RowNum / ColNum – grid row and column (0-indexed)
SectionNum – flattened section index (0 to nrows x ncols - 1)
CenterRR / CenterCC – colony centroid in pixel coordinates
MinRR, MaxRR, MinCC, MaxCC – bounding box
Extract a Single Well#
You can pull out any grid section as a standalone subimage using bracket indexing on the .grid accessor. Let’s look at the well in row 0, column 0 (top-left corner).
[6]:
well = plate.grid[0, 0]
well.dash()
You can also extract an entire row or column with slicing:
first_row = plate.grid[0, :] # All 12 wells in row 0
third_col = plate.grid[:, 2] # All 8 wells in column 2
Count Colonies per Grid Section#
How many colonies are in each well? .grid.get_section_counts() summarizes the detected objects by grid section.
[7]:
counts = plate.grid.get_section_counts()
counts.head(10)
[7]:
Grid_RowMajorIdx
0 1
1 1
2 1
3 1
5 1
6 1
7 1
Name: count, dtype: int64
The index is the section number and the value is the colony count. Sections with zero colonies are omitted by default.
Summary#
You now know how to use grid layout after detection:
``plate.dash(overlay=True, show_grid=True)`` – visual grid overlay with detected colonies
``plate.grid.info()`` – per-colony DataFrame with grid row, column, and section
``plate.grid[row, col]`` – extract a single well as a subimage
``plate.grid.get_section_counts()`` – colony counts per section
This is where GridImage becomes more than a convenient image container: each detected colony can be traced back to the well it came from.
Next up: Tutorial 6: Batch Processing – process many plates at once using the command-line interface.