|
import streamlit as st |
|
import numpy as np |
|
import pandas as pd |
|
from PIL import Image |
|
import cv2 |
|
from scipy.ndimage import gaussian_filter |
|
import tensorflow as tf |
|
import matplotlib.pyplot as plt |
|
import io |
|
from matplotlib.figure import Figure |
|
import base64 |
|
|
|
|
|
|
|
def find_tc_center(ir_image, smoothing_sigma=3): |
|
smoothed_image = gaussian_filter(ir_image, sigma=smoothing_sigma) |
|
min_coords = np.unravel_index(np.argmin(smoothed_image), smoothed_image.shape) |
|
return min_coords[::-1] |
|
|
|
|
|
def generate_comparison_chart(models, mae_values, rmse_values, predicted_values=None): |
|
|
|
baseline_mae = mae_values[0] |
|
baseline_rmse = rmse_values[0] |
|
|
|
mae_improvements = [0] + [((baseline_mae - val) / baseline_mae) * 100 for val in mae_values[1:]] |
|
rmse_improvements = [0] + [((baseline_rmse - val) / baseline_rmse) * 100 for val in rmse_values[1:]] |
|
|
|
|
|
if predicted_values: |
|
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(18, 8)) |
|
else: |
|
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 8)) |
|
|
|
|
|
bars1 = ax1.bar(range(len(models)), mae_values, color='skyblue', edgecolor='black') |
|
ax1.set_title('Mean Absolute Error (MAE)', fontsize=14, fontweight='bold') |
|
ax1.set_ylabel('MAE (knots)', fontsize=12) |
|
ax1.set_xticks(range(len(models))) |
|
ax1.set_xticklabels(models, fontsize=12, rotation=45, ha='right') |
|
ax1.grid(axis='y', linestyle='--', alpha=0.3, color='lightgray') |
|
ax1.set_ylim(0, max(mae_values) * 1.2) |
|
|
|
|
|
bars2 = ax2.bar(range(len(models)), rmse_values, color='lightcoral', edgecolor='black') |
|
ax2.set_title('Root Mean Square Error (RMSE)', fontsize=14, fontweight='bold') |
|
ax2.set_ylabel('RMSE (knots)', fontsize=12) |
|
ax2.set_xticks(range(len(models))) |
|
ax2.set_xticklabels(models, fontsize=12, rotation=45, ha='right') |
|
ax2.grid(axis='y', linestyle='--', alpha=0.3, color='lightgray') |
|
ax2.set_ylim(0, max(rmse_values) * 1.2) |
|
|
|
|
|
for i, bar in enumerate(bars1): |
|
height = bar.get_height() |
|
ax1.text(bar.get_x() + bar.get_width()/2., height + 0.3, |
|
f'{height:.2f}', ha='center', va='bottom', fontsize=12) |
|
|
|
|
|
if i > 0: |
|
ax1.text(bar.get_x() + bar.get_width()/2., height/2, |
|
f'↓{mae_improvements[i]:.1f}%', ha='center', va='center', |
|
color='blue', fontsize=12, fontweight='bold') |
|
|
|
|
|
for i, bar in enumerate(bars2): |
|
height = bar.get_height() |
|
ax2.text(bar.get_x() + bar.get_width()/2., height + 0.3, |
|
f'{height:.2f}', ha='center', va='bottom', fontsize=12) |
|
|
|
|
|
if i > 0: |
|
ax2.text(bar.get_x() + bar.get_width()/2., height/2, |
|
f'↓{rmse_improvements[i]:.1f}%', ha='center', va='center', |
|
color='darkred', fontsize=12, fontweight='bold') |
|
|
|
|
|
min_mae = min(mae_values) |
|
min_rmse = min(rmse_values) |
|
ax1.axhline(y=min_mae, color='blue', linestyle='--', alpha=0.5) |
|
ax2.axhline(y=min_rmse, color='red', linestyle='--', alpha=0.5) |
|
|
|
|
|
if predicted_values: |
|
bars3 = ax3.bar(range(len(models)), predicted_values, color='lightgreen', edgecolor='black') |
|
ax3.set_title('Predicted Vmax', fontsize=14, fontweight='bold') |
|
ax3.set_ylabel('Wind Speed (knots)', fontsize=12) |
|
ax3.set_xticks(range(len(models))) |
|
ax3.set_xticklabels(models, fontsize=12, rotation=45, ha='right') |
|
ax3.grid(axis='y', linestyle='--', alpha=0.3, color='lightgray') |
|
|
|
|
|
for i, bar in enumerate(bars3): |
|
height = bar.get_height() |
|
ax3.text(bar.get_x() + bar.get_width()/2., height + 0.3, |
|
f'{height:.2f}', ha='center', va='bottom', fontsize=12) |
|
|
|
|
|
fig.text(0.5, 0.01, 'Note: Reduction percentages (↓%) are calculated relative to TCIP-Net (3DCNN)', |
|
ha='center', fontsize=12, fontstyle='italic') |
|
|
|
plt.tight_layout(rect=[0, 0.03, 1, 0.95]) |
|
|
|
return fig |
|
|
|
|
|
def fig_to_streamlit(fig): |
|
buf = io.BytesIO() |
|
fig.savefig(buf, format='png', dpi=300, bbox_inches='tight') |
|
buf.seek(0) |
|
return buf |
|
|
|
def extract_local_region(ir_image, center, region_size=95): |
|
h, w = ir_image.shape |
|
half_size = region_size // 2 |
|
x_min = max(center[0] - half_size, 0) |
|
x_max = min(center[0] + half_size, w) |
|
y_min = max(center[1] - half_size, 0) |
|
y_max = min(center[1] + half_size, h) |
|
region = np.full((region_size, region_size), np.nan) |
|
extracted = ir_image[y_min:y_max, x_min:x_max] |
|
region[:extracted.shape[0], :extracted.shape[1]] = extracted |
|
return region |
|
|
|
def generate_hovmoller(X_data): |
|
hovmoller_list = [] |
|
for ir_images in X_data: |
|
time_steps = ir_images.shape[0] |
|
hovmoller_data = np.zeros((time_steps, 95, 95)) |
|
for t in range(time_steps): |
|
tc_center = find_tc_center(ir_images[t]) |
|
hovmoller_data[t] = extract_local_region(ir_images[t], tc_center, 95) |
|
hovmoller_list.append(hovmoller_data) |
|
return np.array(hovmoller_list) |
|
|
|
def reshape_vmax(vmax_values, chunk_size=8): |
|
trimmed_size = (len(vmax_values) // chunk_size) * chunk_size |
|
vmax_values_trimmed = vmax_values[:trimmed_size] |
|
return vmax_values_trimmed.reshape(-1, chunk_size) |
|
def create_3d_vmax(vmax_2d_array): |
|
|
|
vmax_3d_array = np.zeros((vmax_2d_array.shape[0], 8, 8)) |
|
|
|
|
|
for i in range(vmax_2d_array.shape[0]): |
|
np.fill_diagonal(vmax_3d_array[i], vmax_2d_array[i]) |
|
|
|
|
|
vmax_3d_array = vmax_3d_array.reshape(-1, 8, 8, 1) |
|
|
|
return vmax_3d_array |
|
|
|
def process_lat_values(data): |
|
lat_values = data |
|
|
|
|
|
trimmed_size = (len(lat_values) // 8) * 8 |
|
lat_values_trimmed = lat_values[:trimmed_size] |
|
lat_values_trimmed=np.array(lat_values_trimmed) |
|
|
|
lat_2d_array = lat_values_trimmed.reshape(-1, 8) |
|
|
|
return lat_2d_array |
|
|
|
def process_lon_values(data): |
|
lon_values =data |
|
lon_values = np.array(lon_values) |
|
|
|
trimmed_size = (len(lon_values) // 8) * 8 |
|
lon_values_trimmed = lon_values[:trimmed_size] |
|
|
|
|
|
lon_2d_array = lon_values_trimmed.reshape(-1, 8) |
|
|
|
return lon_2d_array |
|
|
|
import numpy as np |
|
|
|
def calculate_intensity_difference(vmax_2d_array): |
|
"""Calculates intensity difference for each row in Vmax 2D array.""" |
|
int_diff = [] |
|
|
|
for i in vmax_2d_array: |
|
k = abs(i[0] - i[-1]) |
|
i = np.append(i, k) |
|
int_diff.append(i) |
|
|
|
return np.array(int_diff) |
|
|
|
import numpy as np |
|
|
|
|
|
def process_images(images, batch_size=8, img_size=(95, 95, 1)): |
|
num_images = images.shape[0] |
|
|
|
|
|
trimmed_size = (num_images // batch_size) * batch_size |
|
images_trimmed = images[:trimmed_size] |
|
|
|
|
|
images_reshaped = images_trimmed.reshape(-1, batch_size, *img_size) |
|
|
|
return images_reshaped |
|
|
|
import numpy as np |
|
|
|
def process_cc_mask(cc_data): |
|
"""Processes CC mask images by trimming and reshaping into (x, 8, 95, 95, 1).""" |
|
num_images = cc_data.shape[0] |
|
batch_size = 8 |
|
trimmed_size = (num_images // batch_size) * batch_size |
|
|
|
images_trimmed = cc_data[:trimmed_size] |
|
cc_images = images_trimmed.reshape(-1, batch_size, 95, 95, 1) |
|
|
|
return cc_images |
|
def extract_convective_cores(ir_data): |
|
""" |
|
Extract Convective Cores (CCs) from IR imagery based on the criteria in the paper. |
|
Args: |
|
ir_data: IR imagery of shape (height, width). |
|
Returns: |
|
cc_mask: Binary mask of CCs (1 for CC, 0 otherwise) of shape (height, width). |
|
""" |
|
height, width,c = ir_data.shape |
|
cc_mask = np.zeros_like(ir_data, dtype=np.float32) |
|
|
|
|
|
neighbors = [(-1, -1), (-1, 0), (-1, 1), |
|
(0, -1), (0,0) , (0, 1), |
|
(1, -1), (1, 0), (1, 1)] |
|
|
|
for i in range(1, height - 1): |
|
for j in range(1, width - 1): |
|
bt_ij = ir_data[i, j] |
|
|
|
|
|
if (bt_ij >= 253).any(): |
|
continue |
|
|
|
|
|
is_local_min = True |
|
for di, dj in neighbors: |
|
if ir_data[i + di, j + dj] < bt_ij: |
|
is_local_min = False |
|
break |
|
if not is_local_min: |
|
continue |
|
|
|
|
|
numerator1 = (ir_data[i - 1, j] + ir_data[i + 1, j] - 2 * bt_ij) / 3.1 |
|
numerator2 = (ir_data[i, j - 1] + ir_data[i, j + 1] - 2 * bt_ij) / 8.0 |
|
lhs = numerator1 + numerator2 |
|
rhs = (4 / 5.8) * np.exp(0.0826 * (bt_ij - 217)) |
|
|
|
if lhs > rhs: |
|
cc_mask[i, j] = 1 |
|
|
|
return cc_mask |
|
|
|
def compute_convective_core_masks(ir_data): |
|
"""Extracts convective core masks for each IR image.""" |
|
cc_mask = [] |
|
|
|
for i in ir_data: |
|
c = extract_convective_cores(i) |
|
c = np.array(c) |
|
cc_mask.append(c) |
|
|
|
return np.array(cc_mask) |
|
|
|
|
|
|
|
|
|
st.set_page_config( |
|
page_title="Tropical Cyclone U-Net Wind Speed Predictor", |
|
layout="wide", |
|
initial_sidebar_state="expanded" |
|
) |
|
|
|
|
|
st.markdown("<h1 style='text-align: center;'>🌀 Tropical Cyclone U-Net Wind Speed (Intensity) Predictor</h1><br>", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown(""" |
|
<div style='text-align: center;'> |
|
<p> |
|
<b>By:</b> |
|
<a href="https://orcid.org/0009-0006-0342-429X" target="_blank" style="text-decoration: none">Dharun Krishna K B</a>, |
|
<a href="https://orcid.org/0009-0008-3214-8065" target="_blank" style="text-decoration: none">Nanduri Prudhvi Reddy</a> and |
|
<a href="https://orcid.org/0009-0006-9052-3623" target="_blank" style="text-decoration: none">Ravipati Venkata Madan Mohan</a>; School of Computing.<br> |
|
<b>Under the guidance of:</b> |
|
<a href="https://orcid.org/0000-0003-1969-3559" target="_blank" style="text-decoration: none">Dr. Gowri L</a>, |
|
Assistant Professor, School of Computing.<br> |
|
SASTRA Deemed University, Thanjavur, Tamil Nadu, India.<br><br> |
|
<b>For:</b> |
|
Main project titled <i>"Tropical Cyclone Intensity Prediction Using Deep Learning Models"</i><br> |
|
May 2025 |
|
</p> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown('<div class="divider"></div>', unsafe_allow_html=True) |
|
|
|
|
|
st.markdown("<br>", unsafe_allow_html=True) |
|
|
|
|
|
st.info('''The *Tropical Cyclone Wind Speed Predictor interface* enables the prediction of maximum sustained wind speeds of tropical cyclones (in knots) using IR and PMW imagery, along with physical attributes from the past 24 hours, while also facilitating comparison between state-of-the-art models and our proposed model. |
|
''') |
|
|
|
ir_images = st.file_uploader("Upload 8 IR images", type=["jpg", "jpeg", "png"], accept_multiple_files=True) |
|
pmw_images = st.file_uploader("Upload 8 PMW images", type=["jpg", "jpeg", "png"], accept_multiple_files=True) |
|
|
|
if len(ir_images) != 8 or len(pmw_images) != 8: |
|
st.warning("Please upload exactly 8 IR and 8 PMW images.") |
|
else: |
|
st.success("Uploaded 8 IR and 8 PMW images successfully.") |
|
|
|
st.header("Input Latitude, Longitude, Vmax") |
|
lat_values, lon_values, vmax_values = [], [], [] |
|
|
|
import pandas as pd |
|
|
|
import numpy as np |
|
|
|
|
|
csv_file = st.file_uploader("Upload CSV file", type=["csv"]) |
|
|
|
if csv_file is not None: |
|
try: |
|
df = pd.read_csv(csv_file) |
|
required_columns = {'Latitude', 'Longitude', 'Vmax'} |
|
|
|
if required_columns.issubset(df.columns): |
|
lat_values = df['Latitude'].values |
|
lon_values = df['Longitude'].values |
|
vmax_values = df['Vmax'].values |
|
|
|
lat_values = np.array(lat_values) |
|
lon_values = np.array(lon_values) |
|
vmax_values = np.array(vmax_values) |
|
|
|
st.success("CSV file loaded and processed successfully!") |
|
|
|
|
|
st.markdown("<h4>Preview of uploaded data:</h4>", unsafe_allow_html=True) |
|
preview_df = df.head(10).reset_index(drop=True) |
|
preview_df.index += 1 |
|
st.dataframe(preview_df, height=200) |
|
|
|
else: |
|
st.error("CSV file must contain 'Latitude', 'Longitude', and 'Vmax' columns.") |
|
except Exception as e: |
|
st.error(f"Error reading CSV: {e}") |
|
else: |
|
st.warning("Please upload a CSV file.") |
|
|
|
|
|
ablation_data = { |
|
"Model": [ |
|
"TCIP-Net (3DCNN)", |
|
"TCIP-Net (ST-LSTM)", |
|
"TCIP-Net (ConvLSTM)", |
|
"TCIP-Net (TrajGRU)", |
|
"TCIP-Net (ConvGRU)", |
|
"TCUWSP-Net (Proposed)" |
|
], |
|
"RMSE": [12.63, 12.52, 12.36, 12.24, 11.17, 8.6549], |
|
"MAE": [10.15, 10.12, 9.97, 9.93, 8.92, 6.309] |
|
} |
|
|
|
|
|
st.markdown("<br>", unsafe_allow_html=True) |
|
st.markdown("<h2 style='text-align: center;'>Select Prediction Model</h2>", unsafe_allow_html=True) |
|
|
|
|
|
col1, col2, col3 = st.columns([1, 2, 1]) |
|
|
|
with col2: |
|
model_choice = st.selectbox( |
|
"Choose a model for prediction", |
|
("TCIP-Net ConvGRU", "TCIP-Net ConvLSTM", "TCIP-Net Traj-GRU", "TCIP-Net 3DCNN", "TCIP-Net Spatio-temporal LSTM", "TCUWSP-Net (Proposed Model)"), |
|
index=0 |
|
) |
|
|
|
|
|
st.markdown("<br>", unsafe_allow_html=True) |
|
col_btn1, col_btn2 = st.columns(2) |
|
with col_btn1: |
|
submit_button = st.button("Predict Intensity", use_container_width=True) |
|
with col_btn2: |
|
all_models_button = st.button("Predict Intensity for All Models", use_container_width=True) |
|
if submit_button: |
|
if len(ir_images) == 8 and len(pmw_images) == 8: |
|
|
|
if model_choice == "TCUWSP-Net (Proposed Model)": |
|
from unetlstm import predict_unetlstm |
|
model_predict_fn = predict_unetlstm |
|
elif model_choice == "TCIP-Net ConvGRU": |
|
from gru_model import predict |
|
model_predict_fn = predict |
|
elif model_choice == "TCIP-Net ConvLSTM": |
|
from convlstm import predict_lstm |
|
model_predict_fn = predict_lstm |
|
elif model_choice == "TCIP-Net 3DCNN": |
|
from cnn3d import predict_3dcnn |
|
model_predict_fn = predict_3dcnn |
|
elif model_choice == "TCIP-Net Traj-GRU": |
|
from trjgru import predict_trajgru |
|
model_predict_fn = predict_trajgru |
|
elif model_choice == "TCIP-Net Spatio-temporal LSTM": |
|
from spaio_temp import predict_stlstm |
|
model_predict_fn = predict_stlstm |
|
|
|
ir_arrays = [] |
|
pmw_arrays = [] |
|
train_vmax_2d = reshape_vmax(np.array(vmax_values)) |
|
|
|
train_vmax_3d= create_3d_vmax(train_vmax_2d) |
|
|
|
lat_processed = process_lat_values(lat_values) |
|
lon_processed = process_lon_values(lon_values) |
|
|
|
v_max_diff = calculate_intensity_difference(train_vmax_2d) |
|
|
|
for ir in ir_images: |
|
img = Image.open(ir).convert("L") |
|
arr = np.array(img).astype(np.float32) |
|
bt_arr = (arr / 255.0) * (310 - 190) + 190 |
|
resized = cv2.resize(bt_arr, (95, 95), interpolation=cv2.INTER_CUBIC) |
|
ir_arrays.append(resized) |
|
|
|
for pmw in pmw_images: |
|
img = Image.open(pmw).convert("L") |
|
arr = np.array(img).astype(np.float32) / 255.0 |
|
resized = cv2.resize(arr, (95, 95), interpolation=cv2.INTER_CUBIC) |
|
pmw_arrays.append(resized) |
|
ir=np.array(ir_arrays) |
|
pmw=np.array(pmw_arrays) |
|
|
|
|
|
ir_seq = process_images(ir) |
|
pmw_seq = process_images(pmw) |
|
|
|
|
|
|
|
X_train_new = ir_seq.reshape((1, 8, 95, 95)) |
|
|
|
cc_mask= compute_convective_core_masks(X_train_new) |
|
hov_m_train = generate_hovmoller(X_train_new) |
|
hov_m_train[np.isnan(hov_m_train)] = 0 |
|
hov_m_train = hov_m_train.transpose(0, 2, 3, 1) |
|
|
|
cc_mask[np.isnan(cc_mask)] = 0 |
|
cc_mask=cc_mask.reshape(1, 8, 95, 95, 1) |
|
i_images=cc_mask+ir_seq |
|
reduced_images = np.concatenate([i_images,pmw_seq ], axis=-1) |
|
reduced_images[np.isnan(reduced_images)] = 0 |
|
|
|
if model_choice == "Unet_LSTM": |
|
import tensorflow as tf |
|
|
|
def tf_gradient_magnitude(images): |
|
|
|
sobel_x = tf.constant([[1, 0, -1], [2, 0, -2], [1, 0, -1]], dtype=tf.float32) |
|
sobel_y = tf.constant([[1, 2, 1], [0, 0, 0], [-1, -2, -1]], dtype=tf.float32) |
|
sobel_x = tf.reshape(sobel_x, [3, 3, 1, 1]) |
|
sobel_y = tf.reshape(sobel_y, [3, 3, 1, 1]) |
|
|
|
images = tf.convert_to_tensor(images, dtype=tf.float32) |
|
images = tf.expand_dims(images, -1) |
|
|
|
gx = tf.nn.conv2d(images, sobel_x, strides=1, padding='SAME') |
|
gy = tf.nn.conv2d(images, sobel_y, strides=1, padding='SAME') |
|
grad_mag = tf.sqrt(tf.square(gx) + tf.square(gy) + 1e-6) |
|
|
|
return tf.squeeze(grad_mag, -1).numpy() |
|
def GM_maps_prep(ir): |
|
GM_maps=[] |
|
for i in ir: |
|
GM_map = tf_gradient_magnitude(i) |
|
GM_maps.append(GM_map) |
|
GM_maps=np.array(GM_maps) |
|
return GM_maps |
|
ir_seq=ir_seq.reshape(8, 95, 95, 1) |
|
GM_maps = GM_maps_prep(ir_seq) |
|
print(GM_maps.shape) |
|
GM_maps=GM_maps.reshape(1, 8, 95, 95, 1) |
|
i_images=cc_mask+ir_seq+GM_maps |
|
reduced_images = np.concatenate([i_images,pmw_seq ], axis=-1) |
|
reduced_images[np.isnan(reduced_images)] = 0 |
|
print(reduced_images.shape) |
|
y = model_predict_fn(reduced_images, hov_m_train, train_vmax_3d, lat_processed, lon_processed, v_max_diff) |
|
else: |
|
y = model_predict_fn(reduced_images, hov_m_train, train_vmax_3d, lat_processed, lon_processed, v_max_diff) |
|
st.write("Predicted Maximum Sustained Wind Speed [Vmax] (in knots):", y) |
|
else: |
|
st.error("Make sure you uploaded exactly 8 IR and 8 PMW images.") |
|
|
|
|
|
if all_models_button: |
|
if len(ir_images) == 8 and len(pmw_images) == 8: |
|
st.info("Running predictions for all models... This may take a moment.") |
|
|
|
|
|
all_model_names = [ |
|
"TCIP-Net (3DCNN)", |
|
"TCIP-Net (ST-LSTM)", |
|
"TCIP-Net (ConvLSTM)", |
|
"TCIP-Net (TrajGRU)", |
|
"TCIP-Net (ConvGRU)", |
|
"TCUWSP-Net (Proposed)" |
|
] |
|
|
|
|
|
ir_arrays = [] |
|
pmw_arrays = [] |
|
train_vmax_2d = reshape_vmax(np.array(vmax_values)) |
|
train_vmax_3d = create_3d_vmax(train_vmax_2d) |
|
lat_processed = process_lat_values(lat_values) |
|
lon_processed = process_lon_values(lon_values) |
|
v_max_diff = calculate_intensity_difference(train_vmax_2d) |
|
|
|
for ir in ir_images: |
|
img = Image.open(ir).convert("L") |
|
arr = np.array(img).astype(np.float32) |
|
bt_arr = (arr / 255.0) * (310 - 190) + 190 |
|
resized = cv2.resize(bt_arr, (95, 95), interpolation=cv2.INTER_CUBIC) |
|
ir_arrays.append(resized) |
|
|
|
for pmw in pmw_images: |
|
img = Image.open(pmw).convert("L") |
|
arr = np.array(img).astype(np.float32) / 255.0 |
|
resized = cv2.resize(arr, (95, 95), interpolation=cv2.INTER_CUBIC) |
|
pmw_arrays.append(resized) |
|
|
|
ir = np.array(ir_arrays) |
|
pmw = np.array(pmw_arrays) |
|
|
|
ir_seq = process_images(ir) |
|
pmw_seq = process_images(pmw) |
|
|
|
X_train_new = ir_seq.reshape((1, 8, 95, 95)) |
|
cc_mask = compute_convective_core_masks(X_train_new) |
|
hov_m_train = generate_hovmoller(X_train_new) |
|
hov_m_train[np.isnan(hov_m_train)] = 0 |
|
hov_m_train = hov_m_train.transpose(0, 2, 3, 1) |
|
|
|
cc_mask[np.isnan(cc_mask)] = 0 |
|
cc_mask = cc_mask.reshape(1, 8, 95, 95, 1) |
|
i_images = cc_mask + ir_seq |
|
reduced_images = np.concatenate([i_images, pmw_seq], axis=-1) |
|
reduced_images[np.isnan(reduced_images)] = 0 |
|
|
|
|
|
import tensorflow as tf |
|
def tf_gradient_magnitude(images): |
|
|
|
sobel_x = tf.constant([[1, 0, -1], [2, 0, -2], [1, 0, -1]], dtype=tf.float32) |
|
sobel_y = tf.constant([[1, 2, 1], [0, 0, 0], [-1, -2, -1]], dtype=tf.float32) |
|
sobel_x = tf.reshape(sobel_x, [3, 3, 1, 1]) |
|
sobel_y = tf.reshape(sobel_y, [3, 3, 1, 1]) |
|
|
|
images = tf.convert_to_tensor(images, dtype=tf.float32) |
|
images = tf.expand_dims(images, -1) |
|
|
|
gx = tf.nn.conv2d(images, sobel_x, strides=1, padding='SAME') |
|
gy = tf.nn.conv2d(images, sobel_y, strides=1, padding='SAME') |
|
grad_mag = tf.sqrt(tf.square(gx) + tf.square(gy) + 1e-6) |
|
|
|
return tf.squeeze(grad_mag, -1).numpy() |
|
|
|
def GM_maps_prep(ir): |
|
GM_maps=[] |
|
for i in ir: |
|
GM_map = tf_gradient_magnitude(i) |
|
GM_maps.append(GM_map) |
|
GM_maps=np.array(GM_maps) |
|
return GM_maps |
|
|
|
|
|
ir_seq_reshaped = ir_seq.reshape(8, 95, 95, 1) |
|
GM_maps = GM_maps_prep(ir_seq_reshaped) |
|
GM_maps = GM_maps.reshape(1, 8, 95, 95, 1) |
|
i_images_unet = cc_mask + ir_seq_reshaped + GM_maps |
|
reduced_images_unet = np.concatenate([i_images_unet, pmw_seq], axis=-1) |
|
reduced_images_unet[np.isnan(reduced_images_unet)] = 0 |
|
|
|
|
|
predictions = [] |
|
progress_bar = st.progress(0) |
|
|
|
|
|
from cnn3d import predict_3dcnn |
|
from spaio_temp import predict_stlstm |
|
from convlstm import predict_lstm |
|
from trjgru import predict_trajgru |
|
from gru_model import predict |
|
from unetlstm import predict_unetlstm |
|
|
|
prediction_functions = [ |
|
predict_3dcnn, |
|
predict_stlstm, |
|
predict_lstm, |
|
predict_trajgru, |
|
predict, |
|
predict_unetlstm |
|
] |
|
|
|
|
|
for i, predict_fn in enumerate(prediction_functions): |
|
progress_bar.progress((i) / len(prediction_functions)) |
|
|
|
|
|
if i == 5: |
|
y = predict_fn(reduced_images_unet, hov_m_train, train_vmax_3d, lat_processed, lon_processed, v_max_diff) |
|
else: |
|
y = predict_fn(reduced_images, hov_m_train, train_vmax_3d, lat_processed, lon_processed, v_max_diff) |
|
|
|
predictions.append(float(y)) |
|
|
|
progress_bar.progress(1.0) |
|
|
|
|
|
results_data = { |
|
"Model": all_model_names, |
|
"RMSE": ablation_data["RMSE"], |
|
"MAE": ablation_data["MAE"], |
|
"Predicted Vmax (kt)": predictions |
|
} |
|
|
|
results_df = pd.DataFrame(results_data) |
|
|
|
|
|
st.subheader("Prediction Results from All Models") |
|
st.dataframe(results_df, use_container_width=True) |
|
|
|
|
|
st.subheader("Visual Comparison of Models") |
|
|
|
|
|
plot_model_names = [name.replace(" ", "\n") for name in all_model_names] |
|
mae_values = results_df["MAE"].tolist() |
|
rmse_values = results_df["RMSE"].tolist() |
|
predicted_values = results_df["Predicted Vmax (kt)"].tolist() |
|
|
|
|
|
fig = generate_comparison_chart(plot_model_names, mae_values, rmse_values, predicted_values) |
|
|
|
|
|
st.pyplot(fig) |
|
|
|
|
|
st.subheader("Interpretation") |
|
st.write(""" |
|
- **RMSE and MAE**: Lower values indicate better model performance. |
|
- **Percentage Improvements**: Show reduction in error compared to the baseline TCIP-Net (3DCNN) model. |
|
- **Predicted Vmax**: The current intensity prediction for the tropical cyclone based on the provided imagery and historical data. |
|
""") |
|
|
|
|
|
best_model_idx = rmse_values.index(min(rmse_values)) |
|
best_model = all_model_names[best_model_idx] |
|
best_prediction = predicted_values[best_model_idx] |
|
|
|
st.success(f"🌟 Best performing model: **{best_model}** with RMSE: **{min(rmse_values):.2f} kt** and predicted intensity: **{best_prediction:.2f} kt**") |
|
else: |
|
st.error("Make sure you uploaded exactly 8 IR and 8 PMW images.") |
|
|