Spaces:
Running on Zero
Running on Zero
File size: 6,392 Bytes
6942820 9e91db2 6942820 9e91db2 6942820 9e91db2 6942820 9e91db2 6942820 9e91db2 6942820 9e91db2 6942820 9e91db2 6942820 9e91db2 6942820 9e91db2 6942820 | 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 | """Builds public/samples/: a few labelled, anonymised scans from public datasets, shown as
"Try a sample scan" on the upload page. Each keeps its dataset label as the known answer.
.venv/bin/pip install pyarrow # only needed for this script
.venv/bin/python build_samples.py
Images already in public/samples/ are kept; delete one to download it again.
"""
import io
import json
import time
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
from PIL import Image
HERE = Path(__file__).parent
OUT = HERE.parent / "public" / "samples"
HF = "https://huggingface.co/datasets"
SOURCES = {
"chest-xray": dict(repo="hf-vision/chest-xray-pneumonia", source="Chest X-ray pneumonia dataset (Kermany et al.)", licence="CC BY 4.0"),
"brain-mri": dict(repo="BJyotibrat/Masoud-Nickparvar-Brain-Tumor-MRI-Dataset", source="Brain tumour MRI dataset (Masoud Nickparvar)", licence="CC BY 4.0"),
"chest-ct": dict(repo="Mahadih534/Chest_CT-Scan_images-Dataset", source="Chest CT-scan images (Mohamed Hany)", licence="CC"),
"chest-xray-tb": dict(repo="realsudarshan/chest-xray-tb-pneumonia", source="Chest X-ray TB, pneumonia and normal (compiled from public sets)", licence="CC BY 4.0"),
"retina-photo": dict(repo="bumbledeep/eyepacs", source="EyePACS diabetic retinopathy photos (Kaggle 2015)", licence="MIT (as listed on the Hugging Face copy)"),
"skin-photo": dict(repo="marmal88/skin_cancer", source="HAM10000 dermatoscopic images (Tschandl et al.)", licence="CC BY-NC 4.0"),
"knee-xray": dict(repo="SilpaCS/kneeosteoarthritis", source="Knee osteoarthritis severity grading dataset (Chen, from the OAI)", licence="CC BY 4.0"),
}
# Row numbers of the parquet-only samples below (see from_rows).
ROWS = {"chest-xray-tuberculosis": 1700, "retina-photo-moderate-dr": 12663, "retina-photo-no-dr": 12600,
"skin-photo-melanoma": 1201, "skin-photo-nevus": 800, "knee-xray-kl3": 8000, "knee-xray-kl0": 500}
# (sample id, scan type, dataset label, folder in the dataset repo). Chest X-rays come from a
# parquet file instead of folders, so their folder is the class index.
WANTED = [
("chest-xray-normal", "chest-xray", "Normal", 0),
("chest-xray-pneumonia", "chest-xray", "Pneumonia", 1),
("brain-mri-glioma", "brain-mri", "Glioma", "Testing/glioma"),
("brain-mri-meningioma", "brain-mri", "Meningioma", "Testing/meningioma"),
("brain-mri-notumor", "brain-mri", "No tumour", "Testing/notumor"),
("chest-ct-adenocarcinoma", "chest-ct", "Adenocarcinoma", "test/adenocarcinoma"),
("chest-ct-normal", "chest-ct", "Normal", "test/normal"),
# These datasets are only parquet files, so samples are picked by row through the datasets server:
# (split, row number). A fifth item names the SOURCES entry when it isn't the scan type.
("chest-xray-tuberculosis", "chest-xray", "Tuberculosis", ("test", ROWS["chest-xray-tuberculosis"]), "chest-xray-tb"),
("retina-photo-moderate-dr", "retina-photo", "Moderate diabetic retinopathy", ("train", ROWS["retina-photo-moderate-dr"])),
("retina-photo-no-dr", "retina-photo", "No diabetic retinopathy", ("train", ROWS["retina-photo-no-dr"])),
("skin-photo-melanoma", "skin-photo", "Melanoma", ("test", ROWS["skin-photo-melanoma"])),
("skin-photo-nevus", "skin-photo", "Mole (melanocytic nevus)", ("test", ROWS["skin-photo-nevus"])),
("knee-xray-kl3", "knee-xray", "Kellgren-Lawrence grade 3", ("train", ROWS["knee-xray-kl3"])),
("knee-xray-kl0", "knee-xray", "Kellgren-Lawrence grade 0", ("train", ROWS["knee-xray-kl0"])),
]
# Colour photographs stay in colour; scans are saved in grayscale.
COLOUR = {"retina-photo", "skin-photo"}
def get(url, tries=3):
for attempt in range(tries):
try:
with urllib.request.urlopen(url, timeout=60) as r:
return r.read()
except urllib.error.URLError: # the datasets server sometimes answers 502 or 429
if attempt == tries - 1:
raise
time.sleep(10)
def from_folder(repo, folder):
"""A file a third of the way through the folder, so reruns pick the same image."""
tree = json.loads(get(f"https://huggingface.co/api/datasets/{repo}/tree/main/{urllib.parse.quote(folder)}"))
files = sorted(f["path"] for f in tree if f["type"] == "file")
return get(f"{HF}/{repo}/resolve/main/{urllib.parse.quote(files[len(files) // 3])}")
def from_parquet(repo, label):
import pyarrow.parquet as pq
data = get(f"{HF}/{repo}/resolve/main/data/validation-00000-of-00001.parquet")
rows = pq.read_table(io.BytesIO(data)).to_pylist()
return next(r for r in rows if r["label"] == label)["image"]["bytes"]
def from_rows(repo, split, row):
q = urllib.parse.quote(repo)
config = json.loads(get(f"https://datasets-server.huggingface.co/splits?dataset={q}"))["splits"][0]["config"]
rows = json.loads(get(f"https://datasets-server.huggingface.co/rows?dataset={q}&config={config}"
f"&split={split}&offset={row}&length=1"))["rows"]
image = next(v for v in rows[0]["row"].values() if isinstance(v, dict) and "src" in v)
return get(image["src"])
def main():
OUT.mkdir(parents=True, exist_ok=True)
manifest = []
for sid, scan, truth, where, *source in WANTED:
src = SOURCES[source[0] if source else scan]
path = OUT / f"{sid}.jpg"
if not path.exists():
if isinstance(where, tuple):
raw = from_rows(src["repo"], *where)
elif isinstance(where, int):
raw = from_parquet(src["repo"], where)
else:
raw = from_folder(src["repo"], where)
img = Image.open(io.BytesIO(raw)).convert("RGB" if scan in COLOUR else "L")
# Colour photos compress less well; smaller keeps each sample under about 150 KB.
img.thumbnail((768, 768) if scan in COLOUR else (1024, 1024))
img.save(path, "JPEG", quality=85 if scan in COLOUR else 90)
print("downloaded", path.name)
manifest.append(dict(id=sid, file=path.name, scan=scan, truth=truth, source=src["source"],
url=f"{HF}/{src['repo']}", licence=src["licence"]))
(OUT / "samples.json").write_text(json.dumps(manifest, indent=2) + "\n")
print(f"wrote {len(manifest)} samples")
if __name__ == "__main__":
main()
|