import gradio as gr import PIL.Image as Image import io import base64 import json from typing import Union def analyze_image(image: Image.Image) -> str: """ Analyze an image and return detailed information about it. Args: image: The image to analyze (can be base64 string or file upload) Returns: str: JSON string with image analysis including dimensions, format, mode, and orientation """ if image is None: return json.dumps({"error": "No image provided"}) try: # Get image properties width, height = image.size format_type = image.format or "Unknown" mode = image.mode orientation = "Portrait" if height > width else "Landscape" if width > height else "Square" # Calculate aspect ratio aspect_ratio = round(width / height, 2) if height > 0 else 0 # Get color information colors = image.getcolors(maxcolors=256*256*256) dominant_colors = len(colors) if colors else "Many" analysis = { "dimensions": {"width": width, "height": height}, "format": format_type, "mode": mode, "orientation": orientation, "aspect_ratio": aspect_ratio, "approximate_colors": dominant_colors, "file_info": f"{width}x{height} {format_type} image in {mode} mode" } return json.dumps(analysis, indent=2) except Exception as e: return json.dumps({"error": f"Error analyzing image: {str(e)}"}) def get_image_orientation(image: Image.Image) -> str: """ Determine if an image is portrait, landscape, or square. Args: image: The image to check orientation Returns: str: "Portrait", "Landscape", or "Square" """ if image is None: return "No image provided" try: width, height = image.size if height > width: return "Portrait" elif width > height: return "Landscape" else: return "Square" except Exception as e: return f"Error: {str(e)}" def count_colors(image: Image.Image) -> str: """ Count the approximate number of unique colors in an image. Args: image: The image to analyze for color count Returns: str: Description of color count and dominant color information """ if image is None: return "No image provided" try: # Convert to RGB if not already if image.mode != 'RGB': image = image.convert('RGB') # Get colors (limit to prevent memory issues) colors = image.getcolors(maxcolors=256*256*256) if colors is None: return "Image has more than 16.7 million unique colors" # Sort by frequency colors.sort(key=lambda x: x[0], reverse=True) # Get top 3 colors top_colors = colors[:3] color_info = [] for count, color in top_colors: if isinstance(color, tuple) and len(color) >= 3: r, g, b = color[:3] hex_color = f"#{r:02x}{g:02x}{b:02x}" percentage = round((count / sum(c[0] for c in colors)) * 100, 1) color_info.append(f"RGB{color} ({hex_color}) - {percentage}%") result = f"Total unique colors: {len(colors)}\n" result += "Top colors by frequency:\n" + "\n".join(color_info) return result except Exception as e: return f"Error analyzing colors: {str(e)}" def extract_text_info(image: Image.Image) -> str: """ Extract basic information about text-like content in an image. Args: image: The image to analyze for text content Returns: str: Basic information about potential text content """ if image is None: return "No image provided" try: # Convert to grayscale for analysis gray = image.convert('L') # Get image statistics extrema = gray.getextrema() # Simple heuristics for text detection contrast = extrema[1] - extrema[0] analysis = { "image_mode": image.mode, "grayscale_range": f"{extrema[0]} to {extrema[1]}", "contrast_level": "High" if contrast > 200 else "Medium" if contrast > 100 else "Low", "potential_text": "Likely contains text" if contrast > 150 else "May contain text" if contrast > 100 else "Unlikely to contain text", "note": "This is a basic analysis. For proper OCR, use specialized text extraction tools." } return json.dumps(analysis, indent=2) except Exception as e: return f"Error analyzing for text: {str(e)}" # Create the Gradio interface with gr.Blocks(title="Image Analysis MCP Server") as demo: gr.Markdown(""" # Image Analysis MCP Server This Gradio app serves as an MCP server that can analyze images sent from Claude or other MCP clients. **Available Tools:** - `analyze_image`: Get comprehensive image analysis (dimensions, format, colors, etc.) - `get_image_orientation`: Check if image is portrait, landscape, or square - `count_colors`: Analyze color information and dominant colors - `extract_text_info`: Basic analysis for potential text content **Usage with Claude Desktop:** 1. Deploy this to HuggingFace Spaces 2. Add the MCP configuration to Claude Desktop 3. Send images to Claude and ask it to analyze them using these tools """) # Create interface for each function (these will be exposed as MCP tools) with gr.Tab("Image Analysis"): with gr.Row(): img_input1 = gr.Image(type="pil", label="Upload Image") analysis_output = gr.JSON(label="Analysis Result") analyze_btn = gr.Button("Analyze Image") analyze_btn.click(analyze_image, inputs=[img_input1], outputs=[analysis_output]) with gr.Tab("Orientation Check"): with gr.Row(): img_input2 = gr.Image(type="pil", label="Upload Image") orientation_output = gr.Textbox(label="Orientation") orientation_btn = gr.Button("Check Orientation") orientation_btn.click(get_image_orientation, inputs=[img_input2], outputs=[orientation_output]) with gr.Tab("Color Analysis"): with gr.Row(): img_input3 = gr.Image(type="pil", label="Upload Image") color_output = gr.Textbox(label="Color Analysis", lines=10) color_btn = gr.Button("Analyze Colors") color_btn.click(count_colors, inputs=[img_input3], outputs=[color_output]) with gr.Tab("Text Detection"): with gr.Row(): img_input4 = gr.Image(type="pil", label="Upload Image") text_output = gr.JSON(label="Text Analysis") text_btn = gr.Button("Analyze for Text") text_btn.click(extract_text_info, inputs=[img_input4], outputs=[text_output]) if __name__ == "__main__": # Launch with MCP server enabled demo.launch(mcp_server=True, share=True)