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:

  1. Apply a detection pipeline to a GridImage

  2. Visualize detected colonies with grid boundaries

  3. Query per-colony row, column, and section assignments

  4. Extract a single well as a subimage

  5. 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.