Spaces:
Running
Running
Commit
·
5c971be
1
Parent(s):
bc5a4e4
feat: implement STA/LTA result caching to enhance UI responsiveness
Browse files- app.py +34 -6
- 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 波的測站才會被納入測站選擇與模型預測。
|