Skip to content

Commit 69322ab

Browse files
committed
feat: Add example data to the frontend
1 parent e7759d8 commit 69322ab

File tree

8 files changed

+24840
-13
lines changed

8 files changed

+24840
-13
lines changed

docker/ui.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ COPY src/ui/requirements.txt ./
2525
RUN pip install --no-cache-dir -r requirements.txt
2626

2727
COPY src/ui/app/ ./app/
28+
COPY src/ui/example_data/ ./example_data/
2829

2930
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist
3031

src/ui/app/main.py

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
FastAPI main application for XRD Analysis Tool.
33
Serves both the API endpoints and the static React frontend.
44
"""
5-
from fastapi import FastAPI, UploadFile, File
5+
from fastapi import FastAPI, UploadFile, File, HTTPException
66
from fastapi.staticfiles import StaticFiles
7-
from fastapi.responses import FileResponse, JSONResponse
7+
from fastapi.responses import FileResponse, JSONResponse, PlainTextResponse
88
from fastapi.middleware.cors import CORSMiddleware
99
from pathlib import Path
1010
from typing import Dict, List
11+
import re
1112
import torch
1213
import numpy as np
1314

@@ -110,7 +111,82 @@ async def predict(data: dict):
110111
)
111112

112113

114+
# ---------------------------------------------------------------------------
115+
# Example data endpoints
116+
# ---------------------------------------------------------------------------
117+
EXAMPLE_DATA_DIR = Path(__file__).parent.parent / "example_data"
118+
119+
# Map of crystal system number -> human-readable name
120+
CRYSTAL_SYSTEM_NAMES = {
121+
"1": "Triclinic",
122+
"2": "Monoclinic",
123+
"3": "Orthorhombic",
124+
"4": "Tetragonal",
125+
"5": "Trigonal",
126+
"6": "Hexagonal",
127+
"7": "Cubic",
128+
}
129+
130+
131+
def _parse_example_metadata(filepath: Path) -> dict:
132+
"""Extract metadata from the header lines of a .dif file."""
133+
meta = {
134+
"filename": filepath.name,
135+
"material_id": None,
136+
"crystal_system": None,
137+
"crystal_system_name": None,
138+
"space_group": None,
139+
"wavelength": None,
140+
}
141+
with open(filepath, "r") as f:
142+
for line in f:
143+
line = line.strip()
144+
# Stop reading once we hit the data section
145+
if line and not line.startswith("#") and not line.startswith("CELL") and not line.startswith("SPACE") and not line.lower().startswith("wavelength"):
146+
break
147+
148+
if m := re.search(r"Material ID:\s*(\S+)", line):
149+
meta["material_id"] = m.group(1)
150+
if m := re.search(r"Crystal System:\s*(\d+)", line):
151+
num = m.group(1)
152+
meta["crystal_system"] = num
153+
meta["crystal_system_name"] = CRYSTAL_SYSTEM_NAMES.get(num, f"Unknown ({num})")
154+
if m := re.search(r"SPACE GROUP:\s*(\d+)", line):
155+
meta["space_group"] = m.group(1)
156+
if m := re.search(r"wavelength:\s*([\d.]+)", line, re.IGNORECASE):
157+
meta["wavelength"] = m.group(1)
158+
return meta
159+
160+
161+
@app.get("/api/examples")
162+
async def list_examples():
163+
"""List available example data files with metadata."""
164+
if not EXAMPLE_DATA_DIR.exists():
165+
return []
166+
167+
examples = []
168+
for fp in sorted(EXAMPLE_DATA_DIR.glob("*.dif")):
169+
examples.append(_parse_example_metadata(fp))
170+
return examples
171+
172+
173+
@app.get("/api/examples/{filename}")
174+
async def get_example(filename: str):
175+
"""Return the raw text content of an example data file."""
176+
# Sanitise: only allow filenames, no path traversal
177+
if "/" in filename or "\\" in filename or ".." in filename:
178+
raise HTTPException(status_code=400, detail="Invalid filename")
179+
180+
filepath = EXAMPLE_DATA_DIR / filename
181+
if not filepath.exists() or not filepath.is_file():
182+
raise HTTPException(status_code=404, detail="Example file not found")
183+
184+
return PlainTextResponse(filepath.read_text())
185+
186+
187+
# ---------------------------------------------------------------------------
113188
# Static files and SPA support
189+
# ---------------------------------------------------------------------------
114190
frontend_dist = Path(__file__).parent.parent / "frontend" / "dist"
115191

116192
if frontend_dist.exists():

0 commit comments

Comments
 (0)