Skip to content

Latest commit

Β 

History

History
354 lines (285 loc) Β· 12.5 KB

File metadata and controls

354 lines (285 loc) Β· 12.5 KB

Architecture: Screenshot Capture Flow

Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         Browser                                 β”‚
β”‚                                                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Parent Page (http://localhost:8090)                     β”‚  β”‚
β”‚  β”‚  File: web/app.js                                        β”‚  β”‚
β”‚  β”‚                                                          β”‚  β”‚
β”‚  β”‚  1. Every 1 second:                                      β”‚  β”‚
β”‚  β”‚     requestScreenshot()                                  β”‚  β”‚
β”‚  β”‚     β”‚                                                    β”‚  β”‚
β”‚  β”‚     β”œβ”€β–Ί postMessage ──────────────────────┐             β”‚  β”‚
β”‚  β”‚     β”‚   {type: 'captureScreenshot'}       β”‚             β”‚  β”‚
β”‚  β”‚     β”‚                                      ↓             β”‚  β”‚
β”‚  β”‚     β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚  Neuroglancer Iframe                       β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚  (Cross-origin isolated)                   β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚  File: web/ng-screenshot-handler.js        β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚                                            β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚  2. On message:                            β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚     Find canvas element                    β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚     const canvas = document.querySelector( β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚       'canvas.neuroglancer-gl-canvas'      β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚     );                                      β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚                                            β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚  3. Capture:                               β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚     canvas.toDataURL('image/jpeg', 0.8)    β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚                                            β”‚  β”‚  β”‚
β”‚  β”‚     β”Œβ”€β”€β”€β”€  4. Send back:                             β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚     postMessage ◄───────────────────────────  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚     {type: 'screenshot',                   β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β”‚      jpeg_b64: '...'}                      β”‚  β”‚  β”‚
β”‚  β”‚     β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚  β”‚
β”‚  β”‚     β”‚                                                    β”‚  β”‚
β”‚  β”‚     β”œβ”€β–Ί 5. handleScreenshotFromIframe(jpeg_b64)         β”‚  β”‚
β”‚  β”‚     β”‚       - Display in <img> tag                      β”‚  β”‚
β”‚  β”‚     β”‚       - Update frame count                        β”‚  β”‚
β”‚  β”‚     β”‚       - Calculate FPS                             β”‚  β”‚
β”‚  β”‚     β”‚                                                    β”‚  β”‚
β”‚  β”‚     └─► 6. sendScreenshotToServer(jpeg_b64)             β”‚  β”‚
β”‚  β”‚             POST /api/screenshot                         β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”‚β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                  β”‚                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                   β”‚
                                   ↓ HTTP POST
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Server (FastAPI)                         β”‚
β”‚                      File: server/stream.py                   β”‚
β”‚                                                               β”‚
β”‚  @app.post("/api/screenshot")                                β”‚
β”‚  async def receive_screenshot(request):                      β”‚
β”‚      7. Decode base64 β†’ JPEG bytes                           β”‚
β”‚      8. Store in ng_tracker.latest_frame                     β”‚
β”‚      9. Broadcast via WebSocket to all clients               β”‚
β”‚                                                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  State Tracker                                         β”‚  β”‚
β”‚  β”‚  File: server/ng.py                                    β”‚  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚  10. latest_frame = {                                 β”‚  β”‚
β”‚  β”‚        'jpeg_bytes': ...,                             β”‚  β”‚
β”‚  β”‚        'jpeg_b64': ...,                               β”‚  β”‚
β”‚  β”‚        'state': current_state_summary,                β”‚  β”‚
β”‚  β”‚        'timestamp': ...                               β”‚  β”‚
β”‚  β”‚      }                                                 β”‚  β”‚
β”‚  β”‚                                                        β”‚  β”‚
β”‚  β”‚  11. AI Narrator can access:                          β”‚  β”‚
β”‚  β”‚      - Screenshot (visual)                            β”‚  β”‚
β”‚  β”‚      - State (position, layers, etc.)                 β”‚  β”‚
β”‚  β”‚      - Generate narration combining both              β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Components

1. Screenshot Handler (web/ng-screenshot-handler.js)

Purpose: Runs inside Neuroglancer iframe to capture screenshots

Key Functions:

// Wait for Neuroglancer to load
waitForNeuroglancer(callback)

// Capture screenshot from canvas
captureScreenshot(width, height)

// Listen for requests from parent
window.addEventListener('message', ...)

// Notify parent when ready
window.parent.postMessage({type: 'ready'}, '*')

Security: Can access iframe's own canvas (no cross-origin restrictions)

2. Parent Page Client (web/app.js)

Purpose: Request screenshots and manage display

Key Methods:

// Setup message listener
setupMessageListener()

// Request screenshot from iframe
requestScreenshot()

// Handle received screenshot
handleScreenshotFromIframe(jpeg_b64)

// Send to server
sendScreenshotToServer(jpeg_b64)

Timing: Requests screenshots at configurable FPS (default 1 fps)

3. Proxy Server (server/stream.py)

Purpose: Inject screenshot handler into Neuroglancer HTML

Key Functions:

# Proxy Neuroglancer requests
@app.api_route("/ng-proxy/{path:path}", ...)
async def neuroglancer_proxy(path, request):
    # Get response from Neuroglancer
    response = await client.request(...)
    
    # Inject script if HTML
    if 'text/html' in content_type:
        html = html.replace('</head>', 
            '<script src="/static/ng-screenshot-handler.js"></script></head>')
        
    return Response(content=html, ...)

Critical: Updates Content-Length header after injection

4. Screenshot Receiver (server/stream.py)

Purpose: Receive and store screenshots from browser

@app.post("/api/screenshot")
async def receive_screenshot(request):
    data = await request.json()
    jpeg_b64 = data.get('jpeg_b64')
    
    # Decode and store
    jpeg_bytes = base64.b64decode(jpeg_b64)
    ng_tracker.latest_frame = {
        'jpeg_bytes': jpeg_bytes,
        'jpeg_b64': jpeg_b64,
        'timestamp': timestamp,
        'state': ng_tracker.current_state_summary
    }

Data Flow

Screenshot Data

Neuroglancer Canvas (WebGL)
    ↓ toDataURL()
Base64 JPEG String (~30-50 KB)
    ↓ postMessage
Parent Page JavaScript
    ↓ Display in <img>
    ↓ HTTP POST
FastAPI Server
    ↓ Base64 decode
JPEG Bytes
    ↓ Store in memory
AI Narrator (future)

State Data

Neuroglancer State Change
    ↓ State callback
State Tracker (ng.py)
    ↓ Summarize
State Summary {position, scale, layers, ...}
    ↓ Attach to screenshot
Combined Frame Data
    ↓ WebSocket
Browser Display

Message Protocol

Parent β†’ Iframe

{
    type: 'captureScreenshot',
    width: 800,    // Optional
    height: 600    // Optional
}

Iframe β†’ Parent

// Ready notification
{
    type: 'ready'
}

// Screenshot response
{
    type: 'screenshot',
    jpeg_b64: 'base64-encoded-jpeg-data',
    timestamp: 1234567890
}

Browser β†’ Server

{
    "jpeg_b64": "base64-encoded-jpeg-data",
    "timestamp": 1234567890.123
}

Security Considerations

postMessage Security

Current: Uses '*' for origin (any origin accepted)

Production: Should validate origin:

if (event.origin !== 'https://expected-domain.com') return;

Canvas Access

Why it works: Script runs in same context as canvas Blocked: External scripts trying to access cross-origin canvas

Content Security Policy

May need to allow:

  • script-src 'self' - Load our injected script
  • connect-src 'self' - WebSocket connections
  • img-src data: - Display base64 images

Performance

Screenshot Rate

  • Default: 1 fps
  • Adjustable: Change screenshotFps in app.js
  • Recommendation: 1-2 fps for live monitoring

Screenshot Size

  • Typical: 20-50 KB per frame (JPEG, quality 0.8)
  • At 1 fps: ~3 MB/minute bandwidth
  • At 2 fps: ~6 MB/minute bandwidth

Optimization Options

  1. Lower quality: toDataURL('image/jpeg', 0.6)
  2. Smaller resolution: Resize canvas before capture
  3. Skip unchanged frames: Compare to previous screenshot
  4. Delta encoding: Only send changed regions

Extension Points

Add Vision AI

# In server/narrator.py
def generate_narration(self, summary, screenshot_b64):
    # Send to Claude, GPT-4V, etc.
    response = anthropic.messages.create(
        model="claude-3-5-sonnet-20241022",
        messages=[{
            "role": "user",
            "content": [
                {
                    "type": "image",
                    "source": {
                        "type": "base64",
                        "media_type": "image/jpeg",
                        "data": screenshot_b64
                    }
                },
                {
                    "type": "text",
                    "text": f"What's in this brain region? Position: {summary['position']}"
                }
            ]
        }]
    )

Add Screenshot History

// In app.js
this.screenshotHistory = [];
handleScreenshotFromIframe(jpeg_b64) {
    this.screenshotHistory.push({
        jpeg_b64: jpeg_b64,
        timestamp: Date.now(),
        state: this.currentState
    });
    // Keep last 10
    if (this.screenshotHistory.length > 10) {
        this.screenshotHistory.shift();
    }
}

Add Screenshot Recording

// Record screenshots to video
const mediaRecorder = new MediaRecorder(canvas.captureStream(30));
mediaRecorder.start();

Troubleshooting

No screenshots?

  1. Check browser console: Should see [MESSAGE] Neuroglancer iframe is ready
  2. Check server logs: Should see [PROXY] Injected screenshot handler
  3. Verify canvas exists: In iframe console, document.querySelector('canvas')

Low FPS?

  1. Increase screenshotFps in app.js
  2. Check network latency (screenshot upload may be slow)
  3. Reduce screenshot quality/size

High CPU usage?

  1. Decrease screenshot rate
  2. Add frame skip logic
  3. Only capture on state changes