MIF_E31231708/cv_app/app.py

268 lines
8.1 KiB
Python

from pathlib import Path
import librosa
import numpy as np
import pandas as pd
import streamlit as st
from audio_utils_app import (
SUPPORTED_AUDIO_EXTENSIONS,
cleanup_temp_files,
convert_audio_to_wav,
save_uploaded_file_to_temp,
)
from predict_app import (
CONFIDENCE_THRESHOLD,
MODEL_NOT_FOUND_MESSAGE,
MODEL_PATH,
get_model_info,
predict_audio,
)
st.set_page_config(
page_title="ConfiVoice",
page_icon="CV",
layout="wide",
)
SUPPORTED_UPLOAD_TYPES = [
extension.replace(".", "") for extension in sorted(SUPPORTED_AUDIO_EXTENSIONS)
]
def load_audio_for_plot(wav_path):
y, sr = librosa.load(wav_path, sr=22050, mono=True)
if y.size == 0:
raise ValueError("Audio kosong atau tidak memiliki sinyal suara.")
return y, sr
def render_waveform(wav_path):
y, sr = load_audio_for_plot(wav_path)
max_points = 3000
step = max(1, len(y) // max_points)
y_plot = y[::step]
time_axis = np.arange(len(y_plot)) * step / sr
waveform = pd.DataFrame(
{
"Waktu (detik)": time_axis,
"Amplitudo": y_plot,
}
).set_index("Waktu (detik)")
st.subheader("Waveform")
st.line_chart(waveform, height=260)
def render_spectrogram(wav_path):
y, sr = load_audio_for_plot(wav_path)
spectrogram = librosa.amplitude_to_db(np.abs(librosa.stft(y)), ref=np.max)
min_value = float(np.min(spectrogram))
max_value = float(np.max(spectrogram))
if max_value > min_value:
spectrogram_image = (spectrogram - min_value) / (max_value - min_value)
else:
spectrogram_image = np.zeros_like(spectrogram)
st.subheader("Spectrogram")
st.image(
np.flipud(spectrogram_image),
caption="Frekuensi rendah di bawah, frekuensi tinggi di atas.",
use_container_width=True,
clamp=True,
)
def render_audio_uploader(key):
return st.file_uploader(
"Upload audio",
type=SUPPORTED_UPLOAD_TYPES,
key=key,
)
def prepare_uploaded_audio(uploaded_file):
temp_input_path = save_uploaded_file_to_temp(uploaded_file)
temp_wav_path = convert_audio_to_wav(temp_input_path)
return temp_input_path, temp_wav_path
def render_prediction_result(result):
if result.get("is_valid_audio") is False:
st.subheader("Audio Tidak Valid")
st.error(result.get("error_message") or result.get("explanation") or "Audio tidak valid.")
with st.expander("Kualitas audio"):
st.json(result.get("audio_quality", {}))
return
probability_pd = result["probabilities"]["PD"]
probability_tpd = result["probabilities"]["TPD"]
confidence = result["confidence"]
st.subheader("Hasil Prediksi")
metric_cols = st.columns(3)
metric_cols[0].metric("Label", result["label"])
metric_cols[1].metric("Keterangan", result["description"])
metric_cols[2].metric("Confidence", f"{confidence * 100:.2f}%")
st.progress(probability_pd, text=f"Probabilitas PD: {probability_pd * 100:.2f}%")
st.progress(probability_tpd, text=f"Probabilitas TPD: {probability_tpd * 100:.2f}%")
if confidence < CONFIDENCE_THRESHOLD:
st.warning("Model belum yakin. Confidence di bawah 60%, pertimbangkan untuk merekam ulang audio.")
else:
st.success(f"Hasil utama: {result['label']} - {result['description']}")
if result.get("explanation"):
st.info(result["explanation"])
with st.expander("Kualitas audio dan indikator suara"):
st.write("Kualitas audio")
st.json(result.get("audio_quality", {}))
st.write("Indikator suara")
st.json(result.get("voice_indicators", {}))
with st.expander("Output dictionary"):
st.json(result)
def render_upload_preview(uploaded_file, show_visuals=True):
temp_input_path = None
temp_wav_path = None
try:
temp_input_path, temp_wav_path = prepare_uploaded_audio(uploaded_file)
st.audio(uploaded_file.getvalue(), format=uploaded_file.type or "audio/wav")
if show_visuals:
waveform_col, spectrogram_col = st.columns(2)
with waveform_col:
render_waveform(temp_wav_path)
with spectrogram_col:
render_spectrogram(temp_wav_path)
return temp_input_path
except Exception as error:
st.error(f"Gagal memproses audio: {error}")
return None
finally:
cleanup_temp_files(temp_wav_path)
def show_home():
st.title("ConfiVoice")
st.write("Aplikasi Streamlit untuk memprediksi suara PD atau TPD dari model machine learning.")
st.info(
"Folder aplikasi ini hanya membaca model dari `../ml/models/` dan fitur dari `../ml/features.py`."
)
st.write("Menu yang tersedia:")
st.write("- Beranda")
st.write("- Prediksi Suara")
st.write("- Visualisasi Audio")
st.write("- Informasi Model")
st.write("- Tentang Sistem")
def show_prediction():
st.title("Prediksi Suara")
st.write("Upload audio dalam format WAV, MP3, M4A, OGG, FLAC, WEBM, atau AAC.")
uploaded_file = render_audio_uploader("prediction_upload")
if uploaded_file is None:
return
temp_input_path = None
temp_wav_path = None
try:
temp_input_path, temp_wav_path = prepare_uploaded_audio(uploaded_file)
st.audio(uploaded_file.getvalue(), format=uploaded_file.type or "audio/wav")
waveform_col, spectrogram_col = st.columns(2)
with waveform_col:
render_waveform(temp_wav_path)
with spectrogram_col:
render_spectrogram(temp_wav_path)
if st.button("Prediksi", type="primary"):
with st.spinner("Mengekstraksi fitur dan menjalankan model..."):
result = predict_audio(temp_input_path)
render_prediction_result(result)
except FileNotFoundError as error:
message = MODEL_NOT_FOUND_MESSAGE if str(error) == MODEL_NOT_FOUND_MESSAGE else str(error)
st.error(message)
except Exception as error:
st.error(f"Gagal memproses prediksi: {error}")
finally:
cleanup_temp_files(temp_input_path, temp_wav_path)
def show_visualization():
st.title("Visualisasi Audio")
uploaded_file = render_audio_uploader("visualization_upload")
if uploaded_file is None:
return
temp_input_path = None
temp_wav_path = None
try:
temp_input_path, temp_wav_path = prepare_uploaded_audio(uploaded_file)
st.audio(uploaded_file.getvalue(), format=uploaded_file.type or "audio/wav")
render_waveform(temp_wav_path)
render_spectrogram(temp_wav_path)
except Exception as error:
st.error(f"Gagal membuat visualisasi: {error}")
finally:
cleanup_temp_files(temp_input_path, temp_wav_path)
def show_model_info():
st.title("Informasi Model")
try:
info = get_model_info()
except FileNotFoundError:
st.error(MODEL_NOT_FOUND_MESSAGE)
return
except Exception as error:
st.error(f"Gagal membaca informasi model: {error}")
return
st.write(f"Path model: `{info['model_path']}`")
st.write(f"Tipe model: `{info['model_type']}`")
st.write(f"Class model: `{', '.join(info['classes'])}`")
st.write(f"Jumlah fitur yang diharapkan: `{info['expected_features']}`")
st.write(f"Model utama: `{MODEL_PATH.relative_to(MODEL_PATH.parents[2])}`")
def show_about():
st.title("Tentang Sistem")
st.write(
"ConfiVoice memisahkan aplikasi Streamlit dari proses machine learning. "
"Folder `cv_app/` berisi antarmuka aplikasi, sementara folder `ml/` tetap menjadi tempat dataset, training, fitur audio, dan model."
)
st.write(
"Audio upload dikonversi sementara ke WAV mono 22050 Hz, lalu fitur diekstraksi memakai `../ml/features.py`. "
"Tidak ada audio upload yang disimpan permanen ke `ml/data/`."
)
MENU_HANDLERS = {
"Beranda": show_home,
"Prediksi Suara": show_prediction,
"Visualisasi Audio": show_visualization,
"Informasi Model": show_model_info,
"Tentang Sistem": show_about,
}
selected_menu = st.sidebar.radio("Menu", list(MENU_HANDLERS.keys()))
MENU_HANDLERS[selected_menu]()