Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -397,8 +397,39 @@ def colorize_mask(mask: np.ndarray, num_colors: int = 512) -> np.ndarray:
|
|
| 397 |
return palette_arr[color_idx]
|
| 398 |
|
| 399 |
# ===== 分割功能 =====
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
def segment_with_choice(use_box_choice, annot_value, mode="Overlay"):
|
| 401 |
-
"""分割
|
| 402 |
if annot_value is None or len(annot_value) < 1:
|
| 403 |
print("❌ No annotation input")
|
| 404 |
return None, "⚠️ 请先上传图像"
|
|
@@ -407,7 +438,6 @@ def segment_with_choice(use_box_choice, annot_value, mode="Overlay"):
|
|
| 407 |
bboxes = annot_value[1] if len(annot_value) > 1 else []
|
| 408 |
|
| 409 |
print(f"🖼️ Image path: {img_path}")
|
| 410 |
-
|
| 411 |
box_array = None
|
| 412 |
if use_box_choice == "Yes" and bboxes:
|
| 413 |
box = parse_first_bbox(bboxes)
|
|
@@ -417,30 +447,25 @@ def segment_with_choice(use_box_choice, annot_value, mode="Overlay"):
|
|
| 417 |
print(f"📦 Using box: {box_array}")
|
| 418 |
|
| 419 |
try:
|
| 420 |
-
#
|
| 421 |
mask = run_seg(SEG_MODEL, img_path, box=box_array, device=SEG_DEVICE)
|
| 422 |
print("📏 Mask shape:", mask.shape, "dtype:", mask.dtype, "unique:", np.unique(mask))
|
| 423 |
except Exception as e:
|
| 424 |
print(f"❌ Error during inference: {e}")
|
| 425 |
return None, f"分割失败: {str(e)}"
|
| 426 |
|
|
|
|
| 427 |
try:
|
| 428 |
-
img = Image.open(img_path)
|
| 429 |
-
|
| 430 |
-
except Exception as e:
|
| 431 |
-
print(f"❌ Failed to open image: {e}")
|
| 432 |
-
return None, f"图像打开失败: {str(e)}"
|
| 433 |
-
|
| 434 |
-
try:
|
| 435 |
-
img_rgb = img.convert("RGB").resize(mask.shape[::-1], resample=Image.BILINEAR)
|
| 436 |
img_np = np.array(img_rgb, dtype=np.float32)
|
| 437 |
if img_np.max() > 1.5:
|
| 438 |
img_np = img_np / 255.0
|
| 439 |
except Exception as e:
|
| 440 |
-
print(f"❌
|
| 441 |
-
return None, f"图像
|
| 442 |
|
| 443 |
-
#
|
| 444 |
mask_np = np.array(mask)
|
| 445 |
inst_mask = mask_np.astype(np.int32)
|
| 446 |
unique_ids = np.unique(inst_mask)
|
|
@@ -451,31 +476,36 @@ def segment_with_choice(use_box_choice, annot_value, mode="Overlay"):
|
|
| 451 |
print("⚠️ No instance found, returning dummy red image")
|
| 452 |
return Image.new("RGB", mask.shape[::-1], (255, 0, 0)), "⚠️ 未检测到细胞"
|
| 453 |
|
| 454 |
-
# ==== 🎨
|
| 455 |
overlay = img_np.copy()
|
| 456 |
-
alpha = 0.
|
| 457 |
-
cmap = cm.get_cmap("
|
| 458 |
|
| 459 |
for inst_id in np.unique(inst_mask):
|
| 460 |
if inst_id == 0:
|
| 461 |
continue
|
| 462 |
binary_mask = (inst_mask == inst_id).astype(np.uint8)
|
| 463 |
-
color = np.array(cmap(inst_id / (num_instances + 1))[:3])
|
| 464 |
overlay[binary_mask == 1] = (1 - alpha) * overlay[binary_mask == 1] + alpha * color
|
| 465 |
|
| 466 |
-
# 🟡 绘制轮廓
|
| 467 |
contours = measure.find_contours(binary_mask, 0.5)
|
| 468 |
for contour in contours:
|
| 469 |
contour = contour.astype(np.int32)
|
| 470 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 471 |
|
| 472 |
overlay = np.clip(overlay * 255.0, 0, 255).astype(np.uint8)
|
|
|
|
| 473 |
|
| 474 |
-
# 🔹 两种显示模式
|
| 475 |
if mode == "Instance Mask Only":
|
| 476 |
-
|
|
|
|
| 477 |
|
| 478 |
-
return Image.fromarray(overlay),
|
| 479 |
|
| 480 |
# ===== 计数功能 =====
|
| 481 |
def count_cells_handler(image_path):
|
|
|
|
| 397 |
return palette_arr[color_idx]
|
| 398 |
|
| 399 |
# ===== 分割功能 =====
|
| 400 |
+
# ===== 彩色 mask 可视化 =====
|
| 401 |
+
def colorize_mask(mask: np.ndarray, num_colors: int = 512) -> np.ndarray:
|
| 402 |
+
"""将实例掩码转换为彩色图像,每个实例一个颜色"""
|
| 403 |
+
mask = mask.astype(np.int32)
|
| 404 |
+
|
| 405 |
+
def hsv_to_rgb(hh, ss, vv):
|
| 406 |
+
i = int(hh * 6.0)
|
| 407 |
+
f = hh * 6.0 - i
|
| 408 |
+
p = vv * (1.0 - ss)
|
| 409 |
+
q = vv * (1.0 - f * ss)
|
| 410 |
+
t = vv * (1.0 - (1.0 - f) * ss)
|
| 411 |
+
i = i % 6
|
| 412 |
+
if i == 0: r, g, b = vv, t, p
|
| 413 |
+
elif i == 1: r, g, b = q, vv, p
|
| 414 |
+
elif i == 2: r, g, b = p, vv, t
|
| 415 |
+
elif i == 3: r, g, b = p, q, vv
|
| 416 |
+
elif i == 4: r, g, b = t, p, vv
|
| 417 |
+
else: r, g, b = vv, p, q
|
| 418 |
+
return int(r*255), int(g*255), int(b*255)
|
| 419 |
+
|
| 420 |
+
palette = [(0, 0, 0)]
|
| 421 |
+
for k in range(1, num_colors):
|
| 422 |
+
hue = (k % num_colors) / float(num_colors)
|
| 423 |
+
palette.append(hsv_to_rgb(hue, 1.0, 0.95))
|
| 424 |
+
|
| 425 |
+
color_idx = mask % num_colors
|
| 426 |
+
palette_arr = np.array(palette, dtype=np.uint8)
|
| 427 |
+
return palette_arr[color_idx]
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
# ===== 推理 + 实例彩色可视化 =====
|
| 431 |
def segment_with_choice(use_box_choice, annot_value, mode="Overlay"):
|
| 432 |
+
"""分割推理 + 可视化,与原始逻辑完全对齐"""
|
| 433 |
if annot_value is None or len(annot_value) < 1:
|
| 434 |
print("❌ No annotation input")
|
| 435 |
return None, "⚠️ 请先上传图像"
|
|
|
|
| 438 |
bboxes = annot_value[1] if len(annot_value) > 1 else []
|
| 439 |
|
| 440 |
print(f"🖼️ Image path: {img_path}")
|
|
|
|
| 441 |
box_array = None
|
| 442 |
if use_box_choice == "Yes" and bboxes:
|
| 443 |
box = parse_first_bbox(bboxes)
|
|
|
|
| 447 |
print(f"📦 Using box: {box_array}")
|
| 448 |
|
| 449 |
try:
|
| 450 |
+
# 调用分割模型
|
| 451 |
mask = run_seg(SEG_MODEL, img_path, box=box_array, device=SEG_DEVICE)
|
| 452 |
print("📏 Mask shape:", mask.shape, "dtype:", mask.dtype, "unique:", np.unique(mask))
|
| 453 |
except Exception as e:
|
| 454 |
print(f"❌ Error during inference: {e}")
|
| 455 |
return None, f"分割失败: {str(e)}"
|
| 456 |
|
| 457 |
+
# 读取原图
|
| 458 |
try:
|
| 459 |
+
img = Image.open(img_path).convert("RGB")
|
| 460 |
+
img_rgb = img.resize(mask.shape[::-1], resample=Image.BILINEAR)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 461 |
img_np = np.array(img_rgb, dtype=np.float32)
|
| 462 |
if img_np.max() > 1.5:
|
| 463 |
img_np = img_np / 255.0
|
| 464 |
except Exception as e:
|
| 465 |
+
print(f"❌ Failed to open/convert image: {e}")
|
| 466 |
+
return None, f"图像读取失败: {str(e)}"
|
| 467 |
|
| 468 |
+
# 生成实例掩码
|
| 469 |
mask_np = np.array(mask)
|
| 470 |
inst_mask = mask_np.astype(np.int32)
|
| 471 |
unique_ids = np.unique(inst_mask)
|
|
|
|
| 476 |
print("⚠️ No instance found, returning dummy red image")
|
| 477 |
return Image.new("RGB", mask.shape[::-1], (255, 0, 0)), "⚠️ 未检测到细胞"
|
| 478 |
|
| 479 |
+
# ==== 🎨 Overlay (每个实例一个颜色 + 黄色轮廓) ====
|
| 480 |
overlay = img_np.copy()
|
| 481 |
+
alpha = 0.75 # 增强透明度,更清晰
|
| 482 |
+
cmap = cm.get_cmap("tab20", num_instances + 1) # 亮色系更清楚
|
| 483 |
|
| 484 |
for inst_id in np.unique(inst_mask):
|
| 485 |
if inst_id == 0:
|
| 486 |
continue
|
| 487 |
binary_mask = (inst_mask == inst_id).astype(np.uint8)
|
| 488 |
+
color = np.array(cmap(inst_id / (num_instances + 1))[:3])
|
| 489 |
overlay[binary_mask == 1] = (1 - alpha) * overlay[binary_mask == 1] + alpha * color
|
| 490 |
|
| 491 |
+
# 🟡 绘制粗黄色轮廓
|
| 492 |
contours = measure.find_contours(binary_mask, 0.5)
|
| 493 |
for contour in contours:
|
| 494 |
contour = contour.astype(np.int32)
|
| 495 |
+
for dy in [-1, 0, 1]:
|
| 496 |
+
for dx in [-1, 0, 1]:
|
| 497 |
+
yy = np.clip(contour[:, 0] + dy, 0, overlay.shape[0] - 1).astype(np.int32)
|
| 498 |
+
xx = np.clip(contour[:, 1] + dx, 0, overlay.shape[1] - 1).astype(np.int32)
|
| 499 |
+
overlay[yy, xx] = [1.0, 1.0, 0.0] # 粗边缘线
|
| 500 |
|
| 501 |
overlay = np.clip(overlay * 255.0, 0, 255).astype(np.uint8)
|
| 502 |
+
result_text = f"✅ 检测到 {num_instances} 个细胞"
|
| 503 |
|
|
|
|
| 504 |
if mode == "Instance Mask Only":
|
| 505 |
+
# 输出纯彩色 mask
|
| 506 |
+
return Image.fromarray(colorize_mask(inst_mask, num_colors=512)), result_text
|
| 507 |
|
| 508 |
+
return Image.fromarray(overlay), result_text
|
| 509 |
|
| 510 |
# ===== 计数功能 =====
|
| 511 |
def count_cells_handler(image_path):
|