File size: 6,402 Bytes
5a1c0ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255303a
5a1c0ee
 
 
 
00694e7
de92129
 
00694e7
 
d84d480
 
00694e7
 
 
 
 
 
 
5a1c0ee
de92129
 
5a1c0ee
 
 
34076e0
de92129
00694e7
 
5a1c0ee
 
 
de92129
 
 
 
 
 
 
 
 
 
 
 
5a1c0ee
 
00694e7
5a1c0ee
de92129
255303a
5a1c0ee
 
255303a
5a1c0ee
255303a
5a1c0ee
 
255303a
5a1c0ee
de92129
 
 
 
5a1c0ee
34076e0
5a1c0ee
de92129
5a1c0ee
34076e0
5a1c0ee
 
 
 
 
 
00694e7
de92129
5a1c0ee
de92129
 
5a1c0ee
de92129
 
 
 
34076e0
de92129
 
34076e0
 
de92129
5a1c0ee
34076e0
 
 
 
 
 
 
 
 
 
 
 
00694e7
34076e0
00694e7
34076e0
 
 
 
 
 
 
 
5a1c0ee
00694e7
5a1c0ee
00694e7
5a1c0ee
 
 
 
 
 
 
 
255303a
5a1c0ee
 
34076e0
5a1c0ee
 
00694e7
5a1c0ee
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
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()