Chris4K commited on
Commit
f7b1120
Β·
verified Β·
1 Parent(s): e169048

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +299 -174
app.py CHANGED
@@ -1,206 +1,331 @@
1
  import gradio as gr
2
- import PIL.Image as Image
3
- import io
4
  import base64
5
  import json
6
- from typing import Union
 
 
 
 
 
7
 
8
- def analyze_image(image: Image.Image) -> str:
9
- """
10
- Analyze an image and return detailed information about it.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
- Args:
13
- image: The image to analyze (can be base64 string or file upload)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
- Returns:
16
- str: JSON string with image analysis including dimensions, format, mode, and orientation
17
- """
18
- if image is None:
19
- return json.dumps({"error": "No image provided"})
20
-
21
- try:
22
- # Get image properties
23
- width, height = image.size
24
- format_type = image.format or "Unknown"
25
- mode = image.mode
26
- orientation = "Portrait" if height > width else "Landscape" if width > height else "Square"
27
 
28
- # Calculate aspect ratio
29
- aspect_ratio = round(width / height, 2) if height > 0 else 0
 
 
 
 
 
 
 
 
 
 
 
30
 
31
- # Get color information
32
- colors = image.getcolors(maxcolors=256*256*256)
33
- dominant_colors = len(colors) if colors else "Many"
34
 
35
- analysis = {
36
- "dimensions": {"width": width, "height": height},
37
- "format": format_type,
38
- "mode": mode,
39
- "orientation": orientation,
40
- "aspect_ratio": aspect_ratio,
41
- "approximate_colors": dominant_colors,
42
- "file_info": f"{width}x{height} {format_type} image in {mode} mode"
43
- }
 
 
 
 
44
 
45
- return json.dumps(analysis, indent=2)
 
46
 
47
- except Exception as e:
48
- return json.dumps({"error": f"Error analyzing image: {str(e)}"})
 
 
 
 
 
 
 
 
 
49
 
50
- def get_image_orientation(image: Image.Image) -> str:
51
- """
52
- Determine if an image is portrait, landscape, or square.
53
 
54
- Args:
55
- image: The image to check orientation
 
56
 
57
- Returns:
58
- str: "Portrait", "Landscape", or "Square"
59
- """
60
- if image is None:
61
- return "No image provided"
62
-
63
- try:
64
- width, height = image.size
65
- if height > width:
66
- return "Portrait"
67
- elif width > height:
68
- return "Landscape"
69
- else:
70
- return "Square"
71
- except Exception as e:
72
- return f"Error: {str(e)}"
73
-
74
- def count_colors(image: Image.Image) -> str:
75
- """
76
- Count the approximate number of unique colors in an image.
77
 
78
- Args:
79
- image: The image to analyze for color count
 
 
 
 
 
80
 
81
- Returns:
82
- str: Description of color count and dominant color information
83
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  if image is None:
85
- return "No image provided"
86
 
87
- try:
88
- # Convert to RGB if not already
89
- if image.mode != 'RGB':
90
- image = image.convert('RGB')
91
-
92
- # Get colors (limit to prevent memory issues)
93
- colors = image.getcolors(maxcolors=256*256*256)
94
-
95
- if colors is None:
96
- return "Image has more than 16.7 million unique colors"
97
-
98
- # Sort by frequency
99
- colors.sort(key=lambda x: x[0], reverse=True)
100
-
101
- # Get top 3 colors
102
- top_colors = colors[:3]
103
- color_info = []
104
-
105
- for count, color in top_colors:
106
- if isinstance(color, tuple) and len(color) >= 3:
107
- r, g, b = color[:3]
108
- hex_color = f"#{r:02x}{g:02x}{b:02x}"
109
- percentage = round((count / sum(c[0] for c in colors)) * 100, 1)
110
- color_info.append(f"RGB{color} ({hex_color}) - {percentage}%")
111
-
112
- result = f"Total unique colors: {len(colors)}\n"
113
- result += "Top colors by frequency:\n" + "\n".join(color_info)
114
-
115
- return result
116
-
117
- except Exception as e:
118
- return f"Error analyzing colors: {str(e)}"
119
 
120
- def extract_text_info(image: Image.Image) -> str:
121
- """
122
- Extract basic information about text-like content in an image.
 
 
 
 
 
 
123
 
124
- Args:
125
- image: The image to analyze for text content
 
 
 
 
 
 
 
 
126
 
127
- Returns:
128
- str: Basic information about potential text content
129
- """
130
- if image is None:
131
- return "No image provided"
132
 
133
- try:
134
- # Convert to grayscale for analysis
135
- gray = image.convert('L')
136
 
137
- # Get image statistics
138
- extrema = gray.getextrema()
139
-
140
- # Simple heuristics for text detection
141
- contrast = extrema[1] - extrema[0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
- analysis = {
144
- "image_mode": image.mode,
145
- "grayscale_range": f"{extrema[0]} to {extrema[1]}",
146
- "contrast_level": "High" if contrast > 200 else "Medium" if contrast > 100 else "Low",
147
- "potential_text": "Likely contains text" if contrast > 150 else "May contain text" if contrast > 100 else "Unlikely to contain text",
148
- "note": "This is a basic analysis. For proper OCR, use specialized text extraction tools."
149
- }
150
 
151
- return json.dumps(analysis, indent=2)
 
 
 
 
 
 
 
 
 
 
 
 
152
 
153
- except Exception as e:
154
- return f"Error analyzing for text: {str(e)}"
155
-
156
- # Create the Gradio interface
157
- with gr.Blocks(title="Image Analysis MCP Server") as demo:
158
- gr.Markdown("""
159
- # Image Analysis MCP Server
160
-
161
- This Gradio app serves as an MCP server that can analyze images sent from Claude or other MCP clients.
162
-
163
- **Available Tools:**
164
- - `analyze_image`: Get comprehensive image analysis (dimensions, format, colors, etc.)
165
- - `get_image_orientation`: Check if image is portrait, landscape, or square
166
- - `count_colors`: Analyze color information and dominant colors
167
- - `extract_text_info`: Basic analysis for potential text content
168
-
169
- **Usage with Claude Desktop:**
170
- 1. Deploy this to HuggingFace Spaces
171
- 2. Add the MCP configuration to Claude Desktop
172
- 3. Send images to Claude and ask it to analyze them using these tools
173
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
- # Create interface for each function (these will be exposed as MCP tools)
176
- with gr.Tab("Image Analysis"):
177
- with gr.Row():
178
- img_input1 = gr.Image(type="pil", label="Upload Image")
179
- analysis_output = gr.JSON(label="Analysis Result")
180
- analyze_btn = gr.Button("Analyze Image")
181
- analyze_btn.click(analyze_image, inputs=[img_input1], outputs=[analysis_output])
182
 
183
- with gr.Tab("Orientation Check"):
184
- with gr.Row():
185
- img_input2 = gr.Image(type="pil", label="Upload Image")
186
- orientation_output = gr.Textbox(label="Orientation")
187
- orientation_btn = gr.Button("Check Orientation")
188
- orientation_btn.click(get_image_orientation, inputs=[img_input2], outputs=[orientation_output])
189
 
190
- with gr.Tab("Color Analysis"):
191
- with gr.Row():
192
- img_input3 = gr.Image(type="pil", label="Upload Image")
193
- color_output = gr.Textbox(label="Color Analysis", lines=10)
194
- color_btn = gr.Button("Analyze Colors")
195
- color_btn.click(count_colors, inputs=[img_input3], outputs=[color_output])
196
 
197
- with gr.Tab("Text Detection"):
198
- with gr.Row():
199
- img_input4 = gr.Image(type="pil", label="Upload Image")
200
- text_output = gr.JSON(label="Text Analysis")
201
- text_btn = gr.Button("Analyze for Text")
202
- text_btn.click(extract_text_info, inputs=[img_input4], outputs=[text_output])
 
 
 
 
 
 
203
 
 
204
  if __name__ == "__main__":
205
- # Launch with MCP server enabled
206
- demo.launch(mcp_server=True, share=True)
 
 
 
 
1
  import gradio as gr
 
 
2
  import base64
3
  import json
4
+ import requests
5
+ from io import BytesIO
6
+ from PIL import Image
7
+ import traceback
8
+ from gradio_client import Client
9
+ from typing import Optional, Tuple, Dict, Any
10
 
11
+ class MCPImageAnalyzer:
12
+ def __init__(self, space_url: str = "https://chris4k-mcp-images.hf.space"):
13
+ """Initialize the MCP Image Analyzer client."""
14
+ self.space_url = space_url.rstrip('/')
15
+ self.client = None
16
+ self.connection_status = "Disconnected"
17
+
18
+ def connect(self) -> Tuple[str, str]:
19
+ """Connect to the MCP server."""
20
+ try:
21
+ self.client = Client(self.space_url)
22
+ # Test connection by checking if we can get the client info
23
+ self.connection_status = "Connected βœ…"
24
+ return f"βœ… Successfully connected to {self.space_url}", "success"
25
+ except Exception as e:
26
+ self.connection_status = "Connection Failed ❌"
27
+ return f"❌ Failed to connect to {self.space_url}: {str(e)}", "error"
28
 
29
+ def analyze_image(self, image: Image.Image) -> Dict[str, Any]:
30
+ """Analyze an image using the MCP server."""
31
+ if not self.client:
32
+ return {"error": "Not connected to MCP server. Please connect first."}
33
+
34
+ if image is None:
35
+ return {"error": "No image provided"}
36
+
37
+ try:
38
+ result = self.client.predict(
39
+ image=image,
40
+ api_name="/analyze_image"
41
+ )
42
+ return json.loads(result) if isinstance(result, str) else result
43
+ except Exception as e:
44
+ return {"error": f"Analysis failed: {str(e)}"}
45
 
46
+ def get_orientation(self, image: Image.Image) -> str:
47
+ """Get image orientation using the MCP server."""
48
+ if not self.client:
49
+ return "❌ Not connected to MCP server"
50
+
51
+ if image is None:
52
+ return "❌ No image provided"
 
 
 
 
 
53
 
54
+ try:
55
+ result = self.client.predict(
56
+ image=image,
57
+ api_name="/get_image_orientation"
58
+ )
59
+ return f"πŸ“ Orientation: {result}"
60
+ except Exception as e:
61
+ return f"❌ Error: {str(e)}"
62
+
63
+ def analyze_colors(self, image: Image.Image) -> str:
64
+ """Analyze colors using the MCP server."""
65
+ if not self.client:
66
+ return "❌ Not connected to MCP server"
67
 
68
+ if image is None:
69
+ return "❌ No image provided"
 
70
 
71
+ try:
72
+ result = self.client.predict(
73
+ image=image,
74
+ api_name="/count_colors"
75
+ )
76
+ return f"🎨 Color Analysis:\n{result}"
77
+ except Exception as e:
78
+ return f"❌ Error: {str(e)}"
79
+
80
+ def extract_text_info(self, image: Image.Image) -> Dict[str, Any]:
81
+ """Extract text info using the MCP server."""
82
+ if not self.client:
83
+ return {"error": "Not connected to MCP server"}
84
 
85
+ if image is None:
86
+ return {"error": "No image provided"}
87
 
88
+ try:
89
+ result = self.client.predict(
90
+ image=image,
91
+ api_name="/extract_text_info"
92
+ )
93
+ return json.loads(result) if isinstance(result, str) else result
94
+ except Exception as e:
95
+ return {"error": f"Text analysis failed: {str(e)}"}
96
+
97
+ # Initialize the analyzer
98
+ analyzer = MCPImageAnalyzer()
99
 
100
+ def create_sample_images():
101
+ """Create sample test images."""
102
+ samples = {}
103
 
104
+ # Red rectangle
105
+ img1 = Image.new('RGB', (400, 300), color='red')
106
+ samples["Red Rectangle (400x300)"] = img1
107
 
108
+ # Blue square
109
+ img2 = Image.new('RGB', (300, 300), color='blue')
110
+ samples["Blue Square (300x300)"] = img2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
+ # Colorful gradient
113
+ img3 = Image.new('RGB', (200, 400))
114
+ pixels = img3.load()
115
+ for i in range(200):
116
+ for j in range(400):
117
+ pixels[i, j] = (i % 256, j % 256, (i + j) % 256)
118
+ samples["Colorful Gradient (200x400)"] = img3
119
 
