Spaces:
Running
Running
| import gradio as gr | |
| from gradio_polygonannotator import PolygonAnnotator | |
| from typing import Literal | |
| example_data = { | |
| "image": "https://images.unsplash.com/photo-1544816155-12df9643f363?w=800&h=1200", | |
| "polygons": [ | |
| { | |
| "id": "date_line", | |
| "coordinates": [[180, 150], [580, 150], [580, 200], [180, 200]], | |
| "color": "#FF0000", | |
| "mask_opacity": 0.2, | |
| "stroke_width": 0.7, | |
| "stroke_opacity": 0.6, | |
| "selected_mask_opacity": 0.5, | |
| "selected_stroke_opacity": 1.0 | |
| }, | |
| { | |
| "id": "salutation", | |
| "coordinates": [[180, 280], [680, 280], [680, 340], [180, 340]], | |
| "color": "#00FF00", | |
| "mask_opacity": 0.2, | |
| "stroke_width": 1.0, | |
| "stroke_opacity": 0.6, | |
| "selected_mask_opacity": 0.4, | |
| "selected_stroke_opacity": 0.9 | |
| }, | |
| { | |
| "id": "main_text_block", | |
| "coordinates": [[100, 400], [750, 400], [750, 950], [100, 950]], | |
| "color": "#0000FF", | |
| "mask_opacity": 0.15, | |
| "stroke_width": 0.5, | |
| "stroke_opacity": 0.5, | |
| "selected_mask_opacity": 0.4, | |
| "selected_stroke_opacity": 0.8 | |
| }, | |
| { | |
| "id": "closing_signature", | |
| "coordinates": [[400, 1020], [650, 1020], [650, 1080], [400, 1080]], | |
| "color": "#FFFF00", | |
| "mask_opacity": 0.25, | |
| "stroke_width": 1.5, | |
| "stroke_opacity": 0.7, | |
| "selected_mask_opacity": 0.6, | |
| "selected_stroke_opacity": 1.0 | |
| } | |
| ] | |
| } | |
| # Create dataframe data from polygon information | |
| polygon_table = [ | |
| ["date_line", "Date Line", "#FF0000", 0.2, 0.7, 0.6], | |
| ["salutation", "Salutation", "#00FF00", 0.2, 1.0, 0.6], | |
| ["main_text_block", "Main Text Block", "#0000FF", 0.15, 0.5, 0.5], | |
| ["closing_signature", "Closing/Signature", "#FFFF00", 0.25, 1.5, 0.7] | |
| ] | |
| def process_viewer_selection(data, evt: gr.SelectData): | |
| """Handle polygon selection from viewer and update dataframe selection""" | |
| if evt.value and data: | |
| selected_ids = evt.value if isinstance(evt.value, list) else [evt.value] | |
| # Create highlighted dataframe data | |
| highlighted_table = [] | |
| for row in polygon_table: | |
| if row[0] in selected_ids: # If this is a selected row | |
| # Add highlighting markers to the selected row | |
| highlighted_row = [f"β {row[0]} β", f"β {row[1]} β", f"β {row[2]} β", f"β {row[3]} β", f"β {row[4]} β", f"β {row[5]} β"] | |
| highlighted_table.append(highlighted_row) | |
| else: | |
| highlighted_table.append(row) | |
| # Create info text for all selected polygons | |
| info_lines = [f"Selected {len(selected_ids)} polygon(s):"] | |
| for selected_id in selected_ids: | |
| selected_polygon = next((p for p in data["polygons"] if p["id"] == selected_id), None) | |
| if selected_polygon: | |
| info_lines.append(f"β’ {selected_id}: {selected_polygon['color']}, mask: {selected_polygon.get('mask_opacity', 0.2)}, stroke: {selected_polygon.get('stroke_width', 0.7)}px") | |
| info_text = "\n".join(info_lines) | |
| return info_text, highlighted_table | |
| return "No polygons selected", polygon_table | |
| def process_dataframe_selection(selected_data, evt: gr.SelectData): | |
| """Handle row selection from dataframe and update viewer selection""" | |
| if evt.index is not None and evt.index[0] < len(polygon_table): | |
| selected_row = polygon_table[evt.index[0]] | |
| polygon_id = selected_row[0] | |
| # Update the viewer data with the selected polygon | |
| updated_data = example_data.copy() | |
| updated_data["selected_polygons"] = [polygon_id] | |
| info_text = f"Selected polygon: {polygon_id}\nName: {selected_row[1]}\nColor: {selected_row[2]}\nMask Opacity: {selected_row[3]}\nStroke Width: {selected_row[4]}\nStroke Opacity: {selected_row[5]}" | |
| return updated_data, info_text | |
| # Deselection | |
| updated_data = example_data.copy() | |
| updated_data["selected_polygons"] = [] | |
| return updated_data, "No polygons selected" | |
| def clear_selection(): | |
| """Clear polygon selection""" | |
| updated_data = example_data.copy() | |
| updated_data["selected_polygons"] = [] | |
| return updated_data, "No polygons selected", polygon_table | |
| def select_polygon_by_id(polygon_id): | |
| """Select polygon by ID from textbox input""" | |
| if not polygon_id or polygon_id.strip() == "": | |
| # Empty input - clear selection | |
| updated_data = example_data.copy() | |
| updated_data["selected_polygons"] = [] | |
| return updated_data, "No polygons selected", polygon_table | |
| # Handle multiple IDs (comma-separated) | |
| polygon_ids = [id.strip() for id in polygon_id.split(",") if id.strip()] | |
| valid_ids = [p["id"] for p in example_data["polygons"]] | |
| # Filter to only valid IDs | |
| valid_selected_ids = [id for id in polygon_ids if id in valid_ids] | |
| invalid_ids = [id for id in polygon_ids if id not in valid_ids] | |
| if not valid_selected_ids: | |
| # No valid IDs | |
| updated_data = example_data.copy() | |
| updated_data["selected_polygons"] = [] | |
| error_msg = f"Invalid polygon ID(s): {', '.join(invalid_ids)}. Valid IDs: {', '.join(valid_ids)}" | |
| return updated_data, error_msg, polygon_table | |
| # Valid IDs - select polygons | |
| updated_data = example_data.copy() | |
| updated_data["selected_polygons"] = valid_selected_ids | |
| # Create highlighted dataframe data | |
| highlighted_table = [] | |
| for row in polygon_table: | |
| if row[0] in valid_selected_ids: # If this is a selected row | |
| # Add highlighting markers to the selected row | |
| highlighted_row = [f"β {row[0]} β", f"β {row[1]} β", f"β {row[2]} β", f"β {row[3]} β", f"β {row[4]} β", f"β {row[5]} β"] | |
| highlighted_table.append(highlighted_row) | |
| else: | |
| highlighted_table.append(row) | |
| # Create info text | |
| info_lines = [f"Selected {len(valid_selected_ids)} polygon(s):"] | |
| for selected_id in valid_selected_ids: | |
| selected_polygon = next((p for p in example_data["polygons"] if p["id"] == selected_id), None) | |
| if selected_polygon: | |
| info_lines.append(f"β’ {selected_id}: {selected_polygon['color']}, mask: {selected_polygon.get('mask_opacity', 0.2)}, stroke: {selected_polygon.get('stroke_width', 0.7)}px") | |
| if invalid_ids: | |
| info_lines.append(f"\nInvalid IDs: {', '.join(invalid_ids)}") | |
| info_text = "\n".join(info_lines) | |
| return updated_data, info_text, highlighted_table | |
| with gr.Blocks() as demo: | |
| gr.Markdown(""" | |
| # PolygonAnnotator - Advanced Interactive Demo | |
| This demo showcases all the features of the PolygonAnnotator component: | |
| - **Click** on polygons to select/deselect them | |
| - **Ctrl/Cmd+Click** for multiple selection | |
| - **Click dataframe rows** to select polygons | |
| - **Enter polygon IDs** manually in the textbox | |
| - **Clear button** to deselect all | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| poly_annotator = PolygonAnnotator( | |
| value=example_data, | |
| label="Document with Interactive Polygon Annotations", | |
| height=600, | |
| ) | |
| with gr.Column(scale=1): | |
| selected_info = gr.Textbox( | |
| label="Selected Polygon Information", | |
| lines=5, | |
| value="Click on a polygon to see its information" | |
| ) | |
| polygon_dataframe = gr.Dataframe( | |
| value=polygon_table, | |
| headers=["ID", "Name", "Color", "Mask", "Stroke W", "Stroke O"], | |
| label="Polygon Data (Click rows to select)", | |
| datatype=[Literal["str", "str", "str", "number", "number", "number"]], | |
| interactive=True | |
| ) | |
| clear_button = gr.Button("ποΈ Clear All Selections", variant="secondary") | |
| with gr.Row(): | |
| polygon_id_input = gr.Textbox( | |
| label="Select by Polygon ID(s)", | |
| placeholder="Enter single ID or comma-separated IDs (e.g., 'date_line' or 'date_line, salutation')", | |
| scale=3 | |
| ) | |
| select_button = gr.Button("Select", variant="primary", scale=1) | |
| gr.Markdown(""" | |
| ### Features Demonstrated | |
| #### π¨ Visual Customization | |
| - Different **mask opacity** for each polygon fill | |
| - Variable **stroke width** (0.5px to 1.5px) | |
| - Custom **stroke opacity** for borders | |
| - Enhanced appearance when selected | |
| #### π±οΈ Interaction Methods | |
| 1. **Direct Click**: Click polygons in the viewer | |
| 2. **Multi-Selection**: Ctrl/Cmd+Click for multiple | |
| 3. **Dataframe**: Click table rows | |
| 4. **Text Input**: Type polygon IDs | |
| 5. **Clear All**: Reset selection | |
| #### π Polygon IDs | |
| - `date_line` - Red header area | |
| - `salutation` - Green greeting section | |
| - `main_text_block` - Blue main content | |
| - `closing_signature` - Yellow signature area | |
| #### π‘ Tips | |
| - Hover over polygons for visual feedback | |
| - Selected polygons have increased opacity | |
| - Use comma-separated IDs for batch selection | |
| - Click selected polygons to deselect them | |
| """) | |
| # Handle selection events | |
| poly_annotator.select( | |
| process_viewer_selection, | |
| inputs=[poly_annotator], | |
| outputs=[selected_info, polygon_dataframe] | |
| ) | |
| polygon_dataframe.select( | |
| process_dataframe_selection, | |
| inputs=[polygon_dataframe], | |
| outputs=[poly_annotator, selected_info] | |
| ) | |
| clear_button.click( | |
| clear_selection, | |
| outputs=[poly_annotator, selected_info, polygon_dataframe] | |
| ) | |
| select_button.click( | |
| select_polygon_by_id, | |
| inputs=[polygon_id_input], | |
| outputs=[poly_annotator, selected_info, polygon_dataframe] | |
| ) | |
| # Also allow Enter key in textbox | |
| polygon_id_input.submit( | |
| select_polygon_by_id, | |
| inputs=[polygon_id_input], | |
| outputs=[poly_annotator, selected_info, polygon_dataframe] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |