| |
| """ |
| Qwen3.5-0.8B MapTrace - Gradio Demo |
| Predicts a traversable path between start (green) and end (red) locations on a map image. |
| """ |
|
|
| import re |
| import ast |
| import math |
| import torch |
| import gradio as gr |
| import numpy as np |
| from PIL import Image, ImageDraw, ImageFont |
| from transformers import AutoModelForImageTextToText, AutoProcessor |
|
|
| |
| MODEL_ID = "TurkishCodeMan/Qwen3.5-0.8B-MapTrace-PartialFT" |
|
|
| |
| print(f"Loading model: {MODEL_ID}") |
| processor = AutoProcessor.from_pretrained(MODEL_ID, trust_remote_code=True) |
| model = AutoModelForImageTextToText.from_pretrained( |
| MODEL_ID, |
| torch_dtype=torch.bfloat16, |
| device_map="auto", |
| trust_remote_code=True, |
| ) |
| model.eval() |
| print("✅ Model loaded and ready!") |
|
|
|
|
| |
|
|
| def parse_coordinates(text: str) -> list[tuple[float, float]]: |
| """Model çıktısından koordinatları ayıklar.""" |
| |
| coords = re.findall(r"\((-?\d+\.?\d*),\s*(-?\d+\.?\d*)\)", text) |
| if coords: |
| return [(float(x), float(y)) for x, y in coords] |
| return [] |
|
|
|
|
| def draw_path_on_image( |
| image: Image.Image, |
| path_coords: list[tuple[float, float]], |
| start_xy: tuple[float, float], |
| end_xy: tuple[float, float], |
| ) -> Image.Image: |
| """ |
| Harita görselinin üzerine tahmin edilen yolu çizer. |
| - Yol: Kalın sarı çizgi + turuncu noktalar |
| - Start: Parlak yeşil daire |
| - End : Parlak kırmızı daire |
| """ |
| img = image.copy().convert("RGBA") |
| overlay = Image.new("RGBA", img.size, (0, 0, 0, 0)) |
| draw = ImageDraw.Draw(overlay) |
|
|
| W, H = img.size |
|
|
| def norm_to_px(x, y): |
| return int(x * W), int(y * H) |
|
|
| |
| if len(path_coords) >= 2: |
| pixel_path = [norm_to_px(*p) for p in path_coords] |
|
|
| |
| draw.line(pixel_path, fill=(0, 0, 0, 160), width=7) |
| |
| draw.line(pixel_path, fill=(255, 215, 0, 230), width=4) |
|
|
| |
| for px, py in pixel_path[1:-1]: |
| r = 5 |
| draw.ellipse([(px - r, py - r), (px + r, py + r)], fill=(255, 140, 0, 255)) |
|
|
| |
| sx, sy = norm_to_px(*start_xy) |
| draw.ellipse([(sx - 10, sy - 10), (sx + 10, sy + 10)], fill=(0, 200, 80, 255), outline=(255, 255, 255, 255), width=2) |
|
|
| |
| ex, ey = norm_to_px(*end_xy) |
| draw.ellipse([(ex - 10, ey - 10), (ex + 10, ey + 10)], fill=(220, 40, 40, 255), outline=(255, 255, 255, 255), width=2) |
|
|
| |
| result = Image.alpha_composite(img, overlay).convert("RGB") |
| return result |
|
|
|
|
| def run_inference(image: Image.Image, start_x: float, start_y: float, end_x: float, end_y: float): |
| """ |
| Model çalıştırır, koordinatları parse eder, görsele yolu çizer. |
| """ |
| if image is None: |
| return None, "❌ Lütfen bir harita görseli yükleyin." |
|
|
| |
| prompt_text = ( |
| f"You are provided an image of a path with a start location denoted in green " |
| f"and an end location denoted in red. \n" |
| f"The normalized xy-coordinates of the start location are ({start_x}, {start_y}) " |
| f"and of the end location ({end_x}, {end_y}). \n" |
| f"Output a list of normalized coordinates in the form of a list [(x1,y1), (x2,y2)...] " |
| f"of the path between the start and end location. \n" |
| f"Ensure that the path follows the traversable locations of the map." |
| ) |
|
|
| messages = [ |
| { |
| "role": "user", |
| "content": [ |
| {"type": "image"}, |
| {"type": "text", "text": prompt_text}, |
| ], |
| } |
| ] |
|
|
| text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) |
|
|
| inputs = processor( |
| text=[text], |
| images=[image], |
| return_tensors="pt", |
| padding=True, |
| min_pixels=256 * 28 * 28, |
| max_pixels=1024 * 768, |
| ).to(model.device) |
|
|
| with torch.no_grad(): |
| generated_ids = model.generate( |
| **inputs, |
| max_new_tokens=512, |
| do_sample=False, |
| temperature=0.0, |
| ) |
|
|
| raw_output = processor.decode( |
| generated_ids[0][inputs.input_ids.shape[1]:], |
| skip_special_tokens=True |
| ).strip() |
|
|
| |
| path = parse_coordinates(raw_output) |
|
|
| if not path: |
| return image, f"⚠️ Model geçerli koordinat üretemedi.\n\nHam çıktı:\n{raw_output}" |
|
|
| |
| result_img = draw_path_on_image(image, path, (start_x, start_y), (end_x, end_y)) |
|
|
| |
| path_str = " → ".join([f"({x:.4f}, {y:.4f})" for x, y in path]) |
| info_text = ( |
| f"✅ Tahmin edilen yol: **{len(path)} nokta**\n\n" |
| f"`{path_str}`\n\n" |
| f"---\n*Ham model çıktısı:*\n```\n{raw_output[:500]}\n```" |
| ) |
|
|
| return result_img, info_text |
|
|
|
|
| |
|
|
| DESCRIPTION = """ |
| # 🗺️ MapTrace Path Planner |
| **Model:** [Qwen3.5-0.8B-MapTrace-PartialFT](https://huggingface.co/TurkishCodeMan/Qwen3.5-0.8B-MapTrace-PartialFT) |
| |
| Upload a map image and enter normalized start/end coordinates. The model will predict a traversable path between the two locations and overlay it on the map. |
| |
| **Coordinates:** Normalized (x, y) values in [0, 1] range. Top-left = (0, 0), Bottom-right = (1, 1). |
| """ |
|
|
| EXAMPLES = [ |
| ["example_map.png", 0.77, 0.47, 0.54, 0.54], |
| ] |
|
|
| with gr.Blocks( |
| title="MapTrace Path Planner", |
| theme=gr.themes.Soft(primary_hue="emerald", secondary_hue="slate"), |
| css=""" |
| .gradio-container { max-width: 1100px !important; } |
| .result-image img { border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.15); } |
| footer { display: none !important; } |
| """, |
| ) as demo: |
| gr.Markdown(DESCRIPTION) |
|
|
| with gr.Row(): |
| |
| with gr.Column(scale=1): |
| gr.Markdown("### 📤 Input") |
| input_image = gr.Image( |
| label="Map Image", |
| type="pil", |
| sources=["upload"], |
| height=350, |
| ) |
| with gr.Row(): |
| start_x = gr.Number(label="Start X", value=0.77, minimum=0.0, maximum=1.0, step=0.01, precision=4) |
| start_y = gr.Number(label="Start Y", value=0.47, minimum=0.0, maximum=1.0, step=0.01, precision=4) |
| with gr.Row(): |
| end_x = gr.Number(label="End X", value=0.54, minimum=0.0, maximum=1.0, step=0.01, precision=4) |
| end_y = gr.Number(label="End Y", value=0.54, minimum=0.0, maximum=1.0, step=0.01, precision=4) |
|
|
| run_btn = gr.Button("🚀 Predict Path", variant="primary", size="lg") |
|
|
| |
| with gr.Column(scale=1): |
| gr.Markdown("### 📍 Result") |
| output_image = gr.Image( |
| label="Predicted Path", |
| type="pil", |
| interactive=False, |
| height=350, |
| elem_classes=["result-image"], |
| ) |
| output_info = gr.Markdown(label="Info", value="*Results will appear here after running the model.*") |
|
|
| |
| with gr.Row(): |
| gr.Markdown(""" |
| --- |
| 🟢 **Start** | 🔴 **End** | 🟡 **Predicted Path** |
| """) |
|
|
| |
| run_btn.click( |
| fn=run_inference, |
| inputs=[input_image, start_x, start_y, end_x, end_y], |
| outputs=[output_image, output_info], |
| api_name="predict_path", |
| show_progress="full", |
| ) |
|
|
| |
| for component in [start_x, start_y, end_x, end_y]: |
| component.submit( |
| fn=run_inference, |
| inputs=[input_image, start_x, start_y, end_x, end_y], |
| outputs=[output_image, output_info], |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch(share=False) |
|
|