SolarImageDownloader / view_images.py
AK51's picture
Upload 13308 files
b610d23 verified
#!/usr/bin/env python3
"""
NASA Solar Image Viewer
Simple image viewer with video-like playback controls.
"""
import sys
import time
import threading
from pathlib import Path
from datetime import datetime
# Add src to Python path
sys.path.insert(0, str(Path(__file__).parent / "src"))
from src.storage.storage_organizer import StorageOrganizer
try:
import tkinter as tk
from tkinter import ttk, messagebox
from PIL import Image, ImageTk
HAS_GUI = True
except ImportError:
HAS_GUI = False
class ImageViewer:
"""Simple image viewer with video-like controls."""
def __init__(self, storage: StorageOrganizer):
"""Initialize the image viewer."""
self.storage = storage
self.images = []
self.current_index = 0
self.is_playing = False
self.fps = 2 # Default 2 FPS
self.play_thread = None
if not HAS_GUI:
raise ImportError("GUI libraries not available. Install with: pip install pillow")
# Create main window
self.root = tk.Tk()
self.root.title("NASA Solar Image Viewer")
self.root.geometry("800x900")
self.setup_ui()
def setup_ui(self):
"""Set up the user interface."""
# Main frame
main_frame = ttk.Frame(self.root)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Title
title_label = ttk.Label(main_frame, text="🌞 NASA Solar Image Viewer",
font=("Arial", 16, "bold"))
title_label.pack(pady=(0, 10))
# Date selection frame
date_frame = ttk.LabelFrame(main_frame, text="Select Date", padding=10)
date_frame.pack(fill=tk.X, pady=(0, 10))
self.date_var = tk.StringVar()
self.date_combo = ttk.Combobox(date_frame, textvariable=self.date_var,
state="readonly", width=30)
self.date_combo.pack(side=tk.LEFT, padx=(0, 10))
load_btn = ttk.Button(date_frame, text="Load Images", command=self.load_images)
load_btn.pack(side=tk.LEFT)
# Image display
self.image_frame = ttk.LabelFrame(main_frame, text="Solar Image", padding=10)
self.image_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
self.image_label = ttk.Label(self.image_frame, text="No image loaded",
background="black", foreground="white")
self.image_label.pack(fill=tk.BOTH, expand=True)
# Info frame
info_frame = ttk.Frame(main_frame)
info_frame.pack(fill=tk.X, pady=(0, 10))
self.info_label = ttk.Label(info_frame, text="Ready", font=("Arial", 10))
self.info_label.pack()
# Controls frame
controls_frame = ttk.LabelFrame(main_frame, text="Playback Controls", padding=10)
controls_frame.pack(fill=tk.X)
# Playback buttons
btn_frame = ttk.Frame(controls_frame)
btn_frame.pack(fill=tk.X, pady=(0, 10))
self.prev_btn = ttk.Button(btn_frame, text="⏮ Previous", command=self.prev_image)
self.prev_btn.pack(side=tk.LEFT, padx=(0, 5))
self.play_btn = ttk.Button(btn_frame, text="▶ Play", command=self.toggle_play)
self.play_btn.pack(side=tk.LEFT, padx=(0, 5))
self.next_btn = ttk.Button(btn_frame, text="Next ⏭", command=self.next_image)
self.next_btn.pack(side=tk.LEFT, padx=(0, 5))
self.stop_btn = ttk.Button(btn_frame, text="⏹ Stop", command=self.stop_play)
self.stop_btn.pack(side=tk.LEFT)
# Speed control
speed_frame = ttk.Frame(controls_frame)
speed_frame.pack(fill=tk.X)
ttk.Label(speed_frame, text="Speed (FPS):").pack(side=tk.LEFT, padx=(0, 5))
self.speed_var = tk.DoubleVar(value=2.0)
speed_scale = ttk.Scale(speed_frame, from_=0.5, to=10.0,
variable=self.speed_var, orient=tk.HORIZONTAL,
command=self.update_speed)
speed_scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 10))
self.speed_label = ttk.Label(speed_frame, text="2.0 FPS")
self.speed_label.pack(side=tk.LEFT)
# Progress bar
self.progress_var = tk.DoubleVar()
self.progress_bar = ttk.Progressbar(controls_frame, variable=self.progress_var,
maximum=100)
self.progress_bar.pack(fill=tk.X, pady=(10, 0))
# Load available dates
self.load_available_dates()
def load_available_dates(self):
"""Load available dates with images."""
dates = []
data_dir = self.storage.base_data_dir
if data_dir.exists():
for year_dir in data_dir.iterdir():
if not year_dir.is_dir() or not year_dir.name.isdigit():
continue
for month_dir in year_dir.iterdir():
if not month_dir.is_dir() or not month_dir.name.isdigit():
continue
for day_dir in month_dir.iterdir():
if not day_dir.is_dir() or not day_dir.name.isdigit():
continue
images = list(day_dir.glob("*_4096_0211.jpg"))
if images:
try:
date = datetime(int(year_dir.name), int(month_dir.name), int(day_dir.name))
date_str = f"{date.strftime('%Y-%m-%d')} ({len(images)} images)"
dates.append((date, date_str))
except ValueError:
continue
if dates:
dates.sort()
date_strings = [date_str for _, date_str in dates]
self.date_combo['values'] = date_strings
self.date_combo.current(0) # Select first date
self.available_dates = {date_str: date for date, date_str in dates}
else:
messagebox.showwarning("No Images", "No downloaded images found!\n\nRun 'python download_real_images.py' first.")
def load_images(self):
"""Load images for the selected date."""
selected = self.date_var.get()
if not selected or selected not in self.available_dates:
messagebox.showerror("Error", "Please select a valid date")
return
date = self.available_dates[selected]
image_files = self.storage.list_local_images(date)
if not image_files:
messagebox.showerror("Error", f"No images found for {date.strftime('%Y-%m-%d')}")
return
# Load image paths
self.images = []
date_path = self.storage.get_date_path(date)
for filename in sorted(image_files):
image_path = date_path / filename
self.images.append((image_path, filename))
self.current_index = 0
self.update_display()
self.info_label.config(text=f"Loaded {len(self.images)} images for {date.strftime('%Y-%m-%d')}")
def update_display(self):
"""Update the image display."""
if not self.images:
return
image_path, filename = self.images[self.current_index]
try:
# Load and resize image
pil_image = Image.open(image_path)
# Calculate size to fit in display area (max 600x600)
display_size = (600, 600)
pil_image.thumbnail(display_size, Image.Resampling.LANCZOS)
# Convert to PhotoImage
photo = ImageTk.PhotoImage(pil_image)
# Update display
self.image_label.config(image=photo, text="")
self.image_label.image = photo # Keep a reference
# Update info
progress = (self.current_index + 1) / len(self.images) * 100
self.progress_var.set(progress)
# Extract timestamp from filename
timestamp = filename.split('_')[1] if '_' in filename else "Unknown"
if len(timestamp) == 6:
formatted_time = f"{timestamp[:2]}:{timestamp[2:4]}:{timestamp[4:6]}"
else:
formatted_time = timestamp
info_text = f"Image {self.current_index + 1}/{len(self.images)} - Time: {formatted_time}"
self.image_frame.config(text=info_text)
except Exception as e:
self.image_label.config(text=f"Error loading image: {e}")
def prev_image(self):
"""Go to previous image."""
if self.images and self.current_index > 0:
self.current_index -= 1
self.update_display()
def next_image(self):
"""Go to next image."""
if self.images and self.current_index < len(self.images) - 1:
self.current_index += 1
self.update_display()
def toggle_play(self):
"""Toggle play/pause."""
if self.is_playing:
self.pause_play()
else:
self.start_play()
def start_play(self):
"""Start playing images."""
if not self.images:
messagebox.showwarning("No Images", "Please load images first")
return
self.is_playing = True
self.play_btn.config(text="⏸ Pause")
# Start play thread
self.play_thread = threading.Thread(target=self._play_loop, daemon=True)
self.play_thread.start()
def pause_play(self):
"""Pause playing."""
self.is_playing = False
self.play_btn.config(text="▶ Play")
def stop_play(self):
"""Stop playing and reset to first image."""
self.is_playing = False
self.play_btn.config(text="▶ Play")
self.current_index = 0
self.update_display()
def _play_loop(self):
"""Play loop running in background thread."""
while self.is_playing and self.images:
if self.current_index >= len(self.images) - 1:
# Reached end, loop back to start
self.current_index = 0
else:
self.current_index += 1
# Update display in main thread
self.root.after(0, self.update_display)
# Wait based on FPS
delay = 1.0 / self.fps
time.sleep(delay)
def update_speed(self, value):
"""Update playback speed."""
self.fps = float(value)
self.speed_label.config(text=f"{self.fps:.1f} FPS")
def run(self):
"""Run the viewer."""
self.root.mainloop()
def main():
"""Main viewer application."""
if not HAS_GUI:
print("❌ GUI libraries not available!")
print("💡 Install with: pip install pillow")
print("💡 Or use the video creator: python create_video.py")
return
try:
storage = StorageOrganizer("data")
viewer = ImageViewer(storage)
viewer.run()
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()