{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.10","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":70203,"databundleVersionId":8068726,"sourceType":"competition"}],"dockerImageVersionId":30474,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Use Librosa to generate some Mel Spectrograms!\n### Maybe use these to train an ImageNet...\n\n#### This version generates images directly (not using matplotlib) - much faster!\n* Generating complete data set this time...","metadata":{}},{"cell_type":"code","source":"import os\n\nimport pandas as pd\nimport numpy as np\nimport pywt\nimport matplotlib.pyplot as plt\n\nimport librosa\nimport librosa.display\n\nfrom IPython.display import Audio\n\nfrom PIL import Image, ImageOps\n\nimport matplotlib.cm as cm","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-04-24T01:58:35.645698Z","iopub.execute_input":"2024-04-24T01:58:35.646191Z","iopub.status.idle":"2024-04-24T01:58:35.653292Z","shell.execute_reply.started":"2024-04-24T01:58:35.646159Z","shell.execute_reply":"2024-04-24T01:58:35.651785Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# File paths","metadata":{}},{"cell_type":"code","source":"audio_input_folder = \"/kaggle/input/birdclef-2024/train_audio\"\nimage_output_folder = \"/kaggle/working/train_images\"\nmeta_data = pd.read_csv(\"/kaggle/input/birdclef-2024/eBird_Taxonomy_v2021.csv\")\n\n","metadata":{"execution":{"iopub.status.busy":"2024-04-24T01:58:35.656306Z","iopub.execute_input":"2024-04-24T01:58:35.656906Z","iopub.status.idle":"2024-04-24T01:58:35.734688Z","shell.execute_reply.started":"2024-04-24T01:58:35.656856Z","shell.execute_reply":"2024-04-24T01:58:35.733166Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Settings","metadata":{}},{"cell_type":"code","source":"#generates spectrograms are generated on audio clips this long\n#audio clips shorter than this are ignored\naudio_duration = 15.0\n\n#generates up to this many clips per file (multiples of audio_duration)\n#remainders of audio clip length / audio_duration are ignored\nmax_clips_per_audio_file = 20\n\n#image size (image_size x image_size)\nimage_size = 224\n\n#processes up to this many audio files per species\nmax_audio_input_per_species = 1000\n\n#setting this above 0 will cause processing to start at a folder after the first\n#useful for restarting / continuing in case of running out of time / crash (folders are processed alphabetically)\nstart_at_folder_offset = 0","metadata":{"execution":{"iopub.status.busy":"2024-04-24T01:58:35.737348Z","iopub.execute_input":"2024-04-24T01:58:35.737768Z","iopub.status.idle":"2024-04-24T01:58:35.743898Z","shell.execute_reply.started":"2024-04-24T01:58:35.737734Z","shell.execute_reply":"2024-04-24T01:58:35.742469Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Function to lookup bird species","metadata":{}},{"cell_type":"code","source":"def bird_species_from_folder(path):\n    if path.endswith('/'):\n        path = path[:-1]\n\n    last_folder = os.path.basename(path)\n    primary_com_name = meta_data.loc[meta_data['SPECIES_CODE'] == last_folder, 'PRIMARY_COM_NAME']\n    primary_com_name_str = primary_com_name.iloc[0] if not primary_com_name.empty else ''    \n    return f\"{primary_com_name_str} ({last_folder})\"","metadata":{"execution":{"iopub.status.busy":"2024-04-24T01:58:35.745075Z","iopub.execute_input":"2024-04-24T01:58:35.745374Z","iopub.status.idle":"2024-04-24T01:58:35.757036Z","shell.execute_reply.started":"2024-04-24T01:58:35.745348Z","shell.execute_reply":"2024-04-24T01:58:35.755807Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Denoise","metadata":{}},{"cell_type":"code","source":"def wavelet_filter(y, thresh=0.1, wavelet=\"db4\", level=None):\n    thresh = thresh * np.nanmax(y)\n    coeffs = pywt.wavedec(y, wavelet, level=level, mode=\"per\")\n    coeffs = [pywt.threshold(c, value=thresh, mode=\"soft\") for c in coeffs]\n    reconstructed_signal = pywt.waverec(coeffs, wavelet, mode=\"per\")\n    return reconstructed_signal","metadata":{"execution":{"iopub.status.busy":"2024-04-24T01:58:35.760764Z","iopub.execute_input":"2024-04-24T01:58:35.761355Z","iopub.status.idle":"2024-04-24T01:58:35.774934Z","shell.execute_reply.started":"2024-04-24T01:58:35.761310Z","shell.execute_reply":"2024-04-24T01:58:35.773457Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Save spectrograms to disk (or preview)","metadata":{}},{"cell_type":"code","source":"def spectrograms_for_audio(dirname, filename, preview=False, denoise = False):\n    # Calculate total duration of the audio file\n    audio_path = os.path.join(dirname, filename)\n    raw_audio_data, sr = librosa.load(audio_path, sr=None)\n    audio_data = raw_audio_data\n    if denoise:\n        audio_data = wavelet_filter(raw_audio_data)\n    total_duration = librosa.get_duration(path=audio_path)\n    \n    # Determine how many full segments of audio_duration fit in the audio file\n    num_segments = int(total_duration // audio_duration)\n    if num_segments == 0: return 0\n    if num_segments > max_clips_per_audio_file: num_segments = max_clips_per_audio_file\n    #only generate the middle segment\n    segment = int(num_segments//2)\n    #for segment in range(num_segments):\n\n    offset_samples = int(segment * audio_duration * sr)\n    end_samples = int(offset_samples + audio_duration * sr)\n        \n    segment_data = audio_data[offset_samples:end_samples]\n        \n    S = librosa.feature.melspectrogram(y=segment_data, sr=sr, n_mels=128)\n    S_db = librosa.amplitude_to_db(S, ref=np.max)\n                \n    #convert spectrogram data into directly into image (much faster than matplotlib)\n    normalized_array = (S_db - np.min(S_db)) / (np.max(S_db) - np.min(S_db))\n        \n    #set color mapping \n    spectrogram_image = cm.magma(normalized_array)[:, :, :3]\n    spectrogram_image = (spectrogram_image * 255).astype(np.uint8)\n    spectrogram_image = Image.fromarray(spectrogram_image)\n        \n    #resize and flip (so consistent with original dataset)\n    spectrogram_image = spectrogram_image.resize((image_size, image_size), Image.ANTIALIAS)\n    spectrogram_image = ImageOps.flip(spectrogram_image)\n        \n    if preview == True:\n        display(Audio(data=segment_data, rate=sr))\n        plt.figure(figsize=(10, 4))\n        librosa.display.waveshow(segment_data, sr=sr)\n        plt.title(f'sr={sr}')\n        plt.xlabel('Time (s)')\n        plt.ylabel('Amplitude')\n        plt.show()\n        return spectrogram_image\n        \n    output_folder = os.path.join(image_output_folder, os.path.basename(os.path.normpath(dirname)))\n    if not os.path.exists(output_folder):\n        os.makedirs(output_folder)\n\n    # Naming files with an offset to indicate seconds offset\n    base_filename = filename.replace('.ogg', '')\n    output_filename = os.path.join(output_folder, f\"{base_filename}_{int((segment + 1) * audio_duration):02d}.png\")\n        \n    spectrogram_image.save(output_filename)\n\n    return num_segments","metadata":{"execution":{"iopub.status.busy":"2024-04-24T01:58:35.776966Z","iopub.execute_input":"2024-04-24T01:58:35.777964Z","iopub.status.idle":"2024-04-24T01:58:35.794283Z","shell.execute_reply.started":"2024-04-24T01:58:35.777917Z","shell.execute_reply":"2024-04-24T01:58:35.792478Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# A few demos\n### Note: If you've started generating spectrograms - you won't be able to generate more previews without restarting the notebook","metadata":{}},{"cell_type":"code","source":"spectrograms_for_audio(\"/kaggle/input/birdclef-2024/train_audio/zitcis1/\", \"XC655341.ogg\", preview=True)","metadata":{"execution":{"iopub.status.busy":"2024-04-24T01:58:35.796421Z","iopub.execute_input":"2024-04-24T01:58:35.797034Z","iopub.status.idle":"2024-04-24T01:58:36.467518Z","shell.execute_reply.started":"2024-04-24T01:58:35.796991Z","shell.execute_reply":"2024-04-24T01:58:36.466123Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\nspectrograms_for_audio(\"/kaggle/input/birdclef-2024/train_audio/zitcis1/\", \"XC655341.ogg\", preview=True, denoise = True)\n","metadata":{"execution":{"iopub.status.busy":"2024-04-24T01:58:36.469492Z","iopub.execute_input":"2024-04-24T01:58:36.469957Z","iopub.status.idle":"2024-04-24T01:58:37.090074Z","shell.execute_reply.started":"2024-04-24T01:58:36.469917Z","shell.execute_reply":"2024-04-24T01:58:37.088478Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"spectrograms_for_audio(\"/kaggle/input/birdclef-2024/train_audio/asikoe2/\", \"XC138196.ogg\", preview=True)","metadata":{"execution":{"iopub.status.busy":"2024-04-24T01:58:37.091859Z","iopub.execute_input":"2024-04-24T01:58:37.092281Z","iopub.status.idle":"2024-04-24T01:58:37.617022Z","shell.execute_reply.started":"2024-04-24T01:58:37.092243Z","shell.execute_reply":"2024-04-24T01:58:37.615402Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"spectrograms_for_audio(\"/kaggle/input/birdclef-2024/train_audio/asikoe2/\", \"XC138196.ogg\", preview=True, denoise = True)\n","metadata":{"execution":{"iopub.status.busy":"2024-04-24T01:58:37.620555Z","iopub.execute_input":"2024-04-24T01:58:37.621061Z","iopub.status.idle":"2024-04-24T01:58:38.104252Z","shell.execute_reply.started":"2024-04-24T01:58:37.621021Z","shell.execute_reply":"2024-04-24T01:58:38.102858Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Generate the spectrograms!\n### 'X' is displayed for any file not processed due to being shorter than audio_duration","metadata":{}},{"cell_type":"code","source":"# List everything in the audio input folder\nentries = os.listdir(audio_input_folder)\n\n# Filter to get only directories\nsubdirectories = [entry for entry in entries if os.path.isdir(os.path.join(audio_input_folder, entry))]\n\n# Sort the directories\nsubdirectories.sort()\n\n#to start processing at a certain folder offset\nsubdirectories = subdirectories[start_at_folder_offset:]\n\nfolder_count = len(subdirectories)\nfolders_processed = 0\n\n# Iterate through each sorted directory\ntotal_images = 0\nfor subdir in subdirectories:\n    subdir_path = os.path.join(audio_input_folder, subdir)\n    folders_processed += 1\n    print(\"\\n\", bird_species_from_folder(subdir_path), folders_processed, \"/\", folder_count)\n\n    # List files in the current directory (in any order)\n    filenames = os.listdir(subdir_path)\n    \n    # Process up to max_audio_input_per_species files\n    images_this_species = 0\n    for filename in filenames[:max_audio_input_per_species]:\n        images_generated = spectrograms_for_audio(subdir_path, filename)\n        if images_generated > 0:\n            images_this_species += images_generated\n            print(\".\", end = \"\")\n        else:\n            print(\"X\", end = \"\")\n    print (f\"({images_this_species} images generated)\")\n    total_images += images_this_species\n\nprint (\"\\n\", total_images, \"total images generated\")\n                ","metadata":{"execution":{"iopub.status.busy":"2024-04-24T01:58:38.105860Z","iopub.execute_input":"2024-04-24T01:58:38.106191Z","iopub.status.idle":"2024-04-24T01:58:45.766081Z","shell.execute_reply.started":"2024-04-24T01:58:38.106165Z","shell.execute_reply":"2024-04-24T01:58:45.764165Z"},"trusted":true},"execution_count":null,"outputs":[]}]}