Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files- README.md +19 -8
- app.py +262 -0
- model_output4/rf_model.pkl +3 -0
- requirements.txt +7 -0
README.md
CHANGED
|
@@ -1,14 +1,25 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version: '3.13'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
-
short_description: raspberyshake sistemi deprem sınıflandırıcı
|
| 12 |
---
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Sismik Siniflandirma
|
| 3 |
+
emoji: 🌍
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: blue
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 4.44.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Sismik Sinyal Sınıflandırma
|
| 13 |
+
|
| 14 |
+
Marmara bölgesi RaspberryShake istasyonlarından (RF9F7, R772A, R6080) toplanan
|
| 15 |
+
verilerle eğitilmiş bir RandomForest modeli. MSEED dosyası yükleyin,
|
| 16 |
+
DEPREM veya NOISE olarak sınıflandırsın.
|
| 17 |
+
|
| 18 |
+
## Özellikler
|
| 19 |
+
- 16 sismik özellik (STA/LTA, spektral oranlar, kurtosis, envelope şekli vb.)
|
| 20 |
+
- Amplitüd-bağımsız özellikler kullanır (farklı istasyon sensör kazançlarından etkilenmez)
|
| 21 |
+
- Ayarlanabilir karar eşiği
|
| 22 |
+
|
| 23 |
+
## Kısıtlar
|
| 24 |
+
- Şu an sınırlı veriyle eğitildi (~126 segment), gelişim aşamasında
|
| 25 |
+
- Sadece Marmara bölgesi RaspberryShake verisiyle test edildi
|
app.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Sismik Sınıflandırma - HuggingFace Space (Gradio)
|
| 4 |
+
===================================================
|
| 5 |
+
RandomForest modeliyle MSEED dosyalarını DEPREM / NOISE olarak sınıflandırır.
|
| 6 |
+
|
| 7 |
+
HF Spaces kurulumu:
|
| 8 |
+
1. huggingface.co/new-space -> SDK: Gradio, Hardware: CPU basic (ücretsiz)
|
| 9 |
+
2. Bu dosyayı app.py olarak yükle
|
| 10 |
+
3. requirements.txt'i yükle
|
| 11 |
+
4. rf_model.pkl dosyasını da Space'e yükle (aynı dizine)
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import os
|
| 15 |
+
import json
|
| 16 |
+
import tempfile
|
| 17 |
+
import warnings
|
| 18 |
+
warnings.filterwarnings("ignore")
|
| 19 |
+
|
| 20 |
+
import numpy as np
|
| 21 |
+
import joblib
|
| 22 |
+
import gradio as gr
|
| 23 |
+
from obspy import read
|
| 24 |
+
from obspy.signal.trigger import classic_sta_lta, trigger_onset
|
| 25 |
+
from obspy.signal.filter import envelope as obspy_envelope
|
| 26 |
+
from scipy import signal as scipy_signal
|
| 27 |
+
from scipy.stats import kurtosis, skew
|
| 28 |
+
|
| 29 |
+
import matplotlib
|
| 30 |
+
matplotlib.use("Agg")
|
| 31 |
+
import matplotlib.pyplot as plt
|
| 32 |
+
|
| 33 |
+
# ---------------------------------------------------------------------------
|
| 34 |
+
# Özellik çıkarımı — train_rf2.py ile BİREBİR AYNI
|
| 35 |
+
# ---------------------------------------------------------------------------
|
| 36 |
+
BANDPASS = (0.5, 15.0)
|
| 37 |
+
STA_S = 2.0
|
| 38 |
+
LTA_S = 20.0
|
| 39 |
+
MIN_NPTS = 200
|
| 40 |
+
MODEL_PATH = "rf_model.pkl"
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def extract_features(tr) -> dict:
|
| 44 |
+
data = tr.data.astype(np.float64)
|
| 45 |
+
n = len(data)
|
| 46 |
+
df = tr.stats.sampling_rate
|
| 47 |
+
if n < MIN_NPTS:
|
| 48 |
+
return None
|
| 49 |
+
|
| 50 |
+
data -= np.mean(data)
|
| 51 |
+
sos = scipy_signal.butter(4, BANDPASS, btype="bandpass", fs=df, output="sos")
|
| 52 |
+
fdata = scipy_signal.sosfiltfilt(sos, data)
|
| 53 |
+
|
| 54 |
+
rms = np.sqrt(np.mean(fdata**2))
|
| 55 |
+
if rms < 1e-10:
|
| 56 |
+
return None
|
| 57 |
+
|
| 58 |
+
max_amp = np.max(np.abs(fdata))
|
| 59 |
+
peak_rms = max_amp / rms
|
| 60 |
+
zcr = ((fdata[:-1] * fdata[1:]) < 0).sum() / n
|
| 61 |
+
|
| 62 |
+
env = obspy_envelope(fdata)
|
| 63 |
+
env_smooth = scipy_signal.savgol_filter(env, min(51, n // 4 * 2 + 1), 3)
|
| 64 |
+
env_smoothness = np.std(np.diff(env_smooth)) / (np.mean(env_smooth) + 1e-10)
|
| 65 |
+
|
| 66 |
+
cum_e = np.cumsum(env ** 2)
|
| 67 |
+
idx90 = np.searchsorted(cum_e, 0.9 * cum_e[-1])
|
| 68 |
+
dur90 = idx90 / df
|
| 69 |
+
|
| 70 |
+
kurt_val = kurtosis(fdata)
|
| 71 |
+
skew_val = skew(fdata)
|
| 72 |
+
|
| 73 |
+
try:
|
| 74 |
+
cft = classic_sta_lta(fdata, int(STA_S * df), int(LTA_S * df))
|
| 75 |
+
sta_lta_max = np.max(cft)
|
| 76 |
+
sta_lta_mean = np.mean(cft)
|
| 77 |
+
triggers = trigger_onset(cft, 4.0, 1.5)
|
| 78 |
+
num_triggers = len(triggers)
|
| 79 |
+
except Exception:
|
| 80 |
+
cft = np.zeros_like(fdata)
|
| 81 |
+
sta_lta_max, sta_lta_mean, num_triggers = 0.0, 0.0, 0
|
| 82 |
+
|
| 83 |
+
nperseg = min(256, n // 2)
|
| 84 |
+
freqs, psd = scipy_signal.welch(fdata, df, nperseg=nperseg)
|
| 85 |
+
total_power = np.sum(psd) + 1e-30
|
| 86 |
+
|
| 87 |
+
def band_ratio(f_low, f_high):
|
| 88 |
+
mask = (freqs >= f_low) & (freqs < f_high)
|
| 89 |
+
return np.sum(psd[mask]) / total_power
|
| 90 |
+
|
| 91 |
+
low_ratio = band_ratio(0.5, 2.0)
|
| 92 |
+
mid1_ratio = band_ratio(2.0, 5.0)
|
| 93 |
+
mid2_ratio = band_ratio(5.0, 10.0)
|
| 94 |
+
high_ratio = band_ratio(10.0, 25.0)
|
| 95 |
+
|
| 96 |
+
dom_freq_idx = np.argmax(psd[freqs <= 25])
|
| 97 |
+
dom_freq = freqs[dom_freq_idx]
|
| 98 |
+
|
| 99 |
+
psd_norm = psd / total_power
|
| 100 |
+
spec_entropy = -np.sum(psd_norm * np.log(psd_norm + 1e-30))
|
| 101 |
+
spec_mean_freq = np.sum(freqs * psd_norm)
|
| 102 |
+
spec_bandwidth = np.sqrt(np.sum(((freqs - spec_mean_freq) ** 2) * psd_norm))
|
| 103 |
+
|
| 104 |
+
is_rf9f7 = 1 if "RF9F7" in (tr.stats.station or "").upper() else 0
|
| 105 |
+
|
| 106 |
+
feats = {
|
| 107 |
+
"peak_rms": peak_rms,
|
| 108 |
+
"zcr": zcr,
|
| 109 |
+
"kurt": kurt_val,
|
| 110 |
+
"skewness": skew_val,
|
| 111 |
+
"env_smoothness": env_smoothness,
|
| 112 |
+
"dur90_s": dur90,
|
| 113 |
+
"sta_lta_max": sta_lta_max,
|
| 114 |
+
"sta_lta_mean": sta_lta_mean,
|
| 115 |
+
"num_triggers": num_triggers,
|
| 116 |
+
"dom_freq": dom_freq,
|
| 117 |
+
"low_ratio": low_ratio,
|
| 118 |
+
"mid1_ratio": mid1_ratio,
|
| 119 |
+
"mid2_ratio": mid2_ratio,
|
| 120 |
+
"high_ratio": high_ratio,
|
| 121 |
+
"spec_entropy": spec_entropy,
|
| 122 |
+
"spec_bandwidth": spec_bandwidth,
|
| 123 |
+
"is_rf9f7": is_rf9f7,
|
| 124 |
+
}
|
| 125 |
+
# Grafik için ek veriler (feature dict'ine karışmaması için ayrı döndürülür)
|
| 126 |
+
return feats, fdata, cft, df
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
# ---------------------------------------------------------------------------
|
| 130 |
+
# Model yükleme (bir kere, global)
|
| 131 |
+
# ---------------------------------------------------------------------------
|
| 132 |
+
_bundle = None
|
| 133 |
+
|
| 134 |
+
def get_model():
|
| 135 |
+
global _bundle
|
| 136 |
+
if _bundle is None:
|
| 137 |
+
_bundle = joblib.load(MODEL_PATH)
|
| 138 |
+
return _bundle
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def make_plot(fdata, cft, df, label, deprem_proba):
|
| 142 |
+
t = np.arange(len(fdata)) / df
|
| 143 |
+
fig, axes = plt.subplots(2, 1, figsize=(9, 5), sharex=True)
|
| 144 |
+
|
| 145 |
+
color = "#d64545" if label == "DEPREM" else "#3b6fd6"
|
| 146 |
+
axes[0].plot(t, fdata, color=color, linewidth=0.6)
|
| 147 |
+
axes[0].set_ylabel("Genlik (filtrelenmiş)")
|
| 148 |
+
axes[0].set_title(f"Tahmin: {label} (DEPREM olasılığı: %{deprem_proba*100:.1f})")
|
| 149 |
+
|
| 150 |
+
axes[1].plot(t, cft, color="purple", linewidth=0.8)
|
| 151 |
+
axes[1].axhline(4.0, color="black", linestyle=":", linewidth=1, label="Tetik eşiği")
|
| 152 |
+
axes[1].set_ylabel("STA/LTA")
|
| 153 |
+
axes[1].set_xlabel("Zaman (s)")
|
| 154 |
+
axes[1].legend(loc="upper right", fontsize=8)
|
| 155 |
+
|
| 156 |
+
plt.tight_layout()
|
| 157 |
+
return fig
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def predict_mseed(file_obj, threshold):
|
| 161 |
+
if file_obj is None:
|
| 162 |
+
return "Lütfen bir MSEED dosyası yükleyin.", None, None
|
| 163 |
+
|
| 164 |
+
try:
|
| 165 |
+
st = read(file_obj.name)
|
| 166 |
+
except Exception as e:
|
| 167 |
+
return f"Dosya okunamadı: {e}", None, None
|
| 168 |
+
|
| 169 |
+
tr = None
|
| 170 |
+
for cha in ["EHZ", "HHZ", "BHZ", "EHN"]:
|
| 171 |
+
sel = st.select(channel=cha)
|
| 172 |
+
if len(sel) > 0:
|
| 173 |
+
tr = sel[0]
|
| 174 |
+
break
|
| 175 |
+
if tr is None:
|
| 176 |
+
tr = st[0]
|
| 177 |
+
|
| 178 |
+
result = extract_features(tr)
|
| 179 |
+
if result is None:
|
| 180 |
+
return f"Özellik çıkarılamadı (dosya çok kısa, min {MIN_NPTS} örnek gerekli).", None, None
|
| 181 |
+
|
| 182 |
+
feats, fdata, cft, df = result
|
| 183 |
+
|
| 184 |
+
bundle = get_model()
|
| 185 |
+
model, le, feature_cols = bundle["model"], bundle["le"], bundle["meta"]["feature_cols"]
|
| 186 |
+
|
| 187 |
+
X = np.array([[feats.get(c, 0.0) for c in feature_cols]])
|
| 188 |
+
X = np.where(~np.isfinite(X), 0.0, X)
|
| 189 |
+
proba = model.predict_proba(X)[0]
|
| 190 |
+
|
| 191 |
+
deprem_idx = list(le.classes_).index("DEPREM")
|
| 192 |
+
deprem_p = float(proba[deprem_idx])
|
| 193 |
+
label = "DEPREM" if deprem_p >= threshold else "NOISE"
|
| 194 |
+
|
| 195 |
+
emoji = "🔴 DEPREM" if label == "DEPREM" else "🔵 NOISE"
|
| 196 |
+
summary = (
|
| 197 |
+
f"## {emoji}\n\n"
|
| 198 |
+
f"**DEPREM olasılığı:** %{deprem_p*100:.1f}\n\n"
|
| 199 |
+
f"**NOISE olasılığı:** %{(1-deprem_p)*100:.1f}\n\n"
|
| 200 |
+
f"**Kanal:** {tr.stats.network}.{tr.stats.station}.{tr.stats.location}.{tr.stats.channel}\n\n"
|
| 201 |
+
f"**Başlangıç:** {tr.stats.starttime}\n\n"
|
| 202 |
+
f"**Süre:** {tr.stats.npts/tr.stats.sampling_rate:.1f} sn"
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
feat_table = "| Özellik | Değer |\n|---|---|\n"
|
| 206 |
+
for k, v in feats.items():
|
| 207 |
+
feat_table += f"| {k} | {v:.4f} |\n"
|
| 208 |
+
|
| 209 |
+
fig = make_plot(fdata, cft, df, label, deprem_p)
|
| 210 |
+
|
| 211 |
+
return summary, feat_table, fig
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
# ---------------------------------------------------------------------------
|
| 215 |
+
# Gradio arayüzü
|
| 216 |
+
# ---------------------------------------------------------------------------
|
| 217 |
+
|
| 218 |
+
with gr.Blocks(title="Sismik Sınıflandırma") as demo:
|
| 219 |
+
gr.Markdown(
|
| 220 |
+
"# 🌍 Sismik Sinyal Sınıflandırma (Marmara / RaspberryShake)\n"
|
| 221 |
+
"MSEED dosyası yükleyin, RandomForest modeli DEPREM veya NOISE olarak sınıflandırsın.\n\n"
|
| 222 |
+
"*Not: Model şu an sınırlı (Marmara bölgesi, RaspberryShake istasyonları) veriyle eğitildi, "
|
| 223 |
+
"gelişim aşamasındadır.*"
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
with gr.Row():
|
| 227 |
+
with gr.Column(scale=1):
|
| 228 |
+
file_input = gr.File(label="MSEED Dosyası (.mseed)", file_types=[".mseed", ".miniseed", ".seed"])
|
| 229 |
+
threshold_slider = gr.Slider(
|
| 230 |
+
minimum=0.1, maximum=0.9, value=0.5, step=0.05,
|
| 231 |
+
label="DEPREM karar eşiği",
|
| 232 |
+
info="Düşürürsen daha hassas (recall↑), yükseltirsen daha seçici (precision↑)"
|
| 233 |
+
)
|
| 234 |
+
submit_btn = gr.Button("Analiz Et", variant="primary")
|
| 235 |
+
|
| 236 |
+
with gr.Column(scale=1):
|
| 237 |
+
result_md = gr.Markdown(label="Sonuç")
|
| 238 |
+
|
| 239 |
+
with gr.Row():
|
| 240 |
+
plot_output = gr.Plot(label="Dalga Formu + STA/LTA")
|
| 241 |
+
|
| 242 |
+
with gr.Accordion("Detaylı özellik değerleri", open=False):
|
| 243 |
+
feat_output = gr.Markdown()
|
| 244 |
+
|
| 245 |
+
submit_btn.click(
|
| 246 |
+
fn=predict_mseed,
|
| 247 |
+
inputs=[file_input, threshold_slider],
|
| 248 |
+
outputs=[result_md, feat_output, plot_output],
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
gr.Markdown(
|
| 252 |
+
"---\n"
|
| 253 |
+
"**API kullanımı:** Bu Space'e programatik erişim için `gradio_client` kütüphanesini kullanabilirsiniz:\n"
|
| 254 |
+
"```python\n"
|
| 255 |
+
"from gradio_client import Client, file\n"
|
| 256 |
+
"client = Client(\"KULLANICI_ADI/SPACE_ADI\")\n"
|
| 257 |
+
"result = client.predict(file(\"ornek.mseed\"), 0.5, api_name=\"/predict\")\n"
|
| 258 |
+
"```"
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
if __name__ == "__main__":
|
| 262 |
+
demo.launch()
|
model_output4/rf_model.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6bfd4d1ffa2488e81175dff19c68e094ce1bf71407008f486114b9f91459cda5
|
| 3 |
+
size 521865
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.0.0
|
| 2 |
+
obspy>=1.4.0
|
| 3 |
+
scikit-learn>=1.3.0
|
| 4 |
+
numpy>=1.24.0
|
| 5 |
+
scipy>=1.10.0
|
| 6 |
+
matplotlib>=3.7.0
|
| 7 |
+
joblib>=1.3.0
|