Spaces:
Sleeping
Sleeping
Update lines and patch matching
Browse files
main.py
CHANGED
|
@@ -183,13 +183,14 @@ def make_correspondence_figure(image_a, image_b, patches_a, patches_b, grid_h, g
|
|
| 183 |
gap = 30
|
| 184 |
canvas_w = img_w * 2 + gap
|
| 185 |
fig, ax = plt.subplots(1, 1, figsize=(14, 6))
|
| 186 |
-
fig.patch.set_facecolor('#0f172a') # Slate 900
|
| 187 |
|
| 188 |
-
canvas = Image.new("RGB", (canvas_w, img_h), (15, 23, 42))
|
| 189 |
canvas.paste(img_a_resized, (0, 0))
|
| 190 |
canvas.paste(img_b_resized, (img_w + gap, 0))
|
| 191 |
ax.imshow(canvas)
|
| 192 |
|
|
|
|
| 193 |
cmap = plt.cm.get_cmap("spring", max(len(matches), 1))
|
| 194 |
|
| 195 |
for i, (idx_a, idx_b, score) in enumerate(matches):
|
|
@@ -198,9 +199,27 @@ def make_correspondence_figure(image_a, image_b, patches_a, patches_b, grid_h, g
|
|
| 198 |
xb_canvas = xb + img_w + gap
|
| 199 |
color = cmap(i % 20)
|
| 200 |
|
| 201 |
-
|
|
|
|
| 202 |
ax.scatter([xa, xb_canvas], [ya, yb], color=color, s=50, zorder=5, edgecolors="white", linewidths=0.5)
|
| 203 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
ax.axis("off")
|
| 205 |
fig.tight_layout(pad=0)
|
| 206 |
return fig, len(matches)
|
|
@@ -253,10 +272,40 @@ async def analyze_artworks(file_a: UploadFile = File(...), file_b: UploadFile =
|
|
| 253 |
cls_g_a, patches_g_a, _, _ = extract_dino_features(img_a, "grayscale")
|
| 254 |
cls_g_b, patches_g_b, _, _ = extract_dino_features(img_b, "grayscale")
|
| 255 |
|
|
|
|
|
|
|
|
|
|
| 256 |
_, a2b_c, b2a_c = compute_patch_matches(patches_c_a, patches_c_b)
|
| 257 |
_, a2b_g, b2a_g = compute_patch_matches(patches_g_a, patches_g_b)
|
| 258 |
_, a2b_e, b2a_e = compute_patch_matches(patches_e_a, patches_e_b)
|
| 259 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
# The missing magic: Combine domains to defeat color-shifting
|
| 261 |
a2b_best = torch.max(torch.max(a2b_c, a2b_g), a2b_e)
|
| 262 |
b2a_best = torch.max(torch.max(b2a_c, b2a_g), b2a_e)
|
|
|
|
| 183 |
gap = 30
|
| 184 |
canvas_w = img_w * 2 + gap
|
| 185 |
fig, ax = plt.subplots(1, 1, figsize=(14, 6))
|
| 186 |
+
fig.patch.set_facecolor('#0f172a') # Slate 900 matching React frontend
|
| 187 |
|
| 188 |
+
canvas = Image.new("RGB", (canvas_w, img_h), (15, 23, 42)) # Slate 950 canvas
|
| 189 |
canvas.paste(img_a_resized, (0, 0))
|
| 190 |
canvas.paste(img_b_resized, (img_w + gap, 0))
|
| 191 |
ax.imshow(canvas)
|
| 192 |
|
| 193 |
+
# Keep the clean, unified 'spring' colormap
|
| 194 |
cmap = plt.cm.get_cmap("spring", max(len(matches), 1))
|
| 195 |
|
| 196 |
for i, (idx_a, idx_b, score) in enumerate(matches):
|
|
|
|
| 199 |
xb_canvas = xb + img_w + gap
|
| 200 |
color = cmap(i % 20)
|
| 201 |
|
| 202 |
+
# Draw the clean lines and dots
|
| 203 |
+
ax.plot([xa, xb_canvas], [ya, yb], color=color, linewidth=2, alpha=0.8)
|
| 204 |
ax.scatter([xa, xb_canvas], [ya, yb], color=color, s=50, zorder=5, edgecolors="white", linewidths=0.5)
|
| 205 |
|
| 206 |
+
# --- THE NEW CLEAN SCORE BADGES ---
|
| 207 |
+
# Calculate the exact midpoint of the line
|
| 208 |
+
mx = (xa + xb_canvas) / 2
|
| 209 |
+
my = (ya + yb) / 2
|
| 210 |
+
|
| 211 |
+
# Style a sleek dark pill with a glowing border matching the line color
|
| 212 |
+
bbox_props = dict(
|
| 213 |
+
boxstyle="round,pad=0.25",
|
| 214 |
+
fc="#1e293b", # Slate 800 background
|
| 215 |
+
ec=color, # Border matches the line color
|
| 216 |
+
alpha=0.95, # High opacity so it covers the line behind it
|
| 217 |
+
lw=1 # Thin, crisp border
|
| 218 |
+
)
|
| 219 |
+
|
| 220 |
+
ax.text(mx, my, f"{score:.2f}", fontsize=8, color="white", fontweight="bold",
|
| 221 |
+
ha="center", va="center", zorder=10, bbox=bbox_props)
|
| 222 |
+
|
| 223 |
ax.axis("off")
|
| 224 |
fig.tight_layout(pad=0)
|
| 225 |
return fig, len(matches)
|
|
|
|
| 272 |
cls_g_a, patches_g_a, _, _ = extract_dino_features(img_a, "grayscale")
|
| 273 |
cls_g_b, patches_g_b, _, _ = extract_dino_features(img_b, "grayscale")
|
| 274 |
|
| 275 |
+
cls_e_a, patches_e_a, _, _ = extract_dino_features(img_a, "edges")
|
| 276 |
+
cls_e_b, patches_e_b, _, _ = extract_dino_features(img_b, "edges")
|
| 277 |
+
|
| 278 |
_, a2b_c, b2a_c = compute_patch_matches(patches_c_a, patches_c_b)
|
| 279 |
_, a2b_g, b2a_g = compute_patch_matches(patches_g_a, patches_g_b)
|
| 280 |
_, a2b_e, b2a_e = compute_patch_matches(patches_e_a, patches_e_b)
|
| 281 |
|
| 282 |
+
# 1. Combine domains to defeat color-shifting (used for Heatmap stats)
|
| 283 |
+
a2b_best = torch.max(torch.max(a2b_c, a2b_g), a2b_e)
|
| 284 |
+
b2a_best = torch.max(torch.max(b2a_c, b2a_g), b2a_e)
|
| 285 |
+
|
| 286 |
+
corr_thresh = (a2b_best.mean() + 0.5 * a2b_best.std()).item()
|
| 287 |
+
corr_thresh = min(max(corr_thresh, 0.4), 0.75)
|
| 288 |
+
|
| 289 |
+
# 2. THE FIX: Dynamically select the best patch domain for the visual lines
|
| 290 |
+
mode_scores = {
|
| 291 |
+
"color": a2b_c.mean().item(),
|
| 292 |
+
"grayscale": a2b_g.mean().item(),
|
| 293 |
+
"edges": a2b_e.mean().item()
|
| 294 |
+
}
|
| 295 |
+
best_mode = max(mode_scores, key=mode_scores.get)
|
| 296 |
+
mode_patches = {
|
| 297 |
+
"color": (patches_c_a, patches_c_b),
|
| 298 |
+
"grayscale": (patches_g_a, patches_g_b),
|
| 299 |
+
"edges": (patches_e_a, patches_e_b),
|
| 300 |
+
}
|
| 301 |
+
corr_pa, corr_pb = mode_patches[best_mode]
|
| 302 |
+
|
| 303 |
+
# 3. Pass the dynamically selected patches to the figure generator
|
| 304 |
+
corr_fig, match_count = make_correspondence_figure(
|
| 305 |
+
img_a, img_b, corr_pa, corr_pb, gh, gw, score_thresh=corr_thresh
|
| 306 |
+
)
|
| 307 |
+
correspondence_map_b64 = fig_to_base64(corr_fig)
|
| 308 |
+
|
| 309 |
# The missing magic: Combine domains to defeat color-shifting
|
| 310 |
a2b_best = torch.max(torch.max(a2b_c, a2b_g), a2b_e)
|
| 311 |
b2a_best = torch.max(torch.max(b2a_c, b2a_g), b2a_e)
|