Spaces:
Sleeping
Sleeping
File size: 2,808 Bytes
3ecb175 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | """Run the morphometrics stage end-to-end across all trials.
Reads YOLO-seg .txt predictions and the matching source images, computes
per-tuber polygon morphometrics, converts to real-scale (inches and
square inches), assigns USDA size class, and concatenates the three
trials into a single combined CSV.
Usage:
uv run python scripts/extract_morphometrics.py \\
--out outputs/df_class_real_scale_ALL.csv
"""
from __future__ import annotations
import argparse
from pathlib import Path
import pandas as pd
from potatopheno.calibration import BELT_WIDTH_IN_DEFAULT, apply_real_scale
from potatopheno.classification import assign_usda_class
from potatopheno.io import build_morphometrics_dataframe, plot_id_from_image_id
TRIALS = ("NCPT", "ADC", "NCR")
def _parse_args() -> argparse.Namespace:
p = argparse.ArgumentParser(description=__doc__.splitlines()[0])
p.add_argument(
"--preds-root",
type=Path,
required=True,
help="Root of YOLO preds. Expects <trial>/preds/*.txt under it.",
)
p.add_argument(
"--images-root",
type=Path,
required=True,
help="Root of raw images. Expects 2025<TRIAL>/*.JPG under it.",
)
p.add_argument(
"--out",
type=Path,
required=True,
help="Path of the combined per-tuber CSV to write.",
)
p.add_argument(
"--trials",
type=lambda s: tuple(t.strip() for t in s.split(",") if t.strip()),
default=TRIALS,
help=f"Comma-separated trial names. Default: {','.join(TRIALS)}.",
)
p.add_argument(
"--belt-width-in",
type=float,
default=BELT_WIDTH_IN_DEFAULT,
help="Conveyor belt width in inches used for pixel-to-inch calibration.",
)
return p.parse_args()
def main() -> None:
args = _parse_args()
args.out.parent.mkdir(parents=True, exist_ok=True)
per_trial: list[pd.DataFrame] = []
for trial in args.trials:
preds_dir = args.preds_root / trial / "preds"
images_dir = args.images_root / f"2025{trial}"
df = build_morphometrics_dataframe(trial, images_dir, preds_dir)
df["Plot"] = plot_id_from_image_id(df["image_id"])
df = apply_real_scale(df, belt_width_in=args.belt_width_in)
df = assign_usda_class(df)
print(
f"[{trial}] images={df['image_id'].nunique()} "
f"instances={len(df)} "
f"class_counts={df['class'].value_counts().to_dict()}"
)
per_trial.append(df)
combined = pd.concat(per_trial, ignore_index=True)
combined.to_csv(args.out, index=False)
print(
f"\n[DONE] wrote {len(combined)} rows across "
f"{combined['trial'].nunique()} trials → {args.out}"
)
if __name__ == "__main__":
main()
|