"""Vectorization pipeline — converts preprocessed binary images to SVG.""" import numpy as np import potrace def potrace_trace( binary_img: np.ndarray, turdsize: int = 2, alphamax: float = 1.0, opticurve: bool = True, opttolerance: float = 0.2, ) -> str: """Trace a binary image using Potrace and return an SVG string. Args: binary_img: 2D numpy array — nonzero pixels are foreground. turdsize: Despeckle threshold; curves with enclosed area below this are removed. alphamax: Corner detection threshold (0.0 = polygon, 1.3333 = no corners). opticurve: Whether to optimize curves by reducing Bezier segments. opttolerance: Tolerance for curve optimization. Returns: Well-formed SVG string. """ if binary_img.ndim != 2: raise ValueError(f"Expected 2D binary image, got shape {binary_img.shape}") h, w = binary_img.shape # Potrace interprets nonzero pixels as foreground. # Convert to uint32 — pypotrace needs values that fit in a C int. data = (binary_img > 0).astype(np.uint32) bmp = potrace.Bitmap(data) path = bmp.trace( turdsize=turdsize, alphamax=alphamax, opticurve=int(opticurve), opttolerance=opttolerance, ) return _path_to_svg(path, w, h) def _path_to_svg(path, width: int, height: int) -> str: """Convert a potrace Path object to an SVG string.""" parts = [] for curve in path: sx, sy = curve.start_point parts.append(f"M {sx:.3f},{sy:.3f}") for segment in curve.segments: if segment.is_corner: cx, cy = segment.c ex, ey = segment.end_point parts.append(f"L {cx:.3f},{cy:.3f} L {ex:.3f},{ey:.3f}") else: c1x, c1y = segment.c1 c2x, c2y = segment.c2 ex, ey = segment.end_point parts.append( f"C {c1x:.3f},{c1y:.3f} {c2x:.3f},{c2y:.3f} {ex:.3f},{ey:.3f}" ) parts.append("Z") d = " ".join(parts) return ( f'' f'' f"" )