Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] Core API #245

Draft
wants to merge 121 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
62e4fa9
add file_base module and class
Gautzilla Dec 3, 2024
cab77b1
add AudioFile class
Gautzilla Dec 3, 2024
f777823
add tests for AudioFile class
Gautzilla Dec 3, 2024
2b60db8
add test read_end_file
Gautzilla Dec 3, 2024
b39326e
move data modules to new package
Gautzilla Dec 4, 2024
3d32b3f
add item base and audio classes
Gautzilla Dec 4, 2024
03d414b
add audio sample generation util
Gautzilla Dec 4, 2024
c9b25e5
add AudioItem tests
Gautzilla Dec 4, 2024
cd88fad
add DataBase and AudioData
Gautzilla Dec 4, 2024
e81c384
cap item boundaries
Gautzilla Dec 4, 2024
8dd587c
add is_overlapping function
Gautzilla Dec 4, 2024
56097a9
parse Data over several Files
Gautzilla Dec 4, 2024
49f1bae
black
Gautzilla Dec 4, 2024
6591490
add concatenate item method
Gautzilla Dec 5, 2024
82cfcf6
add docstrings for ItemBase methods
Gautzilla Dec 6, 2024
9f2ae2b
remove in-place item modification
Gautzilla Dec 6, 2024
56a4490
add tests for item concatenation
Gautzilla Dec 6, 2024
592f2aa
add item fill_gaps method
Gautzilla Dec 6, 2024
ce28be2
add resamble util function
Gautzilla Dec 9, 2024
201e6fd
add empty items until data boundaries
Gautzilla Dec 9, 2024
8917403
add total_seconds item property
Gautzilla Dec 9, 2024
0a9896d
add sr and channels properties
Gautzilla Dec 9, 2024
4a4976a
resample audio data on get_value() call
Gautzilla Dec 9, 2024
3a92fc0
add audio_data write method
Gautzilla Dec 9, 2024
227cb0c
rename remove_overlaps item method
Gautzilla Dec 9, 2024
3c5a03f
add AudioData empty filling test
Gautzilla Dec 9, 2024
04ffbfe
add test out_of_range AudioData
Gautzilla Dec 9, 2024
1cfd18f
add tests for resampling sample count
Gautzilla Dec 9, 2024
8ab6169
add docstrings
Gautzilla Dec 9, 2024
b178a4c
use generic types
Gautzilla Dec 11, 2024
9603d78
fix relations between generic and derived classes
Gautzilla Dec 11, 2024
796ea73
add dataset classes
Gautzilla Dec 11, 2024
d4677b6
add docstrings in dataset_base
Gautzilla Dec 12, 2024
dae7b21
add specialized constructors from generics
Gautzilla Dec 12, 2024
bea4aa4
consider default dataset attributes
Gautzilla Dec 13, 2024
bafa2c3
update docstrings
Gautzilla Dec 13, 2024
e10921f
Merge main into new-data-classes
Gautzilla Dec 13, 2024
59badbe
add audiodataset sample_rate property
Gautzilla Dec 13, 2024
6d95f2c
add is_empty property to data base
Gautzilla Dec 13, 2024
8f928e3
rename SthgBase to BaseSthg
Gautzilla Dec 13, 2024
d9db7c6
add dataset write method
Gautzilla Dec 13, 2024
d5c8908
set sample_rate of empty data in dataset
Gautzilla Dec 16, 2024
a2cdc4e
move remove_overlaps to util functions
Gautzilla Dec 16, 2024
447d892
--amend
Gautzilla Dec 16, 2024
ac1d2e2
--amend
Gautzilla Dec 16, 2024
c6b5746
add dataset tests
Gautzilla Dec 16, 2024
1956e06
add overlap utils as data_utils
Gautzilla Dec 16, 2024
7857ca3
add Event base class for all data classes
Gautzilla Dec 16, 2024
5554aa4
move all data_utils stuff to Event class
Gautzilla Dec 16, 2024
7f7b889
linting
Gautzilla Dec 16, 2024
92446ce
move total_seconds property to Event
Gautzilla Dec 16, 2024
1998e52
add kwargs to Event.fill_gaps
Gautzilla Dec 16, 2024
6f27ac2
create output dirs before writing data
Dec 17, 2024
ef8ddcf
add spectro file
Dec 17, 2024
b3471f8
add spectro item
Dec 17, 2024
08c9e3a
replace event.total_seconds by event.duration
Dec 17, 2024
f18277e
add spectro data
Dec 17, 2024
66d22f8
first spectrogram prototype
Gautzilla Dec 19, 2024
416579a
pyplot.Axes as SpectroData property
Gautzilla Jan 7, 2025
7e14991
replace ax property with ax SpectroData.plot() parameter
Gautzilla Jan 7, 2025
6809796
move log part to get_value method
Gautzilla Jan 7, 2025
912f595
write npz and read npz metadata
Gautzilla Jan 8, 2025
24f5a38
data read from spectro file
Gautzilla Jan 8, 2025
07913d2
add SpectroData from SpectroFile logic
Gautzilla Jan 8, 2025
69a61ca
recreate sft on npz file loading
Gautzilla Jan 8, 2025
cb90bec
add mfft to npz files
Gautzilla Jan 9, 2025
0a0c74e
move spectro_item data logic to SpectroItem class
Gautzilla Jan 9, 2025
fa5e647
move fft from npz logic to SpectroFile
Gautzilla Jan 9, 2025
2525c44
spectro data objects pass np.ndarray values
Gautzilla Jan 9, 2025
6b622f2
resolve overlaps between joined npz files
Gautzilla Jan 10, 2025
a35dbd6
fix spectro data shape
Gautzilla Jan 13, 2025
0737750
add SpectroData docstrings
Gautzilla Jan 13, 2025
c90058e
add docstrings to spectro items and files
Gautzilla Jan 13, 2025
e12fdce
Merge branch 'main' into new-data-classes
Gautzilla Jan 14, 2025
e55409d
add dataset files property
Gautzilla Jan 14, 2025
c330f61
add spectro dataset
Gautzilla Jan 14, 2025
cebbc67
split files property between data and dataset
Gautzilla Jan 15, 2025
572c89d
fix empty AudioData sample_rate
Gautzilla Jan 15, 2025
000480f
add data divide method
Gautzilla Jan 15, 2025
3721d51
add spectro data ltas first version
Gautzilla Jan 15, 2025
673df23
add AudioFileManager
Gautzilla Jan 15, 2025
33726bc
add docstrings for the AudioFileManager
Gautzilla Jan 16, 2025
0e6886e
truncate item values at data level
Gautzilla Jan 16, 2025
d82e8ca
add subtype parameter to AudioData write method
Gautzilla Jan 20, 2025
d2afc84
add audio write tests
Gautzilla Jan 20, 2025
de1cf5e
fix audio data read boundary frames
Gautzilla Jan 20, 2025
227af9b
add tests for AudioData split method
Gautzilla Jan 20, 2025
67bde99
compute item shape from file frame_indexes method
Gautzilla Jan 20, 2025
16cd484
return a float AudioDataset.sample_rate if only 1 sr in its data
Gautzilla Jan 20, 2025
ce6fc22
add read test
Gautzilla Jan 21, 2025
3ed8e48
add afm read error tests
Gautzilla Jan 21, 2025
f0a068f
add AudioFileManager tests
Gautzilla Jan 21, 2025
0b391cd
linting
Gautzilla Jan 21, 2025
ff81e6b
fix issue rejecting last frame of audio file
Gautzilla Jan 21, 2025
5c42879
add nb_bytes SpectroData property
Gautzilla Jan 22, 2025
a989b66
ltas to specific class
Gautzilla Jan 22, 2025
d6b4fe5
move tqdm in list comprehension
Gautzilla Jan 22, 2025
bda9f3f
add docstrings for LTASData
Gautzilla Jan 22, 2025
09c2842
add sx as an optional parameter to SpectroData plot and save methods
Gautzilla Jan 23, 2025
0b4d77b
consider both flac and wav audio files
Gautzilla Feb 3, 2025
7fa97b5
add flac loading test
Gautzilla Feb 3, 2025
878ab51
merge main
Gautzilla Feb 4, 2025
f561564
reject non parsable files on audiodataset instantiation
Gautzilla Feb 4, 2025
991ec80
add tests for audio file check in AudioDataset.from_folder
Gautzilla Feb 4, 2025
4686cc3
fix call to BaseData.create_directory in spectrogram export
Gautzilla Feb 6, 2025
dd1075f
Npz file concat (#3)
Gautzilla Feb 11, 2025
b1c1d3c
fix AudioData.split_frames lower frame computation
Gautzilla Feb 11, 2025
b643217
reject DC components before computing sx values
Gautzilla Feb 11, 2025
a6fc135
rename data module to core_api module
Gautzilla Feb 12, 2025
f9f693a
fill empty spectro items with complex zeros
Gautzilla Feb 12, 2025
b971551
fix SpectroData.nb_bytes property to match complex values
Gautzilla Feb 12, 2025
a621718
add sx parameter to SpectroData.save_spectrogram
Gautzilla Feb 12, 2025
c3a8782
add from_audio_data LTASData classmethod
Gautzilla Feb 13, 2025
14c8048
add matrix_dtype SpectroData attribute
Gautzilla Feb 13, 2025
ec978c2
update SpectroData.nb_bytes depending on matrix_dtype
Gautzilla Feb 13, 2025
e7bbef9
convert sx dtype when read from items
Gautzilla Feb 13, 2025
cf7f728
set SpectroData matrix_dtype based on SpectroFile sx_dtype
Gautzilla Feb 13, 2025
5f42db5
replace Enum with simpler class attribute
Gautzilla Feb 13, 2025
637b73a
add property for SpectroData.sx_dtype
Gautzilla Feb 13, 2025
a2abaa2
add tests for spectro and ltas dtypes
Gautzilla Feb 13, 2025
0d357c3
test spectro dtype parsing from npz file
Gautzilla Feb 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,5 @@ select = ["ALL"]
"D", # Docstring-related stuff
"SLF001", # Access to private variables
"BLE001", # Blind exceptions
"PLR0913", # Too many arguments in methods
]
1 change: 0 additions & 1 deletion src/OSmOSE/Spectrogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,6 @@ def initialize(
i_max = -1

for batch in range(self.batch_number):

i_min = i_max + 1
i_max = (
i_min + batch_size
Expand Down
1 change: 1 addition & 0 deletions src/OSmOSE/Weather.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

@author: cazaudo
"""

import itertools
import os
import sys
Expand Down
1 change: 0 additions & 1 deletion src/OSmOSE/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ def _setup_logging(
config_file: FileName = "logging_config.yaml",
default_level: int = logging.INFO,
) -> None:

user_config_file_path = Path(os.getenv("OSMOSE_USER_CONFIG", ".")) / config_file
default_config_file_path = Path(__file__).parent / config_file

Expand Down
2 changes: 0 additions & 2 deletions src/OSmOSE/cluster/audio_reshaper.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,6 @@ def reshape(
result = []
timestamp_list = []
for i in range(batch_ind_max - batch_ind_min + 1):

audio_data = np.zeros(shape=segment_size * new_sr)

if concat:
Expand All @@ -263,7 +262,6 @@ def reshape(

len_sig = 0
for index, row in file_metadata.iterrows():

file_datetime_begin = row["timestamp"]
file_datetime_end = row["timestamp"] + pd.Timedelta(seconds=row["duration"])

Expand Down
2 changes: 2 additions & 0 deletions src/OSmOSE/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
OSMOSE_PATH = namedtuple("path_list", __global_path_dict.keys())(**__global_path_dict)

TIMESTAMP_FORMAT_AUDIO_FILE = "%Y-%m-%dT%H:%M:%S.%f%z"
TIMESTAMP_FORMAT_TEST_FILES = "%y%m%d%H%M%S%f"
TIMESTAMP_FORMAT_EXPORTED_FILES = "%Y_%m_%d_%H_%M_%S_%f"
FPDEFAULT = 0o664 # Default file permissions
DPDEFAULT = stat.S_ISGID | 0o775 # Default directory permissions

Expand Down
3 changes: 3 additions & 0 deletions src/OSmOSE/core_api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from OSmOSE.core_api.audio_file_manager import AudioFileManager

audio_file_manager = AudioFileManager()
265 changes: 265 additions & 0 deletions src/OSmOSE/core_api/audio_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
"""AudioData represent audio data scattered through different AudioFiles.

The AudioData has a collection of AudioItem.
The data is accessed via an AudioItem object per AudioFile.
"""

from __future__ import annotations

from math import ceil
from typing import TYPE_CHECKING

import numpy as np
import soundfile as sf
from pandas import Timedelta

from OSmOSE.config import TIMESTAMP_FORMAT_EXPORTED_FILES
from OSmOSE.core_api.audio_file import AudioFile
from OSmOSE.core_api.audio_item import AudioItem
from OSmOSE.core_api.base_data import BaseData
from OSmOSE.utils.audio_utils import resample

if TYPE_CHECKING:
from pathlib import Path

from pandas import Timestamp


class AudioData(BaseData[AudioItem, AudioFile]):
"""AudioData represent audio data scattered through different AudioFiles.

The AudioData has a collection of AudioItem.
The data is accessed via an AudioItem object per AudioFile.
"""

def __init__(
self,
items: list[AudioItem] | None = None,
begin: Timestamp | None = None,
end: Timestamp | None = None,
sample_rate: int | None = None,
) -> None:
"""Initialize an AudioData from a list of AudioItems.

Parameters
----------
items: list[AudioItem]
List of the AudioItem constituting the AudioData.
sample_rate: int
The sample rate of the audio data.
begin: Timestamp | None
Only effective if items is None.
Set the begin of the empty data.
end: Timestamp | None
Only effective if items is None.
Set the end of the empty data.

"""
super().__init__(items=items, begin=begin, end=end)
self._set_sample_rate(sample_rate=sample_rate)

@property
def nb_channels(self) -> int:
"""Number of channels of the audio data."""
return max(
[1] + [item.nb_channels for item in self.items if type(item) is AudioItem],
)

@property
def shape(self) -> tuple[int, ...] | int:
"""Shape of the audio data."""
data_length = round(self.sample_rate * self.duration.total_seconds())
return data_length if self.nb_channels <= 1 else (data_length, self.nb_channels)

def __str__(self) -> str:
"""Overwrite __str__."""
return self.begin.strftime(TIMESTAMP_FORMAT_EXPORTED_FILES)

def _set_sample_rate(self, sample_rate: int | None = None) -> None:
"""Set the AudioFile sample rate.

If the sample_rate is specified, it is set.
If it is not specified, it is set to the sampling rate of the
first item that has one.
Else, it is set to None.
"""
if sample_rate is not None:
self.sample_rate = sample_rate
return
if sr := next(
(item.sample_rate for item in self.items if item.sample_rate is not None),
None,
):
self.sample_rate = sr
return
self.sample_rate = None

def get_value(self, reject_dc: bool = False) -> np.ndarray:
"""Return the value of the audio data.

The data from the audio file will be resampled if necessary.

Parameters
----------
reject_dc: bool
If True, the values will be centered on 0.

Returns
-------
np.ndarray:
The value of the audio data.

"""
data = np.empty(shape=self.shape)
idx = 0
for item in self.items:
item_data = self._get_item_value(item)
item_data = item_data[: min(item_data.shape[0], data.shape[0] - idx)]
data[idx : idx + len(item_data)] = item_data
idx += len(item_data)
if reject_dc:
data -= data.mean()
return data

def write(self, folder: Path, subtype: str | None = None) -> None:
"""Write the audio data to file.

Parameters
----------
folder: pathlib.Path
Folder in which to write the audio file.
subtype: str | None
Subtype as provided by the soundfile module.
Defaulted as the default 16-bit PCM for WAV audio files.

"""
super().create_directories(path=folder)
sf.write(
folder / f"{self}.wav",
self.get_value(),
self.sample_rate,
subtype=subtype,
)

def _get_item_value(self, item: AudioItem) -> np.ndarray:
"""Return the resampled (if needed) data from the audio item."""
item_data = item.get_value()
if item.is_empty:
return item_data.repeat(
round(item.duration.total_seconds() * self.sample_rate),
)
if item.sample_rate != self.sample_rate:
return resample(item_data, item.sample_rate, self.sample_rate)
return item_data

def split(self, nb_subdata: int = 2) -> list[AudioData]:
"""Split the audio data object in the specified number of audio subdata.

Parameters
----------
nb_subdata: int
Number of subdata in which to split the data.

Returns
-------
list[AudioData]
The list of AudioData subdata objects.

"""
return [
AudioData.from_base_data(base_data, self.sample_rate)
for base_data in super().split(nb_subdata)
]

def split_frames(self, start_frame: int = 0, stop_frame: int = -1) -> AudioData:
"""Return a new AudioData from a subpart of this AudioData's data.

Parameters
----------
start_frame: int
First frame included in the new AudioData.
stop_frame: int
First frame after the last frame included in the new AudioData.

Returns
-------
AudioData
A new AudioData which data is included between start_frame and stop_frame.

"""
if start_frame < 0:
raise ValueError("Start_frame must be greater than or equal to 0.")
if stop_frame < -1 or stop_frame > self.shape:
raise ValueError("Stop_frame must be lower than the length of the data.")

start_timestamp = self.begin + Timedelta(
seconds=ceil(start_frame / self.sample_rate * 1e9) / 1e9,
)
stop_timestamp = (
self.end
if stop_frame == -1
else self.begin + Timedelta(seconds=stop_frame / self.sample_rate)
)
return AudioData.from_files(
list(self.files),
start_timestamp,
stop_timestamp,
sample_rate=self.sample_rate,
)

@classmethod
def from_files(
cls,
files: list[AudioFile],
begin: Timestamp | None = None,
end: Timestamp | None = None,
sample_rate: float | None = None,
) -> AudioData:
"""Return an AudioData object from a list of AudioFiles.

Parameters
----------
files: list[AudioFile]
List of AudioFiles containing the data.
begin: Timestamp | None
Begin of the data object.
Defaulted to the begin of the first file.
end: Timestamp | None
End of the data object.
Defaulted to the end of the last file.
sample_rate: float | None
Sample rate of the AudioData.

Returns
-------
AudioData:
The AudioData object.

"""
return cls.from_base_data(BaseData.from_files(files, begin, end), sample_rate)

@classmethod
def from_base_data(
cls,
data: BaseData,
sample_rate: float | None = None,
) -> AudioData:
"""Return an AudioData object from a BaseData object.

Parameters
----------
data: BaseData
BaseData object to convert to AudioData.
sample_rate: float | None
Sample rate of the AudioData.

Returns
-------
AudioData:
The AudioData object.

"""
return cls(
items=[AudioItem.from_base_item(item) for item in data.items],
sample_rate=sample_rate,
)
Loading