Spaces:
Paused
Paused
Download app.py from broadfield-dev/detect-cme: direct link, hf CLI and curl.
- Browser
- Download file 6.4 kB
-
https://huggingface.co/spaces/broadfield-dev/detect-cme/resolve/d84d480f1465d739c93b0fb89610c751f8a3b588/app.py
- Command line
-
hf download hf://spaces/broadfield-dev/detect-cme@d84d480f1465d739c93b0fb89610c751f8a3b588/app.py
-
curl -L -o app.py https://huggingface.co/spaces/broadfield-dev/detect-cme/resolve/d84d480f1465d739c93b0fb89610c751f8a3b588/app.py
6.4 kB
| import gradio as gr | |
| import numpy as np | |
| import cv2 | |
| from PIL import Image | |
| 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, None | |
| except Exception as e: | |
| return None, f"Error loading GIF: {str(e)}" | |
| def preprocess_frame(frame): | |
| """Preprocess a frame: isolate mid-to-light pixels and enhance circular patterns.""" | |
| # Apply Gaussian blur to reduce noise | |
| blurred = cv2.GaussianBlur(frame, (9, 9), 0) | |
| # Isolate mid-to-light pixels (intensity range 100–200 in grayscale) | |
| lower_bound = 150 | |
| upper_bound = 255 | |
| mask = cv2.inRange(blurred, lower_bound, upper_bound) | |
| # Apply morphological operation to enhance circular patterns | |
| kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) | |
| enhanced = cv2.dilate(mask, kernel, iterations=2) | |
| return enhanced | |
| def detect_circles(frame_diff, image_center, min_radius=20, max_radius=200): | |
| """Detect circles in a frame difference image, centered at the Sun.""" | |
| circles = cv2.HoughCircles( | |
| frame_diff, | |
| cv2.HOUGH_GRADIENT, | |
| dp=1.5, # Resolution for better detection | |
| minDist=100, # Prevent overlapping circles | |
| param1=80, # Lower edge threshold to capture fainter edges | |
| param2=15, # Lower accumulator threshold to detect faint circles | |
| minRadius=min_radius, | |
| maxRadius=max_radius | |
| ) | |
| if circles is not None: | |
| circles = np.round(circles[0, :]).astype("int") | |
| # Filter circles: only keep those centered near the image center | |
| filtered_circles = [] | |
| center_tolerance = 30 # Allow 30-pixel deviation from the center | |
| for (x, y, r) in circles: | |
| if (abs(x - image_center[0]) < center_tolerance and | |
| abs(y - image_center[1]) < center_tolerance): | |
| filtered_circles.append((x, y, r)) | |
| return filtered_circles if filtered_circles else None | |
| return None | |
| def analyze_gif(gif_file): | |
| """Analyze a GIF for growing concentric circles of mid-to-light pixels.""" | |
| try: | |
| # Handle Gradio file input | |
| gif_path = gif_file.name if hasattr(gif_file, 'name') else gif_file | |
| # Extract frames | |
| frames, error = extract_frames(gif_path) | |
| if error: | |
| return error, [] | |
| if len(frames) < 2: | |
| return "GIF must have at least 2 frames for analysis.", [] | |
| # Determine the image center (Sun's position) | |
| height, width = frames[0].shape | |
| image_center = (width // 2, height // 2) # Assume Sun is at the center | |
| # Initialize results | |
| all_circle_data = [] # Store all detected circles | |
| min_radius = 20 | |
| max_radius = min(height, width) // 2 # Limit max radius to half the image size | |
| # Process frames and detect circles | |
| 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 the difference image | |
| frame_diff = cv2.convertScaleAbs(frame_diff, alpha=3.0, beta=0) | |
| # Detect circles centered at the Sun | |
| circles = detect_circles(frame_diff, image_center, min_radius, max_radius) | |
| if circles: | |
| # Take the largest circle (most prominent CME feature) | |
| largest_circle = max(circles, key=lambda c: c[2]) # Sort by radius | |
| x, y, r = largest_circle | |
| all_circle_data.append({ | |
| "frame": i + 1, | |
| "center": (x, y), | |
| "radius": r, | |
| "output_frame": frames[i + 1] # Store the frame for visualization | |
| }) | |
| # Filter frames where the circle is growing | |
| growing_circle_data = [] | |
| if all_circle_data: | |
| # Start with the first detection | |
| growing_circle_data.append(all_circle_data[0]) | |
| for i in range(1, len(all_circle_data)): | |
| # Compare radius with the last growing circle | |
| if all_circle_data[i]["radius"] > growing_circle_data[-1]["radius"]: | |
| growing_circle_data.append(all_circle_data[i]) | |
| # Generate output frames and report | |
| results = [] | |
| report = "Analysis Report (as of 07:34 PM PDT, May 24, 2025):\n" | |
| if growing_circle_data: | |
| report += f"Detected {len(growing_circle_data)} frames with growing concentric circles of mid-to-light pixels:\n" | |
| for c in growing_circle_data: | |
| # Visualize the frame with detected circle | |
| output_frame = cv2.cvtColor(c["output_frame"], cv2.COLOR_GRAY2RGB) | |
| cv2.circle(output_frame, (c["center"][0], c["center"][1]), c["radius"], (0, 255, 0), 2) | |
| # Convert to PIL Image for Gradio | |
| output_frame = Image.fromarray(output_frame) | |
| results.append(output_frame) | |
| report += f"Frame {c['frame']}: Center at {c['center']}, Radius {c['radius']} pixels\n" | |
| report += "\nConclusion: Growing concentric circles of mid-to-light pixels detected, indicative of a potential Earth-directed CME." | |
| else: | |
| report += "No growing concentric circles of mid-to-light pixels detected. CME may not be Earth-directed." | |
| 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", file_types=[".gif"]), | |
| outputs=[ | |
| gr.Textbox(label="Analysis Report"), | |
| gr.Gallery(label="Frames with Growing Circles") | |
| ], | |
| title="Solar CME Detection", | |
| description="Upload a GIF of solar images to detect growing concentric circles of mid-to-light pixels indicative of Earth-directed coronal mass ejections (CMEs)." | |
| ) | |
| if __name__ == "__main__": | |
| iface.launch() |