Create vision_analysis.py
Browse files- vision_analysis.py +670 -0
vision_analysis.py
ADDED
@@ -0,0 +1,670 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
TinkerIQ Advanced Vision Analysis Module
|
3 |
+
Circuit and schematic analysis using SambaNova and OpenAI vision models
|
4 |
+
"""
|
5 |
+
|
6 |
+
import os
|
7 |
+
import requests
|
8 |
+
import base64
|
9 |
+
import time
|
10 |
+
from PIL import Image
|
11 |
+
import io
|
12 |
+
import re
|
13 |
+
from datetime import datetime
|
14 |
+
|
15 |
+
class AdvancedVisionAnalysis:
|
16 |
+
"""Advanced computer vision for electronic circuits and schematics"""
|
17 |
+
|
18 |
+
def __init__(self):
|
19 |
+
# API credentials
|
20 |
+
self.sambanova_api_key = os.getenv("SAMBANOVA_API_KEY")
|
21 |
+
self.openai_api_key = os.getenv("OPENAI_API_KEY")
|
22 |
+
self.anthropic_api_key = os.getenv("ANTHROPIC_API_KEY")
|
23 |
+
|
24 |
+
# API endpoints
|
25 |
+
self.sambanova_url = "https://api.sambanova.ai/v1/chat/completions"
|
26 |
+
self.openai_vision_url = "https://api.openai.com/v1/chat/completions"
|
27 |
+
self.anthropic_url = "https://api.anthropic.com/v1/messages"
|
28 |
+
|
29 |
+
# Analysis templates for different circuit types
|
30 |
+
self.analysis_prompts = {
|
31 |
+
"detailed": """You are an expert electronics engineer analyzing a circuit image. Provide a comprehensive technical analysis:
|
32 |
+
|
33 |
+
## COMPONENT IDENTIFICATION
|
34 |
+
- List ALL visible electronic components with specific part numbers where readable
|
35 |
+
- Include passive components (resistors, capacitors, inductors) with values if visible
|
36 |
+
- Identify active components (ICs, transistors, diodes) with part numbers
|
37 |
+
- Note any development boards (Arduino, ESP32, Raspberry Pi, etc.)
|
38 |
+
- Identify connectors, switches, and mechanical components
|
39 |
+
|
40 |
+
## CIRCUIT TOPOLOGY ANALYSIS
|
41 |
+
- Describe the overall circuit architecture and design pattern
|
42 |
+
- Explain the signal flow and data paths through the circuit
|
43 |
+
- Identify power supply connections and voltage levels
|
44 |
+
- Note ground connections and power distribution
|
45 |
+
- Describe any special circuit configurations or topologies
|
46 |
+
|
47 |
+
## FUNCTIONAL ANALYSIS
|
48 |
+
- Explain what this circuit is designed to accomplish
|
49 |
+
- Describe the main functional blocks and their purposes
|
50 |
+
- Identify input/output connections and interfaces
|
51 |
+
- Explain any control or feedback mechanisms
|
52 |
+
- Note any protection circuits or safety features
|
53 |
+
|
54 |
+
## TECHNICAL ASSESSMENT
|
55 |
+
- Evaluate if connections appear correct for the intended function
|
56 |
+
- Identify any obvious wiring errors or missing connections
|
57 |
+
- Check for proper component orientations (especially polarized components)
|
58 |
+
- Assess power supply adequacy and current handling
|
59 |
+
- Note any potential signal integrity or noise issues
|
60 |
+
|
61 |
+
## IMPROVEMENT RECOMMENDATIONS
|
62 |
+
- Suggest specific component upgrades or alternatives
|
63 |
+
- Recommend additional protection or filtering components
|
64 |
+
- Propose layout improvements for better performance
|
65 |
+
- Suggest debugging aids or test points
|
66 |
+
- Recommend documentation or labeling improvements
|
67 |
+
|
68 |
+
## SAFETY AND COMPLIANCE
|
69 |
+
- Identify any safety concerns or hazardous conditions
|
70 |
+
- Note compliance with standard practices and conventions
|
71 |
+
- Suggest safety improvements or protective measures
|
72 |
+
- Check for proper isolation and grounding
|
73 |
+
|
74 |
+
Be extremely specific and technical. Include part numbers, values, and precise technical terminology.""",
|
75 |
+
|
76 |
+
"troubleshooting": """Analyze this circuit image for troubleshooting purposes. Focus on identifying potential problems:
|
77 |
+
|
78 |
+
## VISUAL INSPECTION
|
79 |
+
- Check all solder joints for cold solder, bridges, or dry joints
|
80 |
+
- Verify component orientations (LEDs, electrolytic capacitors, ICs, diodes)
|
81 |
+
- Look for damaged components (burned, cracked, or discolored)
|
82 |
+
- Check for loose connections or disconnected wires
|
83 |
+
- Identify any short circuits or unwanted connections
|
84 |
+
|
85 |
+
## WIRING VERIFICATION
|
86 |
+
- Trace power connections (VCC, VDD, +5V, +3.3V, etc.)
|
87 |
+
- Verify ground connections and ground loops
|
88 |
+
- Check data/signal line connections
|
89 |
+
- Validate pin assignments match intended design
|
90 |
+
- Look for crossed or swapped connections
|
91 |
+
|
92 |
+
## COMPONENT ANALYSIS
|
93 |
+
- Verify component values match circuit requirements
|
94 |
+
- Check component ratings (voltage, current, power)
|
95 |
+
- Identify any substituted or incorrect components
|
96 |
+
- Note missing components or empty footprints
|
97 |
+
- Check for counterfeit or suspect components
|
98 |
+
|
99 |
+
## COMMON FAILURE MODES
|
100 |
+
- Identify typical failure points for this circuit type
|
101 |
+
- Look for stress indicators (heat damage, corrosion)
|
102 |
+
- Check for mechanical damage or wear
|
103 |
+
- Note any signs of overcurrent or overvoltage damage
|
104 |
+
- Identify environmental damage (moisture, contamination)
|
105 |
+
|
106 |
+
## DEBUGGING RECOMMENDATIONS
|
107 |
+
- Suggest specific measurement points for multimeter testing
|
108 |
+
- Recommend signal tracing procedures
|
109 |
+
- Propose component substitution tests
|
110 |
+
- Suggest isolation techniques for fault finding
|
111 |
+
- Recommend tools or equipment for further diagnosis
|
112 |
+
|
113 |
+
Focus on actionable troubleshooting steps and specific technical guidance.""",
|
114 |
+
|
115 |
+
"educational": """Analyze this circuit from an educational perspective for learning purposes:
|
116 |
+
|
117 |
+
## CIRCUIT FUNDAMENTALS
|
118 |
+
- Explain the basic operating principles in clear terms
|
119 |
+
- Identify key concepts demonstrated by this circuit
|
120 |
+
- Describe the role of each major component type
|
121 |
+
- Explain power flow and signal paths
|
122 |
+
- Connect theory to practical implementation
|
123 |
+
|
124 |
+
## LEARNING OBJECTIVES
|
125 |
+
- What skills does building this circuit teach?
|
126 |
+
- What theoretical concepts are demonstrated?
|
127 |
+
- How does this relate to broader electronics knowledge?
|
128 |
+
- What prerequisites should learners have?
|
129 |
+
- What follow-up projects would build on this knowledge?
|
130 |
+
|
131 |
+
## COMPONENT EDUCATION
|
132 |
+
- Explain why each component was chosen
|
133 |
+
- Describe component specifications and ratings
|
134 |
+
- Suggest alternatives and their trade-offs
|
135 |
+
- Explain how to read component markings and datasheets
|
136 |
+
- Discuss component sourcing and selection criteria
|
137 |
+
|
138 |
+
## CONSTRUCTION GUIDANCE
|
139 |
+
- Provide step-by-step assembly recommendations
|
140 |
+
- Highlight critical construction points
|
141 |
+
- Suggest testing procedures at each stage
|
142 |
+
- Recommend tools and techniques
|
143 |
+
- Identify common beginner mistakes to avoid
|
144 |
+
|
145 |
+
## EXPERIMENTATION IDEAS
|
146 |
+
- Suggest modifications to explore different behaviors
|
147 |
+
- Propose parameter variations for learning
|
148 |
+
- Recommend additional measurements or observations
|
149 |
+
- Suggest related experiments or variations
|
150 |
+
- Provide ideas for extending the project
|
151 |
+
|
152 |
+
Make the analysis accessible but technically accurate, perfect for STEM education."""
|
153 |
+
}
|
154 |
+
|
155 |
+
# Response validation patterns
|
156 |
+
self.validation_patterns = {
|
157 |
+
"generic_responses": [
|
158 |
+
"i'm unable to view",
|
159 |
+
"i cannot see",
|
160 |
+
"i can't see the image",
|
161 |
+
"i don't have the ability to view",
|
162 |
+
"i cannot analyze images",
|
163 |
+
"without seeing the actual",
|
164 |
+
"based on the description",
|
165 |
+
"typical circuit",
|
166 |
+
"common configuration",
|
167 |
+
"standard setup"
|
168 |
+
],
|
169 |
+
"hallucination_indicators": [
|
170 |
+
"appears to be",
|
171 |
+
"seems to be",
|
172 |
+
"looks like it might be",
|
173 |
+
"could be",
|
174 |
+
"possibly",
|
175 |
+
"probably"
|
176 |
+
]
|
177 |
+
}
|
178 |
+
|
179 |
+
print(f"🔧 Vision Analysis initialized")
|
180 |
+
print(f" SambaNova API: {'✅' if self.sambanova_api_key else '❌'}")
|
181 |
+
print(f" OpenAI API: {'✅' if self.openai_api_key else '❌'}")
|
182 |
+
print(f" Anthropic API: {'✅' if self.anthropic_api_key else '❌'}")
|
183 |
+
|
184 |
+
def encode_image_safely(self, image_path, max_size=1024, quality=85):
|
185 |
+
"""
|
186 |
+
Convert image to base64 with optimization for vision APIs
|
187 |
+
|
188 |
+
Args:
|
189 |
+
image_path: Path to image file or file-like object
|
190 |
+
max_size: Maximum dimension for resizing
|
191 |
+
quality: JPEG quality (1-100)
|
192 |
+
|
193 |
+
Returns:
|
194 |
+
tuple: (base64_string, error_message)
|
195 |
+
"""
|
196 |
+
try:
|
197 |
+
if image_path is None:
|
198 |
+
return None, "No image provided"
|
199 |
+
|
200 |
+
# Handle different input types
|
201 |
+
if hasattr(image_path, 'name') and image_path.name:
|
202 |
+
# Gradio file upload
|
203 |
+
image_file_path = image_path.name
|
204 |
+
print(f"📁 Processing Gradio file: {image_file_path}")
|
205 |
+
elif isinstance(image_path, str):
|
206 |
+
# File path string
|
207 |
+
image_file_path = image_path
|
208 |
+
print(f"📁 Processing file path: {image_file_path}")
|
209 |
+
else:
|
210 |
+
return None, "Unsupported image format"
|
211 |
+
|
212 |
+
# Open and process image
|
213 |
+
with Image.open(image_file_path) as img:
|
214 |
+
# Get original dimensions
|
215 |
+
original_size = img.size
|
216 |
+
print(f"📐 Original image size: {original_size[0]}x{original_size[1]}")
|
217 |
+
|
218 |
+
# Convert to RGB if necessary
|
219 |
+
if img.mode in ('RGBA', 'LA', 'P'):
|
220 |
+
print(f"🔄 Converting from {img.mode} to RGB")
|
221 |
+
img = img.convert('RGB')
|
222 |
+
|
223 |
+
# Resize if too large
|
224 |
+
if img.width > max_size or img.height > max_size:
|
225 |
+
print(f"📏 Resizing to max dimension: {max_size}")
|
226 |
+
img.thumbnail((max_size, max_size), Image.Resampling.LANCZOS)
|
227 |
+
print(f"📐 New size: {img.width}x{img.height}")
|
228 |
+
|
229 |
+
# Convert to bytes with optimization
|
230 |
+
buffer = io.BytesIO()
|
231 |
+
|
232 |
+
# Use PNG for line drawings/schematics, JPEG for photos
|
233 |
+
if self._is_schematic_like(img):
|
234 |
+
img.save(buffer, format='PNG', optimize=True)
|
235 |
+
format_used = "PNG"
|
236 |
+
else:
|
237 |
+
img.save(buffer, format='JPEG', quality=quality, optimize=True)
|
238 |
+
format_used = "JPEG"
|
239 |
+
|
240 |
+
image_data = buffer.getvalue()
|
241 |
+
file_size = len(image_data)
|
242 |
+
|
243 |
+
print(f"✅ Image processed: {file_size} bytes ({format_used})")
|
244 |
+
|
245 |
+
# Check size limits (most APIs have ~20MB limit)
|
246 |
+
if file_size > 15 * 1024 * 1024: # 15MB safety margin
|
247 |
+
return None, f"Image too large: {file_size/1024/1024:.1f}MB (max ~15MB)"
|
248 |
+
|
249 |
+
# Encode to base64
|
250 |
+
base64_string = base64.b64encode(image_data).decode('utf-8')
|
251 |
+
|
252 |
+
return base64_string, None
|
253 |
+
|
254 |
+
except FileNotFoundError:
|
255 |
+
return None, f"Image file not found: {image_path}"
|
256 |
+
except Exception as e:
|
257 |
+
error_msg = f"Image processing error: {str(e)}"
|
258 |
+
print(f"❌ {error_msg}")
|
259 |
+
return None, error_msg
|
260 |
+
|
261 |
+
def _is_schematic_like(self, img):
|
262 |
+
"""Heuristic to determine if image is a schematic vs photo"""
|
263 |
+
# Convert to grayscale for analysis
|
264 |
+
gray = img.convert('L')
|
265 |
+
|
266 |
+
# Count unique colors (schematics typically have fewer)
|
267 |
+
colors = len(set(gray.getdata()))
|
268 |
+
|
269 |
+
# Schematics typically have high contrast and few colors
|
270 |
+
return colors < 50
|
271 |
+
|
272 |
+
def validate_analysis_response(self, response_text, analysis_type="detailed"):
|
273 |
+
"""
|
274 |
+
Validate that the AI actually analyzed the image vs providing generic advice
|
275 |
+
|
276 |
+
Args:
|
277 |
+
response_text: AI response to validate
|
278 |
+
analysis_type: Type of analysis requested
|
279 |
+
|
280 |
+
Returns:
|
281 |
+
tuple: (is_valid, error_message)
|
282 |
+
"""
|
283 |
+
response_lower = response_text.lower()
|
284 |
+
|
285 |
+
# Check for generic/inability responses
|
286 |
+
for pattern in self.validation_patterns["generic_responses"]:
|
287 |
+
if pattern in response_lower:
|
288 |
+
return False, f"Generic response detected: '{pattern}'"
|
289 |
+
|
290 |
+
# Check response length (too short suggests generic response)
|
291 |
+
if len(response_text.strip()) < 200:
|
292 |
+
return False, "Response too short/generic"
|
293 |
+
|
294 |
+
# Check for excessive uncertainty language
|
295 |
+
uncertainty_count = sum(1 for pattern in self.validation_patterns["hallucination_indicators"]
|
296 |
+
if pattern in response_lower)
|
297 |
+
|
298 |
+
if uncertainty_count > 5: # Too many uncertain statements
|
299 |
+
return False, f"Excessive uncertainty in response ({uncertainty_count} indicators)"
|
300 |
+
|
301 |
+
# Check for specific technical content based on analysis type
|
302 |
+
if analysis_type == "detailed":
|
303 |
+
required_sections = ["component", "circuit", "connection"]
|
304 |
+
found_sections = sum(1 for section in required_sections if section in response_lower)
|
305 |
+
|
306 |
+
if found_sections < 2:
|
307 |
+
return False, "Response lacks technical depth"
|
308 |
+
|
309 |
+
print("✅ Response validation passed")
|
310 |
+
return True, None
|
311 |
+
|
312 |
+
def analyze_with_sambanova(self, image_path, analysis_type="detailed", custom_prompt=None):
|
313 |
+
"""
|
314 |
+
Analyze circuit image using SambaNova vision model
|
315 |
+
|
316 |
+
Args:
|
317 |
+
image_path: Path to image file
|
318 |
+
analysis_type: Type of analysis (detailed, troubleshooting, educational)
|
319 |
+
custom_prompt: Custom analysis prompt
|
320 |
+
|
321 |
+
Returns:
|
322 |
+
tuple: (result_dict, error_message)
|
323 |
+
"""
|
324 |
+
print("🚀 Starting SambaNova vision analysis...")
|
325 |
+
|
326 |
+
if not self.sambanova_api_key:
|
327 |
+
return None, "SambaNova API key not configured"
|
328 |
+
|
329 |
+
# Encode image
|
330 |
+
base64_image, error = self.encode_image_safely(image_path)
|
331 |
+
if error:
|
332 |
+
return None, f"Image processing failed: {error}"
|
333 |
+
|
334 |
+
try:
|
335 |
+
# Select prompt
|
336 |
+
if custom_prompt:
|
337 |
+
prompt = custom_prompt
|
338 |
+
else:
|
339 |
+
prompt = self.analysis_prompts.get(analysis_type, self.analysis_prompts["detailed"])
|
340 |
+
|
341 |
+
# Prepare request
|
342 |
+
headers = {
|
343 |
+
"Authorization": f"Bearer {self.sambanova_api_key}",
|
344 |
+
"Content-Type": "application/json"
|
345 |
+
}
|
346 |
+
|
347 |
+
payload = {
|
348 |
+
"model": "Llama-3.2-90B-Vision-Instruct",
|
349 |
+
"messages": [
|
350 |
+
{
|
351 |
+
"role": "user",
|
352 |
+
"content": [
|
353 |
+
{"type": "text", "text": prompt},
|
354 |
+
{
|
355 |
+
"type": "image_url",
|
356 |
+
"image_url": {
|
357 |
+
"url": f"data:image/jpeg;base64,{base64_image}"
|
358 |
+
}
|
359 |
+
}
|
360 |
+
]
|
361 |
+
}
|
362 |
+
],
|
363 |
+
"max_tokens": 2000,
|
364 |
+
"temperature": 0.1, # Low temperature for technical accuracy
|
365 |
+
"top_p": 0.9
|
366 |
+
}
|
367 |
+
|
368 |
+
print("📡 Sending request to SambaNova...")
|
369 |
+
response = requests.post(
|
370 |
+
self.sambanova_url,
|
371 |
+
headers=headers,
|
372 |
+
json=payload,
|
373 |
+
timeout=120 # Longer timeout for vision processing
|
374 |
+
)
|
375 |
+
|
376 |
+
print(f"📊 SambaNova response: HTTP {response.status_code}")
|
377 |
+
|
378 |
+
if response.status_code == 200:
|
379 |
+
result = response.json()
|
380 |
+
|
381 |
+
if "choices" in result and len(result["choices"]) > 0:
|
382 |
+
analysis = result["choices"][0]["message"]["content"]
|
383 |
+
|
384 |
+
# Validate response quality
|
385 |
+
is_valid, validation_error = self.validate_analysis_response(analysis, analysis_type)
|
386 |
+
|
387 |
+
if is_valid:
|
388 |
+
print("✅ SambaNova analysis validated successfully")
|
389 |
+
return {
|
390 |
+
"success": True,
|
391 |
+
"analysis": analysis,
|
392 |
+
"provider": "SambaNova",
|
393 |
+
"model": "Llama-3.2-90B-Vision-Instruct",
|
394 |
+
"analysis_type": analysis_type,
|
395 |
+
"timestamp": datetime.now().isoformat()
|
396 |
+
}, None
|
397 |
+
else:
|
398 |
+
print(f"❌ SambaNova validation failed: {validation_error}")
|
399 |
+
return None, f"SambaNova analysis validation failed: {validation_error}"
|
400 |
+
else:
|
401 |
+
return None, "No analysis content in SambaNova response"
|
402 |
+
else:
|
403 |
+
error_text = response.text
|
404 |
+
print(f"❌ SambaNova API error: {error_text}")
|
405 |
+
return None, f"SambaNova API error {response.status_code}: {error_text}"
|
406 |
+
|
407 |
+
except requests.exceptions.Timeout:
|
408 |
+
return None, "SambaNova request timeout - try again"
|
409 |
+
except requests.exceptions.RequestException as e:
|
410 |
+
return None, f"SambaNova request failed: {str(e)}"
|
411 |
+
except Exception as e:
|
412 |
+
return None, f"SambaNova analysis error: {str(e)}"
|
413 |
+
|
414 |
+
def analyze_with_openai(self, image_path, analysis_type="detailed", custom_prompt=None):
|
415 |
+
"""
|
416 |
+
Analyze circuit image using OpenAI vision model
|
417 |
+
|
418 |
+
Args:
|
419 |
+
image_path: Path to image file
|
420 |
+
analysis_type: Type of analysis
|
421 |
+
custom_prompt: Custom analysis prompt
|
422 |
+
|
423 |
+
Returns:
|
424 |
+
tuple: (result_dict, error_message)
|
425 |
+
"""
|
426 |
+
print("🔄 Starting OpenAI vision analysis...")
|
427 |
+
|
428 |
+
if not self.openai_api_key:
|
429 |
+
return None, "OpenAI API key not configured"
|
430 |
+
|
431 |
+
# Encode image
|
432 |
+
base64_image, error = self.encode_image_safely(image_path)
|
433 |
+
if error:
|
434 |
+
return None, f"Image processing failed: {error}"
|
435 |
+
|
436 |
+
try:
|
437 |
+
# Select prompt
|
438 |
+
if custom_prompt:
|
439 |
+
prompt = custom_prompt
|
440 |
+
else:
|
441 |
+
prompt = self.analysis_prompts.get(analysis_type, self.analysis_prompts["detailed"])
|
442 |
+
|
443 |
+
headers = {
|
444 |
+
"Content-Type": "application/json",
|
445 |
+
"Authorization": f"Bearer {self.openai_api_key}"
|
446 |
+
}
|
447 |
+
|
448 |
+
payload = {
|
449 |
+
"model": "gpt-4o",
|
450 |
+
"messages": [
|
451 |
+
{
|
452 |
+
"role": "user",
|
453 |
+
"content": [
|
454 |
+
{"type": "text", "text": prompt},
|
455 |
+
{
|
456 |
+
"type": "image_url",
|
457 |
+
"image_url": {
|
458 |
+
"url": f"data:image/jpeg;base64,{base64_image}",
|
459 |
+
"detail": "high"
|
460 |
+
}
|
461 |
+
}
|
462 |
+
]
|
463 |
+
}
|
464 |
+
],
|
465 |
+
"max_tokens": 2000,
|
466 |
+
"temperature": 0.1
|
467 |
+
}
|
468 |
+
|
469 |
+
print("📡 Sending request to OpenAI...")
|
470 |
+
response = requests.post(self.openai_vision_url, headers=headers, json=payload, timeout=90)
|
471 |
+
|
472 |
+
print(f"📊 OpenAI response: HTTP {response.status_code}")
|
473 |
+
|
474 |
+
if response.status_code == 200:
|
475 |
+
result = response.json()
|
476 |
+
analysis = result["choices"][0]["message"]["content"]
|
477 |
+
|
478 |
+
# Validate response
|
479 |
+
is_valid, validation_error = self.validate_analysis_response(analysis, analysis_type)
|
480 |
+
|
481 |
+
if is_valid:
|
482 |
+
print("✅ OpenAI analysis validated successfully")
|
483 |
+
return {
|
484 |
+
"success": True,
|
485 |
+
"analysis": analysis,
|
486 |
+
"provider": "OpenAI",
|
487 |
+
"model": "GPT-4o",
|
488 |
+
"analysis_type": analysis_type,
|
489 |
+
"timestamp": datetime.now().isoformat()
|
490 |
+
}, None
|
491 |
+
else:
|
492 |
+
print(f"❌ OpenAI validation failed: {validation_error}")
|
493 |
+
return None, f"OpenAI analysis validation failed: {validation_error}"
|
494 |
+
else:
|
495 |
+
return None, f"OpenAI API error {response.status_code}: {response.text}"
|
496 |
+
|
497 |
+
except Exception as e:
|
498 |
+
return None, f"OpenAI analysis error: {str(e)}"
|
499 |
+
|
500 |
+
def analyze_image(self, image_path, analysis_type="detailed", custom_prompt=None, preferred_provider=None):
|
501 |
+
"""
|
502 |
+
Main image analysis function with multiple provider fallback
|
503 |
+
|
504 |
+
Args:
|
505 |
+
image_path: Path to image file
|
506 |
+
analysis_type: Type of analysis to perform
|
507 |
+
custom_prompt: Custom analysis prompt
|
508 |
+
preferred_provider: Preferred AI provider
|
509 |
+
|
510 |
+
Returns:
|
511 |
+
dict: Analysis result with success/failure information
|
512 |
+
"""
|
513 |
+
if not image_path:
|
514 |
+
return {
|
515 |
+
"success": False,
|
516 |
+
"error": "No image provided",
|
517 |
+
"analysis": "Please upload an image to analyze."
|
518 |
+
}
|
519 |
+
|
520 |
+
print(f"🎯 Starting {analysis_type} image analysis...")
|
521 |
+
|
522 |
+
# Define provider order
|
523 |
+
if preferred_provider == "openai":
|
524 |
+
providers = [
|
525 |
+
("OpenAI", self.analyze_with_openai),
|
526 |
+
("SambaNova", self.analyze_with_sambanova)
|
527 |
+
]
|
528 |
+
else:
|
529 |
+
providers = [
|
530 |
+
("SambaNova", self.analyze_with_sambanova),
|
531 |
+
("OpenAI", self.analyze_with_openai)
|
532 |
+
]
|
533 |
+
|
534 |
+
# Try each provider
|
535 |
+
for provider_name, provider_func in providers:
|
536 |
+
print(f"🔄 Trying {provider_name}...")
|
537 |
+
|
538 |
+
result, error = provider_func(image_path, analysis_type, custom_prompt)
|
539 |
+
|
540 |
+
if result and result.get("success"):
|
541 |
+
print(f"✅ {provider_name} analysis successful!")
|
542 |
+
return result
|
543 |
+
else:
|
544 |
+
print(f"❌ {provider_name} failed: {error}")
|
545 |
+
|
546 |
+
# All providers failed
|
547 |
+
return {
|
548 |
+
"success": False,
|
549 |
+
"error": "All vision providers failed",
|
550 |
+
"analysis": f"""🔍 **Vision Analysis Failed**
|
551 |
+
|
552 |
+
**Problem:** All available vision AI models failed to analyze your image.
|
553 |
+
|
554 |
+
**Possible Issues:**
|
555 |
+
- Image format or quality problems
|
556 |
+
- API service temporarily unavailable
|
557 |
+
- Image too large or unclear
|
558 |
+
- Network connectivity issues
|
559 |
+
|
560 |
+
**Solutions:**
|
561 |
+
1. **Try Different Format:** Convert PNG ↔ JPG
|
562 |
+
2. **Improve Image Quality:** Ensure good lighting and focus
|
563 |
+
3. **Reduce File Size:** Compress image if very large
|
564 |
+
4. **Manual Description:** Describe your circuit below
|
565 |
+
|
566 |
+
**Manual Analysis:**
|
567 |
+
Tell me about your circuit:
|
568 |
+
- What components do you see?
|
569 |
+
- What is the circuit supposed to do?
|
570 |
+
- What specific problems are you experiencing?
|
571 |
+
- What improvements are you looking for?
|
572 |
+
|
573 |
+
I can provide targeted advice based on your description! 🛠️
|
574 |
+
|
575 |
+
**Example:** "I have an Arduino connected to an L298N motor driver and two DC motors. The motors should move forward when I press a button, but nothing happens."
|
576 |
+
""",
|
577 |
+
"provider": "None",
|
578 |
+
"model": "Fallback",
|
579 |
+
"analysis_type": analysis_type,
|
580 |
+
"timestamp": datetime.now().isoformat()
|
581 |
+
}
|
582 |
+
|
583 |
+
def batch_analyze_images(self, image_paths, analysis_type="detailed"):
|
584 |
+
"""
|
585 |
+
Analyze multiple images in batch
|
586 |
+
|
587 |
+
Args:
|
588 |
+
image_paths: List of image paths
|
589 |
+
analysis_type: Type of analysis
|
590 |
+
|
591 |
+
Returns:
|
592 |
+
list: List of analysis results
|
593 |
+
"""
|
594 |
+
results = []
|
595 |
+
|
596 |
+
for i, image_path in enumerate(image_paths):
|
597 |
+
print(f"🔄 Analyzing image {i+1}/{len(image_paths)}")
|
598 |
+
|
599 |
+
result = self.analyze_image(image_path, analysis_type)
|
600 |
+
results.append(result)
|
601 |
+
|
602 |
+
# Rate limiting between requests
|
603 |
+
if i < len(image_paths) - 1:
|
604 |
+
time.sleep(1)
|
605 |
+
|
606 |
+
return results
|
607 |
+
|
608 |
+
def get_analysis_summary(self, analysis_result):
|
609 |
+
"""
|
610 |
+
Extract key information from analysis result
|
611 |
+
|
612 |
+
Args:
|
613 |
+
analysis_result: Result from analyze_image()
|
614 |
+
|
615 |
+
Returns:
|
616 |
+
dict: Summarized information
|
617 |
+
"""
|
618 |
+
if not analysis_result.get("success"):
|
619 |
+
return {
|
620 |
+
"circuit_type": "unknown",
|
621 |
+
"components": [],
|
622 |
+
"issues": [],
|
623 |
+
"recommendations": []
|
624 |
+
}
|
625 |
+
|
626 |
+
analysis_text = analysis_result["analysis"].lower()
|
627 |
+
|
628 |
+
# Extract circuit type
|
629 |
+
circuit_types = {
|
630 |
+
"h_bridge": ["h-bridge", "motor driver", "motor control"],
|
631 |
+
"power_supply": ["power supply", "regulator", "voltage"],
|
632 |
+
"amplifier": ["amplifier", "amp", "audio"],
|
633 |
+
"sensor": ["sensor", "temperature", "humidity", "distance"],
|
634 |
+
"microcontroller": ["arduino", "esp32", "microcontroller"]
|
635 |
+
}
|
636 |
+
|
637 |
+
detected_type = "general"
|
638 |
+
for circuit_type, keywords in circuit_types.items():
|
639 |
+
if any(keyword in analysis_text for keyword in keywords):
|
640 |
+
detected_type = circuit_type
|
641 |
+
break
|
642 |
+
|
643 |
+
# Extract components (simple keyword search)
|
644 |
+
common_components = [
|
645 |
+
"arduino", "esp32", "resistor", "capacitor", "led", "transistor",
|
646 |
+
"diode", "ic", "motor", "sensor", "display", "battery"
|
647 |
+
]
|
648 |
+
|
649 |
+
found_components = [comp for comp in common_components if comp in analysis_text]
|
650 |
+
|
651 |
+
return {
|
652 |
+
"circuit_type": detected_type,
|
653 |
+
"components": found_components,
|
654 |
+
"provider": analysis_result.get("provider", "unknown"),
|
655 |
+
"model": analysis_result.get("model", "unknown"),
|
656 |
+
"confidence": "high" if analysis_result.get("provider") == "SambaNova" else "medium"
|
657 |
+
}
|
658 |
+
|
659 |
+
# Test function
|
660 |
+
if __name__ == "__main__":
|
661 |
+
analyzer = AdvancedVisionAnalysis()
|
662 |
+
|
663 |
+
# Test with a sample analysis
|
664 |
+
print("🧪 Testing vision analysis module...")
|
665 |
+
|
666 |
+
# This would normally test with an actual image file
|
667 |
+
# result = analyzer.analyze_image("test_circuit.jpg", "detailed")
|
668 |
+
# print(json.dumps(result, indent=2))
|
669 |
+
|
670 |
+
print("✅ Vision analysis module loaded successfully")
|