| """Image analysis using Claude Vision. |
| |
| Provides infrastructure image analysis for the FixMyNeighborhood app. |
| """ |
|
|
| import base64 |
| from typing import Optional |
| import anthropic |
|
|
| from config import ANTHROPIC_API_KEY |
|
|
|
|
| |
| _claude_client: Optional[anthropic.Anthropic] = None |
|
|
|
|
| def get_claude_client() -> Optional[anthropic.Anthropic]: |
| """Get or create the Claude client for image analysis.""" |
| global _claude_client |
| if _claude_client is None and ANTHROPIC_API_KEY: |
| try: |
| _claude_client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY) |
| print("Claude client initialized for image analysis") |
| except Exception as e: |
| print(f"Claude client error: {e}") |
| return _claude_client |
|
|
|
|
| class ImageAnalyzer: |
| """ |
| Analyzes infrastructure images using Claude Vision. |
| |
| Provides concise analysis of: |
| - Issue type (pothole, streetlight, drain, etc.) |
| - Severity assessment |
| - Safety hazard evaluation |
| """ |
|
|
| ANALYSIS_PROMPT = ( |
| "Describe this NYC infrastructure issue. What type of issue is it? " |
| "How severe does it appear? Is it a safety hazard? Be concise." |
| ) |
|
|
| def __init__(self, client: anthropic.Anthropic = None): |
| self.client = client or get_claude_client() |
|
|
| def analyze(self, image_path: str) -> Optional[str]: |
| """ |
| Analyze an infrastructure image. |
| |
| Args: |
| image_path: Path to the uploaded image |
| |
| Returns: |
| Analysis text or None if failed |
| """ |
| if not self.client or not image_path: |
| return None |
|
|
| try: |
| with open(image_path, "rb") as f: |
| data = base64.standard_b64encode(f.read()).decode("utf-8") |
|
|
| media_type = self._get_media_type(image_path) |
|
|
| response = self.client.messages.create( |
| model="claude-haiku-4-5-20251001", |
| max_tokens=500, |
| messages=[{ |
| "role": "user", |
| "content": [ |
| { |
| "type": "image", |
| "source": { |
| "type": "base64", |
| "media_type": media_type, |
| "data": data |
| } |
| }, |
| { |
| "type": "text", |
| "text": self.ANALYSIS_PROMPT |
| } |
| ] |
| }] |
| ) |
| return response.content[0].text |
|
|
| except Exception as e: |
| print(f"Vision analysis error: {e}") |
| return None |
|
|
| def _get_media_type(self, image_path: str) -> str: |
| """Determine media type from file extension.""" |
| path_lower = image_path.lower() |
| if path_lower.endswith(".png"): |
| return "image/png" |
| elif path_lower.endswith(".gif"): |
| return "image/gif" |
| elif path_lower.endswith(".webp"): |
| return "image/webp" |
| return "image/jpeg" |
|
|
|
|
| |
| def analyze_image(image_path: str) -> Optional[str]: |
| """ |
| Analyze an infrastructure image using Claude Vision. |
| |
| Args: |
| image_path: Path to the uploaded image |
| |
| Returns: |
| Analysis text or None if failed |
| """ |
| analyzer = ImageAnalyzer() |
| return analyzer.analyze(image_path) |
|
|