Skip to content
Snippets Groups Projects
Commit 0161b845 authored by Stephane Chavin's avatar Stephane Chavin
Browse files

V2

parent febe26dd
No related branches found
No related tags found
No related merge requests found
Showing
with 1354 additions and 351 deletions
README.md 100644 → 100755
# YOLO-DYNI
<h1 align="center"><strong>Raven2YOLO</strong></h1>
Ce git a été créé avec comme objectif une prise en main de YOLOV5 plus facile.
Il contient notamment un script permettant d'extraire les spectrogrammes de plusieurs enregistrements ([get_spectrogram.py](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/get_spectrogram.py)), un script nécessaire à la conversion des annotations LabelMe vers YOLO ([labelme2yolo.py](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/labelme2yolo.py)), un script pour convertir des annotations d'un dataframe vers YOLO ([get_train_annot_YOLO.py](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/get_train_annot_YOLO.py/)), un script permettant de séparer le train et la validation de manière équilibré ([get_train_val_YOLO.py](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/get_train_val_YOLO.py)) et un script qui permet de compiler les détections, d'un modèle entrainé, dans un dataframe ([get_yolo_detection.py](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/get_yolo_detection.py)).
This GitHub repository was created to simplify learning YOLOv5 and to adapt it for bioacoustics. It supports a paper analyzing Humpback Whale (<i>Megaptera novaeangliae</i>) vocalizations through the automatic detection and classification of 28 units.
## Install
See : [Publication]()
```bash
git clone https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni.git
---
pip install -r requirements.txt
This repository includes essential scripts for adapting YOLOv5 to bioacoustics research. Key scripts provided are:
```
* [get_spectrogram.py](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/get_spectrogram.py) : Extracts spectrograms from multiple recordings.
* [labelme2yolo.py](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/labelme2yolo.py) : Converts LabelMe annotations to YOLO format.
* [yolo2labelme.py](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/yolo2labelme.py) : Converts YOLO detections to LabelMe format.
* [get_train_annot.py](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/get_train_annot.py) : Converts Raven format dataframe annotations to YOLO format.
* [get_train_val.py](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/get_train_val.py) : Separates training and validation datasets in a balanced manner.
* [get_time_freq_detection.py](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/get_time_freq_detection.py) : Compiles detections from a trained model into a dataframe and/or into Raven annotation format (.txt).
## Annoter des images :
<details>
<summary>Extraction des spectrogrammes</summary>
<p>
</p>
<br>
```bash
python3 get_spectrogram.py -f NAME_OF_YOUR_FILE.csv -p PATH_TO_THE_DATA -d DIRECTION_OF_SAVED_DATA -m {unique or multiple} -n NAME_OF_THE_COLUMNS_THAT_CONTAIN_PATH -i {file or folder}
```
* Le mode unique est utilisé lorsque les enregistrements font quelques secondes et que l'on veut qu'un seul spectrogramme par enregistrement *(mode __unique__, DURATION = durée de l'enregistrement et NB_IMG_PER_REC = 1)*
* Le mode multiple permet de découper l'enregistrements en plusieurs spectrogrammes de quelques secondes
* -i file est à utiliser lorsque les noms de fichiers à traiter sont dans un .csv, tandis que -i folder est à utiliser pour traiter tous les .wav d'un dossier
**WARNING** : Il est important de modifier la valeur de DURATION, OFFSET et NB_IMG_PER_REC dans le code (ligne 35, 36 et 39), la fréquence d'échantillonage des spectrogrammes est celle du fichier .wav
---
```python
folder = 'Spectrogram/'
DURATION = 5
OFFSET = 2
if args.mode == 'multiple':
NB_IMG_PER_REC = 10
```
<br>
</details>
<details>
<summary>LabelMe</summary>
<p>
</p>
To use the scripts with Raven annotation software ([Raven Lite](https://www.ravensoundsoftware.com/software/raven-lite/)), export annotations in the recommended format and follow these steps:
* Installation de LabelMe
* go into the folder that contains the scripts
* Run get_train_annot.py
* Launch training
```bash
pip install labelme
```
<br>
* Conversion des annotations LabelMe vers YOLO
---
<br>
```bash
python3 labelme2yolo.py -p PATH_TO_THE_DATA -d DIRECTION_OF_THE_CONVERTED_FILES/FOLDER
```
To use the scripts without Raven annotation software, you can follow these steps:
**WARNING** : Les annotations *.json* et les images *.jpg* doivent être dans le même dossier (**PATH_TO_THE_DATA/**)
* Run get_spectrogram.py
* Install Labelme (pip install labelme) and annotate the spectrograms
* Run labelme2yolo.py
* Run get_train_val.py
* Launch training
* Préparation à la séparation train/val
<br>
Il est important de déplacer les images .jpg dans un dossier **images/all/** et que les labels .txt soient dans le dossier **labels/**
---
<br>
```bash
##go to the .jpg direction##
The [get_time_freq_detection.py](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/get_time_freq_detection.py) script compiles detections into a NetCDF (.nc) file, detailing minimum, mid-range, and maximum frequency, duration, detection position, and model confidence.
mkdir images #create a new folder
mkdir images/all/ #create the folder all in the new folder
Additional scripts may be added over time to automate other processes.
mv *.jpg images/all/. #move all the images in the all folder
<br />
mv images ../. #place the folder images in the main direction
```
[<a href="Open in Colab"><img src="https://colab.research.google.com/assets/colab-badge.svg" height="auto" width="120" ></a>](https://colab.research.google.com/drive/1NSEaJyhnAV8BUsSxbWr54EdjhUUODJpt?usp=sharing)
On se retrouve donc avec deux dossiers, un portant le nom **images**, contenant les .jpg, et un second portant le nom **labels/** contenant les .txt
<p>
</p>
<br />
![Demo](demo.gif "Demo")
---
<br />
</details>
* For proper citation when using this methodology, please refer to the provided [CITATION.cff](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/CITATION.cff) file.
<br />
---
<br />
## Créer un dataset à partir d'annotations sur un dataframe :
</p>
* Exemple de dataframe :
# <strong>Install</strong>
| |Path | label | annotation_initial_time |annotation_final_time |duree |min_frequency |max_frequency |avg_frequency |
| :-----: |:---: | :---: | :---: | :---: | :---:|:---: |:---: |:---: |
| 0 | /nfs/NAS7/QUEBEC2/Montmagny_A/142_111_F01/2017/WAV... | btbw | 0.903 | 2.871 | 1.967 | 3263 |6570 |4917 |
| 1 | /nfs/NAS7/QUEBEC2/La_Vallee_de_la_Gatineau_F/124_86_H02/2018/WAV... | coye | 3.678 | 5.734 | 2.056 | 1852 |6262 |4057 |
| 2 | /nfs/NAS7/QUEBEC2/La_Vallee_de_la_Gatineau_F/124_86_H01/2018/WAV... | alfl | 0.238 | 0.998 | 0.76 | 1631 |3836 |2734 |
| ... | ... | ... | ... | ... | ... | ... |... |... |
```bash
git clone https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni.git
**see :** [example.csv](https://gitlab.lis-lab.fr/stephane.chavin/yolo-dyni/-/blob/main/example.csv)
pip install -r requirements.txt
```
<br />
<details>
<summary>Conversion des annotations</summary>
<p>
</p>
<summary><strong>Spectrogram Extraction Script</strong></summary>
<br />
* Conversion des annotations (**temps fréquence**) au format YOLO (**label x y w h**)
## Description
**WARNING** : Ne pas oublier de modifier la valeur de **DURATION** et **NB_CLASS** (ligne 28 et 29)
This script extracts spectrograms from `.wav` files. It allows you to specify various parameters such as duration, window size, hop ratio, high and low pass filters, overlap, resampling frequency, and CPU usage to optimize the process.
<br />
# Usage
```bash
python3 get_train_annot_YOLO.py -f PATH_TO_THE_FILE.csv -p PATH_TO_DATA -d DIRECTION_OF_THE_TXT_AND_IMG -m {uniform or personalized} -u {unique or multiple} -c NAME_OF_THE_COLUMNS_CONTAINS_PATH
```
* Vérifier le bon placement des bounding box :
To run the script, use the following command:
```bash
python3 get_train_annot_YOLO.py -f PATH_TO_THE_FILE.csv -p PATH_TO_DATA -d DIRECTION_OF_THE_TXT_AND_IMG -m {uniform or personalized} -u {unique or multiple} -c NAME_OF_THE_COLUMNS_CONTAINS_PATH --export
python get_spectrogram.py <path> <directory> [options]
```
# Arguments
## Positional Arguments
- path: Path to the folder or file that contains the recordings.
- directory: Directory where the extracted spectrograms will be stored.
*Ajouter **--export** permet d'exporter les spectrogrammes avec les bounding box placées par dessus*
**WARNING** : Le mode uniforme permet d'extraire des bounding box avec y = 0.5 et h = 0.8; En utilisant -u unique seul le début des enregistrements est traité tandis que multiple permet de traiter plusieurs spectrogrammes; la fonction -c correspond au nom de la colonne qui contient le path des fichiers
## Optional Arguments
```
--duration (int): Duration for each spectrogram. Default is 8.
--window (int): Window size for the Fourier Transform. Default is 1024.
--hop (float): Ratio of hop in window. 50%% corresponds to 0.5. Default is 0.5.
--high (int): High Pass Filter value in Hz. Default is 10.
--low (int): Low Pass Filter value in Hz. Default is None.
--overlap (int): Overlap in seconds between two spectrograms. Default is 0.
--rf (int): Resampling Frequency of the signal. If not provided, the original frequency sampling of the recording will be used. Default is None.
--cpu (int): Number of CPUs to use for processing to speed up the process. Provide 2 or more. Default is 1.
--test (flag): If provided, sets the test flag to 1, otherwise it is None.
```
</details>
<br />
<details>
<summary>Séparation train/val</summary>
<p>
</p>
<summary><strong>LabelMe to YOLO Annotation Converter</strong></summary>
<br />
```bash
python3 get_train_val_YOLO.py -r RATIO -p PATH_TO_DATA -d DIRECTION_OF_THE_RESULT
```
## Description
**WARNING** : Le script get_train_annot_YOLO.py exporte les .txt et .jpg dans deux dossiers nommés **labels_X_Y/** et **images_X_Y/** (X : Jour et Y : Mois). Pour faire tourner le code get_train_val_YOLO.py, il faut d'abord supprimer les 4 derniers caractères du nom de ces dossiers pour obtenir uniquement **images/** et **labels/**
This script converts annotations from the LabelMe format to a YOLO compatible format. It allows you to specify the path to the LabelMe annotations and the directory where the converted YOLO annotations will be stored.
```python
##CHANGE TO MAKE##
# Usage
path = str(args.path_to_data)
direction = str(args.direction)
NB_CLASS = 50 #CHOOSE THE X MOST REPRESENTED CLASSES
To run the script, use the following command:
```bash
python labelme2yolo.py <path_to_data> <directory>
```
# Arguments
## Positional Arguments
- path_to_data: Path to the folder that contains the LabelMe annotations.
- directory: Directory where the YOLO annotations will be stored.
</details>
<br />
## Entrainement et Détection YOLO
<details>
<summary><strong>YOLO to JSON/LabelMe Annotation Converter</strong></summary>
<br />
## Description
This script converts annotations from the YOLO format (stored in `.txt` files) to JSON files. It allows you to specify the path to the folder containing the `.txt` files, the path to the folder containing the images, and optionally the directory where the modified JSON files will be stored.
* Si besoin : YOLOV5 (une version de YOLOv5 est déjà présente dans yolo-dyni/yolov5)
# Usage
To run the script, use the following command:
```bash
cd yolo-dyni
git clone https://github.com/lamipaul/yolov5
python yolo2labelme.py <path_to_txt> <path_to_img> [options]
```
# Arguments
## Positional Arguments
- path_to_txt: Path to the folder containing the .txt files.
- path_to_img: Path to the folder containing the .jpg images.
## Optional Arguments
-d, --directory (str): Directory where the modified JSON files will be stored. If not provided, the directory will be the same as path_to_txt.
</details>
<br />
<details>
<summary>Entrainement</summary>
<p>
</p>
<summary><strong>Data Splitting and Storage Script</strong></summary>
<br />
## Description
This script splits data into training, validation, and optionally test sets based on a specified ratio. It allows you to specify the path to the folder containing the `.txt` files, the directory where the spectrogram and `.txt` files will be stored, and an optional test flag to include a test split.
# Usage
To run the script, use the following command:
```bash
python3 train.py --img IMG_SIZE --batch BATCH_SIZE --epochs NB_EPOCHS --data DIRECTION_OF_THE_RESULT/custom_data.yaml --weights yolov5/weights/yolov5s.pt --cache
python get_train_val.py.py <path_to_data> <directory> [options]
```
# Arguments
## Positional Arguments
- path_to_data: Path to the folder that contains the .txt files (should end with labels/).
- directory: Directory where the spectrogram and .txt files will be stored (should be different from <path_to_data>).
</details>
<br />
<details>
<summary>Détection</summary>
<p>
</p>
<summary><strong>CSV to Spectrogram and Annotation Converter</strong></summary>
<br />
* Lancer un entrainement sur de l'audio
## Description
```bash
python3 detect.py --weights yolov5/runs/train/EXP_NB/weights/best.pt --img IMG_SIZE --conf 0.X --source PATH_TO_FOLDER_THAT_CONTAIN_WAV --save-txt --sound --sr X --sampleDur Y
```
This script creates `.txt` and `.jpg` files for each annotation from a CSV file. It takes in the path to the CSV file containing annotations, the path to the folder containing the recordings, and the directory where the spectrograms and `.txt` files will be stored. The script includes options for setting the duration and overlap of the spectrograms, frequency resampling, window size, hop ratio, CPU usage, and an optional test flag to include a test split.
# Usage
To run the script, use the following command:
* Sauvegarde les annotations en .txt ainsi que les images avec les bounding box dessus
```bash
python3 detect.py --weights yolov5/runs/train/EXP_NB/weights/best.pt --img IMG_SIZE --conf 0.X --source PATH_TO_SPECTROGRAM_TO_DETECT --save-txt
python get_train_annot.py <filename_path> <path_to_data> <directory> [options]
```
**WARNING** : Il faut adapter EXP_NB, qui correspond au numéro de l'entrainement *(exp1 pour le premier entrainement)*
--conf correspond à la confiance toléré par YOLO, c'est-à-dire à partir de quelle confiance d'une détection cette dernière est conservée, il faut donc modifier la valeur de X pour faire varier cette tolérence *(minimum : 0.0, maximum : 1)*
# Arguments
## Positional Arguments
- filename_path: Path/name of the folder/file containing the annotations. If a file, use Raven format and add a Path column with the path to the .wav files.
- path_to_data: Path of the folder that contains the recordings.
- directory: Directory where the spectrograms and .txt files will be stored.
* Sauvegarde les annotations en .txt seulement avec la confiance de chaque détections
```bash
python3 detect.py --weights yolov5/runs/train/EXP_NB/weights/best.pt --img IMG_SIZE --conf 0.X --source PATH_TO_SPECTROGRAM_TO_DETECT --save-txt --nosave --save-conf
## Optional Arguments
```
--duration (int): Duration for each spectrogram. Default is 8.
--overlap (int): Overlap in seconds between two spectrograms. Default is 2.
--rf (int): Frequency resampling. Default is None.
--window (int): Window size for the Fourier Transform. Default is 1024.
--hop (float): Ratio of hop in window (e.g., 50% = 0.5). Default is 0.5.
--cpu (int): Number of CPUs to speed up the process. Default is 1.
--test (flag): If provided, splits the data into train/test/validation sets with the ratio 1 - Ratio / 2 for test and the same for validation. If not provided, only train and validation splits are created.
```
</details>
<br />
<details>
<summary>Compilation des détections</summary>
<p>
</p>
<summary><strong>Detection Collector and DataFrame Generator</strong></summary>
<br />
## Description
This script collects detections from `.txt` files and returns a complete dataframe. It takes in the path to the folder containing the `.txt` files, the directory where the dataframe will be stored, and the path to the `YOLOv5` `custom_data.yaml` file. Additionally, it allows specifying the sampling rate and the duration of the spectrogram.
# Usage
To run the script, use the following command:
```bash
cd ../
python3 get_yolo_detection.py -p PATH_TO_THE_TXT -d DIRECTION_OF_THE_RESULT #PATH_TO_THE_TXT ~ yolov5/runs/detect/expX/labels
python get_time_freq_detection.py <path_to_data> <directory> <names> [options]
```
**WARNING** : Il est important d'ajouter sa propre liste de classes (ligne 39)
# Arguments
## Positional Arguments
- path_to_data: Path to the folder that contains the .txt files.
- directory: Directory where the dataframe will be stored.
- names: Path to the YOLOv5 custom_data.yaml file.
```python
#put the classes here
names = []
## Optional Arguments
```
-s, --sr (int): Sampling rate of the spectrogram. This argument is required.
--duration (int): Duration of the spectrogram. Default is 8.
```
</details>
<br />
# Training a YOLOv5 model
<br />
For this project, we adapt the YOLOv5 DataLoader to compute detection on a folder that contains .WAV files.
If you need more informations about YOLOv5, see:
<a href="https://github.com/ultralytics/yolov5" target="_blank">https://github.com/ultralytics/yolov5</a>
<br />
* Calcul des détections en secondes
* Jocher Glenn (2020), "YOLOv5 by Ultralytics", doi: 10.5281/zenodo.3908559, license: AGPL-3.0
<br />
```bash
cd ../
python3 get_time_freq_detection.py -p PATH_TO_THE_TXT -s SR -t DURATION -d DIRECTION_OF_THE_RESULT #PATH_TO_THE_TXT ~ yolov5/runs/detect/expX/labels
python yolov5/train.py --imgsz <IMG_SIZE> --batch <BATCH_SIZE> --epochs <NB_EPOCHS> --data <custom_data.yaml> --weights yolov5/weights/yolov5l.pt --hyp <custom_hyp.yaml> --cache
```
**WARNING** : Il est important d'ajouter sa propre liste de classes (ligne 39)
<br />
# Detection
<br />
```python
DUREE_SPECTRO = args.duration
SR = args.SR
- Detect on audio files
#put the classes here
names = []
```bash
python detect.py --weights yolov5/runs/train/<EXP_NB>/weights/best.pt --imgsz <imgsz> --conf <conf> --source <PATH_TO_FOLDER_THAT_CONTAIN_WAV> --save-txt --sound --sr <SR> --sampleDur <SampleDur> --window <window> --hop <hop> --save-conf
```
</details>
<br />
<details>
<summary>Conversion des détection YOLO vers LabelMe</summary>
<p>
</p>
- Detect on audio files without saving the detection images
```bash
pip install globox
globox convert yolov5/runs/detect/exp/labels/ DIRECTORY_TO_WICH_DATA_WILL_BE_STORED --format yolo --save_fmt labelme --img_folder PATH_TO_IMAGES
python detect.py --weights yolov5/runs/train/<EXP_NB>/weights/best.pt --imgsz <imgsz> --conf <conf> --source <PATH_TO_FOLDER_THAT_CONTAIN_WAV> --save-txt --sound --sr <SR> --sampleDur <SampleDur> --window <window> --hop <hop> --save-conf --nosave
```
* Ce script permet de convertir les *.txt* en *.json* pour les ouvrir dans LabelMe mais il ne prend pas en compte le paramètre 'imageData'. Le script **get_json_file_YOLO.py** permet donc de récupérer ces données et les ajouter aux fichiers *.json*.
<br />
```bash
python3 get_json_file_YOLO.py -p PATH_TO_STORED_DATA -i PATH_TO_IMAGES -d DIRECTORY_TO_WICH_DATA_WILL_BE_STORED
## Arguments :
```
--imgsz: Inference size height and width. Default is 640.
--sampleDur: Duration for each spectrogram for detection. Default is 8.
--sr: Samplerate for each spectrogram for detection. Default is 22050.
--window: Window size for each spectrogram for detection. Default is 1024.
--hop: Hop length for each spectrogram for detection. Default is 50% of window = 512.
--sound: Enable sound. Default is False. Action 'store_true'.
```
<br />
</details>
# Contact
If you have any questions, please contact me at the following e-mail address : stephane.chavin@univ-tln.fr
<br />
# Contributors
<br />
## Contact
[<a href="@stephane.chavin"><img src="https://gitlab.lis-lab.fr/uploads/-/system/user/avatar/700/avatar.png" height="auto" width="50" style="border-radius:50%"></a>](https://gitlab.lis-lab.fr/stephane.chavin) [<a href="@paul.best"><img src="https://secure.gravatar.com/avatar/9476891fe83ae7102b73819cc74a89a76fb1f59e9fc7f96bcf4afbc60d734934?s=80&d=identicon" height="auto" width="50" style="border-radius:50%"></a>](https://gitlab.lis-lab.fr/paul.best) [<a href="@lisa.ferre"><img src="https://secure.gravatar.com/avatar/714493b1b540404b05e08795c1c17abdfc9db8dd1cafec2876051e00117bdc21?s=80&d=identicon" height="auto" width="50" style="border-radius:50%"></a>](https://gitlab.lis-lab.fr/lisa.ferre) [<a href="@nicolas.deloustal"><img src="https://secure.gravatar.com/avatar/32367421a8619488135bddb50ea0cfa9b89c9b9e915cd83501345867d79e3666?s=80&d=identicon&width=96" height="auto" width="50" style="border-radius:50%"></a>](https://gitlab.lis-lab.fr/nicolas.deloustal)
Pour toute question, veuillez me contacter à l'adresse mail suivante : stephanechvn@gmail.com
<br />
---
\ No newline at end of file
"""Extracts spectrograms from multiple recordings"""
import os
import librosa
import glob
import argparse
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import soundfile
from p_tqdm import p_map
from tqdm import tqdm
import utils
import pandas as pd
import warnings
warnings.filterwarnings('ignore')
def arg_directory(path):
if os.path.isdir(path):
return path
else:
raise argparse.ArgumentTypeError(f'`{path}` is not a valid path')
def create_spectrogram(y, directory, filename, offset, duration):
window_size = 1024
window = np.hanning(window_size)
stft = librosa.core.spectrum.stft(y, n_fft=window_size, hop_length=512, window=window)
plt.close()
plt.figure()
log_stft = np.log10(np.abs(stft))
vmin, vmax = log_stft.min(), log_stft.max()
plt.imshow(log_stft[::-1], aspect="auto", interpolation=None, cmap='jet', vmin=vmin, vmax=vmax)
plt.subplots_adjust(top=1, bottom=0, left=0, right=1)
def main(data, arguments):
"""
Load the data and compute n spectrograms with a specific duration and save it into a folder.
:param data (DataFrame): DataFrame containing the path of each file to process.
:param arguments (args): Arguments for signal processing and directory.
"""
_, (i) = data
filename = str(i[0]) # Store the filename of the recording
try:
info = soundfile.info(filename) # Collection recording information
file_duration, fs = info.duration, info.samplerate
except Exception as error:
print(f'`{filename}` cannot be open : : {error}')
return
name = os.path.join(directory, 'Spectrogram', f"{filename.replace('/', '_').split('.')[0]}_{offset}")
# Create the list of all possible offset to compute spectrogram
offset_list = np.arange(0, file_duration, arguments.duration - arguments.overlap)
for offset in offset_list:
file = filename.replace('/', '_').split('.', maxsplit=1)[0]
try:
plt.savefig(name + '.jpg')
except FileNotFoundError:
os.makedirs(os.path.join(directory, 'Spectrogram'), exist_ok=True)
plt.savefig(name + '.jpg')
sig, fs = soundfile.read(filename, start=int(
offset*fs), stop=int((offset+arguments.duration)*fs),
always_2d=True) # Load the signal
sig = sig[:, 0] # Only take channel 0
# Apply resample and low/high pass filter
sig = utils.signal_processing(
sig, rf=arguments.rf, fs=fs, high=arguments.high, low=arguments.low)
# Check if empty .txt annotation file is needed
if arguments.background:
folder = 'background'
name = os.path.join(arguments.directory, folder, f'{file}_{offset}')
empty_dataframe = pd.DataFrame(columns=['specie', 'x', 'y', 'w', 'h'])
empty_dataframe.to_csv(str(name+'.txt'), sep=' ', index=False, header=False)
else:
folder = 'spectrograms'
name = os.path.join(arguments.directory, folder, f'{file}_{offset}')
def process_recordings(args):
_, (i) = args
duration = 8
overlap = 2
utils.create_spectrogram(
sig, arguments.directory, name, window_size=arguments.window,
overlap=arguments.hop)
return folder
except Exception as error:
print(f'`{filename}` cannot be open : {error}')
for count in range(args.img_per_rec):
offset = count * (duration - overlap)
filename = str(i[0])
try:
y, _ = librosa.load(filename, offset=offset, duration=duration, sr=None)
create_spectrogram(y, args.directory, filename, offset, duration)
except Exception:
print(filename)
if __name__ == "__main__":
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, description='TODO')
parser.add_argument('-f', '--file', type=str, help='Name of the file that contains the recording to print')
parser.add_argument('-p', '--path_to_data', type=arg_directory, help='Path of the folder that contains the recordings', required=True)
parser.add_argument('-d', '--directory', type=arg_directory, help='Directory to which spectrograms will be stored', required=True)
parser.add_argument('-m', '--mode', type=str, choices=['unique', 'multiple'], help='Direction of the saved spectrogram')
parser.add_argument('-n', '--columns_name', type=str, help='Name of the columns that contain the path of the .wav')
parser.add_argument('-i', '--input', type=str, choices=['file', 'folder'], help='Choose "file" if you have a .csv file or "folder" to export spectrogram from all the .wav of a folder')
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Extract spectrogram for each .wav file')
parser.add_argument('path', type=utils.arg_directory,
help='Path of the folder/file that contains the recordings')
parser.add_argument('directory', type=utils.arg_directory,
help='Directory to which spectrograms will be stored')
parser.add_argument('--duration', type=int,
help='Duration for each spectrogram', default=8)
parser.add_argument(
'--window', type=int, help='Window size for the Fourier Transform', default=1024)
parser.add_argument('--hop', type=float,
help='Ratio of hop in window : 50%% = 0.5', default=0.5)
parser.add_argument('--high', type=int,
help='High Pass Filter value in Hz', default=10)
parser.add_argument('--low', type=int,
help='Low Pass Filter value in Hz', default=None)
parser.add_argument('--overlap', type=int,
help='Overlap in secondes between 2 spectrograms', default=0)
parser.add_argument('--rf', type=int, help='Resampling Frequency of the signal. If no argument,'
' will be original frequency sampling of the recording', default=None)
parser.add_argument(
'--cpu', type=int, help='To speed up the process, write 2 or more', default=1)
parser.add_argument(
'--background', action='store_const', help='If in arguments, will save an empty .txt file',
const=1, default=None)
args = parser.parse_args()
if args.mode == 'multiple':
img_per_rec = 30
elif args.mode == 'unique':
img_per_rec = 1
path_to_data = args.path_to_data
# Load the data and put it into a DataFrame
df = utils.open_file(args.path)
if args.input == 'file':
df = pd.read_csv(args.file, low_memory=False)
df['Path'] = df[args.columns_name]
elif args.input == 'folder':
df = pd.DataFrame(glob.glob(os.path.join(path_to_data, '*'), recursive=True), columns=['Path'])
if args.cpu == 1: # If no multiprocessing, then loop processing
for num, row in tqdm(df.iterrows(), total=len(df),
desc="Processing", ascii='░▒▓█'):
dest = main([num, [row.Path]], args)
directory = args.directory
else:
args = [args]*len(df.groupby('Path'))
dest = p_map(main, enumerate(df.groupby('Path')), args,
num_cpus=args[0].cpu, total=len(df.groupby('Path')))
directory = args[0].directory
p_map(process_recordings, enumerate(df.groupby('Path'), img_per_rec=img_per_rec), num_cpus=1, total=len(df.groupby('Path')))
print(f'Saved to {args.directory}/Spectrogram')
final_dest = os.path.join(directory, dest[0] if len(dest) is list else dest)
print(f'Saved to {final_dest}')
import pandas as pd
import os
import argparse
from datetime import date
from tqdm import tqdm
def arg_directory(path):
if os.path.isdir(path):
return path
else:
raise argparse.ArgumentTypeError(f'`{path}` is not a valid path')
def process_annotations(annotations_folder, duration, sr):
today = date.today()
out_file = f'YOLO_detection_{today.day}_{today.month}'
"""Compiles detections into a dataframe and/or into Raven annotation format (.txt)"""
df_list = []
names = [] # Add your class names here
for file_name in tqdm(os.listdir(annotations_folder)):
if file_name.endswith('.txt'):
file_path = os.path.join(annotations_folder, file_name)
annotation_df = pd.read_csv(file_path, sep=' ', names=['espece', 'x', 'y', 'w', 'h'])
import argparse
import os
import yaml
import utils
import xarray as xr
import pandas as pd
annotation_df['file'] = file_name
annotation_df['idx'] = annotation_df['file'].str.split('_').str[-1].str.split('.').str[0]
annotation_df['file'] = annotation_df['file'].str.rsplit('.', 1).str[0] + '.wav'
annotation_df['annot'] = annotation_df['espece'].apply(lambda x: names[x])
def main(arguments):
"""
Load all the informations to concatenate detection and get time/frequency informations
and save them into a full file and multiple raven annotation file if --raven
:param arguments (args): All the arguments of the script
"""
with open(arguments.names, 'r', encoding='utf-8') as file:
data = yaml.safe_load(file)
names = data['names']
df, dir_path = utils.detection2time_freq(annotations_folder=arguments.path_to_data,
duration=arguments.duration,
outdir=arguments.directory,
sr=arguments.sr,
names=names,
wav=args.path_to_wav,
raven=args.raven)
# Convert DataFrame to xarray Dataset
ds = xr.Dataset.from_dataframe(df)
annotation_df['midl'] = (annotation_df['x'] * duration) + annotation_df['idx'].astype(int)
annotation_df['freq_center'] = (1 - annotation_df['y']) * (sr / 2)
annotation_df['freq_min'] = annotation_df['freq_center'] - (annotation_df['h'] * (sr / 2)) / 2
annotation_df['freq_max'] = annotation_df['freq_center'] + (annotation_df['h'] * (sr / 2)) / 2
annotation_df['start'] = annotation_df['midl'] - (annotation_df['w'] * duration) / 2
annotation_df['stop'] = annotation_df['midl'] + (annotation_df['w'] * duration) / 2
annotation_df['duration'] = annotation_df['stop'] - annotation_df['start']
# Add metadata attributes
ds.attrs['title'] = 'YOLO detection final table'
ds.attrs['summary'] = 'This dataset contains the YOLO detection with parameters.'
ds.attrs['description'] = str('The data includes positions : "pos", frequencies :'
' "low freq (hz)"; "high freq (hz)", and timings : '
'"begin time (s)"; "end time (s)" and the species detected'
' in the given files.')
ds.attrs['date_created'] = pd.Timestamp.now().isoformat()
ds.attrs['creator_name'] = os.getlogin()
df_list.append(annotation_df)
# Save Dataset to NetCDF file
ds.to_netcdf(dir_path)
print(f'Saved as {dir_path}')
result_df = pd.concat(df_list, ignore_index=True)
result_df.to_csv(os.path.join(outdir, f'{out_file}.csv'), index=False)
print(f'Saved as {os.path.join(outdir, f"{out_file}.csv")}')
if __name__ == "__main__":
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, description='TODO')
parser.add_argument('-p', '--path_to_data', type=arg_directory, help='Path of the folder that contains the .txt files', required=True)
parser.add_argument('-d', '--directory', type=arg_directory, help='Directory where the dataframe will be stored', required=True)
parser.add_argument('-t', '--duration', type=int, help='Duration of the spectrogram', required=True)
parser.add_argument('-s', '--SR', type=int, help='Sampling Rate of the spectrogram')
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Collect detections and return a complete dataframe')
parser.add_argument('path_to_data', type=utils.arg_directory,
help='Path of the folder that contains the .txt files')
parser.add_argument('directory', type=utils.arg_directory,
help='Directory where the dataframe will be stored')
parser.add_argument('names', type=str,
help='path to YOLOv5 custom_data.yaml file')
parser.add_argument('-s', '--sr', type=int,
help='Sampling Rate of the spectrogram', required=True)
parser.add_argument('--duration', type=int,
help='Duration of the spectrogram', default=8)
parser.add_argument('--path_to_wav', type=utils.arg_directory,
help='Path of the folder that contains the .wav files', required=True)
parser.add_argument('--raven', action='store_const', const=1, default=None,
help='Export the .txt per .WAV file to vizualize on Raven')
args = parser.parse_args()
process_annotations(args.path_to_data, args.duration, args.SR)
main(args)
"""Converts Raven format dataframe annotations to YOLO format"""
import argparse
import random
import os
import sys
import cv2
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import soundfile as sf
import utils
from p_tqdm import p_map
from tqdm import tqdm
def process(entry, arguments, species_list, colors):
"""
Precess the annotation to get the .jpg spectrogram and the .txt annotation file
:param x (tuple): Enumerate number, [filename, group] per file
:param arguments (args): Arguments
:param species_list (list): List with corresponding species
"""
_, (filename, grp) = entry
try:
info = sf.info(filename) # Collection recording information
file_duration, fs = info.duration, info.samplerate
except Exception as e:
print(f'`{filename}` cannot be open... : {e}')
return
# create the time list between 0 and 1000 * duration
# Create the list of all possible offset to compute spectrogram
offset_list = np.arange(
0, file_duration, arguments.duration - arguments.overlap)
new_pos = utils.split_annotations(
grp[['start', 'stop']], arguments.duration)
grp = pd.merge(grp, new_pos)
while len(grp) != 0:
# collect all the data between the offset and duration-overlap
if len(offset_list) >= 1:
table = grp[grp.start <= offset_list[0] + arguments.duration]
else:
continue
# create an empty dataframe
annotation = pd.DataFrame(columns=['id', 'x', 'y', 'width', 'height'])
# if no data for this period, go to the next one
if len(table) == 0:
offset_list = offset_list[1:]
continue
offset = offset_list[0] # take initial time for offset
name = str(grp.iloc[0].Path.replace(
'/', '_').replace('.', '_') + '_' + str(offset))
sig, fs = sf.read(filename, start=int(
offset*fs), stop=int((offset+arguments.duration)*fs), always_2d=True) # Load the signal
sig = sig[:, 0] # Only take channel 0
if arguments.rf is None:
arguments.rf = fs
# Apply resample and low/high pass filter
sig = utils.signal_processing(
sig, rf=arguments.rf, fs=fs, high=arguments.high, low=arguments.low)
_ = utils.create_spectrogram(
sig, arguments.directory, names=None,
window_size=arguments.window,
overlap=arguments.hop)
for _, row in table.iterrows():
specie = row.species
x_pxl = (row.midl - offset) / \
arguments.duration # take x value in pixels
width_pxl = (row.stop - row.start) / \
arguments.duration # take width value in pixels
# take y value in pixels
y_pxl = 1 - (row.midl_y / (arguments.rf / 2))
height_pxl = (row.max_freq - row.min_freq) / \
(arguments.rf / 2) # take height value in pixels
if height_pxl > 1:
height_pxl = 1
elif height_pxl > y_pxl * 2:
y_pxl = y_pxl + 0.5 * (height_pxl - y_pxl * 2)
# Store the annotation in a DataFrame
new_table = pd.DataFrame([[str(species_list[species_list.species == specie].index[0]),
x_pxl, y_pxl, width_pxl, height_pxl]],
columns=['id', 'x', 'y', 'width', 'height'])
annotation = annotation.dropna(axis=1, how='all')
new_table = new_table.dropna(axis=1, how='all')
annotation = pd.concat([annotation, new_table])
grp = grp.drop(table.index)
name_file = os.path.join(arguments.directory,
'labels', f'{name}.txt')
# Create all the folder
for folder in ['images', 'labels', 'images/all', 'annotated_images']:
utils.create_directory(os.path.join(
arguments.directory, folder))
for specie_num in species_list:
utils.create_directory(os.path.join(
arguments.directory, 'images', str(specie_num)))
# Save the images and annotation
plt.savefig(os.path.join(arguments.directory,
'images', str(species_list[species_list.species ==
specie].index[0]), f'{name}.jpg'))
annotation.to_csv(name_file, sep=' ', header=False, index=False)
plt.savefig(os.path.join(arguments.directory, 'images', 'all',
f'{name}.jpg'))
plt.close()
# Add annotation to the images in another folder
image = cv2.imread(
os.path.join(arguments.directory, 'images', 'all', f'{name}.jpg'))
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# For each annotation in a spectrogram
for num, annot in annotation.iterrows():
shp1, shape1, shp4, shape4 = utils.get_box_shape(
[annot, num], image)
text_shape = shp1[0], shp1[1] - 5
label = annot['id']
# Add the annotation into the images as a rectangle
cv2.rectangle(image, pt1=shape1, pt2=shape4,
color=colors[species_list[species_list.species ==
specie].index[0]],
thickness=1)
cv2.rectangle(
image, pt1=shp1, pt2=shp4,
color=colors[species_list[species_list.species ==
specie].index[0]],
thickness=-1)
# Add the label associated
cv2.putText(image, label, text_shape,
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
plt.imshow(image)
plt.subplots_adjust(top=1, bottom=0, left=0,
right=1) # Remove the border
plt.savefig(
os.path.join(arguments.directory, 'annotated_images', f'{name}.jpg'))
plt.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Create .txt and .jpg to each annotation '
'from a csv')
parser.add_argument('filename_path', type=str,
help='Path/name of the folder/file containing the annotations. If a file '
'use Raven format and add a Path columns with the path to the '
'.wav files')
parser.add_argument('path_to_data', type=utils.arg_directory,
help='Path of the folder that contains the recordings')
parser.add_argument('directory', type=utils.arg_directory,
help='Directory to which spectrograms and .txt files will be stored')
parser.add_argument('--duration', type=int,
help='Duration for each spectrogram', default=8)
parser.add_argument('--overlap', type=int,
help='Overlap in seconds between 2 spectrograms', default=0)
parser.add_argument(
'--rf', type=int, help='Frequency Resampling ', default=None)
parser.add_argument(
'--window', type=int, help='Window size for the Fourier Transform',
default=1024)
parser.add_argument(
'--hop', type=float, help='Ratio of hop in window : 50%% = 0.5', default=.5)
parser.add_argument(
'--cpu', type=int, help='To speed up the process, write 2 or more',
default=1)
parser.add_argument('--high', type=int,
help='High Pass Filter value in Hz', default=10)
parser.add_argument('--low', type=int,
help='Low Pass Filter value in Hz', default=None)
parser.add_argument('--test', action='store_const', const=1,
help='Split into train/test/val. 1 - Ratio / 2 for test and'
' same for validation', default=None)
args = parser.parse_args()
# Load the data and put it into a DataFrame
df = utils.open_file(args.filename_path)
suffix = input('Which suffix for your recording data? [wav, WAV, Wav, flac, mp3] : ')
if len(df.columns) == 1:
final = []
for file, _ in df.groupby('Path'):
new_df = utils.open_file(file)
if len(new_df) >= 1:
new_df['Path'] = os.path.join(args.path_to_data,
str(file.split('.Table')[0]+f'.{suffix}'))
final.append(new_df)
else:
continue
df = pd.concat(final)
elif 'Path' not in df.columns:
df['Path'] = os.path.join(args.path_to_data,
args.filename_path.split('/')[-1].split('.Table')[0]+f'.{suffix}')
df, species = utils.prepare_dataframe(df, args)
list_colors = [(random.randint(0, 255), random.randint(0, 255),
random.randint(0, 255)) for _ in range(len(species))]
species.to_csv(os.path.join(
args.directory, 'species_list.csv'), index=False)
if args.cpu == 1:
for i in tqdm(enumerate(df.groupby('Path')), total=len(df.groupby('Path')),
desc="Processing", ascii='░▒▓█'):
process(i, args, species, list_colors)
else:
args = [args for _ in range(len(df.groupby('Path')))]
species = [species for _ in range(len(df.groupby('Path')))]
list_colors = [list_colors for _ in range(len(df.groupby('Path')))]
p_map(process, enumerate(df.groupby('Path')), args,
species, list_colors, num_cpus=args[0].cpu, total=len(df.groupby('Path')))
args = args[0]
print('saved to', args.directory)
if not args.test:
# Ask user if the script split the data or not
SPLIT = input(
'Do you want to split your data into a random train/test/val ? [Y/N] : ')
else:
SPLIT = 'Y'
if SPLIT == 'Y':
print('The train set will be 70%, val set 15% and test set 15%')
path = os.getcwd()
# Get the current path to find the split script
script = os.path.join(path, 'get_train_val.py')
data_path = os.path.join(path, args.directory, 'labels')
directory_path = os.path.join(path, args.directory, 'set')
# Create the directory path if not exists
utils.create_directory(directory_path)
try :
# Run the split command
os.system(f'{sys.executable} {script} {data_path} {directory_path} -r 0.7 --test')
print(f'Train saved in {directory_path}\n')
print('To train your model, use the following command : \n')
yolo_path = os.path.join(path, 'yolov5/train.py')
data_path = os.path.join(directory_path, 'custom_data.yaml')
weights_path = os.path.join(path, 'yolov5/weights/yolov5l.pt')
hyp_path = os.path.join(path, 'custom_hyp.yaml')
command = f'python {yolo_path} --data {data_path} --imgsz 640 --epochs 100 --weights {weights_path} --hyp {hyp_path} --cache'
print(command,'\n')
print('\u26A0\uFE0F Be aware that it is recommended to have background images that',
' represents 10% of your dataset. To do so, please use the script "get_spectrogram.py"',
' with --background arguments. Comptue on recordings that contains multiple',
' type of noise...')
except Exception as error:
print(error)
"""Separates training and validation datasets in a balanced manner"""
import argparse
import os
import pandas as pd
import utils
from tqdm import tqdm
def export_split(entry, path, directory):
"""
Export the annotations' sets
:param entry (list): Minor set = [0], Major set = [1]
:param path (str): Path to take the labels files
:param directory (str): Directory to save the data
"""
train_set = entry[0]
val_set = entry[1]
for folder in ['images', 'labels', 'images/train', 'images/val', 'images/test',
'labels/train', 'labels/val', 'labels/test']:
utils.create_directory(os.path.join(directory, folder))
if args.test:
test_set = entry[2]
test_set.file = ['.'.join(x.split('.')[:-1])
for num, x in enumerate(test.file)]
utils.copy_files_to_directory(test_set.file, path, os.path.join(
directory, 'labels/test'), 'txt')
utils.copy_files_to_directory(test_set.file, os.path.join(
path, '../images/all'), os.path.join(directory, 'images/test'), 'jpg')
val_set.file = ['.'.join(x.split('.')[:-1])
for _, x in enumerate(val.file)]
train_set.file = ['.'.join(x.split('.')[:-1])
for _, x in enumerate(train_set.file)]
# Copy the validation set into the folder
utils.copy_files_to_directory(val_set.file, path, os.path.join(
directory, 'labels/val'), 'txt')
utils.copy_files_to_directory(val_set.file, os.path.join(
path, '../images/all'), os.path.join(directory, 'images/val'), 'jpg')
# Copy the trainning set into the folder
utils.copy_files_to_directory(train_set.file, path, os.path.join(
directory, 'labels/train'), 'txt')
utils.copy_files_to_directory(train_set.file, os.path.join(
path, '../images/all'), os.path.join(directory, 'images/train'), 'jpg')
try:
species_list = pd.read_csv(os.path.join(path, '../species_list.csv'))
except FileNotFoundError:
print('No species list detected, please add it to : ',
os.path.join(directory, 'custom_data.yaml'))
with open(os.path.join(directory, 'custom_data.yaml'), 'w', encoding='utf-8') as f:
if args.test == 1:
f.write(f'test: {os.path.join(directory, "images/test")}\n')
f.write(f'train: {os.path.join(directory, "images/train")}\n')
f.write(f'val: {os.path.join(directory, "images/val")}\n')
f.write(f'nc: {len(species_list)}\n')
f.write(f'names: {species_list.species.tolist()}')
def prepare_data(arguments):
"""
Prepare the annotation before getting splited
:param args (args): Argument
:return detection (DataFrame): DataFrame with all the annotation to split
"""
detections = pd.concat({f: pd.read_csv(os.path.join(arguments.path_to_data, f), sep=' ',
names=['species', 'x', 'y', 'w', 'h'])
for f in tqdm(os.listdir(arguments.path_to_data),
desc="Processing", ascii='░▒▓█')},
names=['file'])
detections = detections.reset_index()
detections.species = detections.species.astype(float)
return detections
if __name__ == '__main__':
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Split the annotation into train, val and test if needed')
parser.add_argument('path_to_data', type=utils.arg_directory,
help='Path of the folder that contains the .txt (ending with labels/)')
parser.add_argument('directory', type=utils.arg_directory,
help='Directory to which spectrogram and .txt files will be'
'stored (different from -p)')
parser.add_argument('-r', '--ratio', type=float,
default=0.7, help='Train Ratio (val = 1 - ratio)')
parser.add_argument('--test', action='store_const', const=1,
help='Split into train/test/val. 1 - Ratio / 2 '
' for test and same for validation', default=None)
args = parser.parse_args()
df = prepare_data(args)
train, val = utils.split(df, 'train', args.ratio)
if args.test:
val, test = utils.split(val, 'val', 0.5)
export_split([train, val, test], args.path_to_data, args.directory)
state, proposition = utils.get_set_info([train, val, test])
else:
export_split([train, val], args.path_to_data, args.directory)
state, proposition = utils.get_set_info([train, val])
print(f'\nYour dataset is {state} {proposition}')
print(f'Train saved in {args.directory}\n')
print('To train your model, use the following command : \n')
path = os.getcwd()
directory_path = os.path.join(path, args.directory, 'set')
yolo_path = os.path.join(path, 'yolov5/train.py')
data_path = os.path.join(directory_path, 'custom_data.yaml')
weights_path = os.path.join(path, 'yolov5/weights/yolov5l.pt')
hyp_path = os.path.join(path, 'custom_hyp.yaml')
command = f'python {yolo_path} --data {data_path} --imgsz 640 --epochs 100 --weights {weights_path} --hyp {hyp_path} --cache'
print(command,'\n')
print('\u26A0\uFE0F Be aware that it is recommended to have background images that',
'represents 10% of your dataset. To do so, please use the script "get_spectrogram.py"',
'with --background arguments. Comptue on recordings that contains multiple type of noise...')
\ No newline at end of file
labelme2yolo.py 100644 → 100755
"""Converts LabelMe annotations to YOLO format"""
import os
import json
import base64
import shutil
import argparse
from glob import glob
from pathlib import Path
def arg_directory(path):
if os.path.isdir(path):
return path
else:
raise argparse.ArgumentTypeError(f'`{path}` is not a valid path')
def convert_labelme_to_yolo(labelme_annotation_path, yolo_directory):
# Load LabelMe annotation
image_id = Path(labelme_annotation_path).stem
with open(labelme_annotation_path, 'r') as labelme_annotation_file:
labelme_annotation = json.load(labelme_annotation_file)
import utils
# YOLO annotation and image paths
yolo_annotation_path = os.path.join(yolo_directory, 'labels', f'{image_id}.txt')
yolo_image_path = os.path.join(yolo_directory, 'images', f'{image_id}.jpg')
with open(yolo_annotation_path, 'w') as yolo_annotation_file:
yolo_image_data = base64.b64decode(labelme_annotation['imageData'])
# Write YOLO image
with open(yolo_image_path, 'wb') as yolo_image_file:
yolo_image_file.write(yolo_image_data)
# Write YOLO image annotation
for shape in labelme_annotation['shapes']:
if shape['shape_type'] != 'rectangle':
print(f'Invalid type `{shape["shape_type"]}` in annotation `{labelme_annotation_path}`')
continue
label = shape['label']
x1, y1 = shape['points'][0]
x2, y2 = shape['points'][1]
width = x2 - x1
height = y2 - y1
x_center = (x1 + x2) / 2
y_center = (y1 + y2) / 2
annotation_line = f'{label} {x_center} {y_center} {width} {height}\n'
yolo_annotation_file.write(annotation_line)
def main(args):
def main(arguments):
"""
Launch the processing on each .txt.
:param arguments (args): Argument
"""
yolo_names = set()
for labelme_annotation_path in glob(f'{args.path_to_data}/*.json'):
convert_labelme_to_yolo(labelme_annotation_path, args.directory)
with open(labelme_annotation_path, 'r') as labelme_annotation_file:
for folder in ['images', 'labels', 'images/all']: # Create saving directory
utils.create_directory(os.path.join(arguments.directory, folder))
# For each annotation, convert into YOLO format
for labelme_annotation_path in glob(f'{arguments.path_to_data}/*.json'):
utils.labelme2yolo(labelme_annotation_path, arguments.directory)
# Add all the information in the .txt file
with open(labelme_annotation_path, 'r', encoding='utf-8') as labelme_annotation_file:
labelme_annotation = json.load(labelme_annotation_file)
for shape in labelme_annotation['shapes']:
yolo_names.add(shape['label'])
# Write YOLO names
yolo_names_path = os.path.join(args.directory, 'custom.names')
with open(yolo_names_path, 'w') as yolo_names_file:
# Write YOLO names and save it
yolo_names_path = os.path.join(arguments.directory, 'custom.names')
with open(yolo_names_path, 'w', encoding='utf-8') as yolo_names_file:
yolo_names_file.write('\n'.join(yolo_names))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Convert LabelMe annotations to YOLO compatible')
parser.add_argument('-p', '--path_to_data', type=arg_directory, help='Path to LabelMe annotations', required=True)
parser.add_argument('-d', '--directory', type=arg_directory, help='Directory to which YOLO annotations will be stored', required=True)
parser = argparse.ArgumentParser(
description='Convert LabelMe annotations to YOLO compatible')
parser.add_argument('path_to_data', type=utils.arg_directory,
help='Path to LabelMe annotations')
parser.add_argument('directory', type=utils.arg_directory,
help='Directory to which YOLO annotations will be stored')
args = parser.parse_args()
main(args)
# YOLOv5 requirements
# Raven2YOLO requirements
# Usage: pip install -r requirements.txt
# Base ------------------------------------------------------------------------
gitpython>=3.1.30
matplotlib>=3.2.2
numpy>=1.18.5
opencv-python>=4.1.1
Pillow>=7.1.2
psutil # system resources
PyYAML>=5.3.1
requests>=2.23.0
scipy>=1.4.1
thop>=0.1.1 # FLOPs computation
torch>=1.7.0 # see https://pytorch.org/get-started/locally (recommended)
torchvision>=0.8.1
tqdm>=4.64.0
librosa==0.9.2
# protobuf<=3.20.1 # https://github.com/ultralytics/yolov5/issues/8012
# Logging ---------------------------------------------------------------------
tensorboard>=2.4.1
# clearml>=1.2.0
# comet
argparse==1.4.0 # Command-line argument parsing
matplotlib>=3.2.2 # Plotting library
numpy>=1.18.5 # Numerical computing library
opencv-python>=4.1.1 # Computer vision library
Pillow>=7.1.2 # Python Imaging Library
PyYAML>=5.3.1 # YAML parser and emitter
os>=0.11.0 # Operating system interface
pandas>=2.2.2 # Data manipulation and analysis
tqdm>=4.64.0 # Progress bar library
# Plotting --------------------------------------------------------------------
pandas>=1.1.4
seaborn>=0.11.0
# Export ----------------------------------------------------------------------
# coremltools>=6.0 # CoreML export
# onnx>=1.12.0 # ONNX export
# onnx-simplifier>=0.4.1 # ONNX simplifier
# nvidia-pyindex # TensorRT export
# nvidia-tensorrt # TensorRT export
# scikit-learn<=1.1.2 # CoreML quantization
# tensorflow>=2.4.1 # TF exports (-cpu, -aarch64, -macos)
# tensorflowjs>=3.9.0 # TF.js export
# openvino-dev # OpenVINO export
# Deploy ----------------------------------------------------------------------
setuptools>=65.5.1 # Snyk vulnerability fix
# tritonclient[all]~=2.24.0
matplotlib>=3.7.1 # Plotting library
seaborn>=0.11.0 # Statistical data visualization
# Extras ----------------------------------------------------------------------
# ipython # interactive notebook
# mss # screenshots
# albumentations>=1.0.3
# pycocotools>=2.0.6 # COCO mAP
# roboflow
# ultralytics # HUB https://hub.ultralytics.com
ipdb>=0.13.9 # IPython debugger
ipython>=8.1.1 # Interactive Python shell
# Additional Packages ---------------------------------------------------------
glob>=1.3.0 # Unix-style pathname pattern expansion
shutil>=1.0.0 # High-level file operations
base64>=1.2.0 # Base16, Base32, Base64, Base85 data encoding
json>=1.0.0 # JSON encoder/decoder
datetime>=1.0.0 # Basic date and time types
pathlib>=1.0.0 # Object-oriented filesystem paths
librosa>=0.9.2 # Audio and music processing library
scipy>=1.8.0 # Scientific library for numerical computations
cv2>=1.0.0 # OpenCV computer vision library
soundfile>=0.11.0 # Sound library
p_tqdm==1.4.0 # Parallel tqdm (progress bar) for Python
yaml==6.0 # YAML parser and emitter
torch==1.12.0 # PyTorch deep learning framework
albumentations>=1.0.3 # Image augmentation library
scikit-learn<=1.1.2 # Machine learning library
utils.py 0 → 100755
This diff is collapsed.
"""Converts YOLO detections to LabelMe format"""
import argparse
import os
import utils
def main(arguments):
"""
Launch the processing on each .json.
:param arguments (args): Argument
"""
if arguments.directory is None:
arguments.directory = arguments.path_to_txt
utils.process_json_files(
arguments.path_to_txt, arguments.path_to_img,
arguments.directory)
if __name__ == "__main__":
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter, description='Convert YOLO annotation to .json file')
parser.add_argument('path_to_txt', type=utils.arg_directory,
help='Path to the folder containing the .txt files')
parser.add_argument('path_to_img', type=utils.arg_directory,
help='Path to the folder containing the .jpg images')
parser.add_argument('-d', '--directory', type=utils.arg_directory, help='Directory to which modified .json files will be stored'
'if no argument, directory will be same as path_to_txt', required=False,
default=None)
args = parser.parse_args()
# Convert the .json file into .txt in the labelme format
os.system(
f'globox convert {args.path_to_txt} {args.directory} --format yolo --save_fmt labelme --img_folder {args.path_to_img}')
main(args)
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment