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]:
| 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 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.