jimmy60504 commited on
Commit
5c971be
·
1 Parent(s): bc5a4e4

feat: implement STA/LTA result caching to enhance UI responsiveness

Browse files
Files changed (2) hide show
  1. app.py +34 -6
  2. changelog.md +6 -0
app.py CHANGED
@@ -92,6 +92,10 @@ except Exception as e:
92
  earthquake_metadata = {}
93
  event_json_path = "waveform/event.json"
94
 
 
 
 
 
95
  try:
96
  import json
97
 
@@ -297,17 +301,24 @@ def calculate_distance(lat1, lon1, lat2, lon2):
297
  return np.sqrt((lat1 - lat2) ** 2 + (lon1 - lon2) ** 2)
298
 
299
 
300
- def select_nearest_stations(st, epicenter_lat, epicenter_lon, n_stations=25):
301
  """
302
  從 site_info(1000+ 個輸入測站)中選擇距離震央最近的 n 個測站
303
  並使用 STA/LTA 偵測 P 波到時,只保留成功偵測到 P 波的測站
304
 
305
  少於 25 站可用:UI 明示實際用站數並允許繼續
 
 
306
  """
307
  station_distances = {} # 改用字典避免重複
308
  p_wave_detected_count = 0
309
  p_wave_failed_count = 0
310
 
 
 
 
 
 
311
  # 計算每個測站到震央的距離並偵測 P 波
312
  for tr in st:
313
  station_code = tr.stats.station
@@ -363,6 +374,13 @@ def select_nearest_stations(st, epicenter_lat, epicenter_lon, n_stations=25):
363
  }
364
  p_wave_detected_count += 1
365
 
 
 
 
 
 
 
 
366
  except Exception as e:
367
  logger.warning(f"測站 {station_code} 資訊查詢失敗: {e}")
368
  p_wave_failed_count += 1
