# Project export: Visionify

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: A proactive real-time AI wearable that helps the visually impaired navigate safely—at a price under $100, making smart mobility finally accessible to all.
- Devpost: https://devpost.com/software/visionify
- GitHub: https://github.com/rajashekarcs2023/vision-assistant
- Demo: https://www.youtube.com/watch?v=WCvE3_p3FdY
- Video: https://www.youtube.com/embed/YznmK6QALPo?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (InspectMind: Most Efficiency Improvements Award)
- Team: 2 GitHub contributor(s) — rajashekarcs2023 (8 commits), oviozz (4 commits)

## Devpost submission (written by the team)

### Inspiration

🤔 Have you ever considered how visually impaired people navigate everyday dangers? Crossing busy roads, avoiding potholes, or noticing imminent hazards like moving vehicles can be incredibly risky. Consider these staggering statistics: Approximately 253 million people globally have a form of visual impairment; around 36 million are completely blind. [WHO Source] 40% of visually impaired individuals report frequent collisions or accidents when navigating daily. [VisionAware Source] Blind pedestrians are nearly twice as likely to be involved in accidents or injuries related to road navigation. [Journal of Visual Impairment & Blindness Source] Driven by these realities, we developed visionify.

### What it does

+

### How we built it

🛠️ Visionify is an advanced wearable that combines the best aspects of Waymo’s real-time obstacle detection with the precise location tracking features of Life360, tailored specifically for the visually impaired community. At its core, Visionify provides continuous, real-time spatial awareness using advanced sensor fusion - Camera, accelerometer, Ultrasonics sensor and GPS module. A Raspberry Pi serves as our onboard computing platform, integrating sensor data from a camera, ultrasonic sensor, microphone, and ADXL345 accelerometer. We developed a three-tier intelligent safety system: Local Guardian: Instant (<50ms) detection and alert for critical dangers such as sudden obstacles, falls, or collisions, providing immediate haptic and audio feedback. VAPI AI phone calls: When it detects any unusual activity like a fall of the person through our accelerometer sensor, our voice AI agent automatically calls the emergency contacts of the person. Cloud Intelligence: Utilizing powerful cloud-based AI via REST APIs, we employ Gemini Vision and GPT-4 Turbo for detailed scene description, obstacle classification, and intelligent route guidance. Natural Interaction: Leveraging ElevenLabs VAPI for seamless and natural voice interaction, providing precise and understandable guidance. Indoor Navigation Agent: A real-time voice-activated navigation system that helps users navigate indoor spaces using computer vision and AI. Built specifically for the TAN OAK conference room on the 4th floor of the MLK building. We optimized our pipeline by aggressively tuning models for inference latency, adopting network quantization, and leveraging accelerated matrix multiplication via optimized PyTorch configurations. Our custom lightweight REST protocol ensures rapid transmission of sensor data and AI-driven instructions between the device and cloud services. Built on a scalable Flask backend, Vefficiently processes simultaneous streams from multiple sensors and integrates seamlessly with mobile and web dashboards, giving caregivers real-time visibility into the user’s environment and safety.

### Challenges we ran into

😿 Real-time performance was non-negotiable; even minimal latency could be dangerous. We dedicated extensive effort to refining our data processing pipeline. Despite aggressive model tuning and kernel optimization, initial tests showed bottlenecks due to hardware constraints. Leveraging cloud GPUs, we dramatically reduced inference times. Our data transmission initially used standard JSON, which significantly slowed real-time data exchanges. Recognizing milliseconds matter, we engineered a bespoke lightweight protocol, eliminating redundant metadata and ensuring rapid, efficient communication between edge devices and cloud servers. Hardware affordability posed another major challenge. Existing market solutions are often priced prohibitively (\$800+), inaccessible to many visually impaired users. To address this, we selected widely available, inexpensive components such as the Raspberry Pi, ultrasonic sensors, and affordable haptic motors, assembling a robust wearable at under \$100 total.

### Accomplishments we're proud of

💯 Visionify started as a response to a real, pressing need: making safe, intelligent navigation affordable for all visually impaired individuals. The end result was a powerful, fully operational wearable device capable of: Instantaneous (<50ms) hazard detection and alerting Real-time cloud-based contextual navigation guidance Seamless integration of multimodal sensor data for comprehensive situational awareness Affordable construction and accessibility, priced below \$100 per unit We successfully integrated advanced AI models like GPT-4 Turbo and Gemini Vision with an intuitive interface powered by ElevenLabs VAPI, providing clear, actionable navigation guidance through both haptic and audio cues.

### What we learned

💭 This project expanded our understanding of low-latency AI inference, sensor fusion, and efficient hardware design. We grappled deeply with PyTorch and TensorRT optimizations, pushing the limits of Raspberry Pi’s hardware capabilities. Through rigorous testing and iteration, we found strategies to reduce inference latency significantly, ensuring reliability and speed critical for real-time danger detection. Additionally, the hardware design required precision engineering. Initial prototypes revealed vulnerabilities in durability and comfort, leading to iterative refinements in material choices and sensor placements to optimize usability and robustness.

### What's next

We’re just beginning our journey. The roadmap ahead is exciting: Enhanced directional haptic feedback system, providing intuitive physical navigation cues. Integration of indoor mapping solutions for seamless indoor-outdoor navigation experiences. Customizable object and scene recognition to personalize the experience for individual users. Expanded testing and user feedback sessions with visually impaired communities to refine and perfect usability. We aim to partner with organizations and healthcare providers to broaden accessibility and ensure that SentientSight reaches those who need it most, transforming the independence and daily lives of millions globally. Built With Raspberry Pi Python (Flask, PyTorch, Requests, smbus2) ElevenLabs VAPI Gemini Vision & GPT-4 APIs Ultrasonic & ADXL345 accelerometer sensors Affordable haptic hardware

## README (from the GitHub repository)

# vision-assistant

## Detected evidence (automated analysis)

Indexed codebase: 31 recognized source files, 291 KB.
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- Google Gemini (technology) — claimed on Devpost, not found in the code
- LangChain (technology) — claimed on Devpost, not found in the code
- Next.js (technology) — claimed on Devpost, not found in the code
- Tailwind CSS (technology) — claimed on Devpost, not found in the code
- TensorFlow (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (41 of 41)

```
.env.example
.env.local
.gitignore
.idea/.gitignore
.idea/inspectionProfiles/profiles_settings.xml
.idea/inspectionProfiles/Project_Default.xml
.idea/misc.xml
.idea/modules.xml
.idea/vcs.xml
.idea/vision-assistant.iml
convex_functions/navigation.js
indoor_navigation_agent.py
indoor_navigation_setup.md
navigation_demo_guide.md
photo_analyzer.py
photo_setup_guide.md
pi_camera_service.py
pi-service/navigation_camera_service.py
pi-service/sensor_service_v1.py
pi-service/sensor_service_v2.py
pi-service/sensor_service_v2fall.py
pi-service/sensor_service_v3.py
pi-service/sensor_service.py
README.md
requirements_navigation.txt
tan_oak_embeddings.json
test.html
vision-assistant-frontend/App.tsx
vision-assistant-frontend/components/SensorDisplay.tsx
vision-assistant-frontend/components/StatusIndicator.tsx
vision-assistant-frontend/components/WebcamFeed.tsx
vision-assistant-frontend/constants.ts
vision-assistant-frontend/index.html
vision-assistant-frontend/index.tsx
vision-assistant-frontend/metadata.json
vision-assistant-frontend/package.json
vision-assistant-frontend/README.md
vision-assistant-frontend/services/geminiService.ts
vision-assistant-frontend/tsconfig.json
vision-assistant-frontend/types.ts
vision-assistant-frontend/vite.config.ts
```

### Dependencies

- vision-assistant-frontend/package.json: @google/genai@^1.4.0, @types/node@^22.14.0, @types/react@^19.1.8, @types/react-dom@^19.1.6, react@^19.1.0, react-dom@^19.1.0, typescript@~5.7.2, vite@^6.2.0

### Recent commits (newest first)

- Update .env.local
- commit
- commit all agents
- update prompt
- updated fall
- commit fall detection
- update: v3
- updated v2
- update
- update convex
- commit all
- Initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### photo_setup_guide.md

```markdown
# Photo-Based Indoor Navigation Setup

## How It Works

**Vector Similarity Search**: Your 20-25 photos become the "ground truth" for room positions.

```
Your Photos → CLIP Embeddings → Vector Database
Current Camera View → CLIP Embedding → Find Most Similar Photo → Position!
```

## Setup Steps

### 1. Prepare Your Photos
```bash
# Create directory structure
mkdir -p /Users/radhikadanda/vision-assistant/reference_images/tan_oak

# Copy your 20-25 TAN OAK room photos to this directory
# Name them descriptively:
# - chair_1_facing_screen.jpg
# - chair_2_facing_windows.jpg  
# - standing_center_facing_door.jpg
# - near_door_entrance.jpg
# - corner_table_view.jpg
```

### 2. Install Dependencies
```bash
pip install sentence-transformers pillow numpy
```

### 3. Process Your Photos
```bash
# This will create embeddings from your photos
python photo_analyzer.py
```

### 4. Test the System
```bash
# Run the navigation agent
python indoor_navigation_agent.py
```

## Photo Naming Convention

**Good naming helps with position detection:**

```
Position-based names:
- chair_front_left.jpg
- chair_back_right.jpg
- standing_center.jpg
- near_door.jpg
- corner_window_side.jpg

Orientation-based names:
- facing_screen.jpg
- facing_windows.jpg
- facing_door.jpg
- back_to_door.jpg
```

## How Position Detection Works

### Step 1: Reference Processing
```python
# Your photos are processed once
for photo in your_photos:
    embedding = CLIP_model.encode(photo)
    store_embedding_with_position(embedding, position_from_filename)
```

### Step 2: Live Position Detection
```python
# During demo
current_view = get_camera_image()
current_embedding = CLIP_model.encode(current_view)

# Find most similar reference photo
best_match = find_most_similar(current_embedding, reference_embeddings)
position = get_position_from_match(best_match)

# Result: "You're sitting at chair 3 facing the projector screen"
```

## Advantages of This Approach

✅ **Accurate**: Uses your actual room photos as reference
✅ **Fast**: Vector similarity search is very quick  
✅ **Robust**: Works even with lighting/angle changes
✅ **Scalable**: Easy to add more reference positions
✅ **No Manual Mapping**: Your photos define the positions automatically

## Demo Flow

```
User: "Where am I?"
→ Camera captures current view
→ System finds most similar reference photo (0.87 similarity)
→ "You're sitting at the front left chair facing the projector screen"

User: "How do I exit?"
→ Based on identified position: "Turn right 90 degrees, walk straight to the door"
```

## Troubleshooting

### Low Similarity Scores
- **Add more reference photos** from that position
- **Check lighting conditions** - take photos in similar lighting
- **Improve photo quality** - avoid blurry images

### Wrong Position Detection
- **Better photo naming** - be more specific about positions
- **More reference angles** - take photos from multiple angles at each position
- **Remove duplicate positions** - don't have too many similar p
[truncated — 335 more characters]
```

### navigation_demo_guide.md

```markdown
# Indoor Navigation Demo - Complete Setup Guide

## 🎯 What This Creates

A **dedicated indoor navigation system** with two separate scripts:

1. **Pi**: `navigation_camera_service.py` - Only captures + uploads images
2. **Mac**: `indoor_navigation_agent.py` - Voice interaction + position detection

## 📋 Setup Steps

### 1. **Convex Setup** (Add Navigation Functions)
```javascript
// Add the functions from convex_functions/navigation.js to your Convex project
// This creates dedicated navigation endpoints separate from your main sensor data
```

### 2. **Pi Setup** (Lightweight Camera Service)
```bash
# On your Raspberry Pi
cd /path/to/vision-assistant/pi-service

# Set environment variable
export CONVEX_DEPLOYMENT_URL="https://your-deployment.convex.cloud"

# Test camera first
python3 navigation_camera_service.py --test

# Run the service
python3 navigation_camera_service.py
```

### 3. **Mac Setup** (Navigation Agent)
```bash
# On your Mac
cd /Users/radhikadanda/vision-assistant

# Install dependencies
pip install -r requirements_navigation.txt

# Set up environment
cp .env.example .env
# Edit .env with your Convex URL and API keys

# Add your reference photos
mkdir -p reference_images/tan_oak
# Copy your 20-25 TAN OAK room photos here

# Process photos (one-time)
python photo_analyzer.py

# Run navigation demo
python indoor_navigation_agent.py
```

## 🔄 How It Works

### **Simple Flow**:
```
Pi Camera → navigation_camera_service.py → Convex → indoor_navigation_agent.py → Voice Response
```

### **Detailed Flow**:
1. **Pi captures image** every 2 seconds
2. **Uploads to Convex** via `upload_navigation_image`
3. **Mac agent gets latest image** via `get_latest_navigation_image`
4. **Finds similar reference photo** using CLIP embeddings
5. **Responds with position** via LiveKit voice

## 🎙️ Voice Commands

**Position Detection:**
- "Where am I?"
- "What's my location?"
- "Where am I in the room?"

**Exit Directions:**
- "How do I exit?"
- "How do I get out?"
- "Where's the door?"

## 📸 Expected Results

### **High Confidence (Photo Match)**
```
User: "Where am I?"
Agent: "You're sitting at the front left chair facing the projector screen. 
        Confidence: High (89% similarity match)"
```

### **Exit Directions**
```
User: "How do I exit?"
Agent: "From your current position, turn right 90 degrees and walk straight 
        toward the door. It should be about 6 steps ahead."
```

## 🛠️ Benefits of Separate Scripts

### **Pi Script** (`navigation_camera_service.py`)
✅ **Lightweight** - Only camera capture + upload  
✅ **No AI processing** - Saves Pi resources  
✅ **Dedicated endpoints** - Clean separation from sensor data  
✅ **Simple debugging** - Easy to test camera independently  

### **Mac Script** (`indoor_navigation_agent.py`)  
✅ **Full AI power** - CLIP + GPT-4V for accurate positioning  
✅ **Voice interaction** - LiveKit STT/TTS  
✅ **Photo similarity** - Uses your actual room photos  
✅ **Fallback systems** - Multiple positioning 
[truncated — 1448 more characters]
```

### vision-assistant-frontend/package.json

```
{
  "name": "blind-vision-assistant",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "@google/genai": "^1.4.0",
    "react": "^19.1.0",
    "react-dom": "^19.1.0"
  },
  "devDependencies": {
    "@types/node": "^22.14.0",
    "@types/react": "^19.1.8",
    "@types/react-dom": "^19.1.6",
    "typescript": "~5.7.2",
    "vite": "^6.2.0"
  }
}

```

### vision-assistant-frontend/index.tsx

```typescript

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const rootElement = document.getElementById('root');
if (!rootElement) {
  throw new Error("Could not find root element to mount to");
}

const root = ReactDOM.createRoot(rootElement);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

```

### vision-assistant-frontend/App.tsx

```typescript
import React, { useState, useEffect, useRef, useCallback } from 'react';
import WebcamFeed from './components/WebcamFeed';
import StatusIndicator from './components/StatusIndicator';
import SensorDisplay from './components/SensorDisplay'; // NEW: Add sensor display
import { analyzeImageForHazards, analyzeImageForHazardsWithSensorContext } from './services/geminiService'; // ADD: Enhanced function
import { DetectionStatus, WebcamFeedRef, HazardAnalysisResponse, SensorPackage } from './types'; // ADD: SensorPackage
import { 
  FRAME_CAPTURE_INTERVAL_MS, 
  HAZARD_TYPE_PATH_CLEAR,
  MESSAGE_PATH_CLEAR_DEFAULT,
  MESSAGE_UNCLEAR_IMAGE,
  MESSAGE_ANALYSIS_ERROR,
  WEBSOCKET_URL // NEW: Add WebSocket URL
} from './constants';

const App: React.FC = () => {
  // KEEP: All your existing state
  const [status, setStatus] = useState<DetectionStatus>(DetectionStatus.INITIALIZING);
  const [statusMessage, setStatusMessage] = useState<string>("Initializing application...");
  const [isDetecting, setIsDetecting] = useState<boolean>(false);
  const [apiKeyPresent, setApiKeyPresent] = useState<boolean>(false);
  
  // ADD: New state for Pi integration
  const [wsConnected, setWsConnected] = useState<boolean>(false);
  const [sensorData, setSensorData] = useState<SensorPackage | null>(null);
  const [lastFrameData, setLastFrameData] = useState<string | null>(null);
  
  // KEEP: All your existing refs
  const webcamRef = useRef<WebcamFeedRef>(null);
  const processingFrameRef = useRef<boolean>(false);
  const lastSpokenMessageRef = useRef<string | null>(null);
  const lastHazardTypeRef = useRef<string | null>(null);
  
  // ADD: New refs for WebSocket
  const wsRef = useRef<WebSocket | null>(null);
  const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);

  const statusRef = useRef<DetectionStatus>(status);
  useEffect(() => {
    statusRef.current = status;
  }, [status]);

  // API key check
  useEffect(() => {
    const key = process.env.GEMINI_API_KEY;  // Keep as you had it
    if (key && key.trim() !== "") {
      setApiKeyPresent(true);
      setStatus(DetectionStatus.IDLE); 
      setStatusMessage("API Key found. Connecting to Pi...");
    } else {
      setApiKeyPresent(false);
      setStatus(DetectionStatus.API_KEY_MISSING);
      setStatusMessage("Error: GEMINI_API_KEY environment variable is not set.");
    }
  }, []);

  // ADD: WebSocket connection management
  const connectWebSocket = useCallback(() => {
    if (wsRef.current?.readyState === WebSocket.OPEN) {
      return; // Already connected
    }

    setStatus(DetectionStatus.WEBSOCKET_CONNECTING);
    setStatusMessage("Connecting to Pi sensors...");

    try {
      wsRef.current = new WebSocket(WEBSOCKET_URL);
      
      wsRef.current.onopen = () => {
        console.log('🔗 WebSocket connected to Pi');
        setWsConnected(true);
        setStatus(DetectionStatus.IDLE);
        setStatusMessage("Connected to Pi sensors. Ready to start detection.");
        
        // Clear any reconnection timeout
        if (reconnectTimeoutRef.current) {
          clearTimeout(reconnectTimeoutRef.current);
          reconnectTimeoutRef.current = null;
        }
      };

      wsRef.current.onmessage = (event) => {
        try {
          const sensorPackage: SensorPackage = JSON.parse(event.data);
          setSensorData(sensorPackage);
          
          // FIXED: Store frame data if available
          if (sensorPackage.camera?.image_data) {
            setLastFrameData(sensorPackage.camera.image_data);
          }
          
          // FIXED: Process frame if detection is active and we have frame data
          if (isDetecting && sensorPackage.camera?.image_data && !processingFrameRef.current) {
            processFrameWithSensorContext(sensorPackage);
          }
        } catch (error) {
          console.error('Error parsing sensor data:', error);
        }
      };

      wsRef.current.onclose = () => {
        console.log('🔌 WebSocket disconnected from Pi');
        setWsConnected(false);
        setStatus(DetectionStatus.WEBSOCKET_DISCONNECTED);
        setStatusMessage("Disconnected from Pi. Attempting to reconnect...");
        
        // Attempt to reconnect after 3 seconds
        reconnectTimeoutRef.current = setTimeout(() => {
          connectWebSocket();
        }, 3000);
      };

      wsRef.current.onerror = (error) => {
        console.error('WebSocket error:', error);
        setStatus(DetectionStatus.WEBSOCKET_DISCONNECTED);
        setStatusMessage("Connection error. Check if Pi sensor service is running.");
      };

    } catch (error) {
      console.error('Failed to create WebSocket connection:', error);
      setStatus(DetectionStatus.WEBSOCKET_DISCONNECTED);
      setStatusMessage("Failed to connect to Pi. Is the sensor service running?");
    }
  }, [isDetecting]);

  // ADD: Connect to WebSocket when component mounts and API key is available
  useEffect(() => {
    if (apiKeyPresent) {
      connectWebSocket();
    }

    return () => {
      if (wsRef.current) {
        wsRef.current.close();
      }
      if (reconnectTimeoutRef.current) {
        clearTimeout(reconnectTimeoutRef.current);
      }
    };
  }, [apiKeyPresent, connectWebSocket]);

  // UPDATE: Camera status handler for fallback mode
  const handleCameraStatusChange = useCallback((cameraStatus: DetectionStatus, message?: string) => {
    // Only use webcam fallback if not connected to Pi
    if (!wsConnected && statusRef.current !== DetectionStatus.API_KEY_MISSING) {
      setStatus(cameraStatus);
      if (message) setStatusMessage(message);
      
      if (cameraStatus === DetectionStatus.CAMERA_ERROR) {
        setIsDetecting(false); 
      } else if (cameraStatus === DetectionStatus.IDLE && apiKeyPresent) {
        setStatusMessage(message || "Camera ready. Click 'Start Detection'.");
      }
    }
  }, [wsConnected, apiKeyPresent]);

  // ENHANCE: Speech function with sensor context
  const speak = (text: string, hazardTy
[truncated — 14324 more characters]
```

### test.html

```html
<!DOCTYPE html>
<html>
<head>
    <title>Pi WebSocket Test</title>
    <style>
        body { font-family: Arial, sans-serif; padding: 20px; }
        .status { font-size: 18px; margin: 10px 0; }
        .connected { color: green; }
        .disconnected { color: red; }
        .data { background: #f0f0f0; padding: 10px; margin: 10px 0; }
    </style>
</head>
<body>
    <h1>🤖 SentientSight Pi Connection Test</h1>
    <div id="status" class="status disconnected">🔌 Connecting to Pi...</div>
    <div><strong>Network Quality Test Complete!</strong> 0% packet loss detected.</div>
    <div id="data" class="data">Waiting for sensor data...</div>
    
    <script>
        const statusDiv = document.getElementById('status');
        const dataDiv = document.getElementById('data');
        
        console.log('🔍 Connecting to Pi WebSocket...');
        console.log('⏰ Test started at:', new Date().toLocaleTimeString());
        const ws = new WebSocket('ws://192.168.137.8:8765');
        
        ws.onopen = () => {
            statusDiv.innerHTML = '✅ Connected to Pi!';
            statusDiv.className = 'status connected';
            console.log('✅ WebSocket connected successfully!');
        };
        
        ws.onerror = (error) => {
            statusDiv.innerHTML = '❌ Connection Failed';
            statusDiv.className = 'status disconnected';
            console.log('❌ WebSocket connection failed:', error);
        };
        
        ws.onmessage = (event) => {
            try {
                const sensorData = JSON.parse(event.data);
                const motion = sensorData.accelerometer?.is_moving ? 'Moving' : 'Still';
                const distance = sensorData.ultrasonic?.distance_cm || 'N/A';
                const hasCamera = sensorData.camera?.image_data ? '📷' : '📷❌';
                
                dataDiv.innerHTML = `
                    <strong>Live Sensor Data:</strong><br>
                    Motion: ${motion}<br>
                    Distance: ${distance}cm<br>
                    Camera: ${hasCamera}<br>
                    Time: ${new Date(sensorData.timestamp * 1000).toLocaleTimeString()}
                `;
                
                console.log('📡 Received sensor data:', sensorData);
            } catch (e) {
                console.error('Error parsing sensor data:', e);
            }
        };
        
        ws.onclose = (event) => {
            statusDiv.innerHTML = '🔌 Disconnected';
            statusDiv.className = 'status disconnected';
            console.log('🔌 WebSocket closed. Code:', event.code);
        };
    </script>
</body>
</html>
```

### pi_camera_service.py

```python
#!/usr/bin/env python3
"""
Simple Pi Camera Service for Indoor Navigation
Provides camera images via HTTP API for the navigation agent
"""

import asyncio
import base64
import io
import json
from flask import Flask, jsonify, request
from flask_cors import CORS
import cv2
import logging

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = Flask(__name__)
CORS(app)

# Global camera object
camera = None

def initialize_camera():
    """Initialize camera (Pi Camera or USB camera)"""
    global camera
    try:
        # Try Pi Camera first
        try:
            import picamera
            camera = picamera.PiCamera()
            camera.resolution = (640, 480)
            logger.info("Pi Camera initialized")
            return True
        except ImportError:
            logger.info("Pi Camera not available, trying USB camera")
        
        # Fallback to USB camera via OpenCV
        camera = cv2.VideoCapture(0)
        if camera.isOpened():
            camera.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
            camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
            logger.info("USB Camera initialized")
            return True
        else:
            logger.error("No camera available")
            return False
            
    except Exception as e:
        logger.error(f"Error initializing camera: {e}")
        return False

def capture_image_pi_camera():
    """Capture image using Pi Camera"""
    try:
        stream = io.BytesIO()
        camera.capture(stream, format='jpeg')
        stream.seek(0)
        image_data = stream.read()
        return base64.b64encode(image_data).decode('utf-8')
    except Exception as e:
        logger.error(f"Error capturing Pi Camera image: {e}")
        return None

def capture_image_usb_camera():
    """Capture image using USB camera"""
    try:
        ret, frame = camera.read()
        if ret:
            # Encode frame as JPEG
            _, buffer = cv2.imencode('.jpg', frame)
            image_data = buffer.tobytes()
            return base64.b64encode(image_data).decode('utf-8')
        else:
            logger.error("Failed to capture frame from USB camera")
            return None
    except Exception as e:
        logger.error(f"Error capturing USB camera image: {e}")
        return None

@app.route('/capture', methods=['GET'])
def capture_image():
    """Capture and return current camera image as base64"""
    try:
        if camera is None:
            return jsonify({'error': 'Camera not initialized'}), 500
        
        # Determine camera type and capture accordingly
        if hasattr(camera, 'capture'):  # Pi Camera
            image_base64 = capture_image_pi_camera()
        else:  # USB Camera
            image_base64 = capture_image_usb_camera()
        
        if image_base64:
            return jsonify({
                'status': 'success',
                'image_data': image_base64,
                'timestamp': asyncio.get_event_loop().time()
            })
        else:
            return jsonify({'error': 'Failed to capture image'}), 500
            
    except Exception as e:
        logger.error(f"Error in capture endpoint: {e}")
        return jsonify({'error': str(e)}), 500

@app.route('/status', methods=['GET'])
def camera_status():
    """Get camera status"""
    return jsonify({
        'camera_available': camera is not None,
        'camera_type': 'pi_camera' if hasattr(camera, 'capture') else 'usb_camera'
    })

@app.route('/health', methods=['GET'])
def health_check():
    """Health check endpoint"""
    return jsonify({'status': 'healthy', 'service': 'pi_camera_service'})

def cleanup_camera():
    """Cleanup camera resources"""
    global camera
    if camera:
        try:
            if hasattr(camera, 'close'):  # Pi Camera
                camera.close()
            else:  # USB Camera
                camera.release()
            logger.info("Camera cleaned up")
        except Exception as e:
            logger.error(f"Error cleaning up camera: {e}")

if __name__ == '__main__':
    try:
        logger.info("Starting Pi Camera Service for Indoor Navigation")
        
        if initialize_camera():
            logger.info("Camera service ready")
            # Run Flask app
            app.run(host='0.0.0.0', port=8000, debug=False)
        else:
            logger.error("Failed to initialize camera. Exiting.")
    
    except KeyboardInterrupt:
        logger.info("Service interrupted by user")
    except Exception as e:
        logger.error(f"Service error: {e}")
    finally:
        cleanup_camera()
        logger.info("Pi Camera Service shutdown complete")

```

### photo_analyzer.py

```python
#!/usr/bin/env python3
"""
Photo Analysis Script for TAN OAK Room Navigation
Uses CLIP embeddings to find similar reference images for position detection
"""

import os
import json
import base64
import numpy as np
from pathlib import Path
from typing import Dict, List, Tuple
import logging
from sentence_transformers import SentenceTransformer
from PIL import Image
import io
import requests

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class PhotoAnalyzer:
    def __init__(self, reference_images_dir: str):
        self.reference_dir = Path(reference_images_dir)
        # Use CLIP model for image embeddings
        self.model = SentenceTransformer('clip-ViT-B-32')
        self.reference_embeddings = {}
        self.reference_metadata = {}
        
    def process_reference_images(self):
        """Process all reference images and create embeddings"""
        logger.info("Processing reference images...")
        
        # Support both .jpg and .jpeg extensions
        image_files = list(self.reference_dir.glob("*.jpg")) + list(self.reference_dir.glob("*.jpeg"))
        
        for image_file in image_files:
            try:
                # Load image
                with open(image_file, "rb") as f:
                    image_data = f.read()
                
                # Create embedding
                image = Image.open(io.BytesIO(image_data))
                embedding = self.model.encode(image)
                
                # Store embedding and metadata
                self.reference_embeddings[image_file.stem] = embedding
                self.reference_metadata[image_file.stem] = {
                    "filename": image_file.name,
                    "position": self._extract_position_from_filename(image_file.stem),
                    "landmarks": []  # To be filled manually or via AI
                }
                
                logger.info(f"Processed: {image_file.name}")
                
            except Exception as e:
                logger.error(f"Error processing {image_file.name}: {e}")
    
    def _extract_position_from_filename(self, filename: str) -> str:
        """Extract position info from filename if available"""
        # Example: "chair_1_facing_screen.jpg" → "sitting at chair 1 facing screen"
        # Example: "door_entrance.jpg" → "near door entrance"
        
        filename_lower = filename.lower()
        
        if "chair" in filename_lower:
            return f"sitting at {filename_lower.replace('_', ' ')}"
        elif "door" in filename_lower:
            return f"near {filename_lower.replace('_', ' ')}"
        elif "center" in filename_lower:
            return f"at {filename_lower.replace('_', ' ')}"
        else:
            return filename_lower.replace('_', ' ')
    
    def find_similar_position(self, current_image_base64: str, top_k: int = 3) -> List[Tuple[str, float, str]]:
        """Find most similar reference images to current view"""
        try:
            # Decode current image
            image_data = base64.b64decode(current_image_base64)
            current_image = Image.open(io.BytesIO(image_data))
            
            # Get embedding for current image
            current_embedding = self.model.encode(current_image)
            
            # Calculate similarities
            similarities = []
            for ref_name, ref_embedding in self.reference_embeddings.items():
                similarity = np.dot(current_embedding, ref_embedding) / (
                    np.linalg.norm(current_embedding) * np.linalg.norm(ref_embedding)
                )
                position = self.reference_metadata[ref_name]["position"]
                similarities.append((ref_name, similarity, position))
            
            # Sort by similarity (highest first)
            similarities.sort(key=lambda x: x[1], reverse=True)
            
            return similarities[:top_k]
            
        except Exception as e:
            logger.error(f"Error finding similar position: {e}")
            return []
    
    def analyze_position(self, current_image_base64: str) -> Dict:
        """Analyze current position using similarity search"""
        similar_positions = self.find_similar_position(current_image_base64)
        
        if not similar_positions:
            return {"position": "unknown", "confidence": "low", "matches": []}
        
        # Get best match
        best_match = similar_positions[0]
        best_name, best_similarity, best_position = best_match
        
        # Determine confidence
        if best_similarity > 0.8:
            confidence = "high"
        elif best_similarity > 0.6:
            confidence = "medium"
        else:
            confidence = "low"
        
        return {
            "position": best_position,
            "confidence": confidence,
            "similarity_score": float(best_similarity),
            "best_match": best_name,
            "top_matches": [
                {"name": name, "similarity": float(sim), "position": pos}
                for name, sim, pos in similar_positions
            ]
        }
    
    def save_embeddings(self, filepath: str):
        """Save embeddings and metadata to file"""
        data = {
            "embeddings": {k: v.tolist() for k, v in self.reference_embeddings.items()},
            "metadata": self.reference_metadata
        }
        
        with open(filepath, 'w') as f:
            json.dump(data, f, indent=2)
        
        logger.info(f"Saved embeddings to {filepath}")
    
    def load_embeddings(self, filepath: str):
        """Load embeddings and metadata from file"""
        if not os.path.exists(filepath):
            logger.warning(f"Embeddings file not found: {filepath}")
            return
        
        with open(filepath, 'r') as f:
            data = json.load(f)
        
        # Convert lists back to numpy arrays
        self.reference_embeddings = {
            k: np.array(v) for k,
[truncated — 1334 more characters]
```

### indoor_navigation_agent.py

```python
import asyncio
import logging
from dotenv import load_dotenv
import os
import json
import base64
import requests
from typing import Optional, Dict, List
import datetime
from pathlib import Path
from photo_analyzer import PhotoAnalyzer

from livekit import agents
from livekit.agents import Agent, AgentSession
import livekit.plugins.openai as openai_plugin
from livekit.plugins import silero

# Load environment variables
load_dotenv()

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Reference Images Directory
REFERENCE_IMAGES_DIR = Path(__file__).parent / "reference_images" / "tan_oak"

# Room Database - TAN OAK Room (4th Floor MLK)
ROOM_DATABASE = {
    "tan_oak_room": {
        "name": "TAN OAK Conference Room",
        "floor": "4th Floor",
        "building": "MLK Student Union",
        
        "landmarks": {
            "conference_table": {
                "description": "Large wooden conference table with chairs",
                "visual_cues": ["rectangular table", "multiple chairs", "dark wooden surface"],
                "position": "center"
            },
            "projector_screen": {
                "description": "Wall-mounted projector screen",
                "visual_cues": ["white screen", "mounted on wall", "rectangular"],
                "position": "front_wall"
            },
            "windows": {
                "description": "Large windows with view outside",
                "visual_cues": ["natural light", "glass panels", "window frames"],
                "position": "exterior_wall"
            },
            "whiteboards": {
                "description": "Wall-mounted whiteboards",
                "visual_cues": ["white surface", "marker trays", "rectangular"],
                "position": "side_wall"
            },
            "door_entrance": {
                "description": "Main room entrance door",
                "visual_cues": ["door handle", "door frame", "exit sign"],
                "position": "entrance_wall"
            }
        },
        
        "exits": {
            "main_door": {
                "description": "Main entrance/exit door",
                "location": "entrance_wall",
                "leads_to": "hallway_to_elevator",
                "visual_cues": ["door handle", "exit sign", "hallway visible"]
            }
        },
        
        "navigation_instructions": {
            "from_center_to_exit": {
                "if_facing_screen": "Turn around 180 degrees, walk straight to the door",
                "if_facing_windows": "Turn left 90 degrees, walk straight to the door", 
                "if_facing_door": "Walk straight ahead to the door",
                "if_back_to_door": "Turn around 180 degrees, walk straight to the door"
            },
            "from_chair_to_exit": {
                "default": "Stand up, face the door, and walk straight to the exit"
            }
        },
        
        "reference_positions": {
            # We'll map specific viewpoints from your photos
            # Format: "photo_name": {"position": "description", "landmarks_visible": [...]}
        }
    }
}

class ConvexClient:
    def __init__(self, deployment_url: str):
        """Initialize Convex client for getting navigation images"""
        self.base_url = deployment_url.rstrip('/')
        
    def get_latest_navigation_image(self) -> Optional[str]:
        """Get the latest navigation image as base64 string"""
        url = f"{self.base_url}/get_latest_navigation_image"
        
        try:
            response = requests.post(
                url,
                json={},
                headers={'Content-Type': 'application/json'},
                timeout=10
            )
            response.raise_for_status()
            result = response.json()
            
            if result.get('success') and result.get('image_data'):
                logger.info(f"📸 Got latest image: {result.get('image_id')}")
                return result['image_data']  # Already base64 encoded
            else:
                logger.warning(f"No image available: {result.get('message', 'Unknown error')}")
                return None
                
        except requests.exceptions.RequestException as e:
            logger.error(f"Error getting latest navigation image: {e}")
            return None
    
    def get_navigation_service_status(self) -> Dict:
        """Get status of the navigation camera service"""
        url = f"{self.base_url}/get_navigation_service_status"
        
        try:
            response = requests.post(
                url,
                json={},
                headers={'Content-Type': 'application/json'},
                timeout=5
            )
            response.raise_for_status()
            return response.json()
            
        except requests.exceptions.RequestException as e:
            logger.error(f"Error getting service status: {e}")
            return {"service_active": False, "error": str(e)}

def load_reference_images() -> Dict:
    """Load reference images from the photos directory"""
    reference_images = {}
    
    if REFERENCE_IMAGES_DIR.exists():
        for image_file in REFERENCE_IMAGES_DIR.glob("*.jpg"):
            try:
                with open(image_file, "rb") as f:
                    image_data = base64.b64encode(f.read()).decode('utf-8')
                    reference_images[image_file.stem] = image_data
                    logger.info(f"Loaded reference image: {image_file.name}")
            except Exception as e:
                logger.error(f"Error loading {image_file.name}: {e}")
    
    return reference_images

class IndoorNavigationAgent(Agent):
    """Indoor Navigation Assistant for TAN OAK Room"""
    
    def __init__(self) -> None:
        super().__init__(
            instructions=(
                "You are an indoor navigation assistant helping blind users navigate the TAN OAK conference room. "
                "Yo
[truncated — 11431 more characters]
```

### .idea/vcs.xml

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="VcsDirectoryMappings">
    <mapping directory="" vcs="Git" />
  </component>
</project>
```

[19 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]