Spaces:
Sleeping
Sleeping
| import os | |
| import sys | |
| import gc | |
| import base64 | |
| import tempfile | |
| import traceback | |
| import io | |
| from flask import Flask, request, jsonify, send_file | |
| from flask_cors import CORS | |
| # Import the logic from the backend modules | |
| from centerline_extraction import compute_centerlines_network, load_nrrd_as_vtk_image | |
| from ostium_detection import extract_centerline_data | |
| from endograft_generation import build_endograft_stl_payload | |
| from maquette_generation import generate_fenestrated_maquette | |
| from endograft_selection import start_endograft_pipeline | |
| # Flask API server that wraps the VMTK centerline extraction, ostium detection, and endograft generation pipelines | |
| app = Flask(__name__) | |
| CORS(app) # Allow cross-origin requests from the Next.js frontend | |
| app.config["MAX_CONTENT_LENGTH"] = 200 * 1024 * 1024 # Maximum file upload size (200 MB) | |
| # Simple health check so the deployment platform knows the server is running | |
| def health(): | |
| return jsonify({"status": "ok"}) | |
| # Runs the full VMTK centerline extraction pipeline on NRRD segmentation and returns the result as JSON | |
| def extract(): | |
| # Check that the segmentation file sent to the centerline extraction endpoint exists and is an NRRD file | |
| if "file" not in request.files: | |
| return jsonify({"status": "error", "error": "No file provided"}), 400 | |
| uploaded = request.files["file"] | |
| file_name = uploaded.filename.lower() | |
| if not file_name.endswith(".nrrd"): | |
| return jsonify({"status": "error", "error": "Invalid File Type. Allowed: .nrrd"}), 400 | |
| # Save to a temp file so VMTK can read it from disk | |
| tmp = tempfile.NamedTemporaryFile(suffix=".nrrd", delete=False) | |
| try: | |
| uploaded.save(tmp.name) | |
| tmp.close() | |
| # Read the NRRD segmentation via SimpleITK (preserves full direction matrix) | |
| image, origin, direction = load_nrrd_as_vtk_image(tmp.name) | |
| # Voronoi centerline extraction | |
| centerlines, branch_junctions = compute_centerlines_network(image, origin, direction) | |
| # Extract labelled segments and artery bifurcation data | |
| data = extract_centerline_data(centerlines, branch_junctions=branch_junctions) | |
| # Free the large VTK pipeline objects immediately | |
| del centerlines, branch_junctions | |
| gc.collect() | |
| # Include the NRRD image's physical extent so the frontend can normalize the centerline | |
| dims = image.GetDimensions() | |
| spacing = image.GetSpacing() | |
| phys_size = [dims[i] * spacing[i] for i in range(3)] | |
| phys_center = [origin[i] + phys_size[i] / 2.0 for i in range(3)] | |
| data['volume_info'] = {'center': {'x': phys_center[0], 'y': phys_center[1], 'z': phys_center[2]}, 'size': {'x': phys_size[0], 'y': phys_size[1], 'z': phys_size[2]}} | |
| del image | |
| gc.collect() | |
| return jsonify({"status": "success", "data": data}) | |
| except TimeoutError as e: | |
| return jsonify({"status": "error", "error": str(e)}), 504 | |
| except Exception as e: | |
| traceback.print_exc() | |
| return jsonify({"status": "error", "error": str(e)}), 500 | |
| finally: | |
| # Always clean up the temp file | |
| try: | |
| os.unlink(tmp.name) | |
| except OSError: | |
| pass | |
| # Runs the endograft selection pipeline on computed bounds of aneurysm | |
| def select_graft(): | |
| body = request.get_json(silent=True) or {} | |
| proximal_diameter = body.get("proximal_diameter", 0) | |
| distal_diameter = body.get("distal_diameter", 0) | |
| proximal_start_point = body.get("proximal_start_point") | |
| distal_end_point = body.get("distal_end_point") | |
| length = body.get("length", 0) | |
| fenestrations_exist = body.get("fenestrations_exist", False) | |
| distance_to_last_fenestration = body.get("distance_to_last_fenestration", 0) | |
| try: | |
| selected_graft = start_endograft_pipeline( | |
| proximal_diameter, distal_diameter, proximal_start_point, distal_end_point, length, fenestrations_exist, distance_to_last_fenestration | |
| ) | |
| return jsonify({"status": "success", "data": selected_graft}) | |
| except Exception as e: | |
| traceback.print_exc() | |
| return jsonify({"status": "error", "error": str(e)}), 500 | |
| # Runs the endograft generation pipeline on VMTK centerline and returns the result as JSON | |
| def endograft(): | |
| body = request.get_json(silent=True) or {} | |
| centerline = body.get("centerline") | |
| params = body.get("params") or {} | |
| if not isinstance(centerline, list) or len(centerline) < 2: | |
| return jsonify({"status": "error", "error": "centerline must be a list of at least 2 points"}), 400 | |
| try: | |
| payload = build_endograft_stl_payload(centerline, include_combined=False, **params) | |
| result = {"endograft_stl_b64": base64.b64encode(payload["endograft"]).decode("utf-8"), "struts_stl_b64": base64.b64encode(payload["struts"]).decode("utf-8")} | |
| return jsonify({"status": "success", "data": result}) | |
| except Exception as e: | |
| traceback.print_exc() | |
| return jsonify({"status": "error", "error": str(e)}), 500 | |
| # Generates endograft STL maquette | |
| def maquette(): | |
| body = request.get_json(silent=True) or {} | |
| centerline = body.get("centerline") | |
| params = body.get("params") or {} | |
| fenestrations = body.get("fenestrations") or [] | |
| include_struts = body.get("include_struts", True) | |
| if not isinstance(centerline, list) or len(centerline) < 2: | |
| return jsonify({"status": "error", "error": "centerline must be a list of at least 2 points"}), 400 | |
| try: | |
| stl_bytes = generate_fenestrated_maquette(centerline, params, fenestrations, include_struts) | |
| result = {"maquette_stl_b64": base64.b64encode(stl_bytes).decode("utf-8")} | |
| return jsonify({"status": "success", "data": result}) | |
| except Exception as e: | |
| traceback.print_exc() | |
| return jsonify({"status": "error", "error": str(e)}), 500 | |
| if __name__ == "__main__": | |
| port = int(os.environ.get("PORT", 5050)) | |
| app.run(host="0.0.0.0", port=port) | |