kbressem commited on
Commit
0900543
·
verified ·
1 Parent(s): 9d625f8

Upload scripts/run_pipeline.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. scripts/run_pipeline.py +33 -104
scripts/run_pipeline.py CHANGED
@@ -1,22 +1,9 @@
1
  #!/usr/bin/env python3
2
- """End-to-end coronary segmentation pipeline.
3
-
4
- CT image (DICOM/NIfTI/NRRD) → binary coronary mask �� segmental coronary labels.
5
-
6
- Chains two 5-fold ensemble inferences:
7
- 1. Binary coronary segmentation (CT → vessel mask)
8
- 2. Segmental coronary segmentation (vessel mask → 21-class labels)
9
-
10
- Works both locally and inside Docker. Detects bundle directories automatically:
11
- - HF clone: <root>/ct_binary_coronary_segmentation/
12
- - Git repo: <root>/bundle/ct_binary_coronary_segmentation/
13
 
14
  Usage:
15
- # Local
16
  python scripts/run_pipeline.py --input /path/to/dicoms --output /path/to/results
17
-
18
- # Docker
19
- docker run --gpus all -v /data/patient:/input -v /data/output:/output ct-heart-seg
20
  """
21
 
22
  import argparse
@@ -29,140 +16,82 @@ import tempfile
29
  import time
30
  from pathlib import Path
31
 
32
-
33
- def setup_logging(output_dir: Path):
34
- output_dir.mkdir(parents=True, exist_ok=True)
35
- handlers = [
36
- logging.StreamHandler(),
37
- logging.FileHandler(output_dir / "pipeline.log"),
38
- ]
39
- logging.basicConfig(
40
- level=logging.INFO,
41
- format="%(asctime)s [%(levelname)s] %(message)s",
42
- datefmt="%Y-%m-%d %H:%M:%S",
43
- handlers=handlers,
44
- force=True,
45
- )
46
-
47
-
48
  logger = logging.getLogger(__name__)
49
 
50
  BINARY_BUNDLE = "ct_binary_coronary_segmentation"
51
  SEGMENTAL_BUNDLE = "ct_segmental_coronary_segmentation"
52
 
53
 
54
- def find_bundle_dir(root: Path, bundle_name: str) -> Path:
55
- """Find bundle directory — supports both HF clone and git repo layouts."""
56
- # HF clone: bundles at root level
57
- if (root / bundle_name / "configs").is_dir():
58
- return root / bundle_name
59
- # Git repo: bundles under bundle/
60
- if (root / "bundle" / bundle_name / "configs").is_dir():
61
- return root / "bundle" / bundle_name
62
- raise FileNotFoundError(
63
- f"Cannot find {bundle_name}/ in {root} or {root / 'bundle'}"
64
- )
65
 
66
 
67
- def run_inference(bundle_dir: Path, config: str, extra_args: list[str], gpu: int, label: str):
68
- """Run MONAI bundle inference as subprocess."""
69
  env = os.environ.copy()
70
  env["CUDA_VISIBLE_DEVICES"] = str(gpu)
71
-
72
  cmd = [
73
  sys.executable, "-m", "monai.bundle", "run", "inference",
74
- "--config_file", config,
75
  *extra_args,
76
  ]
77
-
78
- logger.info("[%s] Starting inference", label)
79
- logger.info("[%s] cwd: %s", label, bundle_dir)
80
- logger.info("[%s] cmd: %s", label, " ".join(cmd))
81
-
82
  t0 = time.time()
83
- result = subprocess.run(
84
- cmd,
85
- cwd=str(bundle_dir),
86
- env=env,
87
- capture_output=True,
88
- text=True,
89
- )
90
-
91
  elapsed = time.time() - t0
 
92
  if result.stdout:
93
  for line in result.stdout.strip().split("\n"):
94
  logger.info("[%s] %s", label, line)
95
  if result.stderr:
96
  for line in result.stderr.strip().split("\n"):
97
  logger.info("[%s] %s", label, line)
98
-
99
  if result.returncode != 0:
100
- logger.error("[%s] FAILED (exit code %d) after %.1fs", label, result.returncode, elapsed)
101
  sys.exit(result.returncode)
102
-
103
  logger.info("[%s] Done in %.1fs", label, elapsed)
104
 
105
 
106
  def main():
107
- parser = argparse.ArgumentParser(
108
- description="End-to-end coronary segmentation: CT → binary mask → segmental labels"
109
- )
110
- parser.add_argument("--input", required=True, help="Input directory (DICOM/NIfTI/NRRD files)")
111
- parser.add_argument("--output", required=True, help="Output directory")
112
- parser.add_argument("--gpu", type=int, default=0, help="GPU index (default: 0)")
113
  args = parser.parse_args()
114
 
115
  output_dir = Path(args.output)
116
- setup_logging(output_dir)
 
 
 
 
 
 
117
 
118
- # Find repo root (parent of scripts/)
119
  repo_root = Path(__file__).resolve().parent.parent
120
  binary_dir = find_bundle_dir(repo_root, BINARY_BUNDLE)
121
  segmental_dir = find_bundle_dir(repo_root, SEGMENTAL_BUNDLE)
122
-
123
- logger.info("Input: %s", args.input)
124
- logger.info("Output: %s", output_dir)
125
- logger.info("Binary bundle: %s", binary_dir)
126
- logger.info("Segmental bundle: %s", segmental_dir)
127
- logger.info("GPU: %d", args.gpu)
128
 
129
  t_start = time.time()
130
-
131
- # Stage 1: Binary coronary segmentation
132
  with tempfile.TemporaryDirectory(prefix="binary_output_") as tmp_binary:
133
- run_inference(
134
- bundle_dir=binary_dir,
135
- config="configs/ensemble_inference.yaml",
136
- extra_args=["--dataset_dir", str(Path(args.input).resolve()),
137
- "--output_dir", tmp_binary],
138
- gpu=args.gpu,
139
- label="binary",
140
- )
141
-
142
- # Copy binary output to final location
143
  binary_out = output_dir / "binary"
144
  binary_out.mkdir(parents=True, exist_ok=True)
145
  for f in Path(tmp_binary).glob("*"):
146
  shutil.copy2(f, binary_out / f.name)
147
- n_binary = len(list(binary_out.glob("*.nii.gz")))
148
- logger.info("Binary output: %d files in %s", n_binary, binary_out)
149
 
150
- # Stage 2: Segmental coronary segmentation (uses binary output as input)
151
  segmental_out = output_dir / "segmental"
152
  segmental_out.mkdir(parents=True, exist_ok=True)
153
- run_inference(
154
- bundle_dir=segmental_dir,
155
- config="configs/ensemble_inference.yaml",
156
- extra_args=["--binary_label_dir", tmp_binary,
157
- "--output_dir", str(segmental_out)],
158
- gpu=args.gpu,
159
- label="segmental",
160
- )
161
- n_segmental = len(list(segmental_out.glob("*.nii.gz")))
162
- logger.info("Segmental output: %d files in %s", n_segmental, segmental_out)
163
-
164
- total = time.time() - t_start
165
- logger.info("Pipeline complete in %.1fs. Output: %s", total, output_dir)
166
 
167
 
168
  if __name__ == "__main__":
 
1
  #!/usr/bin/env python3
2
+ """End-to-end coronary segmentation: CT image → binary mask → segmental labels.
 
 
 
 
 
 
 
 
 
 
3
 
4
  Usage:
 
5
  python scripts/run_pipeline.py --input /path/to/dicoms --output /path/to/results
6
+ docker run --gpus all -v /data/in:/input -v /data/out:/output ct-heart-seg
 
 
7
  """
8
 
9
  import argparse
 
16
  import time
17
  from pathlib import Path
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  logger = logging.getLogger(__name__)
20
 
21
  BINARY_BUNDLE = "ct_binary_coronary_segmentation"
22
  SEGMENTAL_BUNDLE = "ct_segmental_coronary_segmentation"
23
 
24
 
