Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -54,24 +54,31 @@ def create_labeled_zip(image, detections):
|
|
| 54 |
zip_path = os.path.join(tempfile.gettempdir(), "rosetta_glyphs.zip")
|
| 55 |
|
| 56 |
try:
|
|
|
|
| 57 |
try:
|
|
|
|
| 58 |
font = ImageFont.truetype("arial.ttf", 16)
|
| 59 |
except IOError:
|
| 60 |
font = ImageFont.load_default()
|
| 61 |
|
| 62 |
for i, d in enumerate(detections):
|
|
|
|
| 63 |
box = d['box']
|
| 64 |
crop = image.crop((box[0], box[1], box[2], box[3]))
|
| 65 |
|
|
|
|
| 66 |
label_text = f"{d['code']} {int(d['confidence']*100)}%"
|
| 67 |
draw = ImageDraw.Draw(crop)
|
| 68 |
|
|
|
|
| 69 |
left, top, right, bottom = draw.textbbox((0, 0), label_text, font=font)
|
| 70 |
text_w = right - left
|
| 71 |
text_h = bottom - top
|
| 72 |
|
| 73 |
img_w, img_h = crop.size
|
| 74 |
|
|
|
|
|
|
|
| 75 |
if img_w > text_w and img_h > text_h:
|
| 76 |
rect_x0 = img_w - text_w - 4
|
| 77 |
rect_y0 = img_h - text_h - 4
|
|
@@ -81,9 +88,11 @@ def create_labeled_zip(image, detections):
|
|
| 81 |
draw.rectangle([rect_x0, rect_y0, rect_x1, rect_y1], fill="black")
|
| 82 |
draw.text((rect_x0 + 2, rect_y0), label_text, fill="white", font=font)
|
| 83 |
|
|
|
|
| 84 |
filename = f"{d['code']}_{i}.png"
|
| 85 |
crop.save(os.path.join(zip_dir, filename))
|
| 86 |
|
|
|
|
| 87 |
shutil.make_archive(zip_path.replace('.zip', ''), 'zip', zip_dir)
|
| 88 |
return zip_path
|
| 89 |
|
|
@@ -113,6 +122,7 @@ def core_detect(image, conf_threshold):
|
|
| 113 |
conf = float(box.conf[0])
|
| 114 |
xyxy = box.xyxy[0].tolist()
|
| 115 |
|
|
|
|
| 116 |
detection = {
|
| 117 |
"code": code,
|
| 118 |
"description": gardiner_map.get(code, "Unknown"),
|
|
@@ -120,6 +130,8 @@ def core_detect(image, conf_threshold):
|
|
| 120 |
"box": xyxy
|
| 121 |
}
|
| 122 |
detections.append(detection)
|
|
|
|
|
|
|
| 123 |
crop_img = image.crop((xyxy[0], xyxy[1], xyxy[2], xyxy[3]))
|
| 124 |
crops.append((crop_img, f"{code}\n({int(conf*100)}%)"))
|
| 125 |
|
|
@@ -227,9 +239,11 @@ def process_pipeline(image, conf_threshold):
|
|
| 227 |
annotated_img, detections, crops = core_detect(image, conf_threshold)
|
| 228 |
|
| 229 |
# 2. Prepare Downloads
|
|
|
|
| 230 |
ann_path = os.path.join(tempfile.gettempdir(), "annotated_hieroglyphs.jpg")
|
| 231 |
annotated_img.save(ann_path)
|
| 232 |
|
|
|
|
| 233 |
zip_path = create_labeled_zip(image, detections)
|
| 234 |
|
| 235 |
# 3. Extract Keywords & Translate
|
|
@@ -251,18 +265,16 @@ def process_pipeline(image, conf_threshold):
|
|
| 251 |
print(f"Pipeline Error: {e}")
|
| 252 |
return None, None, None, f"System Failure: {str(e)}", "", None, str(e), None, []
|
| 253 |
|
| 254 |
-
# --- 6. MCP API FUNCTIONS
|
| 255 |
|
| 256 |
-
def
|
| 257 |
-
"""Detects hieroglyphs in an image. Returns path to annotated image and JSON data."""
|
| 258 |
ann_img, dets, _ = core_detect(image, conf)
|
| 259 |
with tempfile.NamedTemporaryFile(dir="/tmp/gradio_results", suffix=".jpg", delete=False) as t:
|
| 260 |
ann_img.save(t.name)
|
| 261 |
path = t.name
|
| 262 |
return path, {"count": len(dets), "detections": dets}
|
| 263 |
|
| 264 |
-
def
|
| 265 |
-
"""Translates a list of codes or keywords into a mystical story and academic text."""
|
| 266 |
if isinstance(keywords_text, str):
|
| 267 |
keywords = [k.strip() for k in keywords_text.split(',')]
|
| 268 |
else:
|
|
@@ -271,8 +283,7 @@ def translate_story(keywords_text: str) -> Tuple[str, str]:
|
|
| 271 |
clean_mystical = re.sub('<[^<]+?>', '', mystical)
|
| 272 |
return clean_mystical, academic
|
| 273 |
|
| 274 |
-
def
|
| 275 |
-
"""Generates analytics charts from detection JSON. Returns path to chart image."""
|
| 276 |
dets = json_data.get("detections", [])
|
| 277 |
fig = core_analytics(dets, 640, 640)
|
| 278 |
if fig is None: return "No data."
|
|
@@ -282,8 +293,7 @@ def get_stats(json_data: Dict[str, Any]) -> str:
|
|
| 282 |
plt.close(fig)
|
| 283 |
return path
|
| 284 |
|
| 285 |
-
def
|
| 286 |
-
"""Returns the full dictionary of supported Gardiner codes and descriptions."""
|
| 287 |
return gardiner_data
|
| 288 |
|
| 289 |
# --- 7. HTML GENERATORS ---
|
|
@@ -392,18 +402,18 @@ guide_html = """
|
|
| 392 |
<div class="card-title" style="color: #d4af37;">🤖 CLAUDE DESKTOP SETUP GUIDE</div>
|
| 393 |
<div class="guide-step"><span class="step-title">STEP 0: PREREQUISITE</span><p>Ensure you have <b>Node.js</b> installed.</p></div>
|
| 394 |
<div class="guide-step"><span class="step-title">STEP 1: PREPARE WORKSPACE</span>1. Create: <span class="path-highlight">C:\\Claude_Work</span><br>2. Move images inside.</div>
|
| 395 |
-
<div class="guide-step"><span class="step-title">STEP 2: CONFIGURE CLAUDE</span>1. Edit: <code>%APPDATA%\\Claude\\claude_desktop_config.json</code><br>2. Paste the JSON below.<br>3. Restart Claude
|
| 396 |
</div>
|
| 397 |
"""
|
| 398 |
|
| 399 |
-
#
|
| 400 |
claude_json_content = """{
|
| 401 |
"mcpServers": {
|
| 402 |
-
"
|
| 403 |
"command": "npx",
|
| 404 |
"args": [
|
| 405 |
"mcp-remote",
|
| 406 |
-
"https://
|
| 407 |
"--transport",
|
| 408 |
"streamable-http"
|
| 409 |
]
|
|
@@ -414,7 +424,7 @@ claude_json_content = """{
|
|
| 414 |
"-m",
|
| 415 |
"gradio",
|
| 416 |
"upload-mcp",
|
| 417 |
-
"https://
|
| 418 |
"C:\\\\Claude_Work"
|
| 419 |
]
|
| 420 |
}
|
|
@@ -430,19 +440,18 @@ with gr.Blocks(title="Rosetta Decoder Ultimate") as demo:
|
|
| 430 |
gr.HTML(trail_script)
|
| 431 |
|
| 432 |
# --- MCP TOOL REGISTRATION LAYER (Hidden) ---
|
| 433 |
-
# CHANGED: Renamed API names to be short verbs (detect, translate, etc.)
|
| 434 |
with gr.Row(visible=False):
|
| 435 |
btn_detect = gr.Button("Detect")
|
| 436 |
-
btn_detect.click(fn=
|
| 437 |
|
| 438 |
btn_trans = gr.Button("Translate")
|
| 439 |
-
btn_trans.click(fn=
|
| 440 |
|
| 441 |
btn_anal = gr.Button("Analytics")
|
| 442 |
-
btn_anal.click(fn=
|
| 443 |
|
| 444 |
btn_list = gr.Button("List")
|
| 445 |
-
btn_list.click(fn=
|
| 446 |
|
| 447 |
# --- Visible UI ---
|
| 448 |
with gr.Row(elem_classes="header-row"):
|
|
|
|
| 54 |
zip_path = os.path.join(tempfile.gettempdir(), "rosetta_glyphs.zip")
|
| 55 |
|
| 56 |
try:
|
| 57 |
+
# Load a font (try-except block for system compatibility)
|
| 58 |
try:
|
| 59 |
+
# Try loading a standard font, fallback to default if fails
|
| 60 |
font = ImageFont.truetype("arial.ttf", 16)
|
| 61 |
except IOError:
|
| 62 |
font = ImageFont.load_default()
|
| 63 |
|
| 64 |
for i, d in enumerate(detections):
|
| 65 |
+
# Crop
|
| 66 |
box = d['box']
|
| 67 |
crop = image.crop((box[0], box[1], box[2], box[3]))
|
| 68 |
|
| 69 |
+
# Prepare Label
|
| 70 |
label_text = f"{d['code']} {int(d['confidence']*100)}%"
|
| 71 |
draw = ImageDraw.Draw(crop)
|
| 72 |
|
| 73 |
+
# Calculate text size using textbbox (newer PIL versions)
|
| 74 |
left, top, right, bottom = draw.textbbox((0, 0), label_text, font=font)
|
| 75 |
text_w = right - left
|
| 76 |
text_h = bottom - top
|
| 77 |
|
| 78 |
img_w, img_h = crop.size
|
| 79 |
|
| 80 |
+
# Draw background rectangle (bottom right)
|
| 81 |
+
# Check if image is too small for label, if so, skip drawing to avoid crash
|
| 82 |
if img_w > text_w and img_h > text_h:
|
| 83 |
rect_x0 = img_w - text_w - 4
|
| 84 |
rect_y0 = img_h - text_h - 4
|
|
|
|
| 88 |
draw.rectangle([rect_x0, rect_y0, rect_x1, rect_y1], fill="black")
|
| 89 |
draw.text((rect_x0 + 2, rect_y0), label_text, fill="white", font=font)
|
| 90 |
|
| 91 |
+
# Save crop
|
| 92 |
filename = f"{d['code']}_{i}.png"
|
| 93 |
crop.save(os.path.join(zip_dir, filename))
|
| 94 |
|
| 95 |
+
# Create Zip
|
| 96 |
shutil.make_archive(zip_path.replace('.zip', ''), 'zip', zip_dir)
|
| 97 |
return zip_path
|
| 98 |
|
|
|
|
| 122 |
conf = float(box.conf[0])
|
| 123 |
xyxy = box.xyxy[0].tolist()
|
| 124 |
|
| 125 |
+
# Create detection object
|
| 126 |
detection = {
|
| 127 |
"code": code,
|
| 128 |
"description": gardiner_map.get(code, "Unknown"),
|
|
|
|
| 130 |
"box": xyxy
|
| 131 |
}
|
| 132 |
detections.append(detection)
|
| 133 |
+
|
| 134 |
+
# Create crop (Clean crop for Gallery display)
|
| 135 |
crop_img = image.crop((xyxy[0], xyxy[1], xyxy[2], xyxy[3]))
|
| 136 |
crops.append((crop_img, f"{code}\n({int(conf*100)}%)"))
|
| 137 |
|
|
|
|
| 239 |
annotated_img, detections, crops = core_detect(image, conf_threshold)
|
| 240 |
|
| 241 |
# 2. Prepare Downloads
|
| 242 |
+
# A. Annotated Image
|
| 243 |
ann_path = os.path.join(tempfile.gettempdir(), "annotated_hieroglyphs.jpg")
|
| 244 |
annotated_img.save(ann_path)
|
| 245 |
|
| 246 |
+
# B. Zip File with Labels
|
| 247 |
zip_path = create_labeled_zip(image, detections)
|
| 248 |
|
| 249 |
# 3. Extract Keywords & Translate
|
|
|
|
| 265 |
print(f"Pipeline Error: {e}")
|
| 266 |
return None, None, None, f"System Failure: {str(e)}", "", None, str(e), None, []
|
| 267 |
|
| 268 |
+
# --- 6. MCP API FUNCTIONS ---
|
| 269 |
|
| 270 |
+
def detect_hieroglyphs_api(image: Image.Image, conf: float = 0.25) -> Tuple[str, Dict[str, Any]]:
|
|
|
|
| 271 |
ann_img, dets, _ = core_detect(image, conf)
|
| 272 |
with tempfile.NamedTemporaryFile(dir="/tmp/gradio_results", suffix=".jpg", delete=False) as t:
|
| 273 |
ann_img.save(t.name)
|
| 274 |
path = t.name
|
| 275 |
return path, {"count": len(dets), "detections": dets}
|
| 276 |
|
| 277 |
+
def translate_codes_api(keywords_text: str) -> Tuple[str, str]:
|
|
|
|
| 278 |
if isinstance(keywords_text, str):
|
| 279 |
keywords = [k.strip() for k in keywords_text.split(',')]
|
| 280 |
else:
|
|
|
|
| 283 |
clean_mystical = re.sub('<[^<]+?>', '', mystical)
|
| 284 |
return clean_mystical, academic
|
| 285 |
|
| 286 |
+
def get_analytics_chart_api(json_data: Dict[str, Any]) -> str:
|
|
|
|
| 287 |
dets = json_data.get("detections", [])
|
| 288 |
fig = core_analytics(dets, 640, 640)
|
| 289 |
if fig is None: return "No data."
|
|
|
|
| 293 |
plt.close(fig)
|
| 294 |
return path
|
| 295 |
|
| 296 |
+
def list_all_codes_api() -> Dict[str, Any]:
|
|
|
|
| 297 |
return gardiner_data
|
| 298 |
|
| 299 |
# --- 7. HTML GENERATORS ---
|
|
|
|
| 402 |
<div class="card-title" style="color: #d4af37;">🤖 CLAUDE DESKTOP SETUP GUIDE</div>
|
| 403 |
<div class="guide-step"><span class="step-title">STEP 0: PREREQUISITE</span><p>Ensure you have <b>Node.js</b> installed.</p></div>
|
| 404 |
<div class="guide-step"><span class="step-title">STEP 1: PREPARE WORKSPACE</span>1. Create: <span class="path-highlight">C:\\Claude_Work</span><br>2. Move images inside.</div>
|
| 405 |
+
<div class="guide-step"><span class="step-title">STEP 2: CONFIGURE CLAUDE</span>1. Edit: <code>%APPDATA%\\Claude\\claude_desktop_config.json</code><br>2. Paste the JSON below.<br>3. Restart Claude.</div>
|
| 406 |
</div>
|
| 407 |
"""
|
| 408 |
|
| 409 |
+
# URL UPDATED TO: youkii-xr/hieroglyph-mcp-server
|
| 410 |
claude_json_content = """{
|
| 411 |
"mcpServers": {
|
| 412 |
+
"gradio": {
|
| 413 |
"command": "npx",
|
| 414 |
"args": [
|
| 415 |
"mcp-remote",
|
| 416 |
+
"https://youkii-xr-hieroglyph-mcp-server.hf.space/gradio_api/mcp/",
|
| 417 |
"--transport",
|
| 418 |
"streamable-http"
|
| 419 |
]
|
|
|
|
| 424 |
"-m",
|
| 425 |
"gradio",
|
| 426 |
"upload-mcp",
|
| 427 |
+
"https://youkii-xr-hieroglyph-mcp-server.hf.space/",
|
| 428 |
"C:\\\\Claude_Work"
|
| 429 |
]
|
| 430 |
}
|
|
|
|
| 440 |
gr.HTML(trail_script)
|
| 441 |
|
| 442 |
# --- MCP TOOL REGISTRATION LAYER (Hidden) ---
|
|
|
|
| 443 |
with gr.Row(visible=False):
|
| 444 |
btn_detect = gr.Button("Detect")
|
| 445 |
+
btn_detect.click(fn=detect_hieroglyphs_api, inputs=[gr.Image(label="img"), gr.Number(label="conf")], outputs=[gr.Textbox(label="path"), gr.JSON(label="json")], api_name="detect")
|
| 446 |
|
| 447 |
btn_trans = gr.Button("Translate")
|
| 448 |
+
btn_trans.click(fn=translate_codes_api, inputs=[gr.Textbox(label="text")], outputs=[gr.Textbox(label="mystic"), gr.Textbox(label="academic")], api_name="translate")
|
| 449 |
|
| 450 |
btn_anal = gr.Button("Analytics")
|
| 451 |
+
btn_anal.click(fn=get_analytics_chart_api, inputs=[gr.JSON(label="data")], outputs=[gr.Textbox(label="chart_path")], api_name="analytics")
|
| 452 |
|
| 453 |
btn_list = gr.Button("List")
|
| 454 |
+
btn_list.click(fn=list_all_codes_api, inputs=[], outputs=[gr.JSON(label="data")], api_name="list_codes")
|
| 455 |
|
| 456 |
# --- Visible UI ---
|
| 457 |
with gr.Row(elem_classes="header-row"):
|