euler314 commited on
Commit
2af44a9
·
verified ·
1 Parent(s): 08bd0cf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -87
app.py CHANGED
@@ -1,4 +1,3 @@
1
- # -*- coding: utf-8 -*-
2
  import os
3
  import argparse
4
  import logging
@@ -116,7 +115,7 @@ CACHE_EXPIRY_DAYS = 1
116
  # Enhanced color mapping with TD support (for Plotly)
117
  enhanced_color_map = {
118
  'Unknown': 'rgb(200, 200, 200)',
119
- 'Tropical Depression': 'rgb(128, 128, 128)', # NEW: Gray for TD
120
  'Tropical Storm': 'rgb(0, 0, 255)',
121
  'C1 Typhoon': 'rgb(0, 255, 255)',
122
  'C2 Typhoon': 'rgb(0, 255, 0)',
@@ -1028,20 +1027,20 @@ def create_advanced_clustering_visualization(storm_features, typhoon_data, metho
1028
  storm_count = int(cluster_row['SID_count'])
1029
 
1030
  stats_text += f"CLUSTER {cluster}: {storm_count} storms\n"
1031
- stats_text += f" Intensity: {cluster_row['USA_WIND_max_mean']:.1f} ± {cluster_row['USA_WIND_max_std']:.1f} kt\n"
1032
- stats_text += f" Pressure: {cluster_row['USA_PRES_min_mean']:.1f} ± {cluster_row['USA_PRES_min_std']:.1f} hPa\n"
1033
- stats_text += f" Track Length: {cluster_row['track_length_mean']:.1f} ± {cluster_row['track_length_std']:.1f} points\n"
1034
  stats_text += f" Genesis Region: {cluster_row['genesis_lat']:.1f}°N, {cluster_row['genesis_lon']:.1f}°E\n"
1035
  stats_text += f" Avg Distance: {cluster_row['total_distance_mean']:.2f} degrees\n"
1036
  stats_text += f" Avg Curvature: {cluster_row['avg_curvature_mean']:.3f} radians\n\n"
1037
 
1038
  # Add feature importance summary
1039
  stats_text += "CLUSTERING FEATURES USED:\n"
1040
- stats_text += f" Storm intensity (max/mean/std wind & pressure)\n"
1041
- stats_text += f" Track characteristics (length, curvature, distance)\n"
1042
- stats_text += f" Genesis location (lat/lon)\n"
1043
- stats_text += f" Geographic range (lat/lon span)\n"
1044
- stats_text += f" Total features: {len(feature_cols)}\n\n"
1045
 
1046
  stats_text += f"ALGORITHM: {method.upper()} + DBSCAN clustering\n"
1047
  stats_text += f"CLUSTERS FOUND: {len([c for c in storm_features_viz['cluster'].unique() if c != -1])}\n"
@@ -1726,30 +1725,31 @@ def create_interface():
1726
  gr.Markdown("Advanced ML clustering, CNN predictions, and comprehensive tropical cyclone analysis including Tropical Depressions")
1727
 
1728
  with gr.Tab("Overview"):
1729
- gr.Markdown(f"""
1730
  ## Welcome to the Enhanced Typhoon Analysis Dashboard
1731
 
1732
  This dashboard provides comprehensive analysis of typhoon data in relation to ENSO phases with advanced machine learning capabilities.
1733
 
1734
  ### Enhanced Features:
1735
- - **Advanced ML Clustering**: UMAP/t-SNE storm pattern analysis with route visualization
1736
- - **Optional CNN Predictions**: Deep learning intensity forecasting
1737
- - **Complete TD Support**: Now includes Tropical Depressions (< 34 kt)
1738
- - **2025 Data Ready**: Real-time compatibility with current year data
1739
- - **Enhanced Animations**: High-quality storm track visualizations
1740
 
1741
  ### Data Status:
1742
- - **ONI Data**: {len(oni_data)} years loaded
1743
- - **Typhoon Data**: {total_records} records loaded
1744
- - **Merged Data**: {len(merged_data)} typhoons with ONI values
1745
- - **Available Years**: {year_range_display}
1746
 
1747
  ### Technical Capabilities:
1748
- - **UMAP Clustering**: {"Available" if UMAP_AVAILABLE else "Limited to t-SNE/PCA"}
1749
- - **AI Predictions**: {"Deep Learning" if CNN_AVAILABLE else "Physics-based"}
1750
- - **Enhanced Categorization**: Tropical Depression to Super Typhoon
1751
- - **Platform Compatibility**: Optimized for Hugging Face Spaces
1752
- """)
 
1753
 
1754
  with gr.Tab("Advanced ML Clustering with Routes"):
1755
  gr.Markdown("## Storm Pattern Analysis using UMAP/t-SNE with Route Visualization")
@@ -1792,30 +1792,31 @@ def create_interface():
1792
  outputs=[cluster_plot, cluster_stats]
1793
  )
1794
 
1795
- gr.Markdown("""
1796
  ### Advanced Clustering Features:
1797
- - **Multi-dimensional Analysis**: Uses 15+ storm characteristics including intensity, track shape, genesis location
1798
- - **Route Visualization**: Shows actual storm tracks colored by cluster membership
1799
- - **DBSCAN Clustering**: Automatically finds natural groupings without predefined cluster count
1800
- - **Comprehensive Stats**: Detailed cluster analysis including intensity, pressure, track length, curvature
1801
- - **Interactive**: Hover over points to see storm details, zoom and pan the route map
1802
 
1803
  ### How to Interpret:
1804
- - **Left Plot**: Each dot is a storm positioned by similarity (close = similar characteristics)
1805
- - **Right Plot**: Actual geographic storm tracks, colored by which cluster they belong to
1806
- - **Cluster Colors**: Each cluster gets a unique color to identify similar storm patterns
1807
- - **Noise Points**: Gray points represent storms that don't fit clear patterns
1808
- """)
 
1809
 
1810
- with gr.Tab("🤖 Intensity Prediction"):
1811
  gr.Markdown("## AI-Powered Storm Intensity Forecasting")
1812
 
1813
  if CNN_AVAILABLE:
1814
- gr.Markdown("**Deep Learning models available** - TensorFlow loaded successfully")
1815
  method_description = "Using Convolutional Neural Networks for advanced intensity prediction"
1816
  else:
1817
- gr.Markdown("**Physics-based models available** - Using climatological relationships")
1818
- gr.Markdown("*Install TensorFlow for deep learning features: pip install tensorflow-cpu*")
1819
  method_description = "Using established meteorological relationships and climatology"
1820
 
1821
  gr.Markdown(f"**Current Method**: {method_description}")
@@ -1838,22 +1839,22 @@ def create_interface():
1838
  outputs=[intensity_output, confidence_output]
1839
  )