@@ -433,6 +451,11 @@ def extract_waveforms_from_stream(event_name,
433
  end_idx = int(end_time * sampling_rate)
434
  actual_samples = end_idx - start_idx
435
 
 
 
 
 
 
436
  # 檢查是否需要零填充:長度不足 30 秒時尾段以 0 遮罩補齊
437
  needs_padding = duration < min_duration
438
  if needs_padding:
@@ -471,6 +494,7 @@ def extract_waveforms_from_stream(event_name,
471
  # 檢查 Z 分量(必須存在)
472
  if len(z_trace) > 0:
473
  z_data = z_trace[0].data[start_idx:end_idx]
 
474
  else:
475
  continue
476
 
@@ -552,13 +576,17 @@ def plot_waveform(st, selected_stations, first_pick, duration):
552
 
553
  Parameters:
554
  - st: ObsPy Stream object
555
- - selected_stations: 選定的測站列表
556
  - first_pick: 首次到達時間(秒)
557
  - duration: 時間長度(秒)
 
 
558
  """
559
  # 計算結束時間
560
  end_time = first_pick + duration
561
 
 
 
562
  # 創建 Plotly figure
563
  fig = go.Figure()
564
 
@@ -963,17 +991,17 @@ def step1_load_mseed_and_select_stations(event_name):
963
  st = read(mseed_file)
964
  logger.info(f"載入了 {len(st)} 個 trace")
965
 
966
- # 選擇距離震央最近的 25 個測站
967
  logger.info(f"選擇距離震央 ({epicenter_lat}, {epicenter_lon}) 最近的測站...")
968
  selected_stations = select_nearest_stations(
969
- st, epicenter_lat, epicenter_lon, n_stations=25
970
  )
971
 
972
  if len(selected_stations) == 0:
973
  logger.error("找不到有效的測站資料")
974
  return None, None
975
 
976
- logger.info("[步驟 1] 完成 - mseed 已載入,測站已選擇")
977
  return st, selected_stations
978
 
979
  except Exception as e:
@@ -999,7 +1027,7 @@ def step2_extract_and_plot_waveforms(cached_stream, cached_stations, event_name,
999
 
1000
  first_pick = earthquake_metadata[event_name]["first_pick"]
1001
 
1002
- logger.info(f"[步驟 2] 提取波形資料(P 波後 {duration} 秒)...")
1003
 
1004
  # 提取波形資料
1005
  (waveforms, station_info_list, valid_stations,
 
92
  earthquake_metadata = {}
93
  event_json_path = "waveform/event.json"
94
 
95
+ # STA/LTA 計算結果快取(避免每次滑桿更新都重算)
96
+ # 結構: {event_name: {station_code: {"p_arrival_time": float, "cft": array}}}
97
+ sta_lta_cache = {}
98
+
99
  try:
100
  import json
101
 
 
301
  return np.sqrt((lat1 - lat2) ** 2 + (lon1 - lon2) ** 2)
302
 
303
 
304
+ def select_nearest_stations(st, epicenter_lat, epicenter_lon, n_stations=25, event_name=None):
305
  """
306
  從 site_info(1000+ 個輸入測站)中選擇距離震央最近的 n 個測站
307
  並使用 STA/LTA 偵測 P 波到時,只保留成功偵測到 P 波的測站
308
 
309
  少於 25 站可用:UI 明示實際用站數並允許繼續
310
+
311
+ STA/LTA 結果會快取到全域 sta_lta_cache,避免滑桿更新時重複計算
312
  """
313
  station_distances = {} # 改用字典避免重複
314
  p_wave_detected_count = 0
315
  p_wave_failed_count = 0
316
 
317
+ # 初始化此事件的 cache
318
+ if event_name and event_name not in sta_lta_cache:
319
+ sta_lta_cache[event_name] = {}
320
+ logger.info(f"為事件 {event_name} 初始化 STA/LTA 快取")
321
+
322
  # 計算每個測站到震央的距離並偵測 P 波
323
  for tr in st:
324
  station_code = tr.stats.station
 
374
  }
375
  p_wave_detected_count += 1
376
 
377
+ # 快取 STA/LTA 結果(spec: 避免滑桿更新時重複計算,提升反應速度)
378
+ if event_name:
379
+ sta_lta_cache[event_name][station_code] = {
380
+ "p_arrival_time": p_arrival_time,
381
+ "cft": cft
382
+ }
383
+
384
  except Exception as e:
385
  logger.warning(f"測站 {station_code} 資訊查詢失敗: {e}")
386
  p_wave_failed_count += 1
 
451
  end_idx = int(end_time * sampling_rate)
452
  actual_samples = end_idx - start_idx
453
 
454
+ logger.info(
455
+ f"波形提取範圍:[{start_idx/sampling_rate:.2f}s, {end_idx/sampling_rate:.2f}s] "
456
+ f"= {actual_samples} samples (first_pick={first_pick:.2f}s, duration={duration}s)"
457
+ )
458
+
459
  # 檢查是否需要零填充:長度不足 30 秒時尾段以 0 遮罩補齊
460
  needs_padding = duration < min_duration
461
  if needs_padding:
 
494
  # 檢查 Z 分量(必須存在)
495
  if len(z_trace) > 0:
496
  z_data = z_trace[0].data[start_idx:end_idx]
497
+ logger.debug(f"測站 {station_code}: Z 分量切片長度 = {len(z_data)} samples")
498
  else:
499
  continue
500
 
 
576
 
577
  Parameters:
578
  - st: ObsPy Stream object
579
+ - selected_stations: 選定的測站列表(包含快取的 p_arrival_time,避免重複計算 STA/LTA)
580
  - first_pick: 首次到達時間(秒)
581
  - duration: 時間長度(秒)
582
+
583
+ Note: P 波到時資訊來自快取,不會重新計算 STA/LTA(提升反應速度)
584
  """
585
  # 計算結束時間
586
  end_time = first_pick + duration
587
 
588
+ logger.debug(f"繪製波形圖(使用快取的 P 波到時資訊,共 {len(selected_stations)} 個測站)")
589
+
590
  # 創建 Plotly figure
591
  fig = go.Figure()
592
 
 
991
  st = read(mseed_file)
992
  logger.info(f"載入了 {len(st)} 個 trace")
993
 
994
+ # 選擇距離震央最近的 25 個測站(啟用 STA/LTA 快取)
995
  logger.info(f"選擇距離震央 ({epicenter_lat}, {epicenter_lon}) 最近的測站...")
996
  selected_stations = select_nearest_stations(
997
+ st, epicenter_lat, epicenter_lon, n_stations=25, event_name=event_name
998
  )
999
 
1000
  if len(selected_stations) == 0:
1001
  logger.error("找不到有效的測站資料")
1002
  return None, None
1003
 
1004
+ logger.info(f"[步驟 1] 完成 - mseed 已載入,測站已選擇,STA/LTA 結果已快取({len(selected_stations)} 個測站)")
1005
  return st, selected_stations
1006
 
1007
  except Exception as e:
 
1027
 
1028
  first_pick = earthquake_metadata[event_name]["first_pick"]
1029
 
1030
+ logger.info(f"[步驟 2] 提取波形資料(P 波後 {duration} 秒,使用快取的測站與 STA/LTA 資訊)...")
1031
 
1032
  # 提取波形資料
1033
  (waveforms, station_info_list, valid_stations,
changelog.md CHANGED
@@ -5,6 +5,12 @@
5
  ## [Unreleased]
6
 
7
  ### Added
 
 
 
 
 
 
8
  - **P 波自動偵測功能(STA/LTA)**
9
  - 新增 `detect_p_wave_sta_lta()` 函數,使用 STA/LTA (Short-Term Average / Long-Term Average) 演算法自動偵測 P 波到時。
10
  - 只有成功偵測到 P 波的測站才會被納入測站選擇與模型預測。
 
5
  ## [Unreleased]
6
 
7
  ### Added
8
+ - **STA/LTA 計算結果快取**
9
+ - 新增全域快取 `sta_lta_cache`,在選擇事件時儲存所有測站的 STA/LTA 計算結果(P 波到時與 characteristic function)。
10
+ - 當使用者調整時間滑桿時,直接使用快取的 P 波到時資訊,避免重複計算 STA/LTA,大幅提升 UI 反應速度。
11
+ - 快取結構:`{event_name: {station_code: {"p_arrival_time": float, "cft": array}}}`
12
+ - P 波到時資訊已存於 `selected_stations` 並透過 `gr.State` 傳遞,無需每次從原始波形重新計算。
13
+
14
  - **P 波自動偵測功能(STA/LTA)**
15
  - 新增 `detect_p_wave_sta_lta()` 函數,使用 STA/LTA (Short-Term Average / Long-Term Average) 演算法自動偵測 P 波到時。
16
  - 只有成功偵測到 P 波的測站才會被納入測站選擇與模型預測。