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:
Add measurement operations to a pipeline
Use
pipeline.apply_and_measure()to get a DataFrameUnderstand the output columns
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 pixelsSize_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 areaShape_Eccentricity— elongation (0 = circular, approaching 1 = elongated)Shape_MajorAxisLength/Shape_MinorAxisLength— fitted ellipse axes
MeasureIntensity:
Intensity_MeanIntensity/Intensity_MedianIntensity— average colony brightnessIntensity_StandardDeviationIntensity— variation within the colonyIntensity_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.