25
+ def find_bundle_dir(root: Path, name: str) -> Path:
26
+ for prefix in ["", "bundle"]:
27
+ d = root / prefix / name if prefix else root / name
28
+ if (d / "configs").is_dir():
29
+ return d
30
+ raise FileNotFoundError(f"Cannot find {name}/ in {root} or {root / 'bundle'}")
 
 
 
 
 
31
 
32
 
33
+ def run_inference(bundle_dir: Path, extra_args: list[str], gpu: int, label: str):
 
34
  env = os.environ.copy()
35
  env["CUDA_VISIBLE_DEVICES"] = str(gpu)
 
36
  cmd = [
37
  sys.executable, "-m", "monai.bundle", "run", "inference",
38
+ "--config_file", "configs/ensemble_inference.yaml",
39
  *extra_args,
40
  ]
41
+ logger.info("[%s] %s (cwd=%s)", label, " ".join(cmd), bundle_dir)
 
 
 
 
42
  t0 = time.time()
43
+ result = subprocess.run(cmd, cwd=str(bundle_dir), env=env, capture_output=True, text=True)
 
 
 
 
 
 
 
44
  elapsed = time.time() - t0
45
+
46
  if result.stdout:
47
  for line in result.stdout.strip().split("\n"):
48
  logger.info("[%s] %s", label, line)
49
  if result.stderr:
50
  for line in result.stderr.strip().split("\n"):
51
  logger.info("[%s] %s", label, line)
 
52
  if result.returncode != 0:
53
+ logger.error("[%s] FAILED (exit %d) after %.1fs", label, result.returncode, elapsed)
54
  sys.exit(result.returncode)
 
55
  logger.info("[%s] Done in %.1fs", label, elapsed)
56
 
57
 
58
  def main():
59
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
60
+ parser.add_argument("--input", required=True)
61
+ parser.add_argument("--output", required=True)
62
+ parser.add_argument("--gpu", type=int, default=0)
 
 
63
  args = parser.parse_args()
64
 
65
  output_dir = Path(args.output)
66
+ output_dir.mkdir(parents=True, exist_ok=True)
67
+ logging.basicConfig(
68
+ level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s",
69
+ datefmt="%Y-%m-%d %H:%M:%S",
70
+ handlers=[logging.StreamHandler(), logging.FileHandler(output_dir / "pipeline.log")],
71
+ force=True,
72
+ )
73
 
 
74
  repo_root = Path(__file__).resolve().parent.parent
75
  binary_dir = find_bundle_dir(repo_root, BINARY_BUNDLE)
76
  segmental_dir = find_bundle_dir(repo_root, SEGMENTAL_BUNDLE)
77
+ logger.info("Input: %s | Output: %s | GPU: %d", args.input, output_dir, args.gpu)
 
 
 
 
 
78
 
79
  t_start = time.time()
 
 
80
  with tempfile.TemporaryDirectory(prefix="binary_output_") as tmp_binary:
81
+ run_inference(binary_dir, ["--dataset_dir", str(Path(args.input).resolve()), "--output_dir", tmp_binary], args.gpu, "binary")
82
+
 
 
 
 
 
 
 
 
83
  binary_out = output_dir / "binary"
84
  binary_out.mkdir(parents=True, exist_ok=True)
85
  for f in Path(tmp_binary).glob("*"):
86
  shutil.copy2(f, binary_out / f.name)
87
+ logger.info("Binary: %d files → %s", len(list(binary_out.glob("*.nii.gz"))), binary_out)
 
88
 
 
89
  segmental_out = output_dir / "segmental"
90
  segmental_out.mkdir(parents=True, exist_ok=True)
91
+ run_inference(segmental_dir, ["--binary_label_dir", tmp_binary, "--output_dir", str(segmental_out)], args.gpu, "segmental")
92
+ logger.info("Segmental: %d files → %s", len(list(segmental_out.glob("*.nii.gz"))), segmental_out)
93
+
94
+ logger.info("Pipeline complete in %.1fs", time.time() - t_start)
 
 
 
 
 
 
 
 
 
95
 
96
 
97
  if __name__ == "__main__":