File size: 8,921 Bytes
82b8273 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | import os
# import ffmpeg
import numpy as np
import pandas as pd
import json
#####
# Requires the above packages, if not installed: pip install <package name>
#####
def dir_convert_mp32wav(directory, keep_file=False):
'''
Processes a directory, applying file_convert_mp32wav to every mp3 file
Parameters:
- str directory: path to directory
- bool keep_file: whether to delete original mp3 file or not
'''
res = np.array([file_convert_mp32wav(os.path.join(directory, f), keep_file=keep_file) for f in os.listdir(directory)
if os.path.splitext(f)[-1] == '.mp3']).sum(axis=0)
print(f'Directory {directory} processed, {res} conversion and deletions')
def file_convert_mp32wav(input_file, keep_file=False):
'''
Converts a sound file from mp3 to wav using ffmpeg
Parameters:
- str input_file: path to file to convert
- bool keep_file: whether to delete original mp3 file or not
Returns
- tuple (int convert, int delete) indicating if conversion/deletion was performed or not
'''
output_file = '.'.join([os.path.splitext(input_file)[0], 'wav'])
convert = 0
delete = 0
if not os.path.isfile(output_file):
# if output file doesn't already exist
stream = ffmpeg.input(input_file)
stream = ffmpeg.output(stream, output_file)
ffmpeg.run(stream)
convert = 1
if not keep_file:
os.remove(input_file)
delete = 1
return convert, delete
def read_txt_file(file, extra_str_label=''):
'''
Opens a text file and copy content in a pandas df. Audacity outputs that span 2 lines are put back to a single line
Parameters:
- str file: path to txt file
- str extra_str_label: the substring that is added in label files, depending of the birder
Returns
dataframe containing txt file information
'''
df = pd.read_table(file, header=None)
# Separate time and frequency lines (the latter start with "\")
df['line_type'] = (df[0] == '\\').astype(int)
# Assign recording id to each line
df['id'] = [elt for elt in assign_idx(df['line_type'])]
# Suppression of duplicate entries
df.drop_duplicates(['line_type', 'id'], inplace=True)
# From two to one row per recording
df = df.loc[df['line_type'] == 0].merge(df.loc[df['line_type'] == 1], on='id').dropna().rename(columns={
'0_x': 't_start', '1_x': 't_end', '2_x': 'species', '1_y': 'f_start', '2_y': 'f_end'
})
df = df[['t_start', 't_end', 'f_start', 'f_end', 'species']]
df['filename'] = os.path.basename(file).split('.')[0]
df['filename'] = df['filename'].str.replace(extra_str_label, '')
for dt_col in ['t_start', 't_end']:
df[dt_col] = df[dt_col].astype(float)
return df
def create_label_dataset(directory, extra_str_label='', suppress_others=True, suppress_noise=True, suppress_unID=False, is_csv=False):
'''
Concatenates text files contents into a pandas df containing recordings information
Parameters:
- str directory: path to directory
- str extra_str_label: the substring that is added in label files, depending of the birder
- bool suppress_others: whether or not to suppress all non-bird signal
- bool suppress_noise: whether or not to suppress all background sound or noise
- bool suppress_others: whether or not to suppress unidentified bird sounds
Returns:
dataframe containing recordings information
'''
# Bird species to ID dictionary
dict_dir = r'C:\Users\laeri\NBM'
with open(os.path.join(dict_dir, 'bird_dict.json'), 'r') as f:
birds_dict = json.load(f)
# Labels dataframe
if is_csv:
labels = pd.read_csv(os.path.join(directory, 'annotations.csv'))
# Suppress file extension
labels['filename'] = labels['filename'].str.slice_replace(-4, repl='')
else:
df_list = [read_txt_file(os.path.join(directory, f), extra_str_label=extra_str_label)
for f in os.listdir(directory) if os.path.splitext(f)[-1] == '.txt']
labels = pd.concat(df_list)
# Convert to float and clip
for freq in ['f_start', 'f_end']:
labels[freq] = labels[freq].astype(float)
labels['f_start'] = labels['f_start'].clip(lower=0)
labels.loc[labels['f_end'] < 0, 'f_end'] = 20000
# Deduplication of recording labels, label with the largest frequency range is kept
labels['f_delta'] = labels['f_end'] - labels['f_start']
labels = labels.sort_values('f_delta', ascending=False).drop_duplicates(['filename', 't_start']).sort_values(['filename', 't_start'])
del labels['f_delta']
# Clean species
labels['species'] = labels['species'].map(lambda x: replacements[x] if x in replacements.keys() else x)
# Assign species to id
labels['bird_id'] = labels['species'].map(lambda x: birds_dict[x] if x in birds_dict.keys() else np.nan)
# Other sounds - Attention Background et dérivés (p-ê vent aussi) sont à traiter séparément et ne doivent pas être détectés -> on peut
# les garder comme samples négatifs (donc label = -1 dans le RPN) contrairement aux autres qui seront plutôt détectés mais classifiés comme
# "autres sons" par le RCNN.
noise_labels = ['Bruit de fond', 'Background', 'Backgroud', 'passage moto au loin', 'Back ground', 'Back groung', 'Backgroun', 'Bakground', 'backgroound',
'background', 'bruit de fond']
labels.loc[labels['species'].isin(noise_labels), 'bird_id'] = -1
not_bird_labels = ['Capreolus capreolus', 'Pelophylax sp.', 'Vulpes vulpes', 'Oecanthus pellucens', 'ruspolia nitidula', 'orthoptère', 'voix humaine',
'saturation HF par orthoptères', 'Cervus elaphus brame', 'Sus scrofa', 'chien', 'Hannetons par milliers', 'possible battement d\'aile', 'What ??',
'parasite', 'bruit parasite', 'geophonie', 'Vent geophonie', 'vulpes vulpes', 'Capreolus capreolus ', '0: Bruit parasite', '0: Other biophonia',
'0: Other antropophonia']
mask_others = labels['species'].map(lambda x: 'autre' in x.lower())
labels.loc[mask_others | labels['species'].isin(not_bird_labels), 'bird_id'] = 0
# All rarer and unidentified birds (supprimer oiseaux sp. du ds d'apprentissage pour la classif ?)
max_idx = len(birds_dict)
labels['bird_id'].fillna(max_idx + 1, inplace=True)
labels['bird_id'] = labels['bird_id'].astype(int)
if suppress_noise:
labels = labels.loc[labels['bird_id'] != -1]
if suppress_others:
labels = labels.loc[labels['bird_id'] != 0]
if suppress_unID:
labels = labels.loc[~labels['species'].isin(['Oiseau sp', 'Parus sp'])]
labels.index = range(len(labels))
return labels
def assign_idx(col):
'''
Function to be used in read_txt_file, increment a recording index each time a new recording is detected,
corresponding to a float value in column 0
'''
idx = -1
for elt in col:
if elt == 0:
idx += 1
yield idx
replacements = {
'Emberiza ortulana': 'Emberiza hortulana',
'bernicla bernicla': 'Branta bernicla',
'Bernicla bernicla': 'Branta bernicla',
'Grus grus adulte': 'Grus grus',
'Corvus corone alarme': 'Corvus corone',
'Phasianus colchicus ': 'Phasianus colchicus',
'Luscinia megarynchos megarynchos': 'Luscinia megarynchos',
'Luscinia megarynchos megarynchos': 'Luscinia megarhynchos',
'Luscinia megarhynchos megarhynchos ': 'Luscinia megarhynchos',
'Grus grus juvénile': 'Grus grus',
'Strix aluco chant': 'Strix aluco',
'tachybaptus ruficollis': 'Tachybaptus ruficollis',
'Tachybaptus ruficollis ': 'Tachybaptus ruficollis',
'Burhinus burhinus': 'Burhinus oedicnemus',
'Erithacus rubecula ': 'Erithacus rubecula',
'Turdus merula alarme': 'Turdus merula',
'Luscinia megarhynchos': 'Luscinia megarhynchos',
'Burhinus oedicnemus ' : 'Burhinus oedicnemus',
'Gallinula chloropus ': 'Gallinula chloropus',
'chant Luscinia megarhynchos': 'Luscinia megarhynchos',
'Anas platychyncos': 'Anas platyrhynchos',
'Grus grus cris': 'Grus grus',
'Erithacus rubecola': 'Erithacus rubecula',
'Anas platyrhynchos ': 'Anas platyrhynchos',
'Certhia brachydactyla ': 'Certhia brachydactyla',
'Streptopelia decaocto ': 'Streptopelia decaocto',
'Strix aluco ': 'Strix aluco',
'Botaurus stellaris ': 'Botaurus stellaris',
'Numenius arquata XC570503': 'Numenius arquata',
'Chevalier sylvain': 'Tringa glareola',
'caprimulgus europaeus': 'Caprimulgus europaeus',
'ardea cinerea': 'Ardea cinerea',
'Cuculus canorus canorus': 'Cuculus canorus',
'Charadrius dubius curonicus': 'Charadrius dubius',
'Charadrius curonicus': 'Charadrius dubius',
'Erithacus rubecula rubecula': 'Erithacus rubecula',
'Tyto alba alba': 'Tyto alba'
}
|