Skip to content

Commit 97a1f7b

Browse files
jkim810claude
andcommitted
Write per-TIFF quantification outputs instead of single merged file
imc quantify now processes each TIFF individually and writes output files next to the input TIFF by default (e.g. *_quantification.h5ad). The -o flag accepts an output directory to override the default location. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4f265c1 commit 97a1f7b

2 files changed

Lines changed: 57 additions & 41 deletions

File tree

imc/scripts/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -636,11 +636,12 @@ def find_h5ad() -> tp.Optional[Path]:
636636
},
637637
},
638638
{
639-
"args": ["--output"],
639+
"args": ["-o", "--output"],
640640
"kwargs": {
641641
"dest": "output",
642-
"help": "Output file with quantification. "
643-
"Default is 'processed/quantification.csv'.",
642+
"type": Path,
643+
"help": "Output directory for quantification files. "
644+
"Default is to write next to each input TIFF.",
644645
},
645646
},
646647
{

imc/scripts/quantify.py

Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,26 @@
1414
from imc.ops.quant import quantify_cells_rois
1515
from imc.scripts import build_cli, find_tiffs
1616

17+
def _make_anndata(quant, morphology):
18+
"""Convert a quantification DataFrame to an AnnData object."""
19+
v = len(str(quant["obj_id"].max()))
20+
idx = quant["roi"] + "-" + quant["obj_id"].astype(str).str.zfill(v)
21+
quant.index = idx
22+
23+
cols = [
24+
"sample", "roi", "obj_id", "X_centroid", "Y_centroid", "layer",
25+
"area", "perimeter", "minor_axis_length", "major_axis_length",
26+
"eccentricity", "solidity"
27+
]
28+
cols = [c for c in cols if c in quant.columns]
29+
ann = anndata.AnnData(
30+
quant.drop(cols, axis=1, errors="ignore").astype(float), obs=quant[cols]
31+
)
32+
if "X_centroid" in ann.obs.columns:
33+
ann.obsm["spatial"] = ann.obs[["Y_centroid", "X_centroid"]].values
34+
return ann
35+
36+
1737
def main(cli: tp.Sequence[str] = None) -> int:
1838
parser = build_cli("quantify")
1939
args = parser.parse_args(cli)
@@ -45,44 +65,39 @@ def main(cli: tp.Sequence[str] = None) -> int:
4565
error = f"Not all cell masks exist! Missing:\n\t- {m}"
4666
raise ValueError(error)
4767

48-
quant = quantify_cells_rois(
49-
rois, args.layers.split(","), morphology=args.morphology
50-
).reset_index()
51-
52-
# reorder columns for nice effect
53-
ext = ["roi", "obj_id"] + (["X_centroid", "Y_centroid"] if args.morphology else [])
54-
rem = [x for x in quant.columns if x not in ext]
55-
quant = quant[ext + rem]
56-
57-
if args.output is None:
58-
f = Path("processed").mkdir() / "quantification.csv.gz"
59-
else:
60-
f = args.output
61-
quant.to_csv(f, index=False)
62-
print(f"Wrote CSV file to '{f.absolute()}'.")
63-
64-
if args.output_h5ad:
65-
v = len(str(quant["obj_id"].max()))
66-
idx = quant["roi"] + "-" + quant["obj_id"].astype(str).str.zfill(v)
67-
quant.index = idx
68-
69-
# cols = ["sample", "roi", "obj_id", "X_centroid", "Y_centroid", "layer"]
70-
cols = [
71-
"sample", "roi", "obj_id", "X_centroid", "Y_centroid", "layer",
72-
"area", "perimeter", "minor_axis_length", "major_axis_length",
73-
"eccentricity", "solidity"
74-
]
75-
cols = [c for c in cols if c in quant.columns]
76-
ann = anndata.AnnData(
77-
quant.drop(cols, axis=1, errors="ignore").astype(float), obs=quant[cols]
78-
)
79-
if "X_centroid" in ann.obs.columns:
80-
ann.obsm["spatial"] = ann.obs[["Y_centroid", "X_centroid"]].values
81-
f = f.replace_(".csv.gz", ".h5ad")
82-
ann.write(f)
83-
print(f"Wrote h5ad file to '{f.absolute()}'.")
84-
ann2 = anndata.read_h5ad(f)
85-
assert np.allclose(ann.X, ann2.X)
68+
# Process each TIFF individually so each gets its own output file
69+
for tiff, roi in zip(args.tiffs, rois):
70+
print(f"Quantifying '{roi.name}'...")
71+
quant = quantify_cells_rois(
72+
[roi], args.layers.split(","), morphology=args.morphology
73+
).reset_index()
74+
75+
# reorder columns for nice effect
76+
ext = ["roi", "obj_id"] + (["X_centroid", "Y_centroid"] if args.morphology else [])
77+
rem = [x for x in quant.columns if x not in ext]
78+
quant = quant[ext + rem]
79+
80+
# Determine output path
81+
if args.output is not None:
82+
# -o flag: use as output directory, write <roi_name>_quantification files there
83+
out_dir = Path(args.output).mkdir()
84+
stem = tiff.stem.replace("_full", "")
85+
csv_path = out_dir / f"{stem}_quantification.csv.gz"
86+
else:
87+
# Default: write next to the input TIFF
88+
stem = tiff.stem.replace("_full", "")
89+
csv_path = tiff.parent / f"{stem}_quantification.csv.gz"
90+
91+
quant.to_csv(csv_path, index=False)
92+
print(f"Wrote CSV file to '{csv_path.absolute()}'.")
93+
94+
if args.output_h5ad:
95+
ann = _make_anndata(quant, args.morphology)
96+
h5ad_path = csv_path.replace_(".csv.gz", ".h5ad")
97+
ann.write(h5ad_path)
98+
print(f"Wrote h5ad file to '{h5ad_path.absolute()}'.")
99+
ann2 = anndata.read_h5ad(h5ad_path)
100+
assert np.allclose(ann.X, ann2.X)
86101

87102
print("Finished quantification step.")
88103
return 0

0 commit comments

Comments
 (0)