import gradio as gr import numpy as np import cv2 from PIL import Image import io import matplotlib.pyplot as plt def extract_frames(gif_path): """Extract frames from a GIF and return as a list of numpy arrays.""" try: img = Image.open(gif_path) frames = [] while True: frame = img.convert('L') # Convert to grayscale frames.append(np.array(frame)) try: img.seek(img.tell() + 1) except EOFError: break return frames except Exception as e: return None, f"Error loading GIF: {str(e)}" def preprocess_frame(frame): """Preprocess a frame: apply Gaussian blur to reduce noise.""" return cv2.GaussianBlur(frame, (5, 5), 0) def detect_circles(frame_diff, min_radius=20, max_radius=200): """Detect circles in a frame difference image using Hough Circle Transform.""" circles = cv2.HoughCircles( frame_diff, cv2.HOUGH_GRADIENT, dp=1.2, # Inverse ratio of resolution minDist=50, # Minimum distance between detected centers param1=50, # Canny edge detector threshold param2=30, # Accumulator threshold for circle detection minRadius=min_radius, maxRadius=max_radius ) return circles def analyze_gif(gif_file): """Analyze a GIF for growing concentric circles.""" try: # Save uploaded GIF to temporary file with open("temp.gif", "wb") as f: f.write(gif_file.read()) # Extract frames frames, error = extract_frames("temp.gif") if error: return error if len(frames) < 2: return "GIF must have at least 2 frames for analysis." # Initialize results results = [] circle_data = [] min_radius = 20 max_radius = min(max(frames[0].shape) // 2, 200) # Limit max radius based on image size # Process frames for i in range(len(frames) - 1): frame1 = preprocess_frame(frames[i]) frame2 = preprocess_frame(frames[i + 1]) # Compute absolute difference between consecutive frames frame_diff = cv2.absdiff(frame2, frame1) # Enhance contrast for lighter pixels frame_diff = cv2.convertScaleAbs(frame_diff, alpha=2.0, beta=0) # Detect circles in the difference image circles = detect_circles(frame_diff, min_radius, max_radius) if circles is not None: circles = np.round(circles[0, :]).astype("int") for (x, y, r) in circles: circle_data.append({ "frame": i + 1, "center": (x, y), "radius": r }) # Optional: Save frame with detected circles for visualization output_frame = cv2.cvtColor(frames[i + 1], cv2.COLOR_GRAY2RGB) if circles is not None: for (x, y, r) in circles: cv2.circle(output_frame, (x, y), r, (0, 255, 0), 2) # Convert to PIL Image for Gradio output_frame = Image.fromarray(output_frame) results.append(output_frame) # Analyze circle data for growth report = "Analysis Report:\n" if circle_data: radii = [c["radius"] for c in circle_data] centers = [c["center"] for c in circle_data] frames_with_circles = [c["frame"] for c in circle_data] # Check if radii are increasing over frames is_growing = all(radii[i] < radii[i + 1] for i in range(len(radii) - 1)) center_consistent = all( abs(centers[i][0] - centers[0][0]) < 20 and abs(centers[i][1] - centers[0][1]) < 20 for i in range(1, len(centers)) ) report += f"Detected {len(circle_data)} circles across frames.\n" for c in circle_data: report += f"Frame {c['frame']}: Center at {c['center']}, Radius {c['radius']} pixels\n" if is_growing and center_consistent: report += "\nConclusion: Growing concentric circles detected, indicative of a potential Earth-directed CME." else: report += "\nConclusion: Detected circles, but growth pattern or center consistency does not confirm a clear CME." else: report += "No concentric circles detected." return report, results except Exception as e: return f"Error during analysis: {str(e)}", [] # Gradio interface iface = gr.Interface( fn=analyze_gif, inputs=gr.File(label="Upload Solar GIF"), outputs=[ gr.Textbox(label="Analysis Report"), gr.Gallery(label="Frames with Detected Circles") ], title="Solar CME Detection", description="Upload a GIF of solar images to detect growing concentric circles indicative of Earth-directed coronal mass ejections (CMEs)." ) if __name__ == "__main__": iface.launch()