{"metadata":{"kernelspec":{"display_name":".venv","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.12.3"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"sourceType":"competition"},{"sourceId":11001514,"sourceType":"datasetVersion","datasetId":6848569}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"d88b2496-3409-4d70-b3ea-72c7d24211dc","cell_type":"markdown","source":"# Animal Sound Denoising and U-Net Training Pipeline\n\nThis notebook demonstrates how to:\n\n1. **Check for CUDA/GPU availability** and configure TensorFlow to use it if available.\n2. **Load your data:**\n   - The `train_audio` folder (with underscore) contains animal sound recordings. These recordings are organized in subfolders.\n   - The `train_soundscapes` folder contains pure background noise recordings (all .ogg files are in the top-level folder).\n3. **(Optionally) Load CSV metadata** describing the audio clips (useful for later classification tasks).\n4. **Build a combined noise profile** from all the training soundscapes.\n5. **Generate and save spectrograms** before and after noise reduction for demonstration purposes.\n6. **Create paired training data:** Each animal sound file is processed to yield a noisy spectrogram (input) and a denoised spectrogram (target) using a combined noise profile with `noisereduce`.\n7. **Define, train, and save a U-Net model** for spectrogram denoising.\n\nLater, you can integrate the CSV metadata into your classification pipeline once the noise reduction phase is complete.","metadata":{}},{"id":"c6468fa7","cell_type":"code","source":"!pip install tensorflow librosa noisereduce matplotlib opencv-python pandas","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"a13a706f-818b-4a4c-8057-beacacac8e2e","cell_type":"code","source":"# Cell: Check for CUDA/GPU Availability\nimport tensorflow as tf\n\ngpus = tf.config.list_physical_devices('GPU')\nif gpus:\n    print(\"GPU(s) found:\")\n    for gpu in gpus:\n        print(\"  \", gpu)\n    try:\n        # Enable memory growth to avoid allocating all GPU memory at once\n        for gpu in gpus:\n            tf.config.experimental.set_memory_growth(gpu, True)\n        print(\"Memory growth enabled on GPU(s).\")\n    except RuntimeError as e:\n        print(\"Error enabling memory growth:\", e)\nelse:\n    print(\"No GPU found. Using CPU.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"e5da5ca6-3ae4-4cfd-a906-1f9b19d043ab","cell_type":"markdown","source":"## Imports and Helper Functions\n\nThis cell imports necessary libraries and defines helper functions for:\n- Loading audio files\n- Computing spectrograms\n- Saving spectrograms as images and NumPy arrays\n- Building a combined noise profile from training soundscapes\n- Resizing spectrograms\n- Creating (noisy, denoised) training pairs","metadata":{}},{"id":"8116174f-7b28-4496-8ef5-2cf25e64f07f","cell_type":"code","source":"import os\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport librosa\nimport librosa.display\nimport noisereduce as nr\nfrom tensorflow.keras import layers, models\nfrom tensorflow.keras.callbacks import ModelCheckpoint\nimport cv2\n\n# Ensure reproducibility\nnp.random.seed(42)\ntf.random.set_seed(42)\n\ndef load_audio_file(file_path, sr=None):\n    \"\"\"Load an audio file and return the audio time series and sample rate.\"\"\"\n    audio, sr = librosa.load(file_path, sr=sr)\n    return audio, sr\n\ndef compute_spectrogram(audio, sr, n_fft=2048, hop_length=512):\n    \"\"\"Compute a spectrogram (in dB) from an audio signal.\"\"\"\n    S = librosa.stft(audio, n_fft=n_fft, hop_length=hop_length)\n    S_db = librosa.amplitude_to_db(np.abs(S), ref=np.max)\n    return S_db\n\ndef save_spectrogram(S_db, sr, filename_prefix):\n    \"\"\"Save the spectrogram as an image and as a NumPy array.\"\"\"\n    plt.figure(figsize=(10, 4))\n    librosa.display.specshow(S_db, sr=sr, x_axis='time', y_axis='log')\n    plt.colorbar(format='%+2.0f dB')\n    plt.title(f'{filename_prefix} Spectrogram')\n    plt.savefig(f'{filename_prefix}_spectrogram.png')\n    plt.close()\n    \n    np.save(f'{filename_prefix}_spectrogram.npy', S_db)\n\ndef build_noise_profile(noise_dir, sr=None):\n    \"\"\"Load all .ogg noise recordings from a directory and concatenate them into a combined noise profile.\"\"\"\n    noise_files = [os.path.join(noise_dir, f) for f in os.listdir(noise_dir) if f.endswith('.ogg')]\n    noise_profiles = []\n    \n    for nf in noise_files:\n        audio, file_sr = load_audio_file(nf, sr=sr)\n        noise_profiles.append(audio)\n    \n    combined_noise = np.concatenate(noise_profiles)\n    return combined_noise\n\ndef resize_spectrogram(S_db, target_shape=(256, 256)):\n    \"\"\"Resize the spectrogram to a fixed target shape using OpenCV.\"\"\"\n    S_db_resized = cv2.resize(S_db.astype(np.float32), target_shape, interpolation=cv2.INTER_AREA)\n    return S_db_resized\n\ndef create_training_pair(file_path, noise_profile, sr=None, target_shape=(256,256)):\n    \"\"\"Generate a (noisy spectrogram, denoised spectrogram) pair for a given audio file.\"\"\"\n    # Load the animal sound audio\n    audio, sr = load_audio_file(file_path, sr=sr)\n    \n    # Compute and resize the original (noisy) spectrogram\n    S_db_noisy = compute_spectrogram(audio, sr)\n    S_db_noisy = resize_spectrogram(S_db_noisy, target_shape)\n    \n    # Apply noise reduction using the combined noise profile\n    reduced_audio = nr.reduce_noise(audio_clip=audio, noise_clip=noise_profile, verbose=False)\n    S_db_denoised = compute_spectrogram(reduced_audio, sr)\n    S_db_denoised = resize_spectrogram(S_db_denoised, target_shape)\n    \n    # Expand dimensions to add a channel (grayscale image)\n    S_db_noisy = np.expand_dims(S_db_noisy, axis=-1)\n    S_db_denoised = np.expand_dims(S_db_denoised, axis=-1)\n    \n    return S_db_noisy, S_db_denoised\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"a13eff3c-ec94-438a-9e6f-9eb65b99714f","cell_type":"markdown","source":"## Set Paths\n\nDefine the paths for your data folders and CSV metadata file (if applicable):\n\n- **train_audio_dir:** Contains animal sound recordings (with subfolders and .ogg files).\n- **train_soundscapes_dir:** Contains recordings of pure background noise (with .ogg files directly in the folder).\n- **csv_path:** (Optional) Path to a CSV file describing the audio clips (useful later for classification).\n","metadata":{}},{"id":"02fb97bd-901d-4f4f-bd92-7a161b095b82","cell_type":"code","source":"# Set paths (adjust these to your environment)\ntrain_audio_dir = 'birdclef-2025/train_audio' \ntrain_soundscapes_dir = 'birdclef-2025/train_soundscapes'\ncsv_path = 'birdclef-2025/train.csv'  # Optional; use for later classification steps","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"37630374-f312-46e6-80da-d47d94b1eaf9","cell_type":"markdown","source":"## (Optional) Load CSV Metadata\n\nIf you have a CSV file describing your audio clips, you can load it here. This metadata will be useful later for classification or data filtering.\n\nFor now, we load the CSV but do not integrate it into the noise reduction training phase.","metadata":{}},{"id":"6d74eb9c-bbec-4b2f-b68f-4363053d5fb1","cell_type":"code","source":"import pandas as pd\n\nif os.path.exists(csv_path):\n    metadata_df = pd.read_csv(csv_path)\n    print(\"CSV metadata loaded. Number of entries:\", len(metadata_df))\nelse:\n    print(\"CSV file not found. Continuing without metadata.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"e6c8cf04-88c2-4c4b-a66e-14f01bca604b","cell_type":"markdown","source":"## Collect Audio Files from `train_audio`\n\nSince the `train_audio` folder contains subfolders with .ogg files, we use `glob` to recursively find all audio files.","metadata":{}},{"id":"3201f34f-c10f-4513-96b7-816507cb33f2","cell_type":"code","source":"import glob\n\n# Collect all .ogg files from any subfolder within train_audio_dir\ntrain_audio_files = glob.glob(os.path.join(train_audio_dir, '**/*.ogg'), recursive=True)\nprint(f\"Found {len(train_audio_files)} .ogg files in train_audio (including subfolders).\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"291f6dba-97cb-4c40-8d3b-d1fbbe0b5d39","cell_type":"markdown","source":"## Collect Audio Files from `train_soundscapes`\n\nCollect the .ogg files directly from the train_soundscapes folder.","metadata":{}},{"id":"ad31ad50-c698-4f36-9ec7-97aa4a9331e9","cell_type":"code","source":"train_soundscape_files = [\n    os.path.join(train_soundscapes_dir, f)\n    for f in os.listdir(train_soundscapes_dir)\n    if f.endswith('.ogg')\n]\nprint(f\"Found {len(train_soundscape_files)} .ogg files in train_soundscapes.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"f1c69276-344c-4cee-b6d0-67ba891fa9e5","cell_type":"markdown","source":"## Build the Combined Noise Profile\n\nLoad all the noise recordings from the `train_soundscapes` folder and build a combined noise profile.","metadata":{}},{"id":"47889f8b-75c7-4452-a905-14ec93cc1812","cell_type":"code","source":"def build_noise_profile_from_files(file_list, sr=None):\n    noise_profiles = []\n    for nf in file_list:\n        audio, file_sr = load_audio_file(nf, sr=sr)\n        noise_profiles.append(audio)\n    combined_noise = np.concatenate(noise_profiles)\n    return combined_noise\n\nnoise_profile = build_noise_profile_from_files(train_soundscape_files, sr=44100)\nprint(f\"Combined noise profile length: {len(noise_profile)/44100:.2f} seconds\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"a1e11dd8-e473-424f-a620-53fa65f11e2f","cell_type":"markdown","source":"## Demonstration: Process a Sample Animal Sound\n\nFor demonstration, we select one file from the `train_audio` folder, compute its spectrogram, and save the pre-noise reduction version.","metadata":{}},{"id":"8b5011c2-be50-477a-9c44-84a4ba587e29","cell_type":"code","source":"# Pick a sample animal sound from train_audio_files\nif train_audio_files:\n    sample_audio_path = train_audio_files[0]\n    audio, sr = load_audio_file(sample_audio_path, sr=44100)\n    print(f\"Sample audio loaded: {len(audio)/sr:.2f} seconds at {sr} Hz\")\n    S_db_before = compute_spectrogram(audio, sr)\n    save_spectrogram(S_db_before, sr, 'sample_before_noise_reduction')\nelse:\n    print(\"No sample audio found in train_audio.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"dc987b59-44e1-486b-9d22-ff455f94884a","cell_type":"markdown","source":"## Demonstration: Apply Noise Reduction to the Sample Audio\n\nApply noise reduction on the sample animal sound using the combined noise profile and save the post-noise reduction spectrogram.","metadata":{}},{"id":"4edefa7f-4ded-4bc6-af2f-1042582ffc85","cell_type":"code","source":"# Apply noise reduction to the sample audio\nreduced_audio = nr.reduce_noise(audio_clip=audio, noise_clip=noise_profile, verbose=False)\nS_db_after = compute_spectrogram(reduced_audio, sr)\nsave_spectrogram(S_db_after, sr, 'sample_after_noise_reduction')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"45fc13a2-98f1-4ed4-ac01-8743474fbc2b","cell_type":"markdown","source":"## Create a Training Pair from the Sample Audio\n\nGenerate a (noisy spectrogram, denoised spectrogram) pair from the sample animal sound. This demonstrates the process for creating training data.\n\nIn practice, this will be done for every file in the `train_audio` folder.","metadata":{}},{"id":"2b135ae1-4c80-4cfc-9203-d34103ee3171","cell_type":"code","source":"# Create a training pair from the sample audio (for demonstration)\nnoisy_spec, clean_spec = create_training_pair(sample_audio_path, noise_profile, sr=44100)\nprint(\"Noisy spectrogram shape:\", noisy_spec.shape)\nprint(\"Clean spectrogram shape:\", clean_spec.shape)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"efec3055-d4b6-4562-9950-2df4696d88db","cell_type":"markdown","source":"## Define the U-Net Model for Spectrogram Denoising\n\nWe define a simple U-Net model using TensorFlow/Keras that will learn to map noisy spectrograms to their denoised counterparts.","metadata":{}},{"id":"28c860f3-2581-475c-af48-e3549e9876c8","cell_type":"code","source":"def unet_model(input_shape):\n    inputs = tf.keras.Input(input_shape)\n    \n    # Encoder\n    c1 = layers.Conv2D(64, (3, 3), activation='relu', padding='same')(inputs)\n    c1 = layers.Conv2D(64, (3, 3), activation='relu', padding='same')(c1)\n    p1 = layers.MaxPooling2D((2, 2))(c1)\n    \n    c2 = layers.Conv2D(128, (3, 3), activation='relu', padding='same')(p1)\n    c2 = layers.Conv2D(128, (3, 3), activation='relu', padding='same')(c2)\n    p2 = layers.MaxPooling2D((2, 2))(c2)\n    \n    # Bottleneck\n    c3 = layers.Conv2D(256, (3, 3), activation='relu', padding='same')(p2)\n    c3 = layers.Conv2D(256, (3, 3), activation='relu', padding='same')(c3)\n    \n    # Decoder\n    u4 = layers.UpSampling2D((2, 2))(c3)\n    concat4 = layers.Concatenate()([u4, c2])\n    c4 = layers.Conv2D(128, (3, 3), activation='relu', padding='same')(concat4)\n    c4 = layers.Conv2D(128, (3, 3), activation='relu', padding='same')(c4)\n    \n    u5 = layers.UpSampling2D((2, 2))(c4)\n    concat5 = layers.Concatenate()([u5, c1])\n    c5 = layers.Conv2D(64, (3, 3), activation='relu', padding='same')(concat5)\n    c5 = layers.Conv2D(64, (3, 3), activation='relu', padding='same')(c5)\n    \n    outputs = layers.Conv2D(1, (1, 1), activation='linear')(c5)\n    \n    model = models.Model(inputs, outputs)\n    return model\n\ninput_shape = (256, 256, 1)\nmodel = unet_model(input_shape)\nmodel.compile(optimizer='adam', loss='mean_squared_error')\nmodel.summary()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"9655bd89-63aa-4b83-943d-0fc031bb127e","cell_type":"markdown","source":"## Create a TensorFlow Dataset for Training\n\nWe now create a generator that iterates over **all** animal sound files in the `train_audio` folder and yields (noisy, denoised) spectrogram pairs.\n\nThis ensures that all your available data is used to train the noise reduction model.","metadata":{}},{"id":"6c0b1718-34f6-4f4a-9916-1a7516070a2a","cell_type":"code","source":"def data_generator(file_list, noise_profile, sr, target_shape=(256,256)):\n    \"\"\"Generator yielding (noisy_spectrogram, denoised_spectrogram) pairs.\"\"\"\n    for file_path in file_list:\n        try:\n            noisy_spec, clean_spec = create_training_pair(file_path, noise_profile, sr=sr, target_shape=target_shape)\n            yield noisy_spec, clean_spec\n        except Exception as e:\n            print(f\"Error processing {file_path}: {e}\")\n\n# Create a dataset from all .ogg files in train_audio (recursively collected)\nimport tensorflow as tf\n\ndataset = tf.data.Dataset.from_generator(\n    lambda: data_generator(train_audio_files, noise_profile, sr=44100, target_shape=(256,256)),\n    output_signature=(\n        tf.TensorSpec(shape=(256,256,1), dtype=tf.float32),\n        tf.TensorSpec(shape=(256,256,1), dtype=tf.float32)\n    )\n)\nbatch_size = 4\ndataset = dataset.shuffle(buffer_size=50).batch(batch_size)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"e0594246-b17a-4b5e-bfd5-b647231936e7","cell_type":"markdown","source":"## Set Up a Checkpoint Callback to Save the Best Model\n\nThis callback will automatically save the best version of the model during training based on the loss.","metadata":{}},{"id":"7c09f368-97ab-47da-bcea-405ddd3dd7ac","cell_type":"code","source":"checkpoint_path = \"unet_denoiser_best_model.h5\"\ncheckpoint = ModelCheckpoint(checkpoint_path, monitor='loss', verbose=1, save_best_only=True, mode='min')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"8b5059bc-8707-4dce-9b06-af36d56951a5","cell_type":"markdown","source":"## Train the U-Net Model\n\nTrain the U-Net model on the dataset. Adjust the number of epochs as needed. With a large amount of audio, training may take a while so consider testing on a smaller subset first.","metadata":{}},{"id":"cb091347-c228-4a36-bfa0-c297fedef889","cell_type":"code","source":"model.fit(dataset, epochs=5, callbacks=[checkpoint])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"8a36d629-0fa7-42ad-8d06-e3c29d33217d","cell_type":"markdown","source":"## Save the Final Model for Later Inference\n\nAfter training, save the final model so that you can load it later on systems that do not have the resources to train the model.","metadata":{}},{"id":"1239c47b-e77a-4609-8a71-b23d39cb08e3","cell_type":"code","source":"model.save('unet_denoiser_final_model.h5')\nprint(\"Model saved as unet_denoiser_final_model.h5\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}