euler314 commited on
Commit
4d9b5c3
·
verified ·
1 Parent(s): bb0d660

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +95 -123
app.py CHANGED
@@ -1,6 +1,9 @@
1
  import gradio as gr
2
  import pandas as pd
3
  import numpy as np
 
 
 
4
  import plotly.graph_objects as go
5
  import plotly.express as px
6
  import tropycal.tracks as tracks
@@ -15,9 +18,6 @@ import tempfile
15
  import csv
16
  from collections import defaultdict
17
  import filecmp
18
- import uuid
19
- import base64
20
- from io import BytesIO
21
 
22
  # Command-line argument parsing
23
  parser = argparse.ArgumentParser(description='Typhoon Analysis Dashboard')
@@ -32,7 +32,7 @@ iBtrace_uri = 'https://www.ncei.noaa.gov/data/international-best-track-archive-f
32
  CACHE_FILE = 'ibtracs_cache.pkl'
33
  CACHE_EXPIRY_DAYS = 1
34
 
35
- # Color map for typhoon categories
36
  color_map = {
37
  'C5 Super Typhoon': 'rgb(255, 0, 0)',
38
  'C4 Very Strong Typhoon': 'rgb(255, 63, 0)',
@@ -43,22 +43,22 @@ color_map = {
43
  'Tropical Depression': 'rgb(173, 216, 230)'
44
  }
45
 
46
- # Classification standards
47
  atlantic_standard = {
48
- 'C5 Super Typhoon': {'wind_speed': 137, 'color': 'rgb(255, 0, 0)'},
49
- 'C4 Very Strong Typhoon': {'wind_speed': 113, 'color': 'rgb(255, 63, 0)'},
50
- 'C3 Strong Typhoon': {'wind_speed': 96, 'color': 'rgb(255, 127, 0)'},
51
- 'C2 Typhoon': {'wind_speed': 83, 'color': 'rgb(255, 191, 0)'},
52
- 'C1 Typhoon': {'wind_speed': 64, 'color': 'rgb(255, 255, 0)'},
53
- 'Tropical Storm': {'wind_speed': 34, 'color': 'rgb(0, 255, 255)'},
54
- 'Tropical Depression': {'wind_speed': 0, 'color': 'rgb(173, 216, 230)'}
55
  }
56
 
57
  taiwan_standard = {
58
- 'Strong Typhoon': {'wind_speed': 51.0, 'color': 'rgb(255, 0, 0)'},
59
- 'Medium Typhoon': {'wind_speed': 33.7, 'color': 'rgb(255, 127, 0)'},
60
- 'Mild Typhoon': {'wind_speed': 17.2, 'color': 'rgb(255, 255, 0)'},
61
- 'Tropical Depression': {'wind_speed': 0, 'color': 'rgb(173, 216, 230)'}
62
  }
63
 
64
  # Data loading and preprocessing functions
@@ -212,7 +212,7 @@ oni_long = process_oni_data(oni_data)
212
  typhoon_max = process_typhoon_data(typhoon_data)
213
  merged_data = merge_data(oni_long, typhoon_max)
214
 
215
- # Main analysis functions
216
  def generate_typhoon_tracks(filtered_data, typhoon_search):
217
  fig = go.Figure()
218
  for sid in filtered_data['SID'].unique():
@@ -230,7 +230,11 @@ def generate_typhoon_tracks(filtered_data, typhoon_search):
230
  lon=storm_data['LON'], lat=storm_data['LAT'], mode='lines',
231
  name=f'Matched: {typhoon_search}', line=dict(width=5, color='yellow')
232
  ))
233
- fig.update_layout(title='Typhoon Tracks', geo=dict(projection_type='natural earth', showland=True))
 
 
 
 
234
  return fig
235
 
236
  def generate_wind_oni_scatter(filtered_data, typhoon_search):
