Tutorial 7: Measuring and Exporting#

Detection tells you where colonies are. Measurement tells you what they are — how big, how round, how bright. In this tutorial you will add measurements to a pipeline, extract a DataFrame of colony features, and export the results for downstream analysis.

What you will learn:

  1. Add measurement operations to a pipeline

  2. Use pipeline.apply_and_measure() to get a DataFrame

  3. Understand the output columns

  4. Export to CSV and Parquet

Imports#

[1]:
import phenotypic as pht
from phenotypic.data import load_yeast_plate
from phenotypic.enhance import GaussianBlur, EnhanceLocalContrast
from phenotypic.detect import OtsuDetector
from phenotypic.measure import MeasureSize, MeasureShape, MeasureIntensity

Build a Pipeline with Measurements#

The meas parameter accepts a list of measurement operations. Each one extracts a different set of features from the detected colonies.

[2]:
plate = load_yeast_plate()

pipeline = pht.ImagePipeline(
    ops=[GaussianBlur(sigma=2.0), EnhanceLocalContrast(clip_limit=0.01), OtsuDetector()],
    meas=[MeasureSize(), MeasureShape(), MeasureIntensity()],
)

Apply and Measure#

.apply_and_measure() runs the full pipeline (enhance → detect → measure) and returns a pandas DataFrame with one row per detected colony.

[3]:
df = pipeline.apply_and_measure(plate)
print(f"Measured {len(df)} colonies across {df.shape[1]} features")
df.head()
Measured 7 colonies across 50 features
[3]:
Metadata_FileSuffix Metadata_BitDepth Metadata_ImageType Metadata_ImageName Object_Label Bbox_CenterRR Bbox_CenterCC Bbox_MinRR Bbox_MinCC Bbox_MaxRR ... Intensity_MaximumIntensity Intensity_MeanIntensity Intensity_MedianIntensity Intensity_StandardDeviationIntensity Intensity_CoefficientVarianceIntensity Intensity_LowerQuartileIntensity Intensity_UpperQuartileIntensity Intensity_InterquartileRangeIntensity Intensity_Density Intensity_ConvexDensity
0 .png 8 GridImage RhodotorulaYeastCenterCrop 1 189.076930 190.595854 102 107 278 ... 0.686547 0.584217 0.594960 0.044504 0.076180 0.560265 0.616306 0.056040 0.584217 24.665643
1 .png 8 GridImage RhodotorulaYeastCenterCrop 2 180.905600 1003.162298 107 930 258 ... 0.695336 0.589678 0.600256 0.046125 0.078225 0.576309 0.618405 0.042096 0.589678 21.535227
2 .png 8 GridImage RhodotorulaYeastCenterCrop 3 179.499034 1409.025799 108 1339 252 ... 0.672282 0.574344 0.582071 0.038987 0.067886 0.559134 0.599665 0.040532 0.574344 20.536267
3 .png 8 GridImage RhodotorulaYeastCenterCrop 4 184.490509 599.195288 118 535 250 ... 0.659846 0.564370 0.574488 0.042502 0.075314 0.552342 0.590811 0.038469 0.564370 18.341143
4 .png 8 GridImage RhodotorulaYeastCenterCrop 5 587.079492 1410.152127 510 1337 665 ... 0.704480 0.610154 0.621897 0.045821 0.075102 0.594126 0.639978 0.045853 0.610154 23.005908

5 rows × 50 columns

Explore the Columns#

Each measurement operation contributes its own set of columns. Let’s see what we got.

[4]:
print("All columns:")
for col in df.columns:
    print(f"  {col}")
All columns:
  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
  Size_Area
  Size_IntegratedIntensity
  Shape_Area
  Shape_Perimeter
  Shape_Circularity
  Shape_ConvexArea
  Shape_MedianRadius
  Shape_MeanRadius
  Shape_MaxRadius
  Shape_MinFeretDiameter
  Shape_MaxFeretDiameter
  Shape_Eccentricity
  Shape_Solidity
  Shape_Extent
  Shape_BboxArea
  Shape_MajorAxisLength
  Shape_MinorAxisLength
  Shape_Compactness
  Shape_Orientation
  Intensity_IntegratedIntensity
  Intensity_MinimumIntensity
  Intensity_MaximumIntensity
  Intensity_MeanIntensity
  Intensity_MedianIntensity
  Intensity_StandardDeviationIntensity
  Intensity_CoefficientVarianceIntensity
  Intensity_LowerQuartileIntensity
  Intensity_UpperQuartileIntensity
  Intensity_InterquartileRangeIntensity
  Intensity_Density
  Intensity_ConvexDensity

Here are the highlights from each measurement:

MeasureSize:

  • Size_Area — colony size in pixels

  • Size_IntegratedIntensity — sum of grayscale pixel values

MeasureShape:

  • Shape_Circularity — how round the colony is (1.0 = perfect circle)

  • Shape_Solidity — ratio of colony area to convex hull area

  • Shape_Eccentricity — elongation (0 = circular, approaching 1 = elongated)

  • Shape_MajorAxisLength / Shape_MinorAxisLength — fitted ellipse axes

MeasureIntensity:

  • Intensity_MeanIntensity / Intensity_MedianIntensity — average colony brightness

  • Intensity_StandardDeviationIntensity — variation within the colony

  • Intensity_MinimumIntensity / Intensity_MaximumIntensity — intensity extremes

Quick Statistics#

Since the result is a standard pandas DataFrame, you can use all the usual pandas methods to explore it.

[5]:
df[["Size_Area", "Shape_Circularity", "Intensity_MeanIntensity"]].describe()
[5]:
Size_Area Shape_Circularity Intensity_MeanIntensity
count 7.000000 7.000000 7.000000
mean 16869.857143 0.894006 0.582158
std 3076.517210 0.010850 0.015325
min 13329.000000 0.879643 0.564370
25% 14984.500000 0.885698 0.571459
50% 17055.000000 0.897971 0.583767
75% 17533.000000 0.900684 0.586948
max 22670.000000 0.907665 0.610154

Export to CSV#

For sharing with collaborators or importing into spreadsheet software, export to CSV.

[6]:
df.to_csv("colony_measurements.csv")
print("Saved to colony_measurements.csv")
Saved to colony_measurements.csv

Export to Parquet#

For large datasets, Parquet is more efficient — it is compressed, preserves column types, and loads much faster than CSV.

[7]:
df.to_parquet("colony_measurements.parquet")
print("Saved to colony_measurements.parquet")
Saved to colony_measurements.parquet

Clean Up#

[8]:
import os
os.remove("colony_measurements.csv")
os.remove("colony_measurements.parquet")

Summary#

You have extracted colony features and exported them for analysis:

  • ``meas=[MeasureSize(), MeasureShape(), MeasureIntensity()]`` — add measurements to a pipeline

  • ``pipeline.apply_and_measure(plate)`` — run the full pipeline and get a DataFrame

  • ``.to_csv()`` / ``.to_parquet()`` — export for downstream tools

The result is a standard pandas DataFrame, so you can filter, group, plot, and analyze it with any tool in the Python ecosystem.

Next up: Tutorial 8: Using Prefab Pipelines — discover PhenoTypic’s pre-built pipelines for common organisms and plate types.