mirror of
https://github.com/ConsistentlyInconsistentYT/Pixeltovoxelprojector.git
synced 2025-10-13 04:12:07 +00:00
526 lines
No EOL
19 KiB
Python
526 lines
No EOL
19 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Visualization Script for Pixeltovoxelprojector Demo Results
|
||
==========================================================
|
||
|
||
This script loads the saved demo data and creates visualizations using matplotlib.
|
||
It provides multiple visualization modes for astronomical images, voxel grids,
|
||
and celestial sphere textures.
|
||
"""
|
||
|
||
import numpy as np
|
||
import matplotlib.pyplot as plt
|
||
import matplotlib.patches as patches
|
||
from mpl_toolkits.mplot3d import Axes3D
|
||
import os
|
||
import json
|
||
import warnings
|
||
warnings.filterwarnings('ignore')
|
||
|
||
# Try to import optional dependencies
|
||
try:
|
||
import seaborn as sns
|
||
HAS_SEABORN = True
|
||
except ImportError:
|
||
HAS_SEABORN = False
|
||
print("Info: seaborn not available - using matplotlib defaults")
|
||
|
||
try:
|
||
from matplotlib.animation import FuncAnimation
|
||
HAS_ANIMATION = True
|
||
except ImportError:
|
||
HAS_ANIMATION = False
|
||
print("Info: matplotlib animation not available")
|
||
|
||
class PixeltovoxelVisualizer:
|
||
"""
|
||
A comprehensive visualizer for astronomical voxel data.
|
||
"""
|
||
|
||
def __init__(self, data_dir="./demo_output"):
|
||
"""
|
||
Initialize the visualizer with data from the specified directory.
|
||
"""
|
||
self.data_dir = data_dir
|
||
self.check_data_availability()
|
||
self.load_data()
|
||
|
||
# Set up pretty plotting style
|
||
if HAS_SEABORN:
|
||
sns.set_style("darkgrid")
|
||
plt.rcParams['figure.figsize'] = (12, 8)
|
||
plt.rcParams['font.size'] = 10
|
||
plt.rcParams['axes.labelsize'] = 12
|
||
plt.rcParams['axes.titlesize'] = 14
|
||
|
||
def check_data_availability(self):
|
||
"""Check if required data files exist."""
|
||
required_files = [
|
||
'synthetic_image.npy',
|
||
'voxel_grid.npy',
|
||
'celestial_sphere_texture.npy',
|
||
'demo_parameters.json'
|
||
]
|
||
|
||
missing_files = []
|
||
for filename in required_files:
|
||
filepath = os.path.join(self.data_dir, filename)
|
||
if not os.path.exists(filepath):
|
||
missing_files.append(filename)
|
||
|
||
if missing_files:
|
||
raise FileNotFoundError(f"Missing required data files: {missing_files}")
|
||
|
||
print(f"✓ Found all required data files in: {self.data_dir}")
|
||
|
||
def load_data(self):
|
||
"""Load all the demo data from numpy and json files."""
|
||
print("Loading demo data...")
|
||
|
||
# Load numpy arrays
|
||
self.image = np.load(os.path.join(self.data_dir, 'synthetic_image.npy'))
|
||
self.voxel_grid = np.load(os.path.join(self.data_dir, 'voxel_grid.npy'))
|
||
self.celestial_texture = np.load(os.path.join(self.data_dir, 'celestial_sphere_texture.npy'))
|
||
|
||
# Load parameters
|
||
with open(os.path.join(self.data_dir, 'demo_parameters.json'), 'r') as f:
|
||
self.params = json.load(f)
|
||
|
||
print(f"✓ Loaded image: {self.image.shape}")
|
||
print(f"✓ Loaded voxel grid: {self.voxel_grid.shape}")
|
||
print(f"✓ Loaded celestial texture: {self.celestial_texture.shape}")
|
||
|
||
def visualize_astronomical_image(self, save_path=None):
|
||
"""
|
||
Create a comprehensive visualization of the synthetic astronomical image.
|
||
"""
|
||
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
|
||
fig.suptitle('Synthetic Astronomical Image Analysis', fontsize=16, fontweight='bold')
|
||
|
||
# 1. Main image
|
||
im1 = axes[0, 0].imshow(self.image, cmap='inferno', origin='lower')
|
||
axes[0, 0].set_title('Synthetic Astronomical Image')
|
||
axes[0, 0].set_xlabel('X pixel')
|
||
axes[0, 0].set_ylabel('Y pixel')
|
||
plt.colorbar(im1, ax=axes[0, 0], label='Brightness')
|
||
|
||
# 2. Image histogram
|
||
non_zero = self.image[self.image > 0]
|
||
axes[0, 1].hist(non_zero.flatten(), bins=50, alpha=0.7, color='blue', edgecolor='black')
|
||
axes[0, 1].set_title('Image Brightness Distribution')
|
||
axes[0, 1].set_xlabel('Brightness')
|
||
axes[0, 1].set_ylabel('Pixel Count')
|
||
axes[0, 1].set_yscale('log')
|
||
|
||
# 3. Bright star detection
|
||
threshold = np.mean(non_zero) + 2 * np.std(non_zero)
|
||
bright_mask = self.image > threshold
|
||
bright_coords = np.where(bright_mask)
|
||
|
||
axes[1, 0].imshow(self.image, cmap='gray', origin='lower', alpha=0.7)
|
||
if len(bright_coords[0]) > 0:
|
||
axes[1, 0].scatter(bright_coords[1], bright_coords[0],
|
||
c='red', s=10, alpha=0.8, label='Bright regions')
|
||
axes[1, 0].legend()
|
||
axes[1, 0].set_title(f'Detected Bright Regions (>{threshold:.4f})')
|
||
axes[1, 0].set_xlabel('X pixel')
|
||
axes[1, 0].set_ylabel('Y pixel')
|
||
|
||
# 4. Image statistics
|
||
axes[1, 1].axis('off')
|
||
stats_text = f"""Image Statistics:
|
||
|
||
Mean Brightness: {self.image.mean():.6f}
|
||
Max Brightness: {self.image.max():.6f}
|
||
Min Brightness: {self.image[self.image > 0].min():.6f} (non-zero)
|
||
Std Deviation: {self.image.std():.6f}
|
||
|
||
Star Detection:
|
||
Bright Regions: {np.sum(bright_mask)}
|
||
Detection Threshold: {threshold:.6f}
|
||
Total Pixels: {self.image.size}
|
||
Image Shape: {self.image.shape}
|
||
"""
|
||
|
||
axes[1, 1].text(0, 0.5, stats_text, transform=axes[1, 1].transAxes,
|
||
fontsize=12, verticalalignment='center',
|
||
bbox=dict(boxstyle="round,pad=0.5", facecolor="lightblue", alpha=0.5))
|
||
|
||
plt.tight_layout()
|
||
|
||
if save_path:
|
||
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
||
print(f"✓ Saved astronomical image visualization to: {save_path}")
|
||
|
||
return fig
|
||
|
||
def visualize_voxel_grid(self, save_path=None, threshold_percentile=95):
|
||
"""
|
||
Create 3D visualization of the voxel grid.
|
||
"""
|
||
fig = plt.figure(figsize=(16, 8))
|
||
|
||
# Calculate threshold for visualization
|
||
if self.voxel_grid.size > 0 and np.max(self.voxel_grid) > 0:
|
||
threshold = np.percentile(self.voxel_grid[self.voxel_grid > 0], threshold_percentile)
|
||
else:
|
||
threshold = 0
|
||
|
||
# Get non-zero voxels
|
||
nonzero_mask = self.voxel_grid > threshold
|
||
if np.sum(nonzero_mask) == 0:
|
||
# No significant voxels - show summary
|
||
ax = fig.add_subplot(111)
|
||
ax.text(0.5, 0.5,
|
||
'.10f',
|
||
transform=ax.transAxes, ha='center', va='center',
|
||
fontsize=14,
|
||
bbox=dict(boxstyle="round,pad=1", facecolor="lightcoral"))
|
||
ax.set_title('Voxel Grid Analysis - No Significant Data')
|
||
ax.axis('off')
|
||
else:
|
||
# Get coordinates of non-zero voxels
|
||
coords = np.where(nonzero_mask)
|
||
values = self.voxel_grid[nonzero_mask]
|
||
|
||
# Create 3D scatter plot
|
||
ax = fig.add_subplot(121, projection='3d')
|
||
|
||
scatter = ax.scatter(coords[2], coords[1], coords[0],
|
||
c=values, cmap='plasma', alpha=0.7,
|
||
s=20, edgecolors='black', linewidth=0.5)
|
||
|
||
ax.set_xlabel('X coordinate')
|
||
ax.set_ylabel('Y coordinate')
|
||
ax.set_zlabel('Z coordinate')
|
||
ax.set_title(f'3D Voxel Grid Visualization\n(Threshold: {threshold:.2e})')
|
||
|
||
plt.colorbar(scatter, ax=ax, label='Voxel Value', shrink=0.7)
|
||
|
||
# Add 2D projections
|
||
ax2 = fig.add_subplot(222)
|
||
ax2.scatter(coords[2], coords[0], c=values, cmap='plasma', alpha=0.6, s=10)
|
||
ax2.set_xlabel('X')
|
||
ax2.set_ylabel('Z')
|
||
ax2.set_title('X-Z Projection')
|
||
|
||
ax3 = fig.add_subplot(224)
|
||
ax3.scatter(coords[1], coords[0], c=values, cmap='plasma', alpha=0.6, s=10)
|
||
ax3.set_xlabel('Y')
|
||
ax3.set_ylabel('Z')
|
||
ax3.set_title('Y-Z Projection')
|
||
|
||
ax4 = fig.add_subplot(223)
|
||
ax4.scatter(coords[1], coords[2], c=values, cmap='plasma', alpha=0.6, s=10)
|
||
ax4.set_xlabel('Y')
|
||
ax4.set_ylabel('X')
|
||
ax4.set_title('Y-X Projection (top view)')
|
||
|
||
plt.tight_layout()
|
||
|
||
if save_path:
|
||
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
||
print(f"✓ Saved voxel grid visualization to: {save_path}")
|
||
|
||
return fig
|
||
|
||
def visualize_celestial_sphere(self, save_path=None):
|
||
"""
|
||
Visualize the celestial sphere texture with astronomical coordinates.
|
||
"""
|
||
fig = plt.figure(figsize=(14, 6))
|
||
|
||
# Create a RA/Dec grid for the texture
|
||
ra_deg = np.linspace(0, 360, self.celestial_texture.shape[1])
|
||
dec_deg = np.linspace(-90, 90, self.celestial_texture.shape[0])
|
||
|
||
# Plot the celestial sphere
|
||
ax1 = fig.add_subplot(121)
|
||
im1 = ax1.imshow(self.celestial_texture, extent=[0, 360, -90, 90],
|
||
aspect='auto', cmap='viridis', origin='lower')
|
||
ax1.set_xlabel('Right Ascension (degrees)')
|
||
ax1.set_ylabel('Declination (degrees)')
|
||
ax1.set_title('Celestial Sphere Texture')
|
||
plt.colorbar(im1, ax=ax1, label='Intensity')
|
||
|
||
# Add equatorial line
|
||
ax1.axhline(y=0, color='red', linestyle='--', alpha=0.7, label='Celestial Equator')
|
||
ax1.legend()
|
||
|
||
# Plot intensity distribution
|
||
ax2 = fig.add_subplot(222)
|
||
ax2.hist(self.celestial_texture.flatten(), bins=50, alpha=0.7,
|
||
color='blue', edgecolor='black')
|
||
ax2.set_title('Intensity Distribution')
|
||
ax2.set_xlabel('Intensity')
|
||
ax2.set_ylabel('Pixel Count')
|
||
ax2.set_yscale('log')
|
||
|
||
# Polar plot of celestial sphere
|
||
ax3 = fig.add_subplot(224, polar=True)
|
||
|
||
# Convert to polar coordinates
|
||
nonzero = self.celestial_texture[self.celestial_texture > 0]
|
||
if len(nonzero) > 0:
|
||
# Find brightest region in celestial coordinates
|
||
bright_idx = np.unravel_index(np.argmax(self.celestial_texture),
|
||
self.celestial_texture.shape)
|
||
bright_ra = ra_deg[bright_idx[1]]
|
||
bright_dec = dec_deg[bright_idx[0]]
|
||
|
||
# Convert to polar coordinates
|
||
r = 90 - bright_dec # Distance from north celestial pole
|
||
theta = np.radians(bright_ra)
|
||
|
||
ax3.scatter(theta, r, s=100, c='red', alpha=0.8, edgecolors='black')
|
||
ax3.set_title('Brightest Region\n(celestial coordinates)')
|
||
|
||
# Set up polar plot properly
|
||
ax3.set_rlim(0, 90)
|
||
ax3.set_rticks([30, 60, 90])
|
||
ax3.set_rlabel_position(135)
|
||
ax3.grid(True, alpha=0.3)
|
||
|
||
# Statistics
|
||
ax4 = fig.add_subplot(223)
|
||
ax4.axis('off')
|
||
stats_text = ".2f"f"""Celestial Sphere Stats:
|
||
|
||
Shape: {self.celestial_texture.shape}
|
||
Total Pixels: {self.celestial_texture.size}
|
||
Max Intensity: {self.celestial_texture.max():.6f}
|
||
Min Intensity: {self.celestial_texture.min():.6f}
|
||
Non-zero Pixels: {np.count_nonzero(self.celestial_texture)}
|
||
|
||
Coverage: 360° RA × 180° Dec
|
||
Resolution: {360/self.celestial_texture.shape[1]:.2f}°/pixel
|
||
"""
|
||
|
||
ax4.text(0, 0.5, stats_text, transform=ax4.transAxes,
|
||
fontsize=11, verticalalignment='center',
|
||
bbox=dict(boxstyle="round,pad=0.5", facecolor="lightyellow"))
|
||
|
||
plt.tight_layout()
|
||
|
||
if save_path:
|
||
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
||
print(f"✓ Saved celestial sphere visualization to: {save_path}")
|
||
|
||
return fig
|
||
|
||
def create_comprehensive_report(self, save_dir="./visualizations"):
|
||
"""
|
||
Generate a comprehensive visual report with all data visualizations.
|
||
"""
|
||
os.makedirs(save_dir, exist_ok=True)
|
||
|
||
print("\n" + "="*60)
|
||
print("GENERATING COMPREHENSIVE VISUALIZATION REPORT")
|
||
print("="*60)
|
||
|
||
# Astronomical image visualization
|
||
print("\n1. Creating astronomical image visualization...")
|
||
fig1 = self.visualize_astronomical_image()
|
||
fig1.savefig(os.path.join(save_dir, 'astronomical_image_analysis.png'),
|
||
dpi=300, bbox_inches='tight')
|
||
plt.close(fig1)
|
||
|
||
# Voxel grid visualization
|
||
print("\n2. Creating 3D voxel grid visualization...")
|
||
fig2 = self.visualize_voxel_grid()
|
||
if np.sum(self.voxel_grid > 0) > 0: # Only save if there's data
|
||
fig2.savefig(os.path.join(save_dir, 'voxel_grid_3d.png'),
|
||
dpi=300, bbox_inches='tight')
|
||
else:
|
||
print(" → Skipping voxel grid (no significant data)")
|
||
plt.close(fig2)
|
||
|
||
# Celestial sphere visualization
|
||
print("\n3. Creating celestial sphere visualization...")
|
||
fig3 = self.visualize_celestial_sphere()
|
||
fig3.savefig(os.path.join(save_dir, 'celestial_sphere_texture.png'),
|
||
dpi=300, bbox_inches='tight')
|
||
plt.close(fig3)
|
||
|
||
# Create summary plot
|
||
print("\n4. Creating summary dashboard...")
|
||
self.create_summary_dashboard(save_dir)
|
||
|
||
print(f"\n✓ All visualizations saved to: {save_dir}")
|
||
print("✓ Report generation completed!")
|
||
|
||
def create_summary_dashboard(self, save_dir):
|
||
"""
|
||
Create a summary dashboard with key metrics and small previews.
|
||
"""
|
||
fig, axes = plt.subplots(2, 3, figsize=(18, 12))
|
||
fig.suptitle('Pixeltovoxelprojector Demo Results - Summary Dashboard',
|
||
fontsize=16, fontweight='bold')
|
||
|
||
# 1. Image preview
|
||
axes[0, 0].imshow(self.image, cmap='inferno', origin='lower')
|
||
axes[0, 0].set_title('Astronomical Image')
|
||
axes[0, 0].set_xlabel('X pixel')
|
||
axes[0, 0].set_ylabel('Y pixel')
|
||
|
||
# 2. Image histogram
|
||
non_zero = self.image[self.image > 0]
|
||
axes[1, 0].hist(non_zero.flatten(), bins=30, alpha=0.7, color='green')
|
||
axes[1, 0].set_title('Brightness Histogram')
|
||
axes[1, 0].set_xlabel('Brightness')
|
||
axes[1, 0].set_ylabel('Count')
|
||
axes[1, 0].set_yscale('log')
|
||
|
||
# 3. Voxel grid scatter
|
||
voxel_coords = np.where(self.voxel_grid > 0)
|
||
if len(voxel_coords[0]) > 0:
|
||
axes[0, 1].scatter(voxel_coords[2], voxel_coords[1], voxel_coords[0],
|
||
c=self.voxel_grid[voxel_coords], cmap='plasma',
|
||
alpha=0.6, s=5)
|
||
axes[0, 1].set_title(f'Voxel Grid ({len(voxel_coords[0])} points)')
|
||
axes[0, 1].set_xlabel('X')
|
||
axes[0, 1].set_ylabel('Y')
|
||
axes[0, 1].set_zlabel('Z')
|
||
else:
|
||
axes[0, 1].text(0.5, 0.5, 'No significant\nvoxel data',
|
||
transform=axes[0, 1].transAxes, ha='center', va='center',
|
||
fontsize=12, bbox=dict(boxstyle="round", facecolor="lightcoral"))
|
||
|
||
# 4. Celestial sphere polar plot
|
||
axes[1, 1].remove()
|
||
ax_polar = fig.add_subplot(2, 3, 5, polar=True)
|
||
if np.max(self.celestial_texture) > 0:
|
||
ax_polar.text(0, 0.5, 'Celestial\nSphere\nAvailable',
|
||
transform=ax_polar.transAxes, ha='center', va='center')
|
||
else:
|
||
ax_polar.text(0, 0.5, 'Celestial\nSphere\n(Not processed)',
|
||
transform=ax_polar.transAxes, ha='center', va='center')
|
||
ax_polar.set_title('Celestial Coverage', pad=20)
|
||
|
||
# 5. Statistics summary
|
||
axes[0, 2].axis('off')
|
||
stats_text = f"""SUMMARY STATISTICS
|
||
|
||
Image Metrics:
|
||
Shape: {self.params['image_shape']}
|
||
Mean: {self.image.mean():.4e}
|
||
Max: {self.image.max():.4e}
|
||
Std: {self.image.std():.4e}
|
||
|
||
Voxel Grid:
|
||
Shape: {self.params['voxel_shape']}
|
||
Total Voxels: {self.voxel_grid.size:,}
|
||
Non-zero: {np.count_nonzero(self.voxel_grid):,}
|
||
Max Value: {self.voxel_grid.max():.4e}
|
||
|
||
Celestial Texture:
|
||
Shape: {self.params['texture_shape']}
|
||
Total Pixels: {self.celestial_texture.size:,}
|
||
Non-zero: {np.count_nonzero(self.celestial_texture):,}
|
||
Max Value: {self.celestial_texture.max():.4e}
|
||
"""
|
||
|
||
axes[0, 2].text(0, 0.5, stats_text, transform=axes[0, 2].transAxes,
|
||
fontsize=10, verticalalignment='center',
|
||
bbox=dict(boxstyle="round,pad=0.5", facecolor="lightblue", alpha=0.7))
|
||
|
||
# 6. Processing status
|
||
axes[1, 2].axis('off')
|
||
status_text = """PROCESSING STATUS
|
||
|
||
✓ Synthetic astronomical image generated
|
||
✓ Star field simulation completed
|
||
✓ Voxel grid initialized
|
||
✓ Celestial sphere texture initialized
|
||
✓ Data export completed
|
||
|
||
Note: Actual voxel processing requires
|
||
compiled C++ library to be called.
|
||
|
||
Use: python demo_pixeltovoxel.py
|
||
"""
|
||
|
||
axes[1, 2].text(0, 0.5, status_text, transform=axes[1, 2].transAxes,
|
||
fontsize=11, verticalalignment='center',
|
||
bbox=dict(boxstyle="round,pad=0.5", facecolor="lightgreen", alpha=0.7))
|
||
|
||
plt.tight_layout()
|
||
fig.savefig(os.path.join(save_dir, 'summary_dashboard.png'),
|
||
dpi=300, bbox_inches='tight')
|
||
plt.close(fig)
|
||
|
||
def show_interactive_voxel_slice(self):
|
||
"""
|
||
Create an interactive voxel grid slice viewer.
|
||
Requires matplotlib animation support.
|
||
"""
|
||
if not HAS_ANIMATION:
|
||
print("Matplotlib animation not available. Skipping interactive viewer.")
|
||
return
|
||
|
||
fig, ax = plt.subplots(1, 1, figsize=(10, 8))
|
||
|
||
def update_frame(frame):
|
||
ax.clear()
|
||
slice_data = self.voxel_grid[:, :, frame]
|
||
im = ax.imshow(slice_data, cmap='plasma', origin='lower')
|
||
ax.set_title(f'Voxel Grid Slice Z={frame}')
|
||
ax.set_xlabel('X coordinate')
|
||
ax.set_ylabel('Y coordinate')
|
||
plt.colorbar(im, ax=ax, label='Intensity')
|
||
return [im]
|
||
|
||
# Create animation
|
||
num_frames = self.voxel_grid.shape[2]
|
||
if num_frames > 1:
|
||
anim = FuncAnimation(fig, update_frame, frames=num_frames,
|
||
interval=200, blit=False)
|
||
|
||
print("Interactive voxel slice viewer created!")
|
||
return anim
|
||
else:
|
||
print("No animation needed - only one slice available.")
|
||
return None
|
||
|
||
def main():
|
||
"""
|
||
Main function to run all visualizations.
|
||
"""
|
||
print("Pixeltovoxelprojector - Results Visualization")
|
||
print("=" * 50)
|
||
|
||
try:
|
||
# Create visualizer
|
||
visualizer = PixeltovoxelVisualizer()
|
||
|
||
# Generate comprehensive report
|
||
visualizer.create_comprehensive_report()
|
||
|
||
# Try to show individual visualizations
|
||
print("\n" + "="*50)
|
||
print("INDIVIDUAL VISUALIZATIONS AVAILABLE:")
|
||
print("1. Astronomical image analysis")
|
||
print("2. 3D voxel grid visualization")
|
||
print("3. Celestial sphere texture")
|
||
print("4. Summary dashboard")
|
||
print("="*50)
|
||
|
||
# Optional: Show interactive visualization
|
||
if HAS_ANIMATION and input("\nShow interactive voxel slice viewer? (y/N): ").lower().startswith('y'):
|
||
anim = visualizer.show_interactive_voxel_slice()
|
||
if anim:
|
||
plt.show()
|
||
else:
|
||
print("\nVisualization complete! Check the ./visualizations/ directory for all images.")
|
||
|
||
except FileNotFoundError as e:
|
||
print(f"Error: {e}")
|
||
print("\nMake sure you've run the demo script first:")
|
||
print("python demo_pixeltovoxel.py")
|
||
except Exception as e:
|
||
print(f"Visualization error: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
if __name__ == "__main__":
|
||
main() |