From a795aa13bf41bb61e3c72fd93170aaf39e5d5500 Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Sun, 3 May 2026 01:33:18 +0800 Subject: [PATCH] zip deformation outputs on download --- WebSite/src/App.tsx | 16 ++--- web_backend.py | 146 +++++++++++++++++++++----------------------- 2 files changed, 78 insertions(+), 84 deletions(-) diff --git a/WebSite/src/App.tsx b/WebSite/src/App.tsx index ef8b60a..f015590 100644 --- a/WebSite/src/App.tsx +++ b/WebSite/src/App.tsx @@ -295,6 +295,9 @@ export default function App() { }; const fileUrl = (path?: string) => path ? `${API_BASE}/api/file?path=${encodeURIComponent(path)}` : ''; + const deformationDownloadUrl = (target: string) => deformationJob?.id + ? `${API_BASE}/api/deformation/download?job=${encodeURIComponent(deformationJob.id)}&target=${encodeURIComponent(target)}` + : ''; const clearDeformationTask = () => { setDeformationJob(null); @@ -829,10 +832,10 @@ export default function App() {
{deformationJob?.error &&

{deformationJob.error}

} - {deformationJob?.status === 'completed' && deformationJob.result?.zip?.path && ( + {deformationJob?.status === 'completed' && deformationJob.result?.outputs && ( 下载四状态 ZIP @@ -917,7 +920,6 @@ export default function App() { ].map(t => { const screenshotDir = deformationJob?.result?.previews?.screenshots; const imagePath = screenshotDir ? `${screenshotDir}/${t.key}.png` : ''; - const stateZip = deformationJob?.result?.stateZips?.[t.key]; return (
@@ -934,10 +936,10 @@ export default function App() {
)}
- {stateZip?.path && ( + {deformationJob?.status === 'completed' && deformationJob.result?.outputs?.[t.key] && (
下载本状态 DICOM ZIP diff --git a/web_backend.py b/web_backend.py index cb72b4a..c4dc841 100644 --- a/web_backend.py +++ b/web_backend.py @@ -430,40 +430,6 @@ def zip_folder(source_dir, zip_path): return zip_path -def zip_result_bundle(zip_path, state_zips, preview_paths): - zip_path = Path(zip_path).resolve() - safe_mkdir(zip_path.parent) - if zip_path.exists(): - zip_path.unlink() - - comparison_path = Path(preview_paths["comparison"]).resolve() - screenshots_dir = Path(preview_paths["screenshots"]).resolve() - - with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED) as archive: - for state_key, state_zip in state_zips.items(): - source_zip = Path(state_zip).resolve() - archive.write( - source_zip, - f"dicom_zips/{source_zip.name}", - compress_type=zipfile.ZIP_STORED, - ) - - if comparison_path.exists(): - archive.write( - comparison_path, - f"previews/{comparison_path.name}", - ) - - if screenshots_dir.exists(): - for file_path in screenshots_dir.rglob("*"): - if file_path.is_file(): - archive.write( - file_path, - Path("previews/process_screenshots") / file_path.relative_to(screenshots_dir), - ) - return zip_path - - def read_user_tasks(): tasks = read_json_file(USER_TASKS_META, {}) return tasks if isinstance(tasks, dict) else {} @@ -595,30 +561,51 @@ def make_preview(input_dir, angle_degrees): } -def zip_metadata(zip_path): - zip_path = Path(zip_path).resolve() - return { - "path": str(zip_path), - "name": zip_path.name, - "size": zip_path.stat().st_size, - } - - -def serialize_outputs(output_paths, preview_paths, zip_path=None, state_zips=None): +def serialize_outputs(output_paths, preview_paths): return { "outputs": {key: str(Path(value).resolve()) for key, value in output_paths.items()}, "previews": { "comparison": str(Path(preview_paths["comparison"]).resolve()), "screenshots": str(Path(preview_paths["screenshots"]).resolve()), }, - "zip": zip_metadata(zip_path) if zip_path else None, - "stateZips": { - key: zip_metadata(value) - for key, value in (state_zips or {}).items() - }, + "zip": None, + "stateZips": {}, } +def prepare_deformation_zip(job_id, target): + job = get_job(job_id) + if not job: + raise RuntimeError("任务不存在。") + if job.get("kind") != "deformation": + raise RuntimeError("该任务不是四状态生成任务。") + if job.get("status") != "completed": + raise RuntimeError("四状态任务尚未完成,无法下载。") + + result = job.get("result") or {} + outputs = result.get("outputs") or {} + job_root = RESULT_DIR / job_id + if target == "all": + output_dir = job_root / "four_state_output" + if not output_dir.exists(): + raise RuntimeError("四状态输出目录不存在。") + return zip_folder(output_dir, job_root / f"head_ct_morph_{job_id}.zip") + + state_labels = { + "original": "original", + "hard_boundary": "hard_boundary", + "gaussian_smooth": "gaussian_smooth", + "soft_transition": "soft_transition", + } + if target not in state_labels: + raise RuntimeError("未知的四状态下载类型。") + + source_dir = Path(outputs.get(target, "")) + if not source_dir.exists(): + raise RuntimeError("该状态的 DICOM 输出目录不存在。") + return zip_folder(source_dir, job_root / f"{target}_{job_id}.zip") + + class Handler(BaseHTTPRequestHandler): def log_message(self, format, *args): print("%s - %s" % (self.address_string(), format % args)) @@ -679,6 +666,17 @@ class Handler(BaseHTTPRequestHandler): self.send_json({"job": get_user_task_job(username, kind)}) return + if parsed.path == "/api/deformation/download": + params = parse_qs(parsed.query) + job_id = params.get("job", [""])[0] + target = params.get("target", ["all"])[0] + try: + zip_path = prepare_deformation_zip(job_id, target) + self.send_file(zip_path, "application/zip", as_attachment=True) + except Exception as exc: + self.send_json({"error": str(exc)}, status=400) + return + if parsed.path == "/api/file": params = parse_qs(parsed.query) file_path = Path(unquote(params.get("path", [""])[0])).resolve() @@ -692,15 +690,11 @@ class Handler(BaseHTTPRequestHandler): content_type = "video/mp4" elif file_path.suffix.lower() == ".zip": content_type = "application/zip" - data = file_path.read_bytes() - self.send_response(200) - self.send_cors_headers() - self.send_header("Content-Type", content_type) - if file_path.suffix.lower() == ".zip": - self.send_header("Content-Disposition", f'attachment; filename="{file_path.name}"') - self.send_header("Content-Length", str(len(data))) - self.end_headers() - self.wfile.write(data) + self.send_file( + file_path, + content_type, + as_attachment=file_path.suffix.lower() == ".zip", + ) return self.send_json({"error": "接口不存在。"}, status=404) @@ -743,25 +737,7 @@ class Handler(BaseHTTPRequestHandler): transition_width, progress, ) - set_job(job_id, message="正在打包各状态 DICOM ZIP...", progress=88) - state_zips = {} - for state_key in [ - "original", - "hard_boundary", - "gaussian_smooth", - "soft_transition", - ]: - state_zips[state_key] = zip_folder( - output_paths[state_key], - job_root / f"{state_key}_{job_id}.zip", - ) - set_job(job_id, message="正在整理四状态总 ZIP...", progress=96) - zip_path = zip_result_bundle( - job_root / f"head_ct_morph_{job_id}.zip", - state_zips, - preview_paths, - ) - return serialize_outputs(output_paths, preview_paths, zip_path, state_zips) + return serialize_outputs(output_paths, preview_paths) self.send_json( start_job( @@ -848,6 +824,22 @@ class Handler(BaseHTTPRequestHandler): return {} return json.loads(body.decode("utf-8")) + def send_file(self, file_path, content_type="application/octet-stream", as_attachment=False): + file_path = Path(file_path).resolve() + if not file_path.exists() or not file_path.is_file(): + self.send_json({"error": "文件不存在。"}, status=404) + return + + self.send_response(200) + self.send_cors_headers() + self.send_header("Content-Type", content_type) + if as_attachment: + self.send_header("Content-Disposition", f'attachment; filename="{file_path.name}"') + self.send_header("Content-Length", str(file_path.stat().st_size)) + self.end_headers() + with file_path.open("rb") as file_handle: + shutil.copyfileobj(file_handle, self.wfile) + def send_cors_headers(self): self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Methods", "GET,POST,OPTIONS")