Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -605,9 +605,26 @@ def init_state() -> Dict:
|
|
| 605 |
def cb_load_image(upload, state):
|
| 606 |
if upload is None:
|
| 607 |
return None, state, "Upload a floor-plan image to begin."
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 611 |
if img_bgr is None:
|
| 612 |
return None, state, "β Could not decode image."
|
| 613 |
state = init_state()
|
|
@@ -699,25 +716,25 @@ def cb_undo_door_line(state):
|
|
| 699 |
return vis, state, f"β© Last line removed. Remaining: {len(state['user_lines'])}"
|
| 700 |
|
| 701 |
|
| 702 |
-
def cb_run_sam(state
|
| 703 |
walls = state.get("walls")
|
| 704 |
img = state.get("img_cropped")
|
| 705 |
if walls is None or img is None:
|
| 706 |
return None, None, state, "Run preprocessing first."
|
| 707 |
|
| 708 |
-
|
| 709 |
ckpt = download_sam_if_needed()
|
| 710 |
if ckpt is None:
|
| 711 |
return None, None, state, "β SAM checkpoint download failed."
|
| 712 |
|
| 713 |
-
|
| 714 |
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
| 715 |
raw = segment_with_sam(img_rgb, walls.copy(), ckpt)
|
| 716 |
|
| 717 |
-
|
| 718 |
filtered = filter_room_masks(raw, img.shape)
|
| 719 |
|
| 720 |
-
|
| 721 |
rooms = []
|
| 722 |
for idx, entry in enumerate(filtered, 1):
|
| 723 |
cnt = entry["contour"]
|
|
@@ -748,7 +765,7 @@ def cb_run_sam(state, progress=gr.Progress()):
|
|
| 748 |
state["rooms"] = rooms
|
| 749 |
state["selected_ids"] = []
|
| 750 |
|
| 751 |
-
|
| 752 |
annotated = build_annotated_image(img, rooms)
|
| 753 |
state["annotated"] = annotated
|
| 754 |
|
|
@@ -854,29 +871,37 @@ CSS = """
|
|
| 854 |
#title { text-align: center; font-size: 1.8em; font-weight: 700; color: #1F4E79; }
|
| 855 |
#subtitle { text-align: center; color: #555; margin-top: -8px; margin-bottom: 16px; }
|
| 856 |
.step-card { border-left: 4px solid #1F4E79 !important; padding-left: 10px !important; }
|
| 857 |
-
#status-box textarea { font-size: 0.95em !important; color: #1a6b2e !important; font-weight: 600 !important; }
|
| 858 |
"""
|
| 859 |
|
| 860 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 861 |
state = gr.State(init_state())
|
| 862 |
|
| 863 |
gr.Markdown("# π’ Floor Plan Room Analyser", elem_id="title")
|
| 864 |
gr.Markdown(
|
| 865 |
"Upload a floor-plan β auto-extract walls β close doors β SAM segmentation β OCR labels β export Excel",
|
| 866 |
-
elem_id="subtitle"
|
| 867 |
)
|
| 868 |
|
| 869 |
-
status_box = gr.Textbox(
|
| 870 |
-
|
| 871 |
-
|
|
|
|
|
|
|
| 872 |
|
| 873 |
# ββ Row 1: Upload + Preprocessing βββββββββββββββββββββββββββββββββββββββ
|
| 874 |
with gr.Row():
|
| 875 |
with gr.Column(scale=1, elem_classes="step-card"):
|
| 876 |
gr.Markdown("### 1οΈβ£ Upload Floor Plan")
|
| 877 |
-
upload_btn
|
| 878 |
-
"π Upload Image", file_types=["image"], size="sm"
|
| 879 |
-
)
|
| 880 |
raw_preview = gr.Image(label="Loaded Image", height=320)
|
| 881 |
|
| 882 |
with gr.Column(scale=1, elem_classes="step-card"):
|
|
@@ -893,14 +918,15 @@ with gr.Blocks(css=CSS, title="FloorPlan Analyser") as app:
|
|
| 893 |
with gr.Column(elem_classes="step-card"):
|
| 894 |
gr.Markdown("### 3οΈβ£ Draw Door-Closing Lines *(click start β click end)*")
|
| 895 |
gr.Markdown(
|
| 896 |
-
"Click
|
| 897 |
-
"
|
|
|
|
| 898 |
)
|
| 899 |
-
|
| 900 |
-
undo_line_btn = gr.Button("β© Undo Last Line", size="sm")
|
| 901 |
wall_draw_img = gr.Image(
|
| 902 |
-
label="Wall mask β click to draw door lines",
|
| 903 |
-
height=380,
|
|
|
|
| 904 |
)
|
| 905 |
|
| 906 |
# ββ Row 3: SAM + Annotation ββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -909,8 +935,9 @@ with gr.Blocks(css=CSS, title="FloorPlan Analyser") as app:
|
|
| 909 |
gr.Markdown("### 4οΈβ£ SAM Segmentation + OCR")
|
| 910 |
sam_btn = gr.Button("π€ Run SAM + OCR", variant="primary")
|
| 911 |
ann_img = gr.Image(
|
| 912 |
-
label="Annotated rooms β click to select/deselect",
|
| 913 |
-
height=480,
|
|
|
|
| 914 |
)
|
| 915 |
|
| 916 |
with gr.Column(scale=1, elem_classes="step-card"):
|
|
@@ -918,81 +945,80 @@ with gr.Blocks(css=CSS, title="FloorPlan Analyser") as app:
|
|
| 918 |
room_table = gr.Dataframe(
|
| 919 |
headers=["ID", "Label", "Area", "SAM Score"],
|
| 920 |
datatype=["number", "str", "str", "str"],
|
| 921 |
-
interactive=False,
|
|
|
|
| 922 |
)
|
| 923 |
with gr.Group():
|
| 924 |
gr.Markdown("**Edit selected room(s)**")
|
| 925 |
-
rename_txt = gr.Textbox(
|
| 926 |
-
placeholder="New labelβ¦", label="Rename Label"
|
| 927 |
-
)
|
| 928 |
with gr.Row():
|
| 929 |
rename_btn = gr.Button("β Rename", size="sm")
|
| 930 |
remove_btn = gr.Button("π Remove Selected", size="sm", variant="stop")
|
| 931 |
|
| 932 |
gr.Markdown("---")
|
| 933 |
-
export_btn
|
| 934 |
-
excel_file
|
| 935 |
|
| 936 |
-
# ββ Wiring βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 937 |
|
| 938 |
upload_btn.upload(
|
| 939 |
cb_load_image,
|
| 940 |
inputs=[upload_btn, state],
|
| 941 |
-
outputs=[raw_preview, state, status_box]
|
| 942 |
)
|
| 943 |
|
| 944 |
preprocess_btn.click(
|
| 945 |
cb_preprocess,
|
| 946 |
inputs=[state],
|
| 947 |
-
outputs=[clean_img, walls_img, state, status_box]
|
| 948 |
).then(
|
| 949 |
-
|
| 950 |
-
if s.get("walls") is not None else None,
|
| 951 |
inputs=[state],
|
| 952 |
-
outputs=[wall_draw_img]
|
| 953 |
)
|
| 954 |
|
| 955 |
wall_draw_img.select(
|
| 956 |
cb_add_door_line,
|
| 957 |
inputs=[state],
|
| 958 |
-
outputs=[wall_draw_img, state, status_box]
|
| 959 |
)
|
| 960 |
|
| 961 |
undo_line_btn.click(
|
| 962 |
cb_undo_door_line,
|
| 963 |
inputs=[state],
|
| 964 |
-
outputs=[wall_draw_img, state, status_box]
|
| 965 |
)
|
| 966 |
|
| 967 |
sam_btn.click(
|
| 968 |
cb_run_sam,
|
| 969 |
inputs=[state],
|
| 970 |
-
outputs=[ann_img, room_table, state, status_box]
|
| 971 |
)
|
| 972 |
|
| 973 |
ann_img.select(
|
| 974 |
cb_click_room,
|
| 975 |
inputs=[state],
|
| 976 |
-
outputs=[ann_img, state, status_box]
|
| 977 |
)
|
| 978 |
|
| 979 |
remove_btn.click(
|
| 980 |
cb_remove_selected,
|
| 981 |
inputs=[state],
|
| 982 |
-
outputs=[ann_img, room_table, state, status_box]
|
| 983 |
)
|
| 984 |
|
| 985 |
rename_btn.click(
|
| 986 |
cb_rename_selected,
|
| 987 |
inputs=[rename_txt, state],
|
| 988 |
-
outputs=[ann_img, room_table, state, status_box]
|
| 989 |
)
|
| 990 |
|
| 991 |
export_btn.click(
|
| 992 |
cb_export_excel,
|
| 993 |
inputs=[state],
|
| 994 |
-
outputs=[excel_file, status_box]
|
| 995 |
)
|
| 996 |
|
|
|
|
| 997 |
if __name__ == "__main__":
|
| 998 |
-
app.launch(share=False, debug=True)
|
|
|
|
| 605 |
def cb_load_image(upload, state):
|
| 606 |
if upload is None:
|
| 607 |
return None, state, "Upload a floor-plan image to begin."
|
| 608 |
+
# Gradio 6: UploadButton returns a NamedString (file path) or a dict
|
| 609 |
+
try:
|
| 610 |
+
if hasattr(upload, "name"):
|
| 611 |
+
file_path = upload.name # NamedString / tempfile path
|
| 612 |
+
elif isinstance(upload, dict) and "name" in upload:
|
| 613 |
+
file_path = upload["name"]
|
| 614 |
+
elif isinstance(upload, str):
|
| 615 |
+
file_path = upload
|
| 616 |
+
else:
|
| 617 |
+
# bytes fallback
|
| 618 |
+
img_bgr = cv2.imdecode(
|
| 619 |
+
np.frombuffer(bytes(upload), dtype=np.uint8), cv2.IMREAD_COLOR
|
| 620 |
+
)
|
| 621 |
+
file_path = None
|
| 622 |
+
|
| 623 |
+
if file_path is not None:
|
| 624 |
+
img_bgr = cv2.imread(file_path)
|
| 625 |
+
except Exception as e:
|
| 626 |
+
return None, state, f"β Error reading upload: {e}"
|
| 627 |
+
|
| 628 |
if img_bgr is None:
|
| 629 |
return None, state, "β Could not decode image."
|
| 630 |
state = init_state()
|
|
|
|
| 716 |
return vis, state, f"β© Last line removed. Remaining: {len(state['user_lines'])}"
|
| 717 |
|
| 718 |
|
| 719 |
+
def cb_run_sam(state):
|
| 720 |
walls = state.get("walls")
|
| 721 |
img = state.get("img_cropped")
|
| 722 |
if walls is None or img is None:
|
| 723 |
return None, None, state, "Run preprocessing first."
|
| 724 |
|
| 725 |
+
print("[SAM] Downloading checkpoint if neededβ¦")
|
| 726 |
ckpt = download_sam_if_needed()
|
| 727 |
if ckpt is None:
|
| 728 |
return None, None, state, "β SAM checkpoint download failed."
|
| 729 |
|
| 730 |
+
print("[SAM] Segmenting roomsβ¦")
|
| 731 |
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
| 732 |
raw = segment_with_sam(img_rgb, walls.copy(), ckpt)
|
| 733 |
|
| 734 |
+
print("[SAM] Filtering roomsβ¦")
|
| 735 |
filtered = filter_room_masks(raw, img.shape)
|
| 736 |
|
| 737 |
+
print("[SAM] Running OCRβ¦")
|
| 738 |
rooms = []
|
| 739 |
for idx, entry in enumerate(filtered, 1):
|
| 740 |
cnt = entry["contour"]
|
|
|
|
| 765 |
state["rooms"] = rooms
|
| 766 |
state["selected_ids"] = []
|
| 767 |
|
| 768 |
+
print("[SAM] Renderingβ¦")
|
| 769 |
annotated = build_annotated_image(img, rooms)
|
| 770 |
state["annotated"] = annotated
|
| 771 |
|
|
|
|
| 871 |
#title { text-align: center; font-size: 1.8em; font-weight: 700; color: #1F4E79; }
|
| 872 |
#subtitle { text-align: center; color: #555; margin-top: -8px; margin-bottom: 16px; }
|
| 873 |
.step-card { border-left: 4px solid #1F4E79 !important; padding-left: 10px !important; }
|
|
|
|
| 874 |
"""
|
| 875 |
|
| 876 |
+
|
| 877 |
+
def _walls_to_rgb(s):
|
| 878 |
+
"""Helper used in .then() β must be a named function for Gradio 6."""
|
| 879 |
+
w = s.get("walls")
|
| 880 |
+
if w is None:
|
| 881 |
+
return None
|
| 882 |
+
return cv2.cvtColor(w, cv2.COLOR_GRAY2RGB)
|
| 883 |
+
|
| 884 |
+
|
| 885 |
+
with gr.Blocks(title="FloorPlan Analyser") as app:
|
| 886 |
state = gr.State(init_state())
|
| 887 |
|
| 888 |
gr.Markdown("# π’ Floor Plan Room Analyser", elem_id="title")
|
| 889 |
gr.Markdown(
|
| 890 |
"Upload a floor-plan β auto-extract walls β close doors β SAM segmentation β OCR labels β export Excel",
|
| 891 |
+
elem_id="subtitle",
|
| 892 |
)
|
| 893 |
|
| 894 |
+
status_box = gr.Textbox(
|
| 895 |
+
label="Status",
|
| 896 |
+
interactive=False,
|
| 897 |
+
value="Idle β upload a floor plan to begin.",
|
| 898 |
+
)
|
| 899 |
|
| 900 |
# ββ Row 1: Upload + Preprocessing βββββββββββββββββββββββββββββββββββββββ
|
| 901 |
with gr.Row():
|
| 902 |
with gr.Column(scale=1, elem_classes="step-card"):
|
| 903 |
gr.Markdown("### 1οΈβ£ Upload Floor Plan")
|
| 904 |
+
upload_btn = gr.UploadButton("π Upload Image", file_types=["image"], size="sm")
|
|
|
|
|
|
|
| 905 |
raw_preview = gr.Image(label="Loaded Image", height=320)
|
| 906 |
|
| 907 |
with gr.Column(scale=1, elem_classes="step-card"):
|
|
|
|
| 918 |
with gr.Column(elem_classes="step-card"):
|
| 919 |
gr.Markdown("### 3οΈβ£ Draw Door-Closing Lines *(click start β click end)*")
|
| 920 |
gr.Markdown(
|
| 921 |
+
"Click the wall image below: **first click** = line start, "
|
| 922 |
+
"**second click** = line end. Lines are burned into the wall mask "
|
| 923 |
+
"before SAM runs to prevent room leakage through open doors."
|
| 924 |
)
|
| 925 |
+
undo_line_btn = gr.Button("β© Undo Last Line", size="sm")
|
|
|
|
| 926 |
wall_draw_img = gr.Image(
|
| 927 |
+
label="Wall mask β click to draw door-closing lines",
|
| 928 |
+
height=380,
|
| 929 |
+
interactive=False,
|
| 930 |
)
|
| 931 |
|
| 932 |
# ββ Row 3: SAM + Annotation ββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 935 |
gr.Markdown("### 4οΈβ£ SAM Segmentation + OCR")
|
| 936 |
sam_btn = gr.Button("π€ Run SAM + OCR", variant="primary")
|
| 937 |
ann_img = gr.Image(
|
| 938 |
+
label="Annotated rooms β click to select / deselect",
|
| 939 |
+
height=480,
|
| 940 |
+
interactive=False,
|
| 941 |
)
|
| 942 |
|
| 943 |
with gr.Column(scale=1, elem_classes="step-card"):
|
|
|
|
| 945 |
room_table = gr.Dataframe(
|
| 946 |
headers=["ID", "Label", "Area", "SAM Score"],
|
| 947 |
datatype=["number", "str", "str", "str"],
|
| 948 |
+
interactive=False,
|
| 949 |
+
label="Detected Rooms",
|
| 950 |
)
|
| 951 |
with gr.Group():
|
| 952 |
gr.Markdown("**Edit selected room(s)**")
|
| 953 |
+
rename_txt = gr.Textbox(placeholder="New labelβ¦", label="Rename Label")
|
|
|
|
|
|
|
| 954 |
with gr.Row():
|
| 955 |
rename_btn = gr.Button("β Rename", size="sm")
|
| 956 |
remove_btn = gr.Button("π Remove Selected", size="sm", variant="stop")
|
| 957 |
|
| 958 |
gr.Markdown("---")
|
| 959 |
+
export_btn = gr.Button("π Export to Excel", variant="secondary")
|
| 960 |
+
excel_file = gr.File(label="Download Excel", visible=True)
|
| 961 |
|
| 962 |
+
# ββ Event Wiring βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 963 |
|
| 964 |
upload_btn.upload(
|
| 965 |
cb_load_image,
|
| 966 |
inputs=[upload_btn, state],
|
| 967 |
+
outputs=[raw_preview, state, status_box],
|
| 968 |
)
|
| 969 |
|
| 970 |
preprocess_btn.click(
|
| 971 |
cb_preprocess,
|
| 972 |
inputs=[state],
|
| 973 |
+
outputs=[clean_img, walls_img, state, status_box],
|
| 974 |
).then(
|
| 975 |
+
_walls_to_rgb,
|
|
|
|
| 976 |
inputs=[state],
|
| 977 |
+
outputs=[wall_draw_img],
|
| 978 |
)
|
| 979 |
|
| 980 |
wall_draw_img.select(
|
| 981 |
cb_add_door_line,
|
| 982 |
inputs=[state],
|
| 983 |
+
outputs=[wall_draw_img, state, status_box],
|
| 984 |
)
|
| 985 |
|
| 986 |
undo_line_btn.click(
|
| 987 |
cb_undo_door_line,
|
| 988 |
inputs=[state],
|
| 989 |
+
outputs=[wall_draw_img, state, status_box],
|
| 990 |
)
|
| 991 |
|
| 992 |
sam_btn.click(
|
| 993 |
cb_run_sam,
|
| 994 |
inputs=[state],
|
| 995 |
+
outputs=[ann_img, room_table, state, status_box],
|
| 996 |
)
|
| 997 |
|
| 998 |
ann_img.select(
|
| 999 |
cb_click_room,
|
| 1000 |
inputs=[state],
|
| 1001 |
+
outputs=[ann_img, state, status_box],
|
| 1002 |
)
|
| 1003 |
|
| 1004 |
remove_btn.click(
|
| 1005 |
cb_remove_selected,
|
| 1006 |
inputs=[state],
|
| 1007 |
+
outputs=[ann_img, room_table, state, status_box],
|
| 1008 |
)
|
| 1009 |
|
| 1010 |
rename_btn.click(
|
| 1011 |
cb_rename_selected,
|
| 1012 |
inputs=[rename_txt, state],
|
| 1013 |
+
outputs=[ann_img, room_table, state, status_box],
|
| 1014 |
)
|
| 1015 |
|
| 1016 |
export_btn.click(
|
| 1017 |
cb_export_excel,
|
| 1018 |
inputs=[state],
|
| 1019 |
+
outputs=[excel_file, status_box],
|
| 1020 |
)
|
| 1021 |
|
| 1022 |
+
|
| 1023 |
if __name__ == "__main__":
|
| 1024 |
+
app.launch(share=False, debug=True, css=CSS)
|