Instructions to use Lien-Feng/Lightweight-2-5D-LUNA16 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- ultralytics
How to use Lien-Feng/Lightweight-2-5D-LUNA16 with ultralytics:
# Couldn't find a valid YOLO version tag. # Replace XX with the correct version. from ultralytics import YOLOvXX model = YOLOvXX.from_pretrained("Lien-Feng/Lightweight-2-5D-LUNA16") source = 'http://images.cocodataset.org/val2017/000000039769.jpg' model.predict(source=source, save=True) - Notebooks
- Google Colab
- Kaggle
| """Run the detector over one CT volume and emit a LUNA16-format candidate CSV. | |
| This is the whole-volume path: every axial slice is scanned, slice detections | |
| are aggregated into 3D candidates, and centres are written in world coordinates | |
| so the output can be fed straight to the official evaluator. | |
| Usage | |
| ----- | |
| python examples/predict_scan.py \\ | |
| --scan /data/LUNA16/subset0/1.3.6.1.4...mhd \\ | |
| --weights weights/Exp4_2p5D_Strict/fold0/best.pt \\ | |
| --out candidates.csv | |
| Note on fold choice: fold *k* was trained without ``subset<k>``, so use the | |
| checkpoint whose fold matches the subset the scan came from. For a scan from | |
| outside LUNA16, any fold is valid; averaging folds is not implemented here. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import sys | |
| from pathlib import Path | |
| import pandas as pd | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[1])) | |
| from luna_rev import config as cfg | |
| from luna_rev.io_luna import normalize_hu, read_volume, scan_index | |
| from luna_rev.predict import candidates_to_world, cluster_to_3d, detect_volume | |
| def main() -> int: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--scan", required=True, help="path to a .mhd volume, or a bare series UID") | |
| ap.add_argument("--weights", required=True) | |
| ap.add_argument("--out", default="candidates.csv") | |
| ap.add_argument("--representation", default="naive", choices=list(cfg.REPRESENTATIONS), | |
| help="must match how the checkpoint was trained") | |
| ap.add_argument("--top-k", type=int, default=150) | |
| ap.add_argument("--imgsz", type=int, default=cfg.IMG_SIZE) | |
| args = ap.parse_args() | |
| from ultralytics import YOLO | |
| path = Path(args.scan) | |
| uid = path.stem if path.suffix else args.scan | |
| if not path.exists(): | |
| path = scan_index()[uid] | |
| vol_hu, meta = read_volume(uid) if path == scan_index().get(uid) else _read_direct(path, uid) | |
| vol_u8 = normalize_hu(vol_hu) | |
| print(f"{uid}: {vol_u8.shape[0]} slices, spacing {meta.spacing.round(3).tolist()} mm") | |
| model = YOLO(args.weights) | |
| det = detect_volume(model, vol_u8, args.representation) | |
| clusters = cluster_to_3d(det)[:args.top_k] | |
| world = candidates_to_world(clusters, meta) | |
| print(f"{len(det)} slice detections -> {len(clusters)} 3D candidates") | |
| df = pd.DataFrame({ | |
| "seriesuid": uid, | |
| "coordX": world[:, 0], "coordY": world[:, 1], "coordZ": world[:, 2], | |
| "probability": clusters[:, 5], | |
| }) | |
| df.to_csv(args.out, index=False) | |
| print(f"wrote {args.out}") | |
| return 0 | |
| def _read_direct(path: Path, uid: str): | |
| """Read a volume that is not part of the indexed LUNA16 tree.""" | |
| import numpy as np | |
| import SimpleITK as sitk | |
| from luna_rev.io_luna import ScanMeta | |
| img = sitk.ReadImage(str(path)) | |
| meta = ScanMeta( | |
| uid=uid, | |
| size=np.array(img.GetSize(), dtype=int), | |
| spacing=np.array(img.GetSpacing(), dtype=float), | |
| origin=np.array(img.GetOrigin(), dtype=float), | |
| direction=np.array(img.GetDirection(), dtype=float).reshape(3, 3), | |
| ) | |
| return sitk.GetArrayFromImage(img), meta | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |