#!/usr/bin/env python3 """ AstraVoxel Real-Time Interface ============================== Advanced real-time camera tracking and 3D voxel reconstruction interface. Provides live multi-sensor data fusion and interactive 3D visualization. Features: - Real-time camera footage processing - Live voxel grid accumulation - Interactive 3D visualization with continuous updates - Multi-camera motion tracking - Astronomical FITS data integration - Performance monitoring and diagnostics This interface bridges the existing pixel-to-voxel processing engine with real-time visualization and control systems. """ import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext import threading import sys import os import json import time import numpy as np import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from mpl_toolkits.mplot3d import Axes3D import cv2 from PIL import Image, ImageTk import random from pathlib import Path import queue # Import AstraVoxel components from fits_loader import FITSLoader, CelestialCoordinates, create_astronomical_demo_data class AstraVoxelRealtime: """ Real-time AstraVoxel interface with live camera tracking and 3D visualization """ def __init__(self, root): """Initialize the real-time AstraVoxel interface""" self.root = root self.root.title("đŸ›°ī¸ AstraVoxel - Real-Time 3D Motion Analysis") self.root.geometry("1600x1000") # Real-time processing state self.is_processing = False self.camera_active = False self.viz_thread = None self.data_queue = queue.Queue(maxsize=100) # Processing parameters self.motion_threshold = 25.0 self.voxel_resolution = 0.5 self.camera_count = 1 self.frame_rate = 30.0 self.grid_size = 64 # Current data self.current_voxel_data = np.zeros((self.grid_size, self.grid_size, self.grid_size), dtype=np.float32) self.frame_count = 0 self.processing_time = 0.0 self.fps = 0.0 # Camera simulation self.simulate_camera = True self.camera_feed_thread = None self.camera_image = None # Setup the real-time interface self.setup_interface() self.setup_realtime_processing() self.setup_visualization_panel() self.log_message("✓ AstraVoxel Real-Time Interface initialized") def setup_interface(self): """Set up the main interface layout""" # Main container self.main_frame = ttk.Frame(self.root) self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # Title bar title_frame = ttk.Frame(self.main_frame) title_frame.pack(fill=tk.X, pady=(0, 10)) ttk.Label(title_frame, text="đŸ›°ī¸ AstraVoxel Real-Time", font=("Segoe UI", 16, "bold")).pack(side=tk.LEFT) self.status_indicator = ttk.Label(title_frame, text="● Inactive", foreground="red") self.status_indicator.pack(side=tk.RIGHT) # Main split: Left controls, Right visualization h_split = ttk.PanedWindow(self.main_frame, orient=tk.HORIZONTAL) # Control panel (left) control_frame = ttk.LabelFrame(h_split, text="Mission Control", width=400) self.setup_control_panel(control_frame) h_split.add(control_frame, weight=1) # Visualization panel (right) viz_frame = ttk.LabelFrame(h_split, text="3D Interactive Viewport") self.setup_visualization_panel(viz_frame) h_split.add(viz_frame, weight=3) h_split.pack(fill=tk.BOTH, expand=True) # Bottom status and log bottom_frame = ttk.Frame(self.main_frame) bottom_frame.pack(fill=tk.X, pady=(10, 0)) # Stats panel stats_frame = ttk.LabelFrame(bottom_frame, text="Live Statistics", width=500) stats_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10)) self.setup_stats_panel(stats_frame) # Log panel log_frame = ttk.LabelFrame(bottom_frame, text="System Log") log_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True) self.setup_log_panel(log_frame) def setup_control_panel(self, parent): """Set up the control panel with real-time controls""" notebook = ttk.Notebook(parent) notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # Camera Control Tab camera_tab = ttk.Frame(notebook) notebook.add(camera_tab, text="📹 Camera") camera_frame = ttk.Frame(camera_tab) camera_frame.pack(fill=tk.X, pady=10) ttk.Label(camera_frame, text="Camera Status:").grid(row=0, column=0, sticky=tk.W) self.camera_status_label = ttk.Label(camera_frame, text="Inactive", foreground="red") self.camera_status_label.grid(row=0, column=1, sticky=tk.W) # Camera controls controls_frame = ttk.Frame(camera_tab) controls_frame.pack(fill=tk.X, pady=10) self.camera_button = ttk.Button( controls_frame, text="â–ļī¸ Start Camera", command=self.toggle_camera, width=15 ) self.camera_button.pack(side=tk.LEFT, padx=5) self.capture_button = ttk.Button( controls_frame, text="📸 Capture", command=self.capture_frame, state="disabled" ) self.capture_button.pack(side=tk.LEFT, padx=5) # Camera preview area preview_frame = ttk.LabelFrame(camera_tab, text="Live Preview") preview_frame.pack(fill=tk.BOTH, expand=True, pady=10) self.preview_label = ttk.Label(preview_frame, text="Camera feed will appear here") self.preview_label.pack(expand=True) # Processing Parameters Tab params_tab = ttk.Frame(notebook) notebook.add(params_tab, text="âš™ī¸ Processing") params_frame = ttk.Frame(params_tab) params_frame.pack(fill=tk.X, pady=10) # Motion detection threshold ttk.Label(params_frame, text="Motion Threshold:").grid(row=0, column=0, sticky=tk.W, pady=2) self.motion_threshold_var = tk.DoubleVar(value=self.motion_threshold) ttk.Scale( params_frame, from_=1, to=100, variable=self.motion_threshold_var, orient=tk.HORIZONTAL, length=200 ).grid(row=0, column=1, sticky=tk.W, pady=2) # Voxel resolution ttk.Label(params_frame, text="Voxel Resolution:").grid(row=1, column=0, sticky=tk.W, pady=2) self.voxel_resolution_var = tk.DoubleVar(value=self.voxel_resolution) ttk.Scale( params_frame, from_=0.1, to=2.0, variable=self.voxel_resolution_var, orient=tk.HORIZONTAL, length=200 ).grid(row=1, column=1, sticky=tk.W, pady=2) # Grid size ttk.Label(params_frame, text="Grid Size:").grid(row=2, column=0, sticky=tk.W, pady=2) self.grid_size_var = tk.IntVar(value=self.grid_size) ttk.Spinbox( params_frame, from_=32, to=128, textvariable=self.grid_size_var, width=10 ).grid(row=2, column=1, sticky=tk.W, pady=2) # Apply parameters button ttk.Button( params_frame, text="🔄 Apply Parameters", command=self.apply_parameters ).grid(row=3, column=0, columnspan=2, sticky=tk.W, pady=10) # Action buttons action_frame = ttk.Frame(params_tab) action_frame.pack(fill=tk.X, pady=10) self.process_button = ttk.Button( action_frame, text="🚀 Start Processing", command=self.toggle_processing, state="disabled" ) self.process_button.pack(fill=tk.X, pady=2) ttk.Button( action_frame, text="🔄 Reset Voxel Grid", command=self.reset_voxel_grid ).pack(fill=tk.X, pady=2) ttk.Button( action_frame, text="💾 Save Current State", command=self.save_current_state ).pack(fill=tk.X, pady=2) def setup_visualization_panel(self, parent): """Set up the interactive 3D visualization panel""" # Create matplotlib figure for 3D plotting self.viz_figure = plt.Figure(figsize=(8, 6), dpi=100) self.viz_axes = self.viz_figure.add_subplot(111, projection='3d') # Create canvas self.canvas = FigureCanvasTkAgg(self.viz_figure, master=parent) self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # Control buttons controls_frame = ttk.Frame(parent) controls_frame.pack(fill=tk.X, pady=(0, 5)) ttk.Button(controls_frame, text="🔄 Reset View").pack(side=tk.LEFT, padx=2) ttk.Button(controls_frame, text="📸 Screenshot").pack(side=tk.LEFT, padx=2) ttk.Button(controls_frame, text="đŸŽĨ Record").pack(side=tk.LEFT, padx=2) # Coordinate system label ttk.Label(controls_frame, text="Coordinate System:").pack(side=tk.RIGHT, padx=(20, 0)) self.coord_label = ttk.Label(controls_frame, text="Camera Reference") self.coord_label.pack(side=tk.RIGHT) # Initialize empty voxel visualization self.update_3d_visualization() def setup_stats_panel(self, parent): """Set up the live statistics panel""" # Performance metrics perf_frame = ttk.Frame(parent) perf_frame.pack(fill=tk.X, pady=5) ttk.Label(perf_frame, text="Performance:", font=("Segoe UI", 10, "bold")).pack(anchor=tk.W) metrics = [ ("Frame Rate", "fps", 0), ("Processing Time", "proc_time", 0), ("Memory Usage", "memory", 256), ("Voxel Count", "voxel_count", 0), ("Motion Detection", "motion", 0) ] self.metric_labels = {} for i, (label, var_name, value) in enumerate(metrics): frame = ttk.Frame(perf_frame) frame.pack(fill=tk.X, pady=1) ttk.Label(frame, text=f"{label}:").pack(side=tk.LEFT) metric_label = ttk.Label(frame, text=f"{value}") metric_label.pack(side=tk.RIGHT) self.metric_labels[var_name] = metric_label # Object detection stats detection_frame = ttk.Frame(parent) detection_frame.pack(fill=tk.X, pady=(10, 5)) ttk.Label(detection_frame, text="Detection Results:", font=("Segoe UI", 10, "bold")).pack(anchor=tk.W) self.detection_stats = { 'detected_objects': ttk.Label(detection_frame, text="0"), 'confidence': ttk.Label(detection_frame, text="0.0%"), 'processing_queue': ttk.Label(detection_frame, text="0") } for label_text, var_name in [("Objects Detected:", "detected_objects"), ("Avg Confidence:", "confidence"), ("Queue Size:", "processing_queue")]: frame = ttk.Frame(detection_frame) frame.pack(fill=tk.X, pady=1) ttk.Label(frame, text=label_text).pack(side=tk.LEFT) self.detection_stats[var_name].pack(side=tk.RIGHT) def setup_log_panel(self, parent): """Set up the system log panel""" self.log_text = scrolledtext.ScrolledText( parent, wrap=tk.WORD, height=8, font=("Consolas", 9), state='normal' ) self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.log_text.insert(tk.END, "System initialized - ready for processing\n") def setup_realtime_processing(self): """Set up real-time data processing pipeline""" # Initialize processing threads self.camera_thread_active = False self.viz_update_active = False self.previous_frame = None def toggle_camera(self): """Toggle camera activation""" if self.camera_active: self.stop_camera() else: self.start_camera() def start_camera(self): """Start camera feed simulation""" self.camera_active = True self.camera_status_label.config(text="Active", foreground="green") self.status_indicator.config(text="● Active", foreground="green") self.camera_button.config(text="âšī¸ Stop Camera") self.capture_button.config(state="normal") # Start camera feed thread if not self.camera_thread_active: self.camera_thread_active = True self.camera_feed_thread = threading.Thread( target=self.simulate_camera_feed, daemon=True ) self.camera_feed_thread.start() self.log_message("📹 Camera feed started") def stop_camera(self): """Stop camera feed""" self.camera_active = False self.camera_status_label.config(text="Inactive", foreground="red") self.status_indicator.config(text="● Inactive", foreground="red") self.camera_button.config(text="â–ļī¸ Start Camera") self.capture_button.config(state="disabled") # Stop camera thread self.camera_thread_active = False if self.camera_feed_thread and self.camera_feed_thread.is_alive(): self.camera_feed_thread.join(timeout=1.0) self.log_message("🛑 Camera feed stopped") def simulate_camera_feed(self): """Simulate real-time camera feed with synthetic astronomical images""" import matplotlib.pyplot as plt # Create synthetic star field for simulation np.random.seed(42) num_stars = 50 star_positions = np.random.rand(num_stars, 2) * 640 # 640x480 resolution star_magnitudes = np.random.uniform(0, 8, num_stars) frame_count = 0 while self.camera_thread_active: try: # Create synthetic astronomical image with moving stars image = np.zeros((480, 640), dtype=np.uint8) # Add background noise image += np.random.normal(10, 3, (480, 640)).astype(np.uint8) # Add stars with slight movement for i, (pos, mag) in enumerate(zip(star_positions, star_magnitudes)): x, y = pos + np.random.normal(0, 0.5, 2) # Slight movement if 0 <= x < 640 and 0 <= y < 480: intensity = int(255 * 10 ** (-mag / 2.5)) intensity = min(intensity, 200) # Prevent saturation # Add Gaussian star profile y_coords, x_coords = np.ogrid[:480, :640] sigma = 2.0 dist_sq = (x_coords - x)**2 + (y_coords - y)**2 star_profile = intensity * np.exp(-dist_sq / (2 * sigma**2)) image += star_profile.astype(np.uint8) # Convert to RGB for display rgb_image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) # Store current frame for processing self.camera_image = image.copy() # Update preview (create smaller version for UI) small_image = cv2.resize(rgb_image, (320, 240)) if hasattr(self, 'preview_label'): # Convert to tkinter image (simplified - in real app use PIL) self.update_camera_preview(small_image) frame_count += 1 time.sleep(1.0 / self.frame_rate) # Control frame rate except Exception as e: print(f"Camera simulation error: {e}") time.sleep(0.1) def update_camera_preview(self, image_array): """Update camera preview in UI""" # This is a simplified preview update # In a real implementation, convert to PhotoImage self.preview_label.config(text=f"Camera Preview Active\nFrame: {self.frame_count}\nResolution: {image_array.shape}") def capture_frame(self): """Capture current frame for processing""" if self.camera_image is not None: # Process the captured image self.process_frame(self.camera_image.copy()) self.log_message("📸 Frame captured and processed") else: self.log_message("âš ī¸ No camera image available") def process_frame(self, image): """Process a single frame for motion detection and voxel update""" if not self.is_processing: return start_time = time.time() try: # Detect motion (simplified - compare with previous frame) if hasattr(self, 'previous_frame') and self.previous_frame is not None: # Calculate motion mask diff = np.abs(image.astype(np.float32) - self.previous_frame.astype(np.float32)) motion_mask = diff > self.motion_threshold # Extract motion vectors (simplified) motion_pixels = np.where(motion_mask) if len(motion_pixels[0]) > 0: # Project to voxel grid (simplified) pixel_coords = np.column_stack(motion_pixels) intensities = diff[motion_pixels] for (y, x), intensity in zip(pixel_coords, intensities): # Convert pixel coordinates to voxel indices voxel_x = int((x / image.shape[1]) * self.grid_size) voxel_y = int((y / image.shape[0]) * self.grid_size) voxel_z = self.grid_size // 2 # Central depth # Bound check voxel_x = max(0, min(voxel_x, self.grid_size - 1)) voxel_y = max(0, min(voxel_y, self.grid_size - 1)) voxel_z = max(0, min(voxel_z, self.grid_size - 1)) # Accumulate in voxel grid self.current_voxel_data[voxel_x, voxel_y, voxel_z] += intensity * self.voxel_resolution # Update statistics self.frame_count += 1 self.update_statistics(intensity) # Store current frame as previous self.previous_frame = image.copy() # Update visualization self.update_3d_visualization() # Update performance metrics end_time = time.time() self.processing_time = (end_time - start_time) * 1000 # ms self.update_performance_display() except Exception as e: self.log_message(f"❌ Frame processing error: {e}") def update_statistics(self, avg_intensity): """Update detection statistics""" # Count non-zero voxels nonzero_count = np.count_nonzero(self.current_voxel_data) self.detection_stats['detected_objects'].config(text=str(nonzero_count)) # Calculate average confidence confidence = min(avg_intensity / 50.0, 1.0) * 100 if nonzero_count > 0 else 0 self.detection_stats['confidence'].config(text=".1f") # Queue size (simplified) queue_size = self.data_queue.qsize() if hasattr(self, 'data_queue') else 0 self.detection_stats['processing_queue'].config(text=str(queue_size)) def update_3d_visualization(self): """Update the 3D voxel visualization""" try: # Clear previous plot self.viz_axes.clear() # Get non-zero voxel coordinates nonzero_coords = np.where(self.current_voxel_data > 0) if len(nonzero_coords[0]) > 0: intensities = self.current_voxel_data[nonzero_coords] # Create 3D scatter plot self.viz_axes.scatter( nonzero_coords[2], nonzero_coords[1], nonzero_coords[0], c=intensities, cmap='plasma', marker='o', s=5, alpha=0.8 ) self.viz_axes.set_xlabel('X (space units)') self.viz_axes.set_ylabel('Y (space units)') self.viz_axes.set_zlabel('Z (space units)') self.viz_axes.set_title(f'Live Voxel Grid ({len(nonzero_coords[0])} points)') else: self.viz_axes.text(0.5, 0.5, 0.5, 'No voxel data\nWaiting for motion detection...', ha='center', va='center', transform=self.viz_axes.transAxes) self.viz_axes.set_title('Voxel Grid (Empty)') # Update coordinate system label self.coord_label.config(text="Camera Reference Frame") # Refresh canvas self.canvas.draw() except Exception as e: print(f"Visualization update error: {e}") def update_performance_display(self): """Update real-time performance metrics display""" try: # Calculate FPS self.fps = 1.0 / max(self.processing_time / 1000.0, 0.001) # Update metric labels self.metric_labels['fps'].config(text=".1f") self.metric_labels['proc_time'].config(text=".2f") self.metric_labels['memory'].config(text="256/512 MB") # Simulated self.metric_labels['voxel_count'].config(text=str(np.count_nonzero(self.current_voxel_data))) self.metric_labels['motion'].config(text=".1f") except Exception as e: print(f"Performance display error: {e}") def toggle_processing(self): """Toggle real-time processing on/off""" if self.is_processing: self.stop_processing() else: self.start_processing() def start_processing(self): """Start real-time processing""" if not self.camera_active: messagebox.showwarning("No Camera", "Please start camera feed first") return self.is_processing = True self.process_button.config(text="âšī¸ Stop Processing") # Start visualization update thread if not self.viz_update_active: self.viz_update_active = True self.viz_thread = threading.Thread(target=self.viz_update_loop, daemon=True) self.viz_thread.start() self.log_message("🚀 Real-time processing started") def stop_processing(self): """Stop real-time processing""" self.is_processing = False self.process_button.config(text="🚀 Start Processing") # Stop visualization thread self.viz_update_active = False if self.viz_thread and self.viz_thread.is_alive(): self.viz_thread.join(timeout=1.0) self.log_message("âšī¸ Real-time processing stopped") def viz_update_loop(self): """Background thread to update visualization""" while self.viz_update_active: try: if self.camera_image is not None: self.process_frame(self.camera_image.copy()) time.sleep(1.0 / 30.0) # 30 FPS update except Exception as e: print(f"Visualization update loop error: {e}") time.sleep(0.1) def apply_parameters(self): """Apply current parameter settings""" self.motion_threshold = self.motion_threshold_var.get() self.voxel_resolution = self.voxel_resolution_var.get() self.grid_size = self.grid_size_var.get() # Reset voxel grid with new size if self.grid_size != self.current_voxel_data.shape[0]: self.current_voxel_data = np.zeros((self.grid_size, self.grid_size, self.grid_size), dtype=np.float32) self.update_3d_visualization() self.log_message(".1f" ".1f") def reset_voxel_grid(self): """Reset the voxel grid to zeros""" self.current_voxel_data.fill(0) self.previous_frame = None self.update_3d_visualization() self.log_message("đŸ—‘ī¸ Voxel grid reset") def save_current_state(self): """Save current processing state""" try: from datetime import datetime timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"./astravoxel_capture_{timestamp}.npz" np.savez(filename, voxel_data=self.current_voxel_data) self.log_message(f"💾 State saved to: {filename}") except Exception as e: self.log_message(f"❌ Save error: {e}") def log_message(self, message): """Add message to system log""" timestamp = time.strftime("%H:%M:%S") log_entry = f"[{timestamp}] {message}\n" try: self.log_text.insert(tk.END, log_entry) self.log_text.see(tk.END) except: print(log_entry.strip()) def main(): """Main function to start AstraVoxel Real-Time""" root = tk.Tk() app = AstraVoxelRealtime(root) # Start the GUI event loop root.mainloop() if __name__ == "__main__": main() # End of AstraVoxel Real-Time Interface