CoolFace
Apppublic

Kiuyha/Audio-Separation-Inspector

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
app.py266 linesDownload Raw Back to root
1import streamlit as st2import os3import torch4import numpy as np5import pandas as pd6import plotly.express as px7import scipy.io.wavfile8import io9 10BASE_PATH = 'Models'11 12st.set_page_config(layout="wide", page_title="Audio Source Separation Inspector")13 14def process_audio(file_path, gain_factor):15    try:16        # 1. FIX: Check if file is actually a Git LFS pointer (text file)17        with open(file_path, 'rb') as f:18            header = f.read(50)19            if header.startswith(b'version https://git-lfs'):20                st.error(f"❌ **LFS Error:** `{os.path.basename(file_path)}` is a Git LFS pointer, not a WAV file. Run `git lfs pull` in your terminal.")21                return None22 23        sample_rate, data = scipy.io.wavfile.read(file_path)24        25        if data.dtype == np.int16:26            data = data.astype(np.float32) / 32768.027        elif data.dtype == np.int32:28            data = data.astype(np.float32) / 2147483648.029        30        data = data * gain_factor31        32        data = np.clip(data, -1.0, 1.0)33        34        data = (data * 32767).astype(np.int16)35        36        virtual_file = io.BytesIO()37        scipy.io.wavfile.write(virtual_file, sample_rate, data)38        return virtual_file39    except Exception as e:40        st.error(f"Error processing audio: {e}")41        return file_path42 43def get_subdirs(path):44    if not os.path.exists(path):45        return []46    return [d for d in os.listdir(path) if os.path.isdir(os.path.join(path, d))]47 48def load_spectrogram_interactive(pt_path, title="Spectrogram"):49    try:50        # 2. FIX: Added weights_only=False to fix PyTorch 2.6+ error51        spec_tensor = torch.load(pt_path, map_location='cpu', weights_only=False)52 53        if spec_tensor.dim() == 4: 54            spec_tensor = spec_tensor[0]55        if spec_tensor.dim() == 3: 56            spec_data = spec_tensor.mean(dim=0).numpy()57        else: 58            spec_data = spec_tensor.numpy()59 60        if spec_data.min() >= 0:61            spec_data = np.log1p(spec_data)62 63        fig = px.imshow(64            spec_data,65            origin='lower',66            aspect='auto',67            color_continuous_scale='Viridis',68            labels=dict(x="Time Frame", y="Frequency Bin", color="Log Magnitude"),69            title=title70        )71        fig.update_layout(margin=dict(l=0, r=0, t=30, b=0), height=300)72        return fig73    except Exception as e:74        st.error(f"Error loading spectrogram: {e}")75        return None76 77def load_feature_map_interactive(pt_path):78    try:79        # 3. FIX: Added weights_only=False here as well80        feat_tensor = torch.load(pt_path, map_location='cpu', weights_only=False)81 82        if feat_tensor.dim() == 4:83            feat_tensor = feat_tensor[0]84 85        mean_activation = feat_tensor.mean(dim=0).numpy()86 87        fig = px.imshow(88            mean_activation,89            origin='lower',90            aspect='auto',91            color_continuous_scale='Viridis',92            labels=dict(x="Time", y="Freq/Feature", color="Activation"),93            title=f"Mean Activation (Shape: {list(feat_tensor.shape)})"94        )95        fig.update_layout(margin=dict(l=0, r=0, t=40, b=0))96        return fig97    except Exception as e:98        return None99 100st.title("🎵 Audio Source Separation Inspector")101 102st.markdown("""103### Model Interpretation Guide104This tool helps you evaluate how well the model separates audio sources.105* **Audio Quality:** Listen for "artifacts" (robotic sounds or clicking) in the Prediction compared to the Target.106* **Spectrogram Clarity:** In the visuals below, distinct horizontal lines represent clear tones. Vertical smear usually indicates percussion or noise. 107* **Error Analysis:** If the Prediction looks "blurry" compared to the Target, the model is losing high-frequency details.108""")109 110if not os.path.exists(BASE_PATH):111    st.error(f"Models directory not found at {BASE_PATH}. Please ensure your data was uploaded correctly.")112    st.stop()113 114models = get_subdirs(BASE_PATH)115selected_model = st.sidebar.selectbox("Select Model", models)116 117st.sidebar.markdown("### Audio Settings")118volume_boost = st.sidebar.slider(119    "Volume Boost (Gain)", 120    min_value=1.0, 121    max_value=20.0, 122    value=1.0, 123    step=0.5,124    help="Digitally increases the amplitude of the audio signal."125)126 127if selected_model:128    model_path = os.path.join(BASE_PATH, selected_model)129    artifacts_path = os.path.join(model_path, "test_artifacts")130 131    if os.path.exists(artifacts_path):132        samples = get_subdirs(artifacts_path)133        samples.sort(key=lambda x: int(x.split('_')[-1]) if '_' in x else 0)134 135        selected_sample = st.sidebar.selectbox("Select Sample ID", samples)136 137        if selected_sample:138            sample_path = os.path.join(artifacts_path, selected_sample)139            audio_dir = os.path.join(sample_path, "audio")140            specs_dir = os.path.join(sample_path, "specs")141            feats_dir = os.path.join(sample_path, "feats")142 143            all_files = os.listdir(audio_dir)144            target_files = [f for f in all_files if f.startswith("target_") and f.endswith(".wav")]145            classes = [f.replace("target_", "").replace(".wav", "") for f in target_files]146 147            selected_class = st.sidebar.selectbox("Focus Class", classes)148 149            tab1, tab2, tab3 = st.tabs(["🎧 Audio & Spectrograms", "🧠 Internal Activations", "📊 Model Metadata"])150 151            with tab1:152                st.header(f"Sample {selected_sample} | Focus: {selected_class.capitalize()}")153                154                st.subheader("1. Mixture (Input)")155                st.markdown("The raw input containing all sound sources mixed together.")156                mix_audio = os.path.join(audio_dir, "mixture.wav")157                mix_spec = os.path.join(specs_dir, "mixture.pt")158 159                c1, c2 = st.columns([1, 3])160                with c1:161                    if os.path.exists(mix_audio):162                        st.markdown("**Audio:**")163                        processed_mix = process_audio(mix_audio, volume_boost)164                        if processed_mix:165                            st.audio(processed_mix, format='audio/wav')166                with c2:167                    if os.path.exists(mix_spec):168                        fig = load_spectrogram_interactive(mix_spec, title="Mixture Mel-Spectrogram")169                        if fig: st.plotly_chart(fig, width='stretch')170 171                st.divider()172 173                st.subheader(f"2. Target: {selected_class}")174                st.markdown(f"**Interpretation:** This is the 'Ground Truth'. Look at the spectrogram structure here—this is the ideal output.")175                tgt_audio = os.path.join(audio_dir, f"target_{selected_class}.wav")176                tgt_spec = os.path.join(specs_dir, f"target_{selected_class}.pt")177 178                c1, c2 = st.columns([1, 3])179                with c1:180                    if os.path.exists(tgt_audio):181                        st.markdown("**Audio:**")182                        processed_tgt = process_audio(tgt_audio, volume_boost)183                        if processed_tgt:184                            st.audio(processed_tgt, format='audio/wav')185                with c2:186                    if os.path.exists(tgt_spec):187                        fig = load_spectrogram_interactive(tgt_spec, title=f"Target Mel-Spectrogram ({selected_class})")188                        if fig: st.plotly_chart(fig, width='stretch')189 190                st.divider()191 192                st.subheader(f"3. Prediction: {selected_class}")193                st.markdown(f"**Interpretation:** Compare this to the Target above. If you see 'fuzziness' in the dark areas, the model is not silencing background noise correctly.")194                pred_audio = os.path.join(audio_dir, f"pred_{selected_class}.wav")195                pred_spec = os.path.join(specs_dir, f"pred_{selected_class}.pt")196 197                c1, c2 = st.columns([1, 3])198                with c1:199                    if os.path.exists(pred_audio):200                        st.markdown("**Audio:**")201                        processed_pred = process_audio(pred_audio, volume_boost)202                        if processed_pred:203                            st.audio(processed_pred, format='audio/wav')204                with c2:205                    if os.path.exists(pred_spec):206                        fig = load_spectrogram_interactive(pred_spec, title=f"Predicted Mel-Spectrogram ({selected_class})")207                        if fig: st.plotly_chart(fig, width='stretch')208 209            with tab2:210                st.header("Internal Feature Maps")211                212                st.markdown("These heatmaps visualize the neural network's internal state. Bright spots indicate features the model considers important for separation.")213 214                if os.path.exists(feats_dir):215                    feat_files = sorted(os.listdir(feats_dir))216 217                    if feat_files:218                        selected_layer = st.selectbox("Select Probed Layer", feat_files)219                        if selected_layer:220                            st.write(f"Layer: **{selected_layer.replace('.pt', '')}**")221                            fig = load_feature_map_interactive(os.path.join(feats_dir, selected_layer))222                            if fig:223                                st.plotly_chart(fig, width='stretch')224                    else:225                        st.warning("No feature maps found for this sample.")226                else:227                    st.error("Features directory not found.")228 229            with tab3:230                st.header("Training and Testing Logs")231                232                st.markdown("Use these graphs to check for **Overfitting**. If Training Loss decreases but Test Metrics stagnate or drop, the model is memorizing data rather than learning general features.")233 234                c1, c2 = st.columns(2)235                with c1:236                    results_csv = os.path.join(model_path, "test_results.csv")237                    if os.path.exists(results_csv):238                        st.subheader("Test Metrics")239                        df = pd.read_csv(results_csv)240                        x_axis = 'Batch_Index' if 'Batch_Index' in df.columns else df.index241                        numeric_cols = df.select_dtypes(include=np.number).columns242                        fig = px.line(df, title="Test Metrics", x=x_axis, y=numeric_cols)243                        st.plotly_chart(fig, width='stretch')244                        st.dataframe(df, width='stretch')245                    else:246                        st.info("No `test_results.csv` found.")247 248                with c2:249                    loss_csv = os.path.join(model_path, "loss.csv")250                    if os.path.exists(loss_csv):251                        st.subheader("Training Loss")252                        try:253                            df_loss = pd.read_csv(loss_csv)254                            x_axis = 'epoch' if 'epoch' in df_loss.columns else df_loss.index255 256                            numeric_cols = df_loss.select_dtypes(include=np.number).columns257                            fig = px.line(df_loss, x=x_axis, y=numeric_cols, title="Loss Curves")258                            st.plotly_chart(fig, width='stretch')259                            st.dataframe(df_loss, width='stretch')260                        except Exception as e:261                            st.write("Could not parse `loss.csv`.", e)262                    else:263                        st.info("No `loss.csv` found.")264 265    else:266        st.warning(f"No 'test_artifacts' folder found in {selected_model}")