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]:
Size_Area Size_IntegratedIntensity Shape_Area Shape_Perimeter Shape_Circularity Shape_ConvexArea Shape_MedianRadius Shape_MeanRadius Shape_MaxRadius Shape_MinFeretDiameter ... Bbox_MaxRR Bbox_MaxCC Bbox_IntensityWeightedCenterRR Bbox_IntensityWeightedCenterCC Bbox_DistWeightedCenterRR Bbox_DistWeightedCenterCC Grid_RowNum Grid_ColNum Grid_RowMajorIdx Grid_ColMajorIdx
0 22670.0 13244.210029 22670.0 569.085353 0.879643 536.949710 24.698178 27.986768 79.881162 166.762587 ... 278 277 188.628120 190.642286 189.115912 190.522931 0 0 0 0
1 17055.0 10056.952051 17055.0 493.487373 0.880054 467.000044 21.213203 24.216864 70.064256 144.800000 ... 258 1077 180.521263 1003.831491 180.830685 1003.171207 0 2 2 4
2 16047.0 9216.498028 16047.0 471.345238 0.907665 448.791306 21.023796 23.938996 69.814039 139.300036 ... 252 1481 179.111878 1409.794201 179.506605 1409.024055 0 3 3 6
3 13329.0 7522.484134 13329.0 431.889394 0.897971 410.142608 19.104973 21.686005 62.008064 125.262360 ... 250 664 184.258571 599.483198 184.551175 599.125678 0 1 1 2
4 17939.0 10945.558373 17939.0 500.801082 0.898830 475.771621 22.135944 25.191555 72.835431 147.557575 ... 665 1487 586.998752 1410.776659 587.048721 1410.124326 1 3 7 7

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:
  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
  MetadataImage_ImageName
  MetadataImage_ImageType
  MetadataImage_BitDepth
  MetadataImage_FileSuffix
  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

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.