@@ -296,7 +300,7 @@ def generate_main_analysis(start_year, start_month, end_year, end_month, enso_ph
296
 
297
  return tracks_fig, wind_scatter, pressure_scatter, regression_fig, slopes_text
298
 
299
- # Custom animation function with HTML and JavaScript
300
  def categorize_typhoon_by_standard(wind_speed, standard):
301
  if standard == 'taiwan':
302
  wind_speed_ms = wind_speed * 0.514444
@@ -322,9 +326,9 @@ def categorize_typhoon_by_standard(wind_speed, standard):
322
  return 'Tropical Storm', atlantic_standard['Tropical Storm']['color']
323
  return 'Tropical Depression', atlantic_standard['Tropical Depression']['color']
324
 
325
- def generate_track_animation(year, typhoon, standard):
326
  if not typhoon:
327
- return "<p>No typhoon selected.</p>"
328
 
329
  typhoon_id = typhoon.split('(')[-1].strip(')')
330
  storm = ibtracs.get_storm(typhoon_id)
@@ -335,101 +339,54 @@ def generate_track_animation(year, typhoon, standard):
335
  lat_padding = max((max_lat - min_lat) * 0.3, 5)
336
  lon_padding = max((max_lon - min_lon) * 0.3, 5)
337
 
338
- # Generate frames as images and encode them in base64
339
- frames = []
340
- dates = []
341
- for i in range(len(storm.time)):
342
- fig = go.Figure()
343
- category, color = categorize_typhoon_by_standard(storm.vmax[i], standard)
344
- fig.add_trace(go.Scattergeo(
345
- lon=storm.lon[:i+1],
346
- lat=storm.lat[:i+1],
347
- mode='lines+markers',
348
- line=dict(width=2, color='blue'),
349
- marker=dict(size=8, color=color),
350
- name=f"{storm.name}",
351
- text=[f"Time: {storm.time[j].strftime('%Y-%m-%d %H:%M')}<br>Wind: {storm.vmax[j]:.1f} kt<br>Category: {categorize_typhoon_by_standard(storm.vmax[j], standard)[0]}" for j in range(i+1)],
352
- hoverinfo="text"
353
- ))
354
- standard_dict = atlantic_standard if standard == 'atlantic' else taiwan_standard
355
- for cat, details in standard_dict.items():
356
- fig.add_trace(go.Scattergeo(
357
- lon=[None], lat=[None], mode='markers',
358
- marker=dict(size=10, color=details['color']),
359
- name=cat,
360
- showlegend=True
361
- ))
362
- fig.update_layout(
363
- title=f"{year} {storm.name}",
364
- geo=dict(
365
- projection_type='natural earth',
366
- showland=True,
367
- showcoastlines=True,
368
- landcolor='rgb(243, 243, 243)',
369
- countrycolor='rgb(204, 204, 204)',
370
- coastlinecolor='rgb(204, 204, 204)',
371
- showocean=True,
372
- oceancolor='rgb(230, 230, 255)',
373
- lataxis={'range': [min_lat - lat_padding, max_lat + lat_padding]},
374
- lonaxis={'range': [min_lon - lon_padding, max_lon + lon_padding]}
375
- ),
376
- height=700,
377
- showlegend=True,
378
- legend=dict(
379
- yanchor="top",
380
- y=0.99,
381
- xanchor="left",
382
- x=0.01,
383
- bgcolor="rgba(255, 255, 255, 0.8)"
384
- )
385
- )
386
- # Convert figure to PNG and encode in base64
387
- img_bytes = fig.to_image(format="png", width=1000, height=700)
388
- img_base64 = base64.b64encode(img_bytes).decode('utf-8')
389
- frames.append(f"data:image/png;base64,{img_base64}")
390
- dates.append(storm.time[i].strftime('%Y-%m-%d %H:%M'))
391
-
392
- # JavaScript to handle animation
393
- js_script = """
394
- <div style="text-align: center;">
395
- <img id="animationFrame" style="max-width: 100%; height: 700px;" src="{frames[0]}">
396
- <p id="dateDisplay" style="font-size: 18px; margin: 10px;">{dates[0]}</p>
397
- <button id="startBtn" style="margin: 5px;">Start</button>
398
- <button id="pauseBtn" style="margin: 5px;">Pause</button>
399
- </div>
400
- <script>
401
- const frames = {frames_json};
402
- const dates = {dates_json};
403
- let currentFrame = 0;
404
- let intervalId = null;
405
-
406
- function updateFrame() {{
407
- document.getElementById('animationFrame').src = frames[currentFrame];
408
- document.getElementById('dateDisplay').textContent = dates[currentFrame];
409
- currentFrame = (currentFrame + 1) % frames.length;
410
- }}
411
-
412
- document.getElementById('startBtn').addEventListener('click', function() {{
413
- if (!intervalId) {{
414
- intervalId = setInterval(updateFrame, 200); // 200ms per frame
415
- }}
416
- }});
417
-
418
- document.getElementById('pauseBtn').addEventListener('click', function() {{
419
- if (intervalId) {{
420
- clearInterval(intervalId);
421
- intervalId = null;
422
- }}
423
- }});
424
- </script>
425
- """.format(
426
- frames_json=str(frames).replace("'", '"'), # JSON-safe string
427
- dates_json=str(dates).replace("'", '"'),
428
- frames=frames[0], # Initial frame
429
- dates=dates[0] # Initial date
430
- )
431
-
432
- return js_script
433
 
434
  # Logistic regression functions
435
  def perform_wind_regression(start_year, start_month, end_year, end_month):
@@ -478,7 +435,7 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
478
  ### Features:
479
  - **Track Visualization**: View typhoon tracks by time period and ENSO phase
480
  - **Statistical Analysis**: Examine relationships between ONI values and typhoon characteristics
481
- - **Path Animation**: Watch an animated typhoon path with start/pause controls
482
  - **Regression Analysis**: Perform statistical regression on typhoon data
483
 
484
  Select a tab above to begin your analysis.
@@ -639,15 +596,14 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
639
  standard_dropdown = gr.Dropdown(label="Classification Standard", choices=['atlantic', 'taiwan'], value='atlantic')
640
 
641
  animate_btn = gr.Button("Generate Animation")
642
- path_animation = gr.HTML(label="Typhoon Path Animation")
643
  animation_info = gr.Markdown("""
644
  ### Animation Instructions
645
  1. Select a year and typhoon from the dropdowns
646
  2. Choose a classification standard (Atlantic or Taiwan)
647
  3. Click "Generate Animation"
648
- 4. Use the "Start" button to begin the animation and "Pause" to stop it
649
- 5. The date below the image updates with each frame
650
- 6. The blue line grows to show the typhoon's path, with markers indicating intensity
651
  """)
652
 
653
  def update_typhoon_options(year):
@@ -658,9 +614,25 @@ with gr.Blocks(title="Typhoon Analysis Dashboard") as demo:
658
 
659
  year_dropdown.change(fn=update_typhoon_options, inputs=year_dropdown, outputs=typhoon_dropdown)
660
  animate_btn.click(
661
- fn=generate_track_animation,
662
  inputs=[year_dropdown, typhoon_dropdown, standard_dropdown],
663
- outputs=path_animation
664
  )
665
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  demo.launch(share=True)
 
1
  import gradio as gr
2
  import pandas as pd
3
  import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ import matplotlib.animation as animation
6
+ from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
7
  import plotly.graph_objects as go
8
  import plotly.express as px
9
  import tropycal.tracks as tracks
 
18
  import csv
19
  from collections import defaultdict
20
  import filecmp
 
 
 
21
 
22
  # Command-line argument parsing
23
  parser = argparse.ArgumentParser(description='Typhoon Analysis Dashboard')
 
32
  CACHE_FILE = 'ibtracs_cache.pkl'
33
  CACHE_EXPIRY_DAYS = 1
34
 
35
+ # Color maps for Plotly (RGB)
36
  color_map = {
37
  'C5 Super Typhoon': 'rgb(255, 0, 0)',
38
  'C4 Very Strong Typhoon': 'rgb(255, 63, 0)',
 
43
  'Tropical Depression': 'rgb(173, 216, 230)'
44
  }
45
 
46
+ # Classification standards for both Plotly and Matplotlib (HEX for Matplotlib)
47
  atlantic_standard = {
48
+ 'C5 Super Typhoon': {'wind_speed': 137, 'color': '#FF0000'},
49
+ 'C4 Very Strong Typhoon': {'wind_speed': 113, 'color': '#FF3F00'},
50
+ 'C3 Strong Typhoon': {'wind_speed': 96, 'color': '#FF7F00'},
51
+ 'C2 Typhoon': {'wind_speed': 83, 'color': '#FFBF00'},
52
+ 'C1 Typhoon': {'wind_speed': 64, 'color': '#FFFF00'},
53
+ 'Tropical Storm': {'wind_speed': 34, 'color': '#00FFFF'},
54
+ 'Tropical Depression': {'wind_speed': 0, 'color': '#ADD8E6'}
55
  }
56
 
57
  taiwan_standard = {
58
+ 'Strong Typhoon': {'wind_speed': 51.0, 'color': '#FF0000'},
59
+ 'Medium Typhoon': {'wind_speed': 33.7, 'color': '#FF7F00'},
60
+ 'Mild Typhoon': {'wind_speed': 17.2, 'color': '#FFFF00'},
61
+ 'Tropical Depression': {'wind_speed': 0, 'color': '#ADD8E6'}
62
  }
63
 
64
  # Data loading and preprocessing functions
 
212
  typhoon_max = process_typhoon_data(typhoon_data)
213
  merged_data = merge_data(oni_long, typhoon_max)
214
 
215
+ # Main analysis functions (using Plotly)
216
  def generate_typhoon_tracks(filtered_data, typhoon_search):
217
  fig = go.Figure()
218
  for sid in filtered_data['SID'].unique():
 
230
  lon=storm_data['LON'], lat=storm_data['LAT'], mode='lines',
231
  name=f'Matched: {typhoon_search}', line=dict(width=5, color='yellow')
232
  ))