1840
 
1841
- gr.Markdown("""
1842
- gr.Markdown("""
1843
- ### 🧠 Prediction Features:
1844
- - **Environmental Analysis**: Considers ENSO, latitude, seasonality
1845
- - **Real-time Capable**: Predictions in milliseconds
1846
- - **Confidence Scoring**: Uncertainty quantification included
1847
- - **Robust Fallbacks**: Works with or without deep learning libraries
1848
 
1849
- ### 📖 Interpretation Guide:
1850
- - **25-33 kt**: Tropical Depression (TD)
1851
- - **34-63 kt**: Tropical Storm (TS)
1852
- - **64+ kt**: Typhoon categories (C1-C5)
1853
- - **100+ kt**: Major typhoon (C3+)
1854
- """)
1855
-
1856
- with gr.Tab("📍 Track Visualization"):
 
1857
  with gr.Row():
1858
  start_year = gr.Number(label="Start Year", value=2020)
1859
  start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1)
@@ -1870,7 +1871,7 @@ def create_interface():
1870
  outputs=[tracks_plot, typhoon_count]
1871
  )
1872
 
1873
- with gr.Tab("💨 Wind Analysis"):
1874
  with gr.Row():
1875
  wind_start_year = gr.Number(label="Start Year", value=2020)
1876
  wind_start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1)
@@ -1887,7 +1888,7 @@ def create_interface():
1887
  outputs=[wind_scatter, wind_regression_results]
1888
  )
1889
 
1890
- with gr.Tab("🌀 Pressure Analysis"):
1891
  with gr.Row():
1892
  pressure_start_year = gr.Number(label="Start Year", value=2020)
1893
  pressure_start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1)
@@ -1904,7 +1905,7 @@ def create_interface():
1904
  outputs=[pressure_scatter, pressure_regression_results]
1905
  )
1906
 
1907
- with gr.Tab("🌏 Longitude Analysis"):
1908
  with gr.Row():
1909
  lon_start_year = gr.Number(label="Start Year", value=2020)
1910
  lon_start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1)
@@ -1922,7 +1923,7 @@ def create_interface():
1922
  outputs=[regression_plot, slopes_text, lon_regression_results]
1923
  )
1924
 
1925
- with gr.Tab("🎬 Enhanced Track Animation"):
1926
  gr.Markdown("## High-Quality Storm Track Visualization (All Categories Including TD)")
1927
 
1928
  with gr.Row():
@@ -1963,16 +1964,17 @@ def create_interface():
1963
  outputs=[video_output]
1964
  )
1965
 
1966
- gr.Markdown("""
1967
  ### Enhanced Animation Features:
1968
- - **Full TD Support**: Now displays Tropical Depressions (< 34 kt) in gray
1969
- - **2025 Compatibility**: Complete support for current year data
1970
- - **Enhanced Maps**: Better cartographic projections with terrain features
1971
- - **Smart Scaling**: Storm symbols scale dynamically with intensity
1972
- - **Real-time Info**: Live position, time, and meteorological data display
1973
- - **Professional Styling**: Publication-quality animations with proper legends
1974
- - **Optimized Export**: Fast rendering with web-compatible video formats
1975
- """)
 
1976
 
1977
  with gr.Tab("Data Statistics & Insights"):
1978
  gr.Markdown("## Comprehensive Dataset Analysis")
@@ -2036,7 +2038,7 @@ def create_interface():
2036
  except Exception as e:
2037
  gr.Markdown(f"Visualization error: {str(e)}")
2038
 
2039
- # Enhanced statistics
2040
  total_storms = len(typhoon_data['SID'].unique()) if 'SID' in typhoon_data.columns else 0
2041
  total_records = len(typhoon_data)
2042
 
@@ -2079,26 +2081,27 @@ def create_interface():
2079
  td_storms = ts_storms = typhoon_storms = 0
2080
  td_percentage = 0
2081
 
2082
- gr.Markdown(f"""
 
2083
  ### Enhanced Dataset Summary:
2084
- - **Total Unique Storms**: {total_storms:,}
2085
- - **Total Track Records**: {total_records:,}
2086
- - **Year Range**: {year_range} ({years_covered} years)
2087
- - **Basins Available**: {basins_available}
2088
- - **Average Storms/Year**: {avg_storms_per_year:.1f}
2089
 
2090
  ### Storm Category Breakdown:
2091
- - **Tropical Depressions**: {td_storms:,} storms ({td_percentage:.1f}%)
2092
- - **Tropical Storms**: {ts_storms:,} storms
2093
- - **Typhoons (C1-C5)**: {typhoon_storms:,} storms
2094
 
2095
  ### New Platform Capabilities:
2096
- - **Complete TD Analysis** - First platform to include comprehensive TD tracking
2097
- - **Advanced ML Clustering** - DBSCAN pattern recognition with route visualization
2098
- - **Real-time Predictions** - Physics-based and optional CNN intensity forecasting
2099
- - **2025 Data Ready** - Full compatibility with current season data
2100
- - **Enhanced Animations** - Professional-quality storm track videos
2101
- - **Multi-basin Analysis** - Comprehensive Pacific and Atlantic coverage
2102
 
2103
  ### Research Applications:
2104
  - Climate change impact studies
@@ -2106,7 +2109,8 @@ def create_interface():
2106
  - Storm pattern classification
2107
  - ENSO-typhoon relationship analysis
2108
  - Intensity prediction model development
2109
- """)
 
2110
 
2111
  return demo
2112
  except Exception as e:
@@ -2188,7 +2192,7 @@ def test_color_conversion():
2188
  plotly_color = enhanced_color_map.get(category, 'rgb(128,128,128)')
2189
  matplotlib_color = get_matplotlib_color(category)
2190
 
2191
- print(f"Wind: {wind:3d}kt {category:20s} Plotly: {plotly_color:15s} Matplotlib: {matplotlib_color}")
2192
 
2193
  print("Color conversion test complete!")
2194
 
@@ -2204,7 +2208,7 @@ def test_rgb_conversion():
2204
  print("Testing RGB to hex conversion...")
2205
  for rgb_str in test_colors:
2206
  hex_color = rgb_string_to_hex(rgb_str)
2207
- print(f"{rgb_str:20s} {hex_color}")
2208
 
2209
  print("RGB conversion test complete!")
2210
 
 
 
1
  import os
2
  import argparse
3
  import logging
 
115
  # Enhanced color mapping with TD support (for Plotly)
116
  enhanced_color_map = {
117
  'Unknown': 'rgb(200, 200, 200)',
118
+ 'Tropical Depression': 'rgb(128, 128, 128)', # Gray for TD
119
  'Tropical Storm': 'rgb(0, 0, 255)',
120
  'C1 Typhoon': 'rgb(0, 255, 255)',
121
  'C2 Typhoon': 'rgb(0, 255, 0)',
 
1027
  storm_count = int(cluster_row['SID_count'])
1028
 
1029
  stats_text += f"CLUSTER {cluster}: {storm_count} storms\n"
1030
+ stats_text += f" Intensity: {cluster_row['USA_WIND_max_mean']:.1f} +/- {cluster_row['USA_WIND_max_std']:.1f} kt\n"
1031
+ stats_text += f" Pressure: {cluster_row['USA_PRES_min_mean']:.1f} +/- {cluster_row['USA_PRES_min_std']:.1f} hPa\n"
1032
+ stats_text += f" Track Length: {cluster_row['track_length_mean']:.1f} +/- {cluster_row['track_length_std']:.1f} points\n"
1033
  stats_text += f" Genesis Region: {cluster_row['genesis_lat']:.1f}°N, {cluster_row['genesis_lon']:.1f}°E\n"
1034
  stats_text += f" Avg Distance: {cluster_row['total_distance_mean']:.2f} degrees\n"
1035
  stats_text += f" Avg Curvature: {cluster_row['avg_curvature_mean']:.3f} radians\n\n"
1036
 
1037
  # Add feature importance summary
1038
  stats_text += "CLUSTERING FEATURES USED:\n"
1039
+ stats_text += " - Storm intensity (max/mean/std wind & pressure)\n"
1040
+ stats_text += " - Track characteristics (length, curvature, distance)\n"
1041
+ stats_text += " - Genesis location (lat/lon)\n"
1042
+ stats_text += " - Geographic range (lat/lon span)\n"
1043
+ stats_text += f" - Total features: {len(feature_cols)}\n\n"
1044
 
1045
  stats_text += f"ALGORITHM: {method.upper()} + DBSCAN clustering\n"
1046
  stats_text += f"CLUSTERS FOUND: {len([c for c in storm_features_viz['cluster'].unique() if c != -1])}\n"
 
1725
  gr.Markdown("Advanced ML clustering, CNN predictions, and comprehensive tropical cyclone analysis including Tropical Depressions")
1726
 
1727
  with gr.Tab("Overview"):
1728
+ overview_text = f"""
1729
  ## Welcome to the Enhanced Typhoon Analysis Dashboard
1730
 
1731
  This dashboard provides comprehensive analysis of typhoon data in relation to ENSO phases with advanced machine learning capabilities.
1732
 
1733
  ### Enhanced Features:
1734
+ - Advanced ML Clustering: UMAP/t-SNE storm pattern analysis with route visualization
1735
+ - Optional CNN Predictions: Deep learning intensity forecasting
1736
+ - Complete TD Support: Now includes Tropical Depressions (< 34 kt)
1737
+ - 2025 Data Ready: Real-time compatibility with current year data
1738
+ - Enhanced Animations: High-quality storm track visualizations
1739
 
1740
  ### Data Status:
1741
+ - ONI Data: {len(oni_data)} years loaded
1742
+ - Typhoon Data: {total_records} records loaded
1743
+ - Merged Data: {len(merged_data)} typhoons with ONI values
1744
+ - Available Years: {year_range_display}
1745
 
1746
  ### Technical Capabilities:
1747
+ - UMAP Clustering: {"Available" if UMAP_AVAILABLE else "Limited to t-SNE/PCA"}
1748
+ - AI Predictions: {"Deep Learning" if CNN_AVAILABLE else "Physics-based"}
1749
+ - Enhanced Categorization: Tropical Depression to Super Typhoon
1750
+ - Platform Compatibility: Optimized for Hugging Face Spaces
1751
+ """
1752
+ gr.Markdown(overview_text)
1753
 
1754
  with gr.Tab("Advanced ML Clustering with Routes"):
1755
  gr.Markdown("## Storm Pattern Analysis using UMAP/t-SNE with Route Visualization")
 
1792
  outputs=[cluster_plot, cluster_stats]
1793
  )
1794
 
1795
+ cluster_info_text = """
1796
  ### Advanced Clustering Features:
1797
+ - Multi-dimensional Analysis: Uses 15+ storm characteristics including intensity, track shape, genesis location
1798
+ - Route Visualization: Shows actual storm tracks colored by cluster membership
1799
+ - DBSCAN Clustering: Automatically finds natural groupings without predefined cluster count
1800
+ - Comprehensive Stats: Detailed cluster analysis including intensity, pressure, track length, curvature
1801
+ - Interactive: Hover over points to see storm details, zoom and pan the route map
1802
 
1803
  ### How to Interpret:
1804
+ - Left Plot: Each dot is a storm positioned by similarity (close = similar characteristics)
1805
+ - Right Plot: Actual geographic storm tracks, colored by which cluster they belong to
1806
+ - Cluster Colors: Each cluster gets a unique color to identify similar storm patterns
1807
+ - Noise Points: Gray points represent storms that don't fit clear patterns
1808
+ """
1809
+ gr.Markdown(cluster_info_text)
1810
 
1811
+ with gr.Tab("Intensity Prediction"):
1812
  gr.Markdown("## AI-Powered Storm Intensity Forecasting")
1813
 
1814
  if CNN_AVAILABLE:
1815
+ gr.Markdown("Deep Learning models available - TensorFlow loaded successfully")
1816
  method_description = "Using Convolutional Neural Networks for advanced intensity prediction"
1817
  else:
1818
+ gr.Markdown("Physics-based models available - Using climatological relationships")
1819
+ gr.Markdown("*Install TensorFlow for deep learning features: `pip install tensorflow-cpu`*")
1820
  method_description = "Using established meteorological relationships and climatology"
1821
 
1822
  gr.Markdown(f"**Current Method**: {method_description}")
 
1839
  outputs=[intensity_output, confidence_output]
1840
  )
1841
 
1842
+ prediction_info_text = """
1843
+ ### Prediction Features:
1844
+ - Environmental Analysis: Considers ENSO, latitude, seasonality
1845
+ - Real-time Capable: Predictions in milliseconds
1846
+ - Confidence Scoring: Uncertainty quantification included
1847
+ - Robust Fallbacks: Works with or without deep learning libraries
 
1848
 
1849
+ ### Interpretation Guide:
1850
+ - 25-33 kt: Tropical Depression (TD)
1851
+ - 34-63 kt: Tropical Storm (TS)
1852
+ - 64+ kt: Typhoon categories (C1-C5)
1853
+ - 100+ kt: Major typhoon (C3+)
1854
+ """
1855
+ gr.Markdown(prediction_info_text)
1856
+
1857
+ with gr.Tab("Track Visualization"):
1858
  with gr.Row():
1859
  start_year = gr.Number(label="Start Year", value=2020)
1860
  start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1)
 
1871
  outputs=[tracks_plot, typhoon_count]
1872
  )
1873
 
1874
+ with gr.Tab("Wind Analysis"):
1875
  with gr.Row():
1876
  wind_start_year = gr.Number(label="Start Year", value=2020)
1877
  wind_start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1)
 
1888
  outputs=[wind_scatter, wind_regression_results]
1889
  )
1890
 
1891
+ with gr.Tab("Pressure Analysis"):
1892
  with gr.Row():
1893
  pressure_start_year = gr.Number(label="Start Year", value=2020)
1894
  pressure_start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1)
 
1905
  outputs=[pressure_scatter, pressure_regression_results]
1906
  )
1907
 
1908
+ with gr.Tab("Longitude Analysis"):
1909
  with gr.Row():
1910
  lon_start_year = gr.Number(label="Start Year", value=2020)
1911
  lon_start_month = gr.Dropdown(label="Start Month", choices=list(range(1, 13)), value=1)
 
1923
  outputs=[regression_plot, slopes_text, lon_regression_results]
1924
  )
1925
 
1926
+ with gr.Tab("Enhanced Track Animation"):
1927
  gr.Markdown("## High-Quality Storm Track Visualization (All Categories Including TD)")
1928
 
1929
  with gr.Row():
 
1964
  outputs=[video_output]
1965
  )
1966
 
1967
+ animation_info_text = """
1968
  ### Enhanced Animation Features:
1969
+ - Full TD Support: Now displays Tropical Depressions (< 34 kt) in gray
1970
+ - 2025 Compatibility: Complete support for current year data
1971
+ - Enhanced Maps: Better cartographic projections with terrain features
1972
+ - Smart Scaling: Storm symbols scale dynamically with intensity
1973
+ - Real-time Info: Live position, time, and meteorological data display
1974
+ - Professional Styling: Publication-quality animations with proper legends
1975
+ - Optimized Export: Fast rendering with web-compatible video formats
1976
+ """
1977
+ gr.Markdown(animation_info_text)
1978
 
1979
  with gr.Tab("Data Statistics & Insights"):
1980
  gr.Markdown("## Comprehensive Dataset Analysis")
 
2038
  except Exception as e:
2039
  gr.Markdown(f"Visualization error: {str(e)}")
2040
 
2041
+ # Enhanced statistics - FIXED formatting
2042
  total_storms = len(typhoon_data['SID'].unique()) if 'SID' in typhoon_data.columns else 0
2043
  total_records = len(typhoon_data)
2044
 
 
2081
  td_storms = ts_storms = typhoon_storms = 0
2082
  td_percentage = 0
2083
 
2084
+ # Create statistics text safely
2085
+ stats_text = f"""
2086
  ### Enhanced Dataset Summary:
2087
+ - Total Unique Storms: {total_storms:,}
2088
+ - Total Track Records: {total_records:,}
2089
+ - Year Range: {year_range} ({years_covered} years)
2090
+ - Basins Available: {basins_available}
2091
+ - Average Storms/Year: {avg_storms_per_year:.1f}
2092
 
2093
  ### Storm Category Breakdown:
2094
+ - Tropical Depressions: {td_storms:,} storms ({td_percentage:.1f}%)
2095
+ - Tropical Storms: {ts_storms:,} storms
2096
+ - Typhoons (C1-C5): {typhoon_storms:,} storms
2097
 
2098
  ### New Platform Capabilities:
2099
+ - Complete TD Analysis - First platform to include comprehensive TD tracking
2100
+ - Advanced ML Clustering - DBSCAN pattern recognition with route visualization
2101
+ - Real-time Predictions - Physics-based and optional CNN intensity forecasting
2102
+ - 2025 Data Ready - Full compatibility with current season data
2103
+ - Enhanced Animations - Professional-quality storm track videos
2104
+ - Multi-basin Analysis - Comprehensive Pacific and Atlantic coverage
2105
 
2106
  ### Research Applications:
2107
  - Climate change impact studies
 
2109
  - Storm pattern classification
2110
  - ENSO-typhoon relationship analysis
2111
  - Intensity prediction model development
2112
+ """
2113
+ gr.Markdown(stats_text)
2114
 
2115
  return demo
2116
  except Exception as e:
 
2192
  plotly_color = enhanced_color_map.get(category, 'rgb(128,128,128)')
2193
  matplotlib_color = get_matplotlib_color(category)
2194
 
2195
+ print(f"Wind: {wind:3d}kt -> {category:20s} -> Plotly: {plotly_color:15s} -> Matplotlib: {matplotlib_color}")
2196
 
2197
  print("Color conversion test complete!")
2198
 
 
2208
  print("Testing RGB to hex conversion...")
2209
  for rgb_str in test_colors:
2210
  hex_color = rgb_string_to_hex(rgb_str)
2211
+ print(f"{rgb_str:20s} -> {hex_color}")
2212
 
2213
  print("RGB conversion test complete!")
2214