120
+ # Simple pattern
121
+ img4 = Image.new('RGB', (100, 100), color='white')
122
+ pixels = img4.load()
123
+ for i in range(100):
124
+ for j in range(100):
125
+ if (i // 10 + j // 10) % 2:
126
+ pixels[i, j] = (0, 0, 0)
127
+ samples["Checkerboard Pattern (100x100)"] = img4
128
+
129
+ return samples
130
+
131
+ def connect_to_server():
132
+ """Connect to the MCP server."""
133
+ status, status_type = analyzer.connect()
134
+ if status_type == "success":
135
+ return status, gr.update(variant="primary"), gr.update(visible=True)
136
+ else:
137
+ return status, gr.update(variant="stop"), gr.update(visible=False)
138
+
139
+ def run_comprehensive_analysis(image):
140
+ """Run all analysis functions on the uploaded image."""
141
  if image is None:
142
+ return "❌ Please upload an image first", "", "", ""
143
 
144
+ # Run all analyses
145
+ analysis = analyzer.analyze_image(image)
146
+ orientation = analyzer.get_orientation(image)
147
+ colors = analyzer.analyze_colors(image)
148
+ text_info = analyzer.extract_text_info(image)
149
+
150
+ # Format results
151
+ analysis_result = json.dumps(analysis, indent=2) if isinstance(analysis, dict) else str(analysis)
152
+ text_result = json.dumps(text_info, indent=2) if isinstance(text_info, dict) else str(text_info)
153
+
154
+ return analysis_result, orientation, colors, text_result
155
+
156
+ def load_sample_image(sample_name):
157
+ """Load a sample image."""
158
+ samples = create_sample_images()
159
+ return samples.get(sample_name, None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
+ # Create the Gradio interface
162
+ with gr.Blocks(title="MCP Image Analysis Test Client", theme=gr.themes.Soft()) as demo:
163
+ gr.HTML("""
164
+ <div style="text-align: center; padding: 20px;">
165
+ <h1>πŸ–ΌοΈ MCP Image Analysis Test Client</h1>
166
+ <p>Test your Gradio MCP Image Analysis server with this interactive client</p>
167
+ <p><strong>Server:</strong> <code>https://chris4k-mcp-images.hf.space</code></p>
168
+ </div>
169
+ """)
170
 
171
+ # Connection section
172
+ with gr.Row():
173
+ with gr.Column():
174
+ gr.Markdown("## πŸ”Œ Connection")
175
+ connect_btn = gr.Button("Connect to MCP Server", variant="primary", size="lg")
176
+ connection_status = gr.Textbox(
177
+ label="Connection Status",
178
+ value="Not connected",
179
+ interactive=False
180
+ )
181
 
182
+ # Main testing interface (initially hidden)
183
+ main_interface = gr.Column(visible=False)
 
 
 
184
 
185
+ with main_interface:
186
+ gr.Markdown("## πŸ§ͺ Image Analysis Testing")
 
187
 
188
+ with gr.Row():
189
+ with gr.Column(scale=1):
190
+ gr.Markdown("### πŸ“€ Upload Image")
191
+ image_input = gr.Image(
192
+ label="Upload Image for Analysis",
193
+ type="pil",
194
+ height=300
195
+ )
196
+
197
+ gr.Markdown("### 🎯 Quick Test Samples")
198
+ sample_dropdown = gr.Dropdown(
199
+ choices=list(create_sample_images().keys()),
200
+ label="Load Sample Image",
201
+ value=None
202
+ )
203
+ load_sample_btn = gr.Button("Load Sample", size="sm")
204
+
205
+ gr.Markdown("### πŸš€ Run Analysis")
206
+ analyze_btn = gr.Button("Analyze Image", variant="primary", size="lg")
207
+
208
+ with gr.Column(scale=2):
209
+ gr.Markdown("### πŸ“Š Analysis Results")
210
+
211
+ with gr.Tabs():
212
+ with gr.Tab("πŸ“ˆ Comprehensive Analysis"):
213
+ analysis_output = gr.Code(
214
+ label="Full Image Analysis",
215
+ language="json",
216
+ lines=15
217
+ )
218
+
219
+ with gr.Tab("πŸ“ Orientation"):
220
+ orientation_output = gr.Textbox(
221
+ label="Image Orientation",
222
+ lines=3
223
+ )
224
+
225
+ with gr.Tab("🎨 Color Analysis"):
226
+ color_output = gr.Textbox(
227
+ label="Color Information",
228
+ lines=10
229
+ )
230
+
231
+ with gr.Tab("πŸ“ Text Detection"):
232
+ text_output = gr.Code(
233
+ label="Text Analysis",
234
+ language="json",
235
+ lines=10
236
+ )
237
 
238
+ # Individual tool testing section
239
+ gr.Markdown("## πŸ”§ Individual Tool Testing")
 
 
 
 
 
240
 
241
+ with gr.Row():
242
+ with gr.Column():
243
+ gr.Markdown("### Single Tool Tests")
244
+ single_image = gr.Image(label="Image for Single Tool Test", type="pil", height=200)
245
+
246
+ with gr.Row():
247
+ orient_btn = gr.Button("Check Orientation", size="sm")
248
+ color_btn = gr.Button("Analyze Colors", size="sm")
249
+
250
+ single_result = gr.Textbox(
251
+ label="Single Tool Result",
252
+ lines=5
253
+ )
254
 
255
+ # Usage examples and help
256
+ with gr.Accordion("πŸ“– Usage Guide & Examples", open=False):
257
+ gr.Markdown("""
258
+ ## How to Use This Test Client
259
+
260
+ 1. **Connect**: Click "Connect to MCP Server" to establish connection
261
+ 2. **Upload Image**: Use the image upload area or load a sample image
262
+ 3. **Analyze**: Click "Analyze Image" to run all analysis tools
263
+ 4. **Review Results**: Check different tabs for specific analysis results
264
+
265
+ ## Available Analysis Tools
266
+
267
+ - **πŸ“ˆ Comprehensive Analysis**: Complete image metadata (dimensions, format, colors, etc.)
268
+ - **πŸ“ Orientation Detection**: Portrait, Landscape, or Square
269
+ - **🎨 Color Analysis**: Dominant colors and color count
270
+ - **πŸ“ Text Detection**: Basic text presence analysis
271
+
272
+ ## Sample Images
273
+
274
+ Try the built-in sample images to test different scenarios:
275
+ - Different orientations (portrait vs landscape)
276
+ - Various color schemes
277
+ - Different dimensions and formats
278
+
279
+ ## Testing with Claude Desktop
280
+
281
+ This same MCP server can be used with Claude Desktop by adding this configuration:
282
+
283
+ ```json
284
+ {
285
+ "mcpServers": {
286
+ "image-analysis": {
287
+ "url": "https://chris4k-mcp-images.hf.space/gradio_api/mcp/sse"
288
+ }
289
+ }
290
+ }
291
+ ```
292
+ """)
293
 
294
+ # Event handlers
295
+ connect_btn.click(
296
+ connect_to_server,
297
+ outputs=[connection_status, connect_btn, main_interface]
298
+ )
 
 
299
 
300
+ load_sample_btn.click(
301
+ load_sample_image,
302
+ inputs=[sample_dropdown],
303
+ outputs=[image_input]
304
+ )
 
305
 
306
+ analyze_btn.click(
307
+ run_comprehensive_analysis,
308
+ inputs=[image_input],
309
+ outputs=[analysis_output, orientation_output, color_output, text_output]
310
+ )
 
311
 
312
+ # Individual tool tests
313
+ orient_btn.click(
314
+ analyzer.get_orientation,
315
+ inputs=[single_image],
316
+ outputs=[single_result]
317
+ )
318
+
319
+ color_btn.click(
320
+ analyzer.analyze_colors,
321
+ inputs=[single_image],
322
+ outputs=[single_result]
323
+ )
324
 
325
+ # Launch the app
326
  if __name__ == "__main__":
327
+ demo.launch(
328
+ debug=True,
329
+ share=True,
330
+ show_error=True
331
+ )