broadfield-dev's picture
Update app.py
2df0af8 verified
import gradio as gr
import cv2
import numpy as np
from scipy.spatial import distance
import requests
from bs4 import BeautifulSoup
from datetime import datetime, timedelta
import urllib.request
from PIL import Image
import io# Function to fetch image filenames from SDO website for today and yesterday
def get_sdo_images():
base_url = "https://sdo.gsfc.nasa.gov/assets/img/browse/"
today = datetime.now()
yesterday = today - timedelta(days=1)
dates = [today, yesterday]
image_list = []
for date in dates:
date_str = date.strftime("%Y/%m/%d")
url = f"{base_url}{date_str}/"
try:
response = requests.get(url)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
for link in soup.find_all('a'):
href = link.get('href')
if href and href.endswith('_1024_HMIBC.jpg'):
filename = href.split('/')[-1].replace('_1024_HMIBC.jpg', '')
image_list.append((filename, f"{url}{href}"))
except requests.RequestException as e:
print(f"Error fetching images for {date_str}: {e}")
image_list.sort(reverse=True, key=lambda x: x[0])
filenames = [item[0] for item in image_list]
url_map = {item[0]: item[1] for item in image_list}
return filenames, url_map
# Function to fetch the selected image
def fetch_image(selected_image, url_map):
if not selected_image:
return None
image_url = url_map.get(selected_image)
if not image_url:
return None
try:
with urllib.request.urlopen(image_url) as response:
img_data = response.read()
img = Image.open(io.BytesIO(img_data))
return img
except Exception as e:
print(f"Error fetching image {selected_image}: {e}")
return None# Function to analyze the solar image with adjustable parameters
def analyze_solar_image(
image,
yellow_hue_min=20, yellow_hue_max=30, yellow_sat_min=100, yellow_val_min=100,
grey_hue_max=180, grey_sat_max=30, grey_val_min=50, grey_val_max=200,
blue_hue_min=90, blue_hue_max=150, blue_sat_min=50, blue_val_min=30,
red_hue_min1=0, red_hue_max1=15, red_hue_min2=155, red_hue_max2=180,
red_sat_min=50, red_val_min=30,
flare_dist_threshold=50, min_area_threshold=10, darkness_weight=0.5
):
if image is None:
return None, "No image selected or image could not be loaded."
image = np.array(image)
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
hsv_image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
yellow_lower = np.array([yellow_hue_min, yellow_sat_min, yellow_val_min])
yellow_upper = np.array([yellow_hue_max, 255, 255])
grey_lower = np.array([0, 0, grey_val_min])
grey_upper = np.array([grey_hue_max, grey_sat_max, grey_val_max])
blue_lower = np.array([blue_hue_min, blue_sat_min, blue_val_min])
blue_upper = np.array([blue_hue_max, 255, 255])
red_lower1 = np.array([red_hue_min1, red_sat_min, red_val_min])
red_lower2 = np.array([red_hue_min2, red_sat_min, red_val_min])
red_upper1 = np.array([red_hue_max1, 255, 255])
red_upper2 = np.array([red_hue_max2, 255, 255])
mask_yellow = cv2.inRange(hsv_image, yellow_lower, yellow_upper)
mask_grey = cv2.inRange(hsv_image, grey_lower, grey_upper)
mask_blue = cv2.inRange(hsv_image, blue_lower, blue_upper)
mask_red1 = cv2.inRange(hsv_image, red_lower1, red_upper1)
mask_red2 = cv2.inRange(hsv_image, red_lower2, red_upper2)
mask_red = cv2.bitwise_or(mask_red1, mask_red2)
blue_contours, _ = cv2.findContours(mask_blue, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
red_contours, _ = cv2.findContours(mask_red, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
sunspots = []
used_blue = set()
used_red = set()
for i, bc in enumerate(blue_contours):
if i in used_blue:
continue
bc_area = cv2.contourArea(bc)
if bc_area < min_area_threshold:
continue
bc_centroid = None
M = cv2.moments(bc)
if M["m00"] != 0:
bcX = int(M["m10"] / M["m00"])
bcY = int(M["m01"] / M["m00"])
bc_centroid = (bcX, bcY)
if bc_centroid is None:
continue
sunspot_contours = [bc]
used_blue.add(i)
for j, rc in enumerate(red_contours):
if j in used_red:
continue
rc_area = cv2.contourArea(rc)
if rc_area < min_area_threshold:
continue
M = cv2.moments(rc)
if M["m00"] != 0:
rcX = int(M["m10"] / M["m00"])
rcY = int(M["m01"] / M["m00"])
rc_centroid = (rcX, rcY)
dist = distance.euclidean(bc_centroid, rc_centroid)
if dist < flare_dist_threshold:
sunspot_contours.append(rc)
used_red.add(j)
for k, bc2 in enumerate(blue_contours):
if k in used_blue or k == i:
continue
bc2_area = cv2.contourArea(bc2)
if bc2_area < min_area_threshold:
continue
M = cv2.moments(bc2)
if M["m00"] != 0:
bc2X = int(M["m10"] / M["m00"])
bc2Y = int(M["m01"] / M["m00"])
bc2_centroid = (bc2X, bc2Y)
dist = distance.euclidean(bc_centroid, bc2_centroid)
if dist < flare_dist_threshold:
sunspot_contours.append(bc2)
used_blue.add(k)
for j, rc in enumerate(red_contours):
if j in used_red:
continue
rc_area = cv2.contourArea(rc)
if rc_area < min_area_threshold:
continue
M = cv2.moments(rc)
if M["m00"] != 0:
rcX = int(M["m10"] / M["m00"])
rcY = int(M["m01"] / M["m00"])
rc_centroid = (rcX, rcY)
dist = distance.euclidean(bc_centroid, rc_centroid)
if dist < flare_dist_threshold:
sunspot_contours.append(rc)
used_red.add(j)
sunspots.append(sunspot_contours)
for j, rc in enumerate(red_contours):
if j in used_red:
continue
rc_area = cv2.contourArea(rc)
if rc_area >= min_area_threshold:
sunspots.append([rc])
used_red.add(j)
sunspot_image = image.copy()
report = "Solar Image Analysis Report\n\n"
overall_flare_risk = "Low"
overall_flare_percentage = 0
mixed_sunspot_count = 0
for i, sunspot_contours in enumerate(sunspots):
all_points = np.concatenate(sunspot_contours)
x, y, w, h = cv2.boundingRect(all_points)
cv2.rectangle(sunspot_image, (x, y), (x+w, y+h), (0, 255, 0), 2)
cX, cY = x + w//2, y + h//2
sunspot_region = np.zeros_like(mask_blue)
cv2.drawContours(sunspot_region, sunspot_contours, -1, 255, thickness=cv2.FILLED)
blue_in_sunspot = cv2.bitwise_and(mask_blue, sunspot_region)
red_in_sunspot = cv2.bitwise_and(mask_red, sunspot_region)
blue_contours_in_sunspot, _ = cv2.findContours(blue_in_sunspot, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
red_contours_in_sunspot, _ = cv2.findContours(red_in_sunspot, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
blue_area = sum(cv2.contourArea(bc) for bc in blue_contours_in_sunspot)
red_area = sum(cv2.contourArea(rc) for rc in red_contours_in_sunspot)
blue_darkness = 0
red_darkness = 0
if blue_contours_in_sunspot:
blue_mask = np.zeros_like(hsv_image[..., 2])
cv2.drawContours(blue_mask, blue_contours_in_sunspot, -1, 255, thickness=cv2.FILLED)
blue_values = hsv_image[..., 2][blue_mask == 255]
if len(blue_values) > 0:
blue_darkness = 255 - np.mean(blue_values)
if red_contours_in_sunspot:
red_mask = np.zeros_like(hsv_image[..., 2])
cv2.drawContours(red_mask, red_contours_in_sunspot, -1, 255, thickness=cv2.FILLED)
red_values = hsv_image[..., 2][red_mask == 255]
if len(red_values) > 0:
red_darkness = 255 - np.mean(red_values)
blue_centroids = []
red_centroids = []
min_dist = float('inf')
mixing_score = 0
for bc in blue_contours_in_sunspot:
M = cv2.moments(bc)
if M["m00"] != 0:
bcX = int(M["m10"] / M["m00"])
bcY = int(M["m01"] / M["m00"])
blue_centroids.append((bcX, bcY))
for rc in red_contours_in_sunspot:
M = cv2.moments(rc)
if M["m00"] != 0:
rcX = int(M["m10"] / M["m00"])
rcY = int(M["m01"] / M["m00"])
red_centroids.append((rcX, rcY))
if blue_centroids and red_centroids:
for bc in blue_centroids:
for rc in red_centroids:
dist = distance.euclidean(bc, rc)
min_dist = min(min_dist, dist)
mixing_score = 1000 / (min_dist + 1)
darkness_score = (blue_darkness + red_darkness) / 2 if blue_darkness and red_darkness else max(blue_darkness, red_darkness)
flare_risk_percentage = 0
risk_level = "Low"
if blue_centroids and red_centroids:
flare_risk_score = (1 - darkness_weight) * (flare_dist_threshold - min_dist) / flare_dist_threshold + darkness_weight * (darkness_score / 255)
flare_risk_percentage = flare_risk_score * 100
mixed_sunspot_count += 1
overall_flare_percentage = (overall_flare_percentage * (mixed_sunspot_count - 1) + flare_risk_percentage) / mixed_sunspot_count
if flare_risk_percentage >= 70:
risk_level = "High"
overall_flare_risk = "High"
elif flare_risk_percentage >= 40:
risk_level = "Medium"
if overall_flare_risk != "High":
overall_flare_risk = "Medium"
else:
if overall_flare_risk not in ["High", "Medium"]:
overall_flare_risk = "Low"
else:
flare_risk_percentage = 0
risk_level = "Low"
line_height = 30
y = 0
lines = [f"Sunspot {i+1}", f"Risk: {risk_level}, {flare_risk_percentage:.1f}%"]
for line in lines:
cv2.putText(sunspot_image, line, (cX, cY + y), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
y += line_height
for bc in blue_contours_in_sunspot:
cv2.drawContours(sunspot_image, [bc], -1, (255, 0, 0), 2)
for rc in red_contours_in_sunspot:
cv2.drawContours(sunspot_image, [rc], -1, (0, 0, 255), 2)
report += f"Sunspot {i+1}:\n"
report += f" Blue Area: {blue_area:.2f} pixels\n"
report += f" Red Area: {red_area:.2f} pixels\n"
report += f" Minimum Distance Between Blue and Red: {min_dist:.2f} pixels\n"
report += f" Mixing Score (Higher = Closer): {mixing_score:.2f}\n"
report += f" Blue Darkness (Higher = Darker): {blue_darkness:.2f}\n"
report += f" Red Darkness (Higher = Darker): {red_darkness:.2f}\n"
report += f" Flare Risk: {risk_level}, {flare_risk_percentage:.1f}%\n\n"
if mixed_sunspot_count == 0:
overall_flare_percentage = 0
overall_flare_risk = "Low"
report += f"Overall Flare Risk: {overall_flare_risk}, {overall_flare_percentage:.1f}%\n"
sunspot_image = cv2.cvtColor(sunspot_image, cv2.COLOR_BGR2RGB)
return sunspot_image, report
# Gradio interface with adjustable controls
def create_interface():
with gr.Blocks() as interface:
# State to store image options and URL map
image_options = gr.State(value=[])
url_map = gr.State(value={})
gr.Markdown("# Solar Image Analysis for Sunspots and Flare Prediction")
gr.Markdown("Select a solar image from the dropdown or upload your own to detect sunspots, identify blue and red regions, and predict solar flare potential based on magnetic mixing. Adjust the sliders to fine-tune detection.")
with gr.Sidebar(open=False):
yellow_hue_min = gr.Slider(minimum=0, maximum=180, value=20, label="Yellow Hue Min")
yellow_hue_max = gr.Slider(minimum=0, maximum=180, value=30, label="Yellow Hue Max")
yellow_sat_min = gr.Slider(minimum=0, maximum=255, value=100, label="Yellow Saturation Min")
yellow_val_min = gr.Slider(minimum=0, maximum=255, value=100, label="Yellow Value Min")
grey_hue_max = gr.Slider(minimum=0, maximum=180, value=180, label="Grey Hue Max")
grey_sat_max = gr.Slider(minimum=0, maximum=255, value=30, label="Grey Saturation Max")
grey_val_min = gr.Slider(minimum=0, maximum=255, value=50, label="Grey Value Min")
grey_val_max = gr.Slider(minimum=0, maximum=255, value=200, label="Grey Value Max")
blue_hue_min = gr.Slider(minimum=0, maximum=180, value=90, label="Blue Hue Min")
blue_hue_max = gr.Slider(minimum=0, maximum=180, value=150, label="Blue Hue Max")
blue_sat_min = gr.Slider(minimum=0, maximum=255, value=50, label="Blue Saturation Min")
blue_val_min = gr.Slider(minimum=0, maximum=255, value=30, label="Blue Value Min")
red_hue_min1 = gr.Slider(minimum=0, maximum=180, value=0, label="Red Hue Min 1")
red_hue_max1 = gr.Slider(minimum=0, maximum=180, value=15, label="Red Hue Max 1")
red_hue_min2 = gr.Slider(minimum=0, maximum=180, value=100, label="Red Hue Min 2")
red_hue_max2 = gr.Slider(minimum=0, maximum=180, value=180, label="Red Hue Max 2")
red_sat_min = gr.Slider(minimum=0, maximum=255, value=50, label="Red Saturation Min")
red_val_min = gr.Slider(minimum=0, maximum=255, value=30, label="Red Value Min")
flare_dist_threshold = gr.Slider(minimum=0, maximum=200, value=50, label="Flare Distance Threshold (pixels)")
min_area_threshold = gr.Slider(minimum=0, maximum=100, value=1, label="Minimum Area Threshold (pixels)")
darkness_weight = gr.Slider(minimum=0, maximum=1, value=0.5, label="Darkness Weight in Flare Risk (0 to 1)")
with gr.Row():
with gr.Column():
update_button = gr.Button("Update Image List")
image_dropdown = gr.Dropdown(label="Select SDO Image (YYYYMMDD_HHMMSS)", choices=[])
fetch_button = gr.Button("Fetch Selected Image")
image_input = gr.Image(type="pil", label="Solar Image")
analyze_button = gr.Button("Analyze Image")
with gr.Column():
analyzed_image = gr.Image(type="pil", label="Analyzed Image")
report = gr.Textbox(label="Analysis Report")
# Function to update dropdown choices
def update_image_list():
filenames, new_url_map = get_sdo_images()
return gr.update(choices=filenames), new_url_map
# Function to handle fetch button
def fetch_and_update(selected_image, url_map):
img = fetch_image(selected_image, url_map)
return img
# Update image list on button click
update_button.click(
fn=update_image_list,
inputs=None,
outputs=[image_dropdown, url_map]
)
# Update image list on app load
interface.load(
fn=update_image_list,
inputs=None,
outputs=[image_dropdown, url_map]
)
# Fetch button
fetch_button.click(
fn=fetch_and_update,
inputs=[image_dropdown, url_map],
outputs=image_input
)
# Analyze button
analyze_button.click(
fn=analyze_solar_image,
inputs=[
image_input,
yellow_hue_min, yellow_hue_max, yellow_sat_min, yellow_val_min,
grey_hue_max, grey_sat_max, grey_val_min, grey_val_max,
blue_hue_min, blue_hue_max, blue_sat_min, blue_val_min,
red_hue_min1, red_hue_max1, red_hue_min2, red_hue_max2,
red_sat_min, red_val_min,
flare_dist_threshold, min_area_threshold, darkness_weight
],
outputs=[analyzed_image, report]
)
return interface
# Launch the interface
interface = create_interface()
interface.launch()