henavis/stippleart
0
1from fastapi import FastAPI, UploadFile, File, Form2from fastapi.responses import StreamingResponse, HTMLResponse, JSONResponse3from fastapi.staticfiles import StaticFiles4import io, cv2, numpy as np5 6from stipple_core import (7 run_stipple, export_svg_bytes, export_csv_bytes, export_gcode_bytes8)9 10app = FastAPI(title="Stipple Art Web (Exact Vector Preview)")11app.mount("/static", StaticFiles(directory="static"), name="static")12 13@app.get("/", response_class=HTMLResponse)14def home():15 return open("static/index.html", "r", encoding="utf-8").read()16 17def _read_bgr(upload: UploadFile):18 data = np.frombuffer(upload.file.read(), np.uint8)19 bgr = cv2.imdecode(data, cv2.IMREAD_COLOR)20 if bgr is None:21 raise ValueError("Bad image file")22 return bgr23 24@app.post("/process")25def process(26 image: UploadFile = File(...),27 dpi: int = Form(5),28 cell_size_mm: int = Form(3),29 max_dots: int = Form(60),30 sensitivity: float = Form(2.0),31 min_dia_mm: float = Form(0.2),32 max_dia_mm: float = Form(1.5),33 brightness: int = Form(0),34 contrast: float = Form(1.0),35 gamma_val: float = Form(1.0),36 blur_sigma: float = Form(0.0),37 sharpen_amt: float = Form(0.0),38 sharpen_sigma: float = Form(1.0),39 clahe_clip: float = Form(0.0),40 clahe_tile: int = Form(8),41 flip_y: bool = Form(False),42 fix_seed: bool = Form(True),43):44 bgr = _read_bgr(image)45 png_bytes, _ = run_stipple(46 bgr, dpi, cell_size_mm, max_dots, sensitivity,47 min_dia_mm, max_dia_mm, brightness, contrast, gamma_val,48 blur_sigma, sharpen_amt, sharpen_sigma, clahe_clip, clahe_tile,49 flip_y, fix_seed, preview_fast=False50 )51 return StreamingResponse(io.BytesIO(png_bytes), media_type="image/png")52 53# ✅ תצוגת ווקטור חדה בזמן אמת54@app.post("/preview/vector")55def preview_vector(56 image: UploadFile = File(...),57 dpi: int = Form(5),58 cell_size_mm: int = Form(3),59 max_dots: int = Form(60),60 sensitivity: float = Form(2.0),61 min_dia_mm: float = Form(0.2),62 max_dia_mm: float = Form(1.5),63 brightness: int = Form(0),64 contrast: float = Form(1.0),65 gamma_val: float = Form(1.0),66 blur_sigma: float = Form(0.0),67 sharpen_amt: float = Form(0.0),68 sharpen_sigma: float = Form(1.0),69 clahe_clip: float = Form(0.0),70 clahe_tile: int = Form(8),71 flip_y: bool = Form(False),72 fix_seed: bool = Form(True),73):74 data = np.frombuffer(image.file.read(), np.uint8)75 bgr = cv2.imdecode(data, cv2.IMREAD_COLOR)76 _, points = run_stipple(77 bgr, dpi, cell_size_mm, max_dots, sensitivity,78 min_dia_mm, max_dia_mm, brightness, contrast, gamma_val,79 blur_sigma, sharpen_amt, sharpen_sigma, clahe_clip, clahe_tile,80 flip_y, fix_seed, preview_fast=False81 )82 _, svg_bytes = export_svg_bytes(points)83 return StreamingResponse(io.BytesIO(svg_bytes), media_type="image/svg+xml")84 85@app.post("/export/{fmt}")86def export(87 fmt: str,88 image: UploadFile = File(...),89 dpi: int = Form(5),90 cell_size_mm: int = Form(3),91 max_dots: int = Form(60),92 sensitivity: float = Form(2.0),93 min_dia_mm: float = Form(0.2),94 max_dia_mm: float = Form(1.5),95 brightness: int = Form(0),96 contrast: float = Form(1.0),97 gamma_val: float = Form(1.0),98 blur_sigma: float = Form(0.0),99 sharpen_amt: float = Form(0.0),100 sharpen_sigma: float = Form(1.0),101 clahe_clip: float = Form(0.0),102 clahe_tile: int = Form(8),103 flip_y: bool = Form(False),104 fix_seed: bool = Form(True),105 servo_up: int = Form(30),106 servo_down: int = Form(90),107 dwell_up: int = Form(80),108 dwell_down: int = Form(120),109 feedrate: int = Form(3000),110):111 bgr = _read_bgr(image)112 _, points = run_stipple(113 bgr, dpi, cell_size_mm, max_dots, sensitivity,114 min_dia_mm, max_dia_mm, brightness, contrast, gamma_val,115 blur_sigma, sharpen_amt, sharpen_sigma, clahe_clip, clahe_tile,116 flip_y, fix_seed, preview_fast=False117 )118 if fmt == "svg":119 fname, data = export_svg_bytes(points)120 mt = "image/svg+xml"121 elif fmt == "csv":122 fname, data = export_csv_bytes(points)123 mt = "text/csv"124 elif fmt == "gcode":125 fname, data = export_gcode_bytes(points, servo_up, servo_down, dwell_up, dwell_down, feedrate)126 mt = "text/plain"127 else:128 return JSONResponse({"error": "format not supported"}, status_code=400)129 headers = {"Content-Disposition": f'attachment; filename="{fname}"'}130 return StreamingResponse(io.BytesIO(data), media_type=mt, headers=headers)131 