from fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import List, Optional, Dict, Any from datetime import datetime import traceback import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from io import BytesIO import os import uvicorn import sys root_path = os.getcwd() sys.path.append(root_path) plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False app = FastAPI() # BRDF data item model class BRDFItem(BaseModel): sampleModel: str temperatureK: float wavelengthUm: str thetaIncident: float phiIncident: float thetaReflected: float phiReflected: float measurement1: float measurement2: float measurement3: float measurement4: float measurement5: float meanValue: float repeatabilityPct: float # Request payload model class RequestPayload(BaseModel): filePath: str brdfList: List[BRDFItem] # Response model class ResponseModel(BaseModel): status: int msg: str filePath: Optional[str] = None # Global exception handler @app.exception_handler(Exception) async def global_exception_handler(request, exc): print(f"Unhandled exception: {str(exc)}") print(traceback.format_exc()) return {"status": 500, "msg": "Failed"} def sph2cart(theta: float, phi: float): x = np.sin(theta) * np.cos(phi) y = np.sin(theta) * np.sin(phi) z = np.cos(theta) return x, y, z def plot_all_hemispheres(brdf_list: List[BRDFItem], save_path: str): os.makedirs(os.path.dirname(save_path), exist_ok=True) # Generate hemisphere surface phi = np.linspace(0, 2 * np.pi, 60) theta = np.linspace(0, np.pi / 2, 30) PHI, THETA = np.meshgrid(phi, theta) X = np.sin(THETA) * np.cos(PHI) Y = np.sin(THETA) * np.sin(PHI) Z = np.cos(THETA) # Create 3D plot fig = plt.figure(figsize=(10, 8)) ax = fig.add_subplot(111, projection='3d') ax.plot_surface(X, Y, Z, rstride=5, cstride=5, color='lightblue', alpha=0.1, edgecolor='k') # Draw vectors for each BRDF data item for i, item in enumerate(brdf_list): # Convert spherical to cartesian coordinates xi, yi, zi = sph2cart(np.deg2rad(item.thetaIncident), np.deg2rad(item.phiIncident)) xr, yr, zr = sph2cart(np.deg2rad(item.thetaReflected), np.deg2rad(item.phiReflected)) # Assign colors color_i = plt.cm.tab10(i % 10) # Incident light color color_r = plt.cm.tab10((i + 5) % 10) # Reflected light color # Incident light - points inward (toward center) ax.quiver(xi, yi, zi, -xi, -yi, -zi, color=color_i, linewidth=2, arrow_length_ratio=0.1) # Reflected light - points outward (away from center) ax.quiver(0, 0, 0, xr, yr, zr, color=color_r, linewidth=2, arrow_length_ratio=0.1) # Set labels and limits ax.set_xlim([-1, 1]) ax.set_ylim([-1, 1]) ax.set_zlim([0, 1]) ax.set_xlabel('sinθ·cosφ') ax.set_ylabel('sinθ·sinφ') ax.set_zlabel('Z') ax.set_title('BRDF扫描轨迹图') plt.tight_layout() plt.savefig(save_path, format='png', dpi=300) plt.close(fig) buf = BytesIO() plt.savefig(buf, format='png', dpi=300) buf.seek(0) return buf @app.post("/process_brdf", response_model=ResponseModel) async def process_brdf(payload: RequestPayload): """ Process BRDF data endpoint Parameters: - filePath: File save path - brdfList: List of BRDF data items Returns: - status: Status code - msg: Message - filePath: Saved file path """ try: if not payload.filePath: return {"status": 500, "msg": "File path is empty"} if not payload.brdfList: return {"status": 500, "msg": "BRDF list is empty"} # Plot all BRDF data buf = plot_all_hemispheres(payload.brdfList, payload.filePath) return { "status": 200, "msg": "Success", "filePath": payload.filePath } except Exception as e: print(f"Processing error: {str(e)}") print(traceback.format_exc()) return {"status": 500, "msg": "Failed"} # if __name__ == "__main__": # import uvicorn # # uvicorn.run(app, host="0.0.0.0", port=8081) # if __name__ == "__main__": # uvicorn.run("apiServer:app", host="0.0.0.0", port=8081, reload=False) if __name__ == "__main__": # 更可靠的获取模块名方式 name_app = os.path.splitext(os.path.basename(__file__))[0] # 更完整的日志配置 log_config = { "version": 1, "disable_existing_loggers": False, "formatters": { "default": { "()": "uvicorn.logging.DefaultFormatter", "fmt": "%(levelprefix)s %(asctime)s %(message)s", "datefmt": "%Y-%m-%d %H:%M:%S", } }, "handlers": { "default": { "formatter": "default", "class": "logging.StreamHandler", "stream": "ext://sys.stderr", }, "file_handler": { "class": "logging.FileHandler", "filename": "logfile.log", "formatter": "default", }, }, "root": { "handlers": ["default", "file_handler"], "level": "INFO", }, } uvicorn.run( app, host="0.0.0.0", port=8081, reload=False, log_config=log_config )