233
+ fig.update_layout(
234
+ title='Typhoon Tracks',
235
+ geo=dict(projection_type='natural earth', showland=True),
236
+ height=700
237
+ )
238
  return fig
239
 
240
  def generate_wind_oni_scatter(filtered_data, typhoon_search):
 
300
 
301
  return tracks_fig, wind_scatter, pressure_scatter, regression_fig, slopes_text
302
 
303
+ # Video animation function (using Matplotlib)
304
  def categorize_typhoon_by_standard(wind_speed, standard):
305
  if standard == 'taiwan':
306
  wind_speed_ms = wind_speed * 0.514444
 
326
  return 'Tropical Storm', atlantic_standard['Tropical Storm']['color']
327
  return 'Tropical Depression', atlantic_standard['Tropical Depression']['color']
328
 
329
+ def generate_track_video(year, typhoon, standard):
330
  if not typhoon:
331
+ return None
332
 
333
  typhoon_id = typhoon.split('(')[-1].strip(')')
334
  storm = ibtracs.get_storm(typhoon_id)
 
339
  lat_padding = max((max_lat - min_lat) * 0.3, 5)
340
  lon_padding = max((max_lon - min_lon) * 0.3, 5)
341
 
342
+ # Set up the figure
343
+ fig, ax = plt.subplots(figsize=(10, 7))
344
+ ax.set_xlim(min_lon - lon_padding, max_lon + lon_padding)
345
+ ax.set_ylim(min_lat - lat_padding, max_lat + lat_padding)
346
+ ax.set_xlabel('Longitude')
347
+ ax.set_ylabel('Latitude')
348
+ ax.set_title(f"{year} {storm.name} Typhoon Path")
349
+ ax.grid(True)
350
+
351
+ # Plot background (simple map-like grid)
352
+ ax.plot([], [], 'b-', label='Track') # Placeholder for legend
353
+
354
+ # Legend for categories
355
+ standard_dict = atlantic_standard if standard == 'atlantic' else taiwan_standard
356
+ for cat, details in standard_dict.items():
357
+ ax.plot([], [], 'o', color=details['color'], label=cat)
358
+ ax.legend(loc='upper left', bbox_to_anchor=(1, 1))
359
+
360
+ # Initialize the line and point
361
+ line, = ax.plot([], [], 'b-', linewidth=2)
362
+ point, = ax.plot([], [], 'o', markersize=8)
363
+ date_text = ax.text(0.02, 0.02, '', transform=ax.transAxes, fontsize=12)
364
+
365
+ def init():
366
+ line.set_data([], [])
367
+ point.set_data([], [])
368
+ date_text.set_text('')
369
+ return line, point, date_text
370
+
371
+ def update(frame):
372
+ line.set_data(storm.lon[:frame+1], storm.lat[:frame+1])
373
+ category, color = categorize_typhoon_by_standard(storm.vmax[frame], standard)
374
+ point.set_data([storm.lon[frame]], [storm.lat[frame]])
375
+ point.set_color(color)
376
+ date_text.set_text(storm.time[frame].strftime('%Y-%m-%d %H:%M'))
377
+ return line, point, date_text
378
+
379
+ # Create animation
380
+ ani = animation.FuncAnimation(fig, update, init_func=init, frames=len(storm.time),
381
+ interval=200, blit=True, repeat=True)
382
+
383
+ # Save as video
384
+ temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.mp4')
385
+ writer = animation.FFMpegWriter(fps=5, bitrate=1800)
386
+ ani.save(temp_file.name, writer=writer)
387
+ plt.close(fig)
388
+
389
+ return temp_file.name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
 
391
  # Logistic regression functions
392
  def perform_wind_regression(start_year, start_month, end_year, end_month):
 
435
  ### Features:
436
  - **Track Visualization**: View typhoon tracks by time period and ENSO phase
437
  - **Statistical Analysis**: Examine relationships between ONI values and typhoon characteristics
438
+ - **Path Animation**: Watch an animated typhoon path with video controls
439
  - **Regression Analysis**: Perform statistical regression on typhoon data
440
 
441
  Select a tab above to begin your analysis.
 
596
  standard_dropdown = gr.Dropdown(label="Classification Standard", choices=['atlantic', 'taiwan'], value='atlantic')
597
 
598
  animate_btn = gr.Button("Generate Animation")
599
+ path_video = gr.Video(label="Typhoon Path Animation", elem_id="path_video")
600
  animation_info = gr.Markdown("""
601
  ### Animation Instructions
602
  1. Select a year and typhoon from the dropdowns
603
  2. Choose a classification standard (Atlantic or Taiwan)
604
  3. Click "Generate Animation"
605
+ 4. Use the video player's built-in controls to play, pause, or scrub through the animation
606
+ 5. The date is displayed on each frame, and the track grows with intensity markers
 
607
  """)
608
 
609
  def update_typhoon_options(year):
 
614
 
615
  year_dropdown.change(fn=update_typhoon_options, inputs=year_dropdown, outputs=typhoon_dropdown)
616
  animate_btn.click(
617
+ fn=generate_track_video,
618
  inputs=[year_dropdown, typhoon_dropdown, standard_dropdown],
619
+ outputs=path_video
620
  )
621
 
622
+ # Custom CSS for better visibility
623
+ gr.HTML("""
624
+ <style>
625
+ #tracks_plot, #path_video {
626
+ height: 700px !important;
627
+ width: 100%;
628
+ }
629
+ .plot-container {
630
+ min-height: 600px;
631
+ }
632
+ .gr-plotly {
633
+ width: 100% !important;
634
+ }
635
+ </style>
636
+ """)
637
+
638
  demo.launch(share=True)