files / tools /viewer_old.py
milkyroad's picture
Upload folder using huggingface_hub (part 5)
d13c6ce verified
Raw
History Blame Contribute Delete
12.6 kB
"""Dataset E viewer — browse images with labels and filters."""
import os, io, math, pyarrow.parquet as pq
from flask import Flask, send_file, request, render_template_string
from PIL import Image
from collections import Counter
ROOT = "/home/kccitadmin/jupyterlab/sandbox/data/E"
app = Flask(__name__)
# Load parquet once
table = pq.read_table(os.path.join(ROOT, "data", "data-00000-of-00001.parquet"))
N = table.num_rows
DATA = {
"cancer": table.column("cancer_label").to_pylist(),
"grade": table.column("grade_label").to_pylist(),
"subclass": table.column("subclass_label").to_pylist(),
"source": table.column("source_dataset").to_pylist(),
"filename": table.column("original_filename").to_pylist(),
"pid": table.column("patient_id").to_pylist(),
"imaging": table.column("imaging_type").to_pylist(),
"img_paths": [v["path"] for v in table.column("image").to_pylist()],
}
LABELS = {
"cancer": {0: "non_cancer", 1: "cancer"},
"grade": {0: "low_grade", 1: "high_grade", 2: "not_applicable"},
"subclass": {0: "malignant", 1: "non_malignant", 2: "normal", 3: "landmark", 4: "foreign_body"},
"imaging": {0: "WLI", 1: "NBI", 2: "BLC"},
}
HTML = """
<!DOCTYPE html>
<html>
<head>
<title>Dataset E Viewer</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: system-ui, sans-serif; background: #1a1a2e; color: #e0e0e0; padding: 20px; }
h1 { color: #0ff; margin-bottom: 10px; }
.stats { background: #16213e; padding: 12px 16px; border-radius: 8px; margin-bottom: 16px; font-size: 13px; display: flex; gap: 20px; flex-wrap: wrap; }
.stats span { color: #0fa; }
.filters { background: #16213e; padding: 16px; border-radius: 8px; margin-bottom: 16px; display: flex; gap: 12px; flex-wrap: wrap; align-items: end; }
.filters label { font-size: 12px; color: #aaa; display: block; margin-bottom: 4px; }
.filters select { background: #0f3460; color: #e0e0e0; border: 1px solid #0fa; border-radius: 4px; padding: 6px 10px; font-size: 13px; }
.filters button { background: #0fa; color: #000; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer; font-weight: bold; }
.filters button:hover { background: #0fc; }
.pagination { display: flex; gap: 8px; align-items: center; margin-bottom: 16px; }
.pagination a { background: #0f3460; color: #0fa; padding: 6px 14px; border-radius: 4px; text-decoration: none; font-size: 13px; }
.pagination a:hover { background: #1a4a80; }
.pagination a.active { background: #0fa; color: #000; }
.pagination span { color: #888; font-size: 13px; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }
.card { background: #16213e; border-radius: 8px; overflow: hidden; cursor: pointer; transition: transform 0.15s; border: 2px solid transparent; }
.card:hover { transform: scale(1.03); border-color: #0fa; }
.card img { width: 100%; height: 180px; object-fit: cover; display: block; }
.card .info { padding: 8px; font-size: 11px; }
.card .info .src { font-weight: bold; color: #0fa; }
.card .info .pid { color: #888; }
.card .info .labels { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 4px; }
.card .badge { padding: 2px 6px; border-radius: 3px; font-size: 10px; }
.badge.cancer { background: #e74c3c; color: #fff; }
.badge.non_cancer { background: #2ecc71; color: #000; }
.badge.grade { background: #3498db; color: #fff; }
.badge.na { background: #555; color: #ccc; }
.badge.subclass { background: #9b59b6; color: #fff; }
.badge.imaging { background: #f39c12; color: #000; }
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); z-index: 1000; justify-content: center; align-items: center; }
.modal-overlay.show { display: flex; }
.modal { background: #16213e; border-radius: 12px; max-width: 800px; max-height: 90vh; overflow: auto; padding: 20px; }
.modal img { max-width: 100%; max-height: 500px; border-radius: 8px; display: block; margin: 0 auto 16px; }
.modal .meta { font-size: 14px; line-height: 1.8; }
.modal .meta .row { display: flex; gap: 8px; }
.modal .meta .key { color: #888; min-width: 140px; }
.modal .meta .val { color: #0fa; }
.modal .close { position: absolute; top: 20px; right: 30px; font-size: 28px; color: #fff; cursor: pointer; }
</style>
</head>
<body>
<h1>Dataset E Viewer</h1>
<div class="stats">
<span>Total: {{ total }}</span>
<span>Showing: {{ filtered_count }}</span>
<span>Page: {{ page }}/{{ pages }}</span>
<span>Cancer: {{ stats.cancer }} | Non-cancer: {{ stats.non_cancer }}</span>
<span>WLI: {{ stats.wli }} | NBI: {{ stats.nbi }} | BLC: {{ stats.blc }}</span>
</div>
<form class="filters" method="GET">
<div><label>Source</label><select name="source"><option value="">All</option>{% for s in sources %}<option {{ 'selected' if filters.source==s }}>{{ s }}</option>{% endfor %}</select></div>
<div><label>Cancer</label><select name="cancer"><option value="">All</option><option value="0" {{ 'selected' if filters.cancer=='0' }}>non_cancer</option><option value="1" {{ 'selected' if filters.cancer=='1' }}>cancer</option></select></div>
<div><label>Grade</label><select name="grade"><option value="">All</option><option value="0" {{ 'selected' if filters.grade=='0' }}>low_grade</option><option value="1" {{ 'selected' if filters.grade=='1' }}>high_grade</option><option value="2" {{ 'selected' if filters.grade=='2' }}>not_applicable</option></select></div>
<div><label>Subclass</label><select name="subclass"><option value="">All</option><option value="0" {{ 'selected' if filters.subclass=='0' }}>malignant</option><option value="1" {{ 'selected' if filters.subclass=='1' }}>non_malignant</option><option value="2" {{ 'selected' if filters.subclass=='2' }}>normal</option><option value="3" {{ 'selected' if filters.subclass=='3' }}>landmark</option><option value="4" {{ 'selected' if filters.subclass=='4' }}>foreign_body</option></select></div>
<div><label>Imaging</label><select name="imaging"><option value="">All</option><option value="0" {{ 'selected' if filters.imaging=='0' }}>WLI</option><option value="1" {{ 'selected' if filters.imaging=='1' }}>NBI</option><option value="2" {{ 'selected' if filters.imaging=='2' }}>BLC</option></select></div>
<div><label>Patient ID</label><input type="text" name="pid" value="{{ filters.pid }}" style="background:#0f3460;color:#e0e0e0;border:1px solid #0fa;border-radius:4px;padding:6px;width:80px;"></div>
<button type="submit">Filter</button>
<a href="/" style="background:#555;color:#fff;padding:8px 16px;border-radius:4px;text-decoration:none;">Reset</a>
</form>
<div class="pagination">
{% if page > 1 %}<a href="?{{ qs }}&page={{ page-1 }}">← Prev</a>{% endif %}
<span>Page {{ page }} of {{ pages }} ({{ filtered_count }} results)</span>
{% if page < pages %}<a href="?{{ qs }}&page={{ page+1 }}">Next →</a>{% endif %}
</div>
<div class="grid">
{% for i in indices %}
<div class="card" onclick="showModal({{ i }})">
<img src="/img/{{ i }}/thumb" loading="lazy">
<div class="info">
<div><span class="src">{{ items[loop.index0].source }}</span> <span class="pid">pid={{ items[loop.index0].pid }}</span></div>
<div class="labels">
<span class="badge {{ 'cancer' if items[loop.index0].cancer==1 else 'non_cancer' }}">{{ items[loop.index0].cancer_label }}</span>
<span class="badge {{ 'grade' if items[loop.index0].grade!=2 else 'na' }}">{{ items[loop.index0].grade_label }}</span>
<span class="badge subclass">{{ items[loop.index0].subclass_label }}</span>
<span class="badge imaging">{{ items[loop.index0].imaging_label }}</span>
</div>
<div style="color:#666;margin-top:2px;font-size:10px;">{{ items[loop.index0].filename }}</div>
</div>
</div>
{% endfor %}
</div>
<div class="modal-overlay" id="modal" onclick="closeModal()">
<div class="modal" onclick="event.stopPropagation()">
<span class="close" onclick="closeModal()">×</span>
<img id="modal-img" src="">
<div class="meta" id="modal-meta"></div>
</div>
</div>
<script>
const allData = {{ all_data|tojson }};
function showModal(i) {
const d = allData[i];
document.getElementById('modal-img').src = '/img/' + i + '/full';
let html = '';
for (const [k, v] of Object.entries(d)) {
html += '<div class="row"><span class="key">' + k + '</span><span class="val">' + v + '</span></div>';
}
document.getElementById('modal-meta').innerHTML = html;
document.getElementById('modal').classList.add('show');
}
function closeModal() { document.getElementById('modal').classList.remove('show'); }
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); });
</script>
</body>
</html>
"""
PER_PAGE = 48
@app.route("/")
def index():
# Parse filters
f = {
"source": request.args.get("source", ""),
"cancer": request.args.get("cancer", ""),
"grade": request.args.get("grade", ""),
"subclass": request.args.get("subclass", ""),
"imaging": request.args.get("imaging", ""),
"pid": request.args.get("pid", ""),
}
page = max(1, int(request.args.get("page", 1)))
# Filter indices
indices = list(range(N))
if f["source"]:
indices = [i for i in indices if DATA["source"][i] == f["source"]]
if f["cancer"] != "":
indices = [i for i in indices if DATA["cancer"][i] == int(f["cancer"])]
if f["grade"] != "":
indices = [i for i in indices if DATA["grade"][i] == int(f["grade"])]
if f["subclass"] != "":
indices = [i for i in indices if DATA["subclass"][i] == int(f["subclass"])]
if f["imaging"] != "":
indices = [i for i in indices if DATA["imaging"][i] == int(f["imaging"])]
if f["pid"]:
indices = [i for i in indices if str(DATA["pid"][i]) == f["pid"]]
filtered_count = len(indices)
pages = max(1, math.ceil(filtered_count / PER_PAGE))
page = min(page, pages)
start = (page - 1) * PER_PAGE
page_indices = indices[start:start + PER_PAGE]
# Build item data for template
items = []
for i in page_indices:
items.append({
"source": DATA["source"][i],
"pid": DATA["pid"][i],
"cancer": DATA["cancer"][i],
"cancer_label": LABELS["cancer"][DATA["cancer"][i]],
"grade": DATA["grade"][i],
"grade_label": LABELS["grade"][DATA["grade"][i]],
"subclass": DATA["subclass"][i],
"subclass_label": LABELS["subclass"][DATA["subclass"][i]],
"imaging": DATA["imaging"][i],
"imaging_label": LABELS["imaging"][DATA["imaging"][i]],
"filename": DATA["filename"][i],
})
# Build all_data for JS modal (all filtered indices on this page)
all_data = {}
for idx, i in enumerate(page_indices):
all_data[str(i)] = {
"index": str(i),
"source": DATA["source"][i],
"patient_id": str(DATA["pid"][i]),
"cancer_label": LABELS["cancer"][DATA["cancer"][i]],
"grade_label": LABELS["grade"][DATA["grade"][i]],
"subclass_label": LABELS["subclass"][DATA["subclass"][i]],
"imaging_type": LABELS["imaging"][DATA["imaging"][i]],
"original_filename": DATA["filename"][i],
"image_path": DATA["img_paths"][i].split("data/E/")[-1],
}
# Stats for filtered set
fc = Counter(DATA["cancer"][i] for i in indices)
fi = Counter(DATA["imaging"][i] for i in indices)
# Query string for pagination links
qs_parts = []
for k, v in f.items():
if v:
qs_parts.append(f"{k}={v}")
qs = "&".join(qs_parts)
return render_template_string(HTML,
total=N, filtered_count=filtered_count, page=page, pages=pages,
indices=page_indices, items=items, all_data=all_data,
sources=["B", "C", "D"], filters=f,
stats={"cancer": fc[1], "non_cancer": fc[0], "wli": fi[0], "nbi": fi[1], "blc": fi[2]},
qs=qs)
@app.route("/img/<int:i>/<size>")
def serve_img(i, size):
path = DATA["img_paths"][i]
img = Image.open(path)
if img.mode in ("RGBA", "P"):
img = img.convert("RGB")
if size == "thumb":
img.thumbnail((300, 300))
else:
img.thumbnail((1000, 1000))
buf = io.BytesIO()
img.save(buf, format="JPEG", quality=85)
buf.seek(0)
return send_file(buf, mimetype="image/jpeg")
if __name__ == "__main__":
print(f"Dataset E viewer: {N} images loaded")
print("Starting server at http://localhost:5173")
app.run(host="0.0.0.0", port=5173, debug=False)