File size: 8,401 Bytes
37fbec9 | 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 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 | """
data/download.py
----------------
Downloads the NIH ChestX-ray14 dataset from Kaggle using the Kaggle API.
SETUP (one-time):
1. Create a Kaggle account at https://www.kaggle.com
2. Go to: Account β Settings β Create New API Token β downloads kaggle.json
3. Place kaggle.json at:
Windows : C:\\Users\\<YourName>\\.kaggle\\kaggle.json
Linux : ~/.kaggle/kaggle.json
4. Run: python data/download.py
Dataset: nih-chest-xrays/data
- 112,120 frontal chest X-ray images (PNG, 1024Γ1024)
- Data_Entry_2017.csv β image metadata + labels
- train_val_list.txt β official train+val split
- test_list.txt β official test split
Total size: ~42 GB (compressed ~11 GB β the script downloads zip chunks)
"""
import os
import sys
import zipfile
import shutil
from pathlib import Path
from typing import Optional
# ββ Rich for pretty console output βββββββββββββββββββββββββββββββββββββββββββ
try:
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn
from rich.panel import Panel
console = Console()
except ImportError:
import builtins
class _FallbackConsole:
def print(self, *args, **kwargs):
builtins.print(*args)
def rule(self, *args, **kwargs):
builtins.print("β" * 60)
console = _FallbackConsole()
# ββ Constants βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
KAGGLE_DATASET = "nih-chest-xrays/data"
DEFAULT_RAW_DIR = Path("./data/raw")
REQUIRED_FILES = [
"Data_Entry_2017.csv",
"train_val_list.txt",
"test_list.txt",
]
def _check_kaggle_credentials() -> bool:
"""Verify kaggle.json exists and is readable."""
kaggle_dir = Path.home() / ".kaggle"
kaggle_json = kaggle_dir / "kaggle.json"
if not kaggle_json.exists():
console.print(
"[bold red]β kaggle.json not found![/bold red]\n"
f" Expected location: {kaggle_json}\n\n"
" Steps to fix:\n"
" 1. Visit https://www.kaggle.com β Account β Settings\n"
" 2. Click 'Create New API Token' β downloads kaggle.json\n"
f" 3. Move it to: {kaggle_dir}\n"
" 4. Re-run this script.",
style="red",
)
return False
# Ensure correct permissions (important on Linux/macOS)
if os.name != "nt":
kaggle_json.chmod(0o600)
return True
def _verify_images_dir(raw_dir: Path) -> bool:
"""
Check if images are already extracted to avoid re-downloading.
We consider extraction complete if >1000 .png files exist.
"""
images_dir = raw_dir / "images"
if not images_dir.exists():
return False
png_count = sum(1 for _ in images_dir.glob("*.png"))
console.print(f" Found {png_count:,} existing PNG files in {images_dir}")
return png_count > 1000
def download_dataset(
raw_dir: Optional[Path] = None,
force: bool = False,
) -> Path:
"""
Download and extract NIH ChestX-ray14 from Kaggle.
Args:
raw_dir: Directory to extract data into.
force: Re-download even if files already exist.
Returns:
Path to the extracted raw data directory.
"""
raw_dir = Path(raw_dir or DEFAULT_RAW_DIR)
raw_dir.mkdir(parents=True, exist_ok=True)
# ββ 1. Check credentials ββββββββββββββββββββββββββββββββββββββββββββββββββ
console.print(Panel("[bold cyan]NIH ChestX-ray14 β Dataset Download[/bold cyan]",
subtitle="Kaggle API"))
if not _check_kaggle_credentials():
sys.exit(1)
# ββ 2. Check if already downloaded βββββββββββββββββββββββββββββββββββββββ
required_present = all((raw_dir / f).exists() for f in REQUIRED_FILES)
images_present = _verify_images_dir(raw_dir)
if required_present and images_present and not force:
console.print("[bold green]β Dataset already downloaded and extracted.[/bold green]")
console.print(f" Data root: {raw_dir.resolve()}")
return raw_dir
# ββ 3. Import kaggle (after credentials check) ββββββββββββββββββββββββββββ
try:
import kaggle # noqa: F401 β triggers credential load
from kaggle.api.kaggle_api_extended import KaggleApiExtended
except ImportError:
console.print("[red]β kaggle package not installed. Run: pip install kaggle[/red]")
sys.exit(1)
api = KaggleApiExtended()
api.authenticate()
# ββ 4. Download dataset zip(s) ββββββββββββββββββββββββββββββββββββββββββββ
console.print(f"\n[yellow]β¬ Downloading '{KAGGLE_DATASET}' β {raw_dir.resolve()}[/yellow]")
console.print(" β This dataset is ~42 GB. Download time depends on your connection.")
console.print(" β The Kaggle API downloads each image zip file sequentially.\n")
api.dataset_download_files(
KAGGLE_DATASET,
path=str(raw_dir),
unzip=False, # We handle extraction ourselves for better control
quiet=False,
)
console.print("[green]β Download complete.[/green]")
# ββ 5. Extract all zip files ββββββββββββββββββββββββββββββββββββββββββββββ
images_dir = raw_dir / "images"
images_dir.mkdir(exist_ok=True)
zip_files = sorted(raw_dir.glob("*.zip")) + sorted(raw_dir.glob("images_*.tar.gz"))
console.print(f"\n[yellow]π¦ Extracting {len(zip_files)} archive(s)...[/yellow]")
for zip_path in zip_files:
console.print(f" Extracting: {zip_path.name}")
if zip_path.suffix == ".zip":
with zipfile.ZipFile(zip_path, "r") as zf:
# Extract images to images_dir, metadata to raw_dir
for member in zf.namelist():
if member.endswith(".png"):
# Extract flat into images_dir
target = images_dir / Path(member).name
if not target.exists():
with zf.open(member) as src, open(target, "wb") as dst:
shutil.copyfileobj(src, dst)
elif not Path(member).is_dir():
zf.extract(member, raw_dir)
console.print(f" β {zip_path.name} extracted.")
# ββ 6. Verify essential files βββββββββββββββββββββββββββββββββββββββββββββ
console.rule("Verification")
all_ok = True
for fname in REQUIRED_FILES:
fpath = raw_dir / fname
status = "[green]β[/green]" if fpath.exists() else "[red]β MISSING[/red]"
console.print(f" {status} {fname}")
if not fpath.exists():
all_ok = False
png_count = sum(1 for _ in images_dir.glob("*.png"))
console.print(f" [cyan]PNG images found: {png_count:,}[/cyan]")
if all_ok and png_count > 1000:
console.print("\n[bold green]π Dataset ready![/bold green]")
console.print(f" Data root : {raw_dir.resolve()}")
console.print(f" Images : {images_dir.resolve()}")
else:
console.print("\n[bold red]β Some files may be missing. Check the output above.[/bold red]")
return raw_dir
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Download NIH ChestX-ray14 from Kaggle")
parser.add_argument(
"--output-dir", type=str, default=str(DEFAULT_RAW_DIR),
help="Directory to save the dataset (default: ./data/raw)",
)
parser.add_argument(
"--force", action="store_true",
help="Re-download even if dataset already exists",
)
args = parser.parse_args()
download_dataset(raw_dir=Path(args.output_dir), force=args.force)
|