Spaces:
Sleeping
Sleeping
REVERT to K=5
Browse files
app.py
CHANGED
|
@@ -53,57 +53,8 @@ def depth_to_normal(depth):
|
|
| 53 |
# CORE PROCESSING FUNCTION
|
| 54 |
# ===============================
|
| 55 |
def _process_saree_core(base_image: Image.Image, pattern_image: Image.Image):
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
# --- ORIGINAL (white matte) kept for reference ---
|
| 60 |
-
# base_rgba = base_image.convert("RGBA")
|
| 61 |
-
# _arr = np.array(base_rgba).astype(np.float32)
|
| 62 |
-
# _rgb = _arr[..., :3]
|
| 63 |
-
# _a = (_arr[..., 3:4] / 255.0)
|
| 64 |
-
# _rgb_over_white = _rgb * _a + (1.0 - _a) * 255.0
|
| 65 |
-
# _rgb_over_white = _rgb_over_white.astype(np.uint8)
|
| 66 |
-
# img_pil = Image.fromarray(_rgb_over_white, mode="RGB")
|
| 67 |
-
# img_np = _rgb_over_white
|
| 68 |
-
# --- end ORIGINAL ---
|
| 69 |
-
|
| 70 |
-
# --- NEW: alpha-aware RGB using median color along interior boundary as matte ---
|
| 71 |
-
base_rgba = base_image.convert("RGBA")
|
| 72 |
-
_arr = np.array(base_rgba).astype(np.float32) # (H,W,4), RGB in 0..255, A in 0..255
|
| 73 |
-
_rgb = _arr[..., :3] # (H,W,3)
|
| 74 |
-
_alpha8 = _arr[..., 3].astype(np.uint8) # (H,W) uint8 alpha
|
| 75 |
-
_a = (_alpha8.astype(np.float32) / 255.0)[..., None] # (H,W,1) float alpha
|
| 76 |
-
|
| 77 |
-
# Build a foreground mask from alpha (slightly strict to avoid wispy edges)
|
| 78 |
-
_fg_mask = (_alpha8 > 128).astype(np.uint8) * 255 # (H,W) 0/255
|
| 79 |
-
|
| 80 |
-
# Morphological interior boundary: foreground minus a 1-iteration erosion
|
| 81 |
-
_k = np.ones((3, 3), np.uint8)
|
| 82 |
-
_eroded = cv2.erode(_fg_mask, _k, iterations=1)
|
| 83 |
-
_boundary = cv2.bitwise_and(_fg_mask, cv2.bitwise_not(_eroded)) # thin interior ring
|
| 84 |
-
|
| 85 |
-
# If boundary is too thin/few pixels, widen the ring via morphological gradient
|
| 86 |
-
if int((_boundary > 0).sum()) < 100:
|
| 87 |
-
_dil = cv2.dilate(_fg_mask, _k, iterations=2)
|
| 88 |
-
_ero = cv2.erode(_fg_mask, _k, iterations=2)
|
| 89 |
-
_boundary = cv2.subtract(_dil, _ero)
|
| 90 |
-
|
| 91 |
-
_idx = (_boundary > 0)
|
| 92 |
-
if not np.any(_idx):
|
| 93 |
-
# Fallback: use entire foreground if boundary not found
|
| 94 |
-
_idx = (_fg_mask > 0)
|
| 95 |
-
|
| 96 |
-
# Compute median color over the selected boundary pixels (in 0..255 space)
|
| 97 |
-
_median_color = np.median(_rgb[_idx], axis=0) if np.any(_idx) else np.array([255.0, 255.0, 255.0], dtype=np.float32)
|
| 98 |
-
_median_color = _median_color.reshape(1, 1, 3) # (1,1,3)
|
| 99 |
-
|
| 100 |
-
# Composite RGB over median matte (avoid introducing black/white bias)
|
| 101 |
-
_rgb_over_matte = _rgb * _a + (1.0 - _a) * _median_color
|
| 102 |
-
_rgb_over_matte = np.clip(_rgb_over_matte, 0.0, 255.0).astype(np.uint8)
|
| 103 |
-
|
| 104 |
-
img_pil = Image.fromarray(_rgb_over_matte, mode="RGB")
|
| 105 |
-
img_np = _rgb_over_matte
|
| 106 |
-
# --- end NEW ---
|
| 107 |
|
| 108 |
# Prepare tensor
|
| 109 |
img_resized = img_pil.resize((384, 384))
|
|
@@ -135,107 +86,28 @@ def _process_saree_core(base_image: Image.Image, pattern_image: Image.Image):
|
|
| 135 |
l_clahe = clahe.apply(l_channel)
|
| 136 |
shading_map = l_clahe / 255.0
|
| 137 |
|
| 138 |
-
#
|
| 139 |
-
|
| 140 |
-
# Replace the block from "# Tile pattern" through
|
| 141 |
-
# "pattern_folded = np.clip(pattern_folded, 0, 1)"
|
| 142 |
-
# and keep everything below (BG removal etc.) unchanged.
|
| 143 |
-
# ==========================================================
|
| 144 |
-
|
| 145 |
-
# ----- ORIGINAL (hard tiling) — kept for reference -----
|
| 146 |
-
# # Tile pattern
|
| 147 |
-
# pattern_np = np.array(pattern_image.convert("RGB"))
|
| 148 |
-
# target_h, target_w = img_np.shape[:2]
|
| 149 |
-
# pattern_h, pattern_w = pattern_np.shape[:2]
|
| 150 |
-
# pattern_tiled = np.zeros((target_h, target_w, 3), dtype=np.uint8)
|
| 151 |
-
# for y in range(0, target_h, pattern_h):
|
| 152 |
-
# for x in range(0, target_w, pattern_w):
|
| 153 |
-
# end_y = min(y + pattern_h, target_h)
|
| 154 |
-
# end_x = min(x + pattern_w, target_w)
|
| 155 |
-
# pattern_tiled[y:end_y, x:end_x] = pattern_np[0:(end_y - y), 0:(end_x - x)]
|
| 156 |
-
#
|
| 157 |
-
# # Blend pattern
|
| 158 |
-
# normal_map_loaded = normal_map.astype(np.float32)
|
| 159 |
-
# shading_map_loaded = np.stack([shading_map] * 3, axis=-1)
|
| 160 |
-
# alpha = 0.7
|
| 161 |
-
# blended_shading = alpha * shading_map_loaded + (1 - alpha)
|
| 162 |
-
# pattern_folded = pattern_tiled.astype(np.float32) / 255.0 * blended_shading
|
| 163 |
-
# normal_boost = 0.5 + 0.5 * normal_map_loaded[..., 2:3]
|
| 164 |
-
# pattern_folded *= normal_boost
|
| 165 |
-
# pattern_folded = np.clip(pattern_folded, 0, 1)
|
| 166 |
-
# ----- END ORIGINAL -----
|
| 167 |
-
|
| 168 |
-
# ---------- NEW: seam‑free overlapped tiling ----------
|
| 169 |
-
eps = 1e-6
|
| 170 |
-
|
| 171 |
-
# 1) Clean the tile (unpremultiply RGBA to remove black fringe; optional 2px crop)
|
| 172 |
-
_tile_rgba = np.array(pattern_image.convert("RGBA")).astype(np.float32) / 255.0
|
| 173 |
-
_tile_rgb = _tile_rgba[..., :3]
|
| 174 |
-
_tile_a = _tile_rgba[..., 3:4]
|
| 175 |
-
_tile_rgb = np.where(_tile_a > eps, _tile_rgb / np.clip(_tile_a, eps, 1.0), _tile_rgb)
|
| 176 |
-
|
| 177 |
-
_crop = 2 # try 2–3 if fringe persists
|
| 178 |
-
if _tile_rgb.shape[0] > 2*_crop and _tile_rgb.shape[1] > 2*_crop:
|
| 179 |
-
_tile_rgb = _tile_rgb[_crop:-_crop, _crop:-_crop, :]
|
| 180 |
-
|
| 181 |
-
pattern_np = (np.clip(_tile_rgb, 0.0, 1.0) * 255).astype(np.uint8)
|
| 182 |
-
|
| 183 |
-
# 2) Overlapped tiling with cosine crossfade
|
| 184 |
target_h, target_w = img_np.shape[:2]
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
ramp = 0.5 - 0.5 * np.cos(t)
|
| 196 |
-
wy[:ovh] = np.minimum(wy[:ovh], ramp)
|
| 197 |
-
wy[-ovh:] = np.minimum(wy[-ovh:], ramp[::-1])
|
| 198 |
-
if ovw > 0:
|
| 199 |
-
t = np.linspace(0, np.pi, ovw, endpoint=False)
|
| 200 |
-
ramp = 0.5 - 0.5 * np.cos(t)
|
| 201 |
-
wx[:ovw] = np.minimum(wx[:ovw], ramp)
|
| 202 |
-
wx[-ovw:] = np.minimum(wx[-ovw:], ramp[::-1])
|
| 203 |
-
return np.outer(wy, wx).astype(np.float32)
|
| 204 |
-
|
| 205 |
-
acc = np.zeros((target_h, target_w, 3), dtype=np.float32)
|
| 206 |
-
wacc = np.zeros((target_h, target_w, 1), dtype=np.float32)
|
| 207 |
-
|
| 208 |
-
stride_y = max(1, th - ov)
|
| 209 |
-
stride_x = max(1, tw - ov)
|
| 210 |
-
|
| 211 |
-
for y in range(0, target_h, stride_y):
|
| 212 |
-
for x in range(0, target_w, stride_x):
|
| 213 |
-
h_crop = min(th, target_h - y)
|
| 214 |
-
w_crop = min(tw, target_w - x)
|
| 215 |
-
tile = pattern_np[:h_crop, :w_crop, :].astype(np.float32) / 255.0
|
| 216 |
-
wmask = _make_weight(h_crop, w_crop, ov)[..., None] # (h,w,1)
|
| 217 |
-
|
| 218 |
-
acc[y:y+h_crop, x:x+w_crop] += tile * wmask
|
| 219 |
-
wacc[y:y+h_crop, x:x+w_crop] += wmask
|
| 220 |
-
|
| 221 |
-
pattern_tiled = acc / np.clip(wacc, eps, None)
|
| 222 |
-
pattern_tiled = np.clip(pattern_tiled, 0.0, 1.0) # float32 in [0,1]
|
| 223 |
-
|
| 224 |
-
# 3) Continue with your shading/normal blending (unchanged)
|
| 225 |
-
normal_map_loaded = normal_map.astype(np.float32)
|
| 226 |
shading_map_loaded = np.stack([shading_map] * 3, axis=-1)
|
| 227 |
-
alpha = 0.7
|
| 228 |
-
blended_shading = alpha * shading_map_loaded + (1 - alpha)
|
| 229 |
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
pattern_folded *= normal_boost
|
| 233 |
-
pattern_folded = np.clip(pattern_folded, 0.0, 1.0)
|
| 234 |
-
|
| 235 |
-
# ==========================================================
|
| 236 |
-
# [END PATCHED]
|
| 237 |
-
# ==========================================================
|
| 238 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
|
| 240 |
# ==========================================================
|
| 241 |
# Background removal with post-processing (no duplicate blur)
|
|
@@ -257,107 +129,22 @@ def _process_saree_core(base_image: Image.Image, pattern_image: Image.Image):
|
|
| 257 |
mask_binary = (mask_alpha > k/100).astype(np.uint8) * 255 # slightly stricter threshold
|
| 258 |
mask_eroded = cv2.erode(mask_binary, kernel, iterations=3) # balanced erosion
|
| 259 |
|
|
|
|
| 260 |
# 2. Feather edges (blur)
|
| 261 |
mask_blurred = cv2.GaussianBlur(mask_eroded, (15, 15), sigmaX=3, sigmaY=3)
|
| 262 |
|
| 263 |
# 3. Normalize
|
| 264 |
mask_blurred = mask_blurred.astype(np.float32) / 255.0
|
| 265 |
|
| 266 |
-
# ================================
|
| 267 |
-
# NEW: SEAM-FIX UPSTREAM (3 steps)
|
| 268 |
-
# ================================
|
| 269 |
-
# (A) MASK EXPANSION / OVERLAP: expand slightly to ensure overlap across seams
|
| 270 |
-
overlap_iters = 2 # <-- tune: 1..3 (px-ish with 5x5 kernel)
|
| 271 |
-
# mask_expanded = cv2.dilate(mask_eroded, kernel, iterations=overlap_iters) # old idea
|
| 272 |
-
# --- Better: expand the FLOAT feathered mask to preserve soft edge continuity ---
|
| 273 |
-
_mask_float = (mask_blurred * 255).astype(np.uint8)
|
| 274 |
-
_mask_expanded_u8 = cv2.dilate(_mask_float, kernel, iterations=overlap_iters)
|
| 275 |
-
mask_expanded = _mask_expanded_u8.astype(np.float32) / 255.0 # [0..1]
|
| 276 |
-
|
| 277 |
-
# (B) FEATHER AGAIN after expansion (very light) for a smooth transition band
|
| 278 |
-
mask_expanded = cv2.GaussianBlur((mask_expanded * 255).astype(np.uint8), (7, 7), sigmaX=1.5, sigmaY=1.5)
|
| 279 |
-
mask_expanded = mask_expanded.astype(np.float32) / 255.0
|
| 280 |
-
|
| 281 |
-
# (C) BLEED-COLOR FILLING in premultiplied space for near-edge pixels
|
| 282 |
-
# - Create a thin edge band where alpha is small (e.g., up to 8%)
|
| 283 |
-
edge_upper = 0.08
|
| 284 |
# Final RGBA
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
pattern_final = pattern_folded * mask_stack # premultiplied RGB (color * alpha) with overlap
|
| 290 |
-
|
| 291 |
-
# - Dilate premultiplied RGB slightly so edge pixels borrow nearby garment color
|
| 292 |
-
bleed_iters = 2 # <-- tune: 1..3
|
| 293 |
-
_kernel_bleed = np.ones((3, 3), np.uint8)
|
| 294 |
-
_premul_u8 = (pattern_final * 255).astype(np.uint8)
|
| 295 |
-
_premul_bleed = cv2.dilate(_premul_u8, _kernel_bleed, iterations=bleed_iters).astype(np.float32) / 255.0
|
| 296 |
-
|
| 297 |
-
# - Replace only in the very thin edge band (alpha between 0 and edge_upper)
|
| 298 |
-
edge_band = (mask_expanded > 0.0) & (mask_expanded <= edge_upper)
|
| 299 |
-
if np.any(edge_band):
|
| 300 |
-
pattern_final[edge_band] = _premul_bleed[edge_band]
|
| 301 |
-
# ================================
|
| 302 |
-
# END NEW: SEAM-FIX UPSTREAM
|
| 303 |
-
# ================================
|
| 304 |
-
|
| 305 |
-
# ================================
|
| 306 |
-
# POST-PROCESS: SEAM / BLACK-LINE FILL
|
| 307 |
-
# (place this right before: "Premultiplied → Straight alpha")
|
| 308 |
-
# ================================
|
| 309 |
-
|
| 310 |
-
# Build a temporary straight-RGB for detection (do NOT export this)
|
| 311 |
-
_alpha_tmp = np.clip(mask_expanded, eps, 1.0) # [H,W]
|
| 312 |
-
_alpha_tmp3 = _alpha_tmp[..., None] # [H,W,1]
|
| 313 |
-
straight_tmp = np.clip(pattern_final / _alpha_tmp3, 0.0, 1.0) # [H,W,3], float
|
| 314 |
-
|
| 315 |
-
# Convert to 8-bit for OpenCV ops
|
| 316 |
-
straight_u8 = (straight_tmp * 255).astype(np.uint8)
|
| 317 |
-
gray8 = cv2.cvtColor(straight_u8, cv2.COLOR_RGB2GRAY)
|
| 318 |
-
|
| 319 |
-
# Local median to find narrow dark troughs (seams)
|
| 320 |
-
med8 = cv2.medianBlur(gray8, 5)
|
| 321 |
-
|
| 322 |
-
# Heuristics: dark + locally darker than neighbors + inside garment
|
| 323 |
-
dark_thr = int(0.22 * 255) # tune: 0.18..0.28
|
| 324 |
-
delta_thr = int(0.08 * 255) # tune: 0.06..0.10
|
| 325 |
-
dark = gray8 < dark_thr
|
| 326 |
-
contrast = (med8.astype(np.int16) - gray8.astype(np.int16)) > delta_thr
|
| 327 |
-
inside = (mask_expanded > 0.06) # avoid true background
|
| 328 |
-
|
| 329 |
-
seam_mask = (dark & contrast & inside).astype(np.uint8) * 255
|
| 330 |
-
|
| 331 |
-
# Keep seams thin (remove blobs); nudge toward single‑pixel cores
|
| 332 |
-
k3 = np.ones((3, 3), np.uint8)
|
| 333 |
-
seam_mask = cv2.morphologyEx(seam_mask, cv2.MORPH_OPEN, k3, iterations=1)
|
| 334 |
-
seam_mask = cv2.erode(seam_mask, np.ones((2, 2), np.uint8), iterations=1)
|
| 335 |
-
|
| 336 |
-
# Inpaint on straight space (Telea = good for thin scratches)
|
| 337 |
-
inpaint_radius = 3 # try 2..4
|
| 338 |
-
inpainted_u8 = cv2.inpaint(straight_u8, seam_mask, inpaint_radius, cv2.INPAINT_TELEA)
|
| 339 |
-
inpainted = inpainted_u8.astype(np.float32) / 255.0
|
| 340 |
-
|
| 341 |
-
# Re-premultiply and replace
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
# Premultiplied → Straight alpha
|
| 345 |
-
eps = 1e-6
|
| 346 |
-
# _alpha = np.clip(mask_blurred, eps, 1.0)
|
| 347 |
-
# --- Use the expanded mask for export, with a small alpha floor to hide hairlines ---
|
| 348 |
-
alpha_floor = 0.02 # 2% floor; increase to 0.03 if a faint line persists
|
| 349 |
-
_alpha = np.clip(mask_expanded, max(alpha_floor, eps), 1.0)
|
| 350 |
-
_alpha3 = _alpha[..., None]
|
| 351 |
-
rgb_straight = np.clip(pattern_final / _alpha3, 0.0, 1.0)
|
| 352 |
-
|
| 353 |
-
pattern_rgb = (rgb_straight * 255).astype(np.uint8)
|
| 354 |
-
alpha_channel = (_alpha * 255).astype(np.uint8)
|
| 355 |
pattern_rgba = np.dstack((pattern_rgb, alpha_channel))
|
| 356 |
|
| 357 |
return Image.fromarray(pattern_rgba, mode="RGBA")
|
| 358 |
|
| 359 |
-
|
| 360 |
-
|
| 361 |
# ===============================
|
| 362 |
# WRAPPER: ACCEPT BYTES OR BASE64
|
| 363 |
# ===============================
|
|
|
|
| 53 |
# CORE PROCESSING FUNCTION
|
| 54 |
# ===============================
|
| 55 |
def _process_saree_core(base_image: Image.Image, pattern_image: Image.Image):
|
| 56 |
+
img_pil = base_image.convert("RGB")
|
| 57 |
+
img_np = np.array(img_pil)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
# Prepare tensor
|
| 60 |
img_resized = img_pil.resize((384, 384))
|
|
|
|
| 86 |
l_clahe = clahe.apply(l_channel)
|
| 87 |
shading_map = l_clahe / 255.0
|
| 88 |
|
| 89 |
+
# Tile pattern
|
| 90 |
+
pattern_np = np.array(pattern_image.convert("RGB"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
target_h, target_w = img_np.shape[:2]
|
| 92 |
+
pattern_h, pattern_w = pattern_np.shape[:2]
|
| 93 |
+
pattern_tiled = np.zeros((target_h, target_w, 3), dtype=np.uint8)
|
| 94 |
+
for y in range(0, target_h, pattern_h):
|
| 95 |
+
for x in range(0, target_w, pattern_w):
|
| 96 |
+
end_y = min(y + pattern_h, target_h)
|
| 97 |
+
end_x = min(x + pattern_w, target_w)
|
| 98 |
+
pattern_tiled[y:end_y, x:end_x] = pattern_np[0:(end_y - y), 0:(end_x - x)]
|
| 99 |
+
|
| 100 |
+
# Blend pattern
|
| 101 |
+
normal_map_loaded = normal_map.astype(np.float32)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
shading_map_loaded = np.stack([shading_map] * 3, axis=-1)
|
|
|
|
|
|
|
| 103 |
|
| 104 |
+
alpha = 0.7
|
| 105 |
+
blended_shading = alpha * shading_map_loaded + (1 - alpha)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
|
| 107 |
+
pattern_folded = pattern_tiled.astype(np.float32) / 255.0 * blended_shading
|
| 108 |
+
normal_boost = 0.5 + 0.5 * normal_map_loaded[..., 2:3]
|
| 109 |
+
pattern_folded *= normal_boost
|
| 110 |
+
pattern_folded = np.clip(pattern_folded, 0, 1)
|
| 111 |
|
| 112 |
# ==========================================================
|
| 113 |
# Background removal with post-processing (no duplicate blur)
|
|
|
|
| 129 |
mask_binary = (mask_alpha > k/100).astype(np.uint8) * 255 # slightly stricter threshold
|
| 130 |
mask_eroded = cv2.erode(mask_binary, kernel, iterations=3) # balanced erosion
|
| 131 |
|
| 132 |
+
|
| 133 |
# 2. Feather edges (blur)
|
| 134 |
mask_blurred = cv2.GaussianBlur(mask_eroded, (15, 15), sigmaX=3, sigmaY=3)
|
| 135 |
|
| 136 |
# 3. Normalize
|
| 137 |
mask_blurred = mask_blurred.astype(np.float32) / 255.0
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
# Final RGBA
|
| 140 |
+
mask_stack = np.stack([mask_blurred] * 3, axis=-1)
|
| 141 |
+
pattern_final = pattern_folded * mask_stack
|
| 142 |
+
pattern_rgb = (pattern_final * 255).astype(np.uint8)
|
| 143 |
+
alpha_channel = (mask_blurred * 255).astype(np.uint8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
pattern_rgba = np.dstack((pattern_rgb, alpha_channel))
|
| 145 |
|
| 146 |
return Image.fromarray(pattern_rgba, mode="RGBA")
|
| 147 |
|
|
|
|
|
|
|
| 148 |
# ===============================
|
| 149 |
# WRAPPER: ACCEPT BYTES OR BASE64
|
| 150 |
# ===============================
|