Upload sample_pairs.py with huggingface_hub
Browse files- sample_pairs.py +59 -11
sample_pairs.py
CHANGED
|
@@ -2,10 +2,13 @@ from __future__ import annotations
|
|
| 2 |
|
| 3 |
import argparse
|
| 4 |
import json
|
|
|
|
| 5 |
import random
|
| 6 |
from collections import defaultdict
|
| 7 |
from pathlib import Path
|
| 8 |
|
|
|
|
|
|
|
| 9 |
|
| 10 |
def classify_transformation(pair: dict) -> str:
|
| 11 |
age_changed = pair.get("source_age") != pair.get("target_age")
|
|
@@ -19,6 +22,13 @@ def classify_transformation(pair: dict) -> str:
|
|
| 19 |
return "none"
|
| 20 |
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
def stratified_sample(
|
| 23 |
pairs: list[dict],
|
| 24 |
n_per_stratum: int,
|
|
@@ -35,19 +45,52 @@ def stratified_sample(
|
|
| 35 |
selected: list[int] = []
|
| 36 |
for stratum in ["age_only", "sex_only", "intersectional"]:
|
| 37 |
pool = by_stratum[stratum]
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
)
|
| 46 |
|
|
|
|
|
|
|
| 47 |
return selected
|
| 48 |
|
| 49 |
|
| 50 |
def main() -> None:
|
|
|
|
| 51 |
parser = argparse.ArgumentParser(
|
| 52 |
description="Stratified sampling of pairs for radiologist validation"
|
| 53 |
)
|
|
@@ -67,18 +110,23 @@ def main() -> None:
|
|
| 67 |
pair["_transform_type"] = classify_transformation(pair)
|
| 68 |
|
| 69 |
counts: dict[str, int] = defaultdict(int)
|
|
|
|
| 70 |
for pair in sampled:
|
| 71 |
-
|
|
|
|
|
|
|
| 72 |
|
| 73 |
output_path = Path(args.output)
|
| 74 |
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 75 |
with open(output_path, "w") as f:
|
| 76 |
json.dump(sampled, f, indent=2)
|
| 77 |
|
| 78 |
-
|
| 79 |
for stratum, count in sorted(counts.items()):
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
| 82 |
|
| 83 |
|
| 84 |
if __name__ == "__main__":
|
|
|
|
| 2 |
|
| 3 |
import argparse
|
| 4 |
import json
|
| 5 |
+
import logging
|
| 6 |
import random
|
| 7 |
from collections import defaultdict
|
| 8 |
from pathlib import Path
|
| 9 |
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
|
| 13 |
def classify_transformation(pair: dict) -> str:
|
| 14 |
age_changed = pair.get("source_age") != pair.get("target_age")
|
|
|
|
| 22 |
return "none"
|
| 23 |
|
| 24 |
|
| 25 |
+
def _get_pathology_key(pair: dict) -> str:
|
| 26 |
+
labels = pair.get("pathology_labels", [])
|
| 27 |
+
if not labels:
|
| 28 |
+
return "unlabeled"
|
| 29 |
+
return ",".join(str(v) for v in sorted(labels))
|
| 30 |
+
|
| 31 |
+
|
| 32 |
def stratified_sample(
|
| 33 |
pairs: list[dict],
|
| 34 |
n_per_stratum: int,
|
|
|
|
| 45 |
selected: list[int] = []
|
| 46 |
for stratum in ["age_only", "sex_only", "intersectional"]:
|
| 47 |
pool = by_stratum[stratum]
|
| 48 |
+
if not pool:
|
| 49 |
+
logger.warning("No pairs available for stratum %s", stratum)
|
| 50 |
+
continue
|
| 51 |
+
|
| 52 |
+
by_pathology: dict[str, list[int]] = defaultdict(list)
|
| 53 |
+
for idx in pool:
|
| 54 |
+
key = _get_pathology_key(pairs[idx])
|
| 55 |
+
by_pathology[key].append(idx)
|
| 56 |
+
|
| 57 |
+
pathology_keys = sorted(by_pathology.keys())
|
| 58 |
+
n_categories = len(pathology_keys)
|
| 59 |
+
per_category = n_per_stratum // n_categories
|
| 60 |
+
remainder = n_per_stratum % n_categories
|
| 61 |
+
|
| 62 |
+
stratum_selected: list[int] = []
|
| 63 |
+
overflow_pool: list[int] = []
|
| 64 |
+
|
| 65 |
+
for k_idx, key in enumerate(pathology_keys):
|
| 66 |
+
candidates = by_pathology[key]
|
| 67 |
+
rng.shuffle(candidates)
|
| 68 |
+
target = per_category + (1 if k_idx < remainder else 0)
|
| 69 |
+
taken = min(target, len(candidates))
|
| 70 |
+
stratum_selected.extend(candidates[:taken])
|
| 71 |
+
if taken < target:
|
| 72 |
+
overflow_pool.extend([])
|
| 73 |
+
if taken < len(candidates):
|
| 74 |
+
overflow_pool.extend(candidates[taken:])
|
| 75 |
+
|
| 76 |
+
if len(stratum_selected) < n_per_stratum and overflow_pool:
|
| 77 |
+
rng.shuffle(overflow_pool)
|
| 78 |
+
needed = n_per_stratum - len(stratum_selected)
|
| 79 |
+
stratum_selected.extend(overflow_pool[:needed])
|
| 80 |
+
|
| 81 |
+
if len(stratum_selected) < n_per_stratum:
|
| 82 |
+
logger.warning(
|
| 83 |
+
"Only %d pairs available for %s (requested %d)",
|
| 84 |
+
len(stratum_selected), stratum, n_per_stratum,
|
| 85 |
)
|
| 86 |
|
| 87 |
+
selected.extend(stratum_selected)
|
| 88 |
+
|
| 89 |
return selected
|
| 90 |
|
| 91 |
|
| 92 |
def main() -> None:
|
| 93 |
+
logging.basicConfig(level=logging.INFO)
|
| 94 |
parser = argparse.ArgumentParser(
|
| 95 |
description="Stratified sampling of pairs for radiologist validation"
|
| 96 |
)
|
|
|
|
| 110 |
pair["_transform_type"] = classify_transformation(pair)
|
| 111 |
|
| 112 |
counts: dict[str, int] = defaultdict(int)
|
| 113 |
+
pathology_counts: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
|
| 114 |
for pair in sampled:
|
| 115 |
+
t = pair["_transform_type"]
|
| 116 |
+
counts[t] += 1
|
| 117 |
+
pathology_counts[t][_get_pathology_key(pair)] += 1
|
| 118 |
|
| 119 |
output_path = Path(args.output)
|
| 120 |
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 121 |
with open(output_path, "w") as f:
|
| 122 |
json.dump(sampled, f, indent=2)
|
| 123 |
|
| 124 |
+
logger.info("Sampled %d pairs from %d total", len(sampled), len(all_pairs))
|
| 125 |
for stratum, count in sorted(counts.items()):
|
| 126 |
+
logger.info(" %s: %d", stratum, count)
|
| 127 |
+
for pkey, pcount in sorted(pathology_counts[stratum].items()):
|
| 128 |
+
logger.info(" pathology [%s]: %d", pkey, pcount)
|
| 129 |
+
logger.info("Saved to: %s", output_path)
|
| 130 |
|
| 131 |
|
| 132 |
if __name__ == "__main__":
|