Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import folium | |
| import requests | |
| import json | |
| import numpy as np | |
| from PIL import Image | |
| import base64 | |
| import io | |
| from radar_analyzer import CanadianRadarAnalyzer | |
| import cv2 | |
| class RadarAnalysisApp: | |
| def __init__(self): | |
| # Always use Canadian radar data source | |
| self.wms_url = "https://geo.weather.gc.ca/geomet" | |
| self.analyzer = CanadianRadarAnalyzer() | |
| # Color display mode | |
| self.american_colors = False # False = Canadian colors, True = American colors | |
| # Extended North America bounds for map centering - covers Alaska to southeastern US | |
| self.canada_bounds = { | |
| "center": [52.5, -105.0], | |
| "southwest": [20, -170], | |
| "northeast": [85, -40] | |
| } | |
| def toggle_color_scheme(self, use_american_colors: bool): | |
| """Toggle between Canadian and American color schemes for the same radar data.""" | |
| self.american_colors = use_american_colors | |
| color_scheme = "American" if use_american_colors else "Canadian" | |
| print(f"π¨ Switched to {color_scheme} color scheme") | |
| return f"π¨ Using {color_scheme} Colors" | |
| def fetch_current_radar(self): | |
| """Fetch the most recent Canadian radar image.""" | |
| try: | |
| url = f"{self.wms_url}?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&LAYERS=RADAR_1KM_RRAI&STYLES=&CRS=EPSG:4326&BBOX=20,-170,85,-40&WIDTH=1200&HEIGHT=800&FORMAT=image/png" | |
| response = requests.get(url, timeout=30) | |
| response.raise_for_status() | |
| # Save the radar image | |
| with open('current_radar_fetch.png', 'wb') as f: | |
| f.write(response.content) | |
| return 'current_radar_fetch.png' | |
| except Exception as e: | |
| print(f"Error fetching radar: {e}") | |
| return None | |
| def analyze_current_radar(self): | |
| """Fetch and analyze the current radar image with optional color conversion.""" | |
| radar_file = self.fetch_current_radar() | |
| if not radar_file: | |
| return None, "Failed to fetch radar data" | |
| try: | |
| # Analyze the radar image using Canadian legend | |
| result = self.analyzer.analyze_radar(radar_file, "radar_legendwellcropped.png") | |
| # Apply American color conversion if requested | |
| if self.american_colors: | |
| print("π Converting to American color scheme...") | |
| original_image = cv2.imread(radar_file) | |
| if original_image is not None: | |
| original_rgb = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB) | |
| converted_image = self.analyzer.convert_colors_to_american_scale(original_rgb) | |
| # Save converted image | |
| converted_file = radar_file.replace('.png', '_american_colors.png') | |
| converted_bgr = cv2.cvtColor(converted_image, cv2.COLOR_RGB2BGR) | |
| cv2.imwrite(converted_file, converted_bgr) | |
| # Update the result to point to converted image | |
| result['output_file'] = converted_file | |
| print(f"β Saved American color version: {converted_file}") | |
| # Debug: Print what we actually got | |
| print("Analysis result keys:", result.keys()) | |
| print("Analysis keys:", result['analysis'].keys()) | |
| # Create analysis summary with safe access | |
| analysis = result['analysis'] | |
| analysis_text = f""" | |
| **Radar Analysis Results:** | |
| - **Precipitation Pixels:** {analysis.get('precipitation_pixels', 0):,} | |
| - **Total Image Pixels:** {analysis.get('total_pixels', 0):,} | |
| - **Coverage:** {analysis.get('precipitation_percentage', 0):.2f}% | |
| - **Regions Found:** {len(result.get('regions', []))} | |
| **Detected Precipitation Levels:** | |
| """ | |
| # Add intensity breakdown | |
| intensity_levels = analysis.get('intensity_levels', analysis.get('pixel_statistics', {})) | |
| for intensity, count in intensity_levels.items(): | |
| if count > 0: | |
| analysis_text += f"\n- {intensity}: {count:,} pixels" | |
| # Add dBZ scale reference | |
| analysis_text += f""" | |
| **dBZ Scale Reference:** | |
| - 0.1-1.0 dBZ: Very light precipitation (blue) | |
| - 1.0-8.0 dBZ: Light to moderate (green-yellow) | |
| - 8.0-32.0 dBZ: Heavy precipitation (orange-red) | |
| - 32.0+ dBZ: Intense precipitation (red-purple) | |
| *Hover over radar pixels to see precise dBZ values* | |
| """ | |
| return result['output_file'], analysis_text | |
| except Exception as e: | |
| return None, f"Analysis failed: {str(e)}" | |
| def create_radar_map_with_analysis(self, show_analysis=True): | |
| """Create a Folium map with radar overlay and optional analysis.""" | |
| try: | |
| # Create base map | |
| m = folium.Map( | |
| location=self.canada_bounds["center"], | |
| zoom_start=4, | |
| tiles="OpenStreetMap" | |
| ) | |
| if show_analysis: | |
| # Analyze current radar | |
| annotated_file, analysis_text = self.analyze_current_radar() | |
| if annotated_file: | |
| # Add the analyzed radar as an overlay | |
| # Note: For this demo, we'll add the WMS layer | |
| # In a full implementation, you'd convert the analyzed image to map overlay | |
| pass | |
| # Add live radar overlay with hover functionality | |
| color_scheme = "American" if self.american_colors else "Canadian" | |
| wms_layer = folium.raster_layers.WmsTileLayer( | |
| url=self.wms_url, | |
| layers="RADAR_1KM_RRAI", | |
| version="1.3.0", | |
| transparent=True, | |
| format="image/png", | |
| name=f"π¨π¦ Canadian Radar ({color_scheme} Colors)", | |
| overlay=True, | |
| control=True, | |
| opacity=0.7, | |
| attr='<a href="https://eccc-msc.github.io/open-data/licence/readme_en/">ECCC</a>' | |
| ) | |
| wms_layer.add_to(m) | |
| folium.LayerControl().add_to(m) | |
| # Add dBZ hover points if analysis data is available | |
| if show_analysis: | |
| try: | |
| # Try to get the latest analysis | |
| radar_file = self.fetch_current_radar() | |
| if radar_file: | |
| result = self.analyzer.analyze_radar(radar_file, "radar_legendwellcropped.png") | |
| hover_data = result.get('hover_data', {}) | |
| hover_points = hover_data.get('points', []) | |
| print(f"π Adding {len(hover_points)} MAXIMUM RESOLUTION hover points to map") | |
| print(f"π― Coverage: ENTIRE radar image, EVERY precipitation pixel") | |
| # Add ALL precipitation pixels as hover points for MAXIMUM resolution | |
| # NO limits, NO caps, COMPLETE coverage | |
| for i, point in enumerate(hover_points): | |
| if i % 1000 == 0 and i > 0: | |
| progress = (i / len(hover_points)) * 100 | |
| print(f" π Added {i:,}/{len(hover_points):,} hover points ({progress:.1f}%)") | |
| # Create ultra-precise markers for maximum zoom resolution | |
| folium.CircleMarker( | |
| location=[point['lat'], point['lon']], | |
| radius=0.8, # Ultra-small for high-resolution detail | |
| popup=folium.Popup( | |
| f""" | |
| <div style='font-family: monospace;'> | |
| <b>π§οΈ dBZ: {point['dbz']}</b><br> | |
| <b>Intensity:</b> {point['intensity']}<br> | |
| <b>Pixel:</b> ({point['pixel_x']}, {point['pixel_y']})<br> | |
| <b>Lat:</b> {point['lat']:.6f}Β°N<br> | |
| <b>Lon:</b> {point['lon']:.6f}Β°W<br> | |
| <small><i>Ultra-high resolution point</i></small> | |
| </div> | |
| """, | |
| max_width=250 | |
| ), | |
| tooltip=f"β‘ {point['dbz']} dBZ ({point['intensity']})", | |
| color='red', | |
| fillColor='red', | |
| fillOpacity=0.4, | |
| opacity=0.6, | |
| weight=1 | |
| ).add_to(m) | |
| print(f"β Added ALL {len(hover_points):,} hover points for MAXIMUM zoom resolution") | |
| print(f"β NO caps applied - full dBZ range coverage") | |
| print(f"β Complete radar image coverage achieved") | |
| except Exception as e: | |
| print(f"Error adding hover data: {e}") | |
| pass | |
| # Add info panel | |
| info_html = """ | |
| <div style="position: fixed; | |
| top: 10px; right: 10px; width: 250px; height: auto; | |
| background-color: rgba(255,255,255,0.95); border:2px solid #333; z-index:9999; | |
| font-size:12px; padding: 10px; border-radius: 8px;"> | |
| <div style="font-weight: bold; margin-bottom: 8px; color: #333;">π¨π¦ Canadian Radar Analysis</div> | |
| <div style="margin-bottom: 4px;">β’ Real-time precipitation data</div> | |
| <div style="margin-bottom: 4px;">β’ Pixel-level dBZ analysis</div> | |
| <div style="margin-bottom: 4px;">β’ Color-mapped intensity regions</div> | |
| <div style="font-size:10px; margin-top:8px; color: #666;">Data: ECCC MSC GeoMet</div> | |
| </div> | |
| """ | |
| m.get_root().html.add_child(folium.Element(info_html)) | |
| return m | |
| except Exception as e: | |
| # Return error map | |
| error_map = folium.Map(location=self.canada_bounds["center"], zoom_start=4) | |
| error_html = f""" | |
| <div style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); | |
| background-color: #ffebee; border: 2px solid #f44336; | |
| padding: 20px; border-radius: 5px; z-index: 9999;"> | |
| <h3 style="color: #f44336;">Radar Analysis Error</h3> | |
| <p>Error: {str(e)}</p> | |
| </div> | |
| """ | |
| error_map.get_root().html.add_child(folium.Element(error_html)) | |
| return error_map | |
| # Initialize the app | |
| app = RadarAnalysisApp() | |
| def update_radar_analysis(): | |
| """Update radar analysis and return results.""" | |
| try: | |
| # Analyze current radar | |
| annotated_file, analysis_text = app.analyze_current_radar() | |
| if annotated_file: | |
| # Create map | |
| radar_map = app.create_radar_map_with_analysis(show_analysis=True) | |
| return radar_map._repr_html_(), analysis_text, annotated_file | |
| else: | |
| return "Analysis failed", analysis_text, None | |
| except Exception as e: | |
| return f"Error: {str(e)}", "Analysis failed", None | |
| def show_live_radar(): | |
| """Show live radar without analysis.""" | |
| radar_map = app.create_radar_map_with_analysis(show_analysis=False) | |
| return radar_map._repr_html_() | |
| def toggle_color_scheme(use_american_colors): | |
| """Toggle between Canadian and American color schemes.""" | |
| status_msg = app.toggle_color_scheme(use_american_colors) | |
| return status_msg | |
| # Create Gradio interface | |
| with gr.Blocks(title="Canadian Weather Radar with Dual Color Schemes", theme=gr.themes.Soft()) as interface: | |
| gr.Markdown("# π¨π¦ Canadian Weather Radar Analyzer") | |
| gr.Markdown("Canadian radar data with switchable color schemes: Canadian (ECCC) β American (NWS) colors") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("## Controls") | |
| # Color scheme toggle | |
| color_toggle = gr.Checkbox( | |
| value=False, | |
| label="π¨ Use American Colors", | |
| info="Toggle to convert Canadian radar to American color scale" | |
| ) | |
| gr.Markdown("---") | |
| analyze_btn = gr.Button("π Analyze Current Radar", variant="primary", size="lg") | |
| live_btn = gr.Button("π‘ Show Live Radar", variant="secondary") | |
| # Status display | |
| status_display = gr.Textbox( | |
| value="π¨ Using Canadian Colors", | |
| label="Color Scheme Status", | |
| interactive=False | |
| ) | |
| gr.Markdown("---") | |
| gr.Markdown("### Features:") | |
| gr.Markdown(""" | |
| - **Pixel-Perfect Analysis**: Every pixel analyzed for precise dBZ values | |
| - **Color Mapping**: Uses official Canadian radar color scale | |
| - **Region Detection**: Groups same-intensity pixels into regions | |
| - **Real-time Data**: Fresh data from ECCC every 10 minutes | |
| - **Quantitative Results**: Pixel counts and coverage statistics | |
| """) | |
| gr.Markdown("### Analysis Output:") | |
| analysis_output = gr.Textbox( | |
| label="Analysis Results", | |
| lines=15, | |
| placeholder="Click 'Analyze Current Radar' to see detailed analysis...", | |
| interactive=False | |
| ) | |
| with gr.Column(scale=6): | |
| with gr.Row(): | |
| radar_map = gr.HTML( | |
| value=app.create_radar_map_with_analysis(show_analysis=False)._repr_html_(), | |
| label="Radar Map" | |
| ) | |
| with gr.Row(): | |
| annotated_image = gr.Image( | |
| label="Analyzed Radar Image (with dBZ annotations)", | |
| type="filepath" | |
| ) | |
| # Event handlers | |
| color_toggle.change( | |
| fn=toggle_color_scheme, | |
| inputs=[color_toggle], | |
| outputs=[status_display] | |
| ) | |
| analyze_btn.click( | |
| fn=update_radar_analysis, | |
| outputs=[radar_map, analysis_output, annotated_image] | |
| ) | |
| live_btn.click( | |
| fn=show_live_radar, | |
| outputs=[radar_map] | |
| ) | |
| # Launch the app | |
| if __name__ == "__main__": | |
| interface.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=True, | |
| debug=True | |
| ) |