osekit 0.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- osekit/__init__.py +51 -0
- osekit/audio_backend/audio_backend.py +49 -0
- osekit/audio_backend/audio_file_manager.py +104 -0
- osekit/audio_backend/mseed_backend.py +85 -0
- osekit/audio_backend/soundfile_backend.py +84 -0
- osekit/config.py +28 -0
- osekit/core_api/__init__.py +3 -0
- osekit/core_api/audio_data.py +581 -0
- osekit/core_api/audio_dataset.py +396 -0
- osekit/core_api/audio_file.py +134 -0
- osekit/core_api/audio_item.py +66 -0
- osekit/core_api/base_data.py +370 -0
- osekit/core_api/base_dataset.py +606 -0
- osekit/core_api/base_file.py +180 -0
- osekit/core_api/base_item.py +84 -0
- osekit/core_api/event.py +228 -0
- osekit/core_api/frequency_scale.py +232 -0
- osekit/core_api/instrument.py +141 -0
- osekit/core_api/json_serializer.py +156 -0
- osekit/core_api/ltas_data.py +309 -0
- osekit/core_api/ltas_dataset.py +105 -0
- osekit/core_api/spectro_data.py +1009 -0
- osekit/core_api/spectro_dataset.py +776 -0
- osekit/core_api/spectro_file.py +169 -0
- osekit/core_api/spectro_item.py +77 -0
- osekit/logging_config.yaml +36 -0
- osekit/logging_context.py +59 -0
- osekit/public_api/__init__.py +0 -0
- osekit/public_api/analysis.py +181 -0
- osekit/public_api/dataset.py +842 -0
- osekit/public_api/export_analysis.py +342 -0
- osekit/utils/__init__.py +0 -0
- osekit/utils/audio_utils.py +204 -0
- osekit/utils/core_utils.py +230 -0
- osekit/utils/formatting_utils.py +179 -0
- osekit/utils/job.py +565 -0
- osekit/utils/multiprocess_utils.py +63 -0
- osekit/utils/path_utils.py +53 -0
- osekit/utils/timestamp_utils.py +256 -0
- osekit-0.4.3.dist-info/METADATA +84 -0
- osekit-0.4.3.dist-info/RECORD +44 -0
- osekit-0.4.3.dist-info/WHEEL +4 -0
- osekit-0.4.3.dist-info/entry_points.txt +2 -0
- osekit-0.4.3.dist-info/licenses/LICENSE +0 -0
osekit/__init__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging.config
|
|
4
|
+
import os.path
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from osekit import utils
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"setup_logging",
|
|
13
|
+
"utils",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def setup_logging(
|
|
18
|
+
config_file: str | Path = "logging_config.yaml",
|
|
19
|
+
default_level: int = logging.INFO,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""Configure logger using a configuration yaml file.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
config_file: str | Path
|
|
26
|
+
Path to a logging configuration file
|
|
27
|
+
default_level: int
|
|
28
|
+
Logging level to use
|
|
29
|
+
Default value, `logging.INFO`
|
|
30
|
+
|
|
31
|
+
"""
|
|
32
|
+
user_config_file_path = Path(os.getenv("OSMOSE_USER_CONFIG", ".")) / config_file
|
|
33
|
+
default_config_file_path = Path(__file__).parent / config_file
|
|
34
|
+
|
|
35
|
+
config_file_path = next(
|
|
36
|
+
(
|
|
37
|
+
file
|
|
38
|
+
for file in (user_config_file_path, default_config_file_path)
|
|
39
|
+
if file.exists()
|
|
40
|
+
),
|
|
41
|
+
None,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if config_file_path:
|
|
45
|
+
with Path.open(config_file_path) as configuration:
|
|
46
|
+
logging_config = yaml.safe_load(configuration)
|
|
47
|
+
logging.config.dictConfig(logging_config)
|
|
48
|
+
else:
|
|
49
|
+
logging.basicConfig(level=default_level)
|
|
50
|
+
msg = "Configuration file not found, using default configuration."
|
|
51
|
+
logging.getLogger(__name__).warning(msg)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from os import PathLike
|
|
2
|
+
from typing import Protocol
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AudioBackend(Protocol):
|
|
8
|
+
def info(self, path: PathLike | str) -> tuple[int, int, int]:
|
|
9
|
+
"""Return the sample rate, number of frames and channels of the audio file.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
path: PathLike | str
|
|
14
|
+
Path to the audio file.
|
|
15
|
+
|
|
16
|
+
Returns
|
|
17
|
+
-------
|
|
18
|
+
tuple[int,int,int]:
|
|
19
|
+
Sample rate, number of frames and channels of the audio file.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
def read(self, path: PathLike | str, start: int, stop: int) -> np.ndarray:
|
|
25
|
+
"""Read the content of an audio file.
|
|
26
|
+
|
|
27
|
+
If the audio file is not the current opened file,
|
|
28
|
+
the current opened file is switched.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
path: PathLike | str
|
|
33
|
+
Path to the audio file.
|
|
34
|
+
start: int
|
|
35
|
+
First frame to read.
|
|
36
|
+
stop: int
|
|
37
|
+
Frame after the last frame to read.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
np.ndarray:
|
|
42
|
+
A ``(channel * frames)`` array containing the audio data.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
def close(self) -> None:
|
|
48
|
+
"""Close the currently opened file."""
|
|
49
|
+
...
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Audio File Manager which keeps an audio file open until a request in another file is made.
|
|
2
|
+
|
|
3
|
+
This workflow avoids closing/opening a same file repeatedly.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from osekit.audio_backend.mseed_backend import MSeedBackend
|
|
12
|
+
from osekit.audio_backend.soundfile_backend import SoundFileBackend
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from os import PathLike
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AudioFileManager:
|
|
21
|
+
"""Audio File Manager which keeps an audio file open until a request in another file is made."""
|
|
22
|
+
|
|
23
|
+
def __init__(self) -> None:
|
|
24
|
+
"""Initialize an audio file manager."""
|
|
25
|
+
self._soundfile = SoundFileBackend()
|
|
26
|
+
self._mseed: MSeedBackend | None = None
|
|
27
|
+
|
|
28
|
+
def close(self) -> None:
|
|
29
|
+
"""Close the currently opened file."""
|
|
30
|
+
self._soundfile.close()
|
|
31
|
+
if self._mseed:
|
|
32
|
+
self._mseed.close()
|
|
33
|
+
|
|
34
|
+
def _backend(self, path: PathLike | str) -> SoundFileBackend | MSeedBackend:
|
|
35
|
+
suffix = Path(path).suffix.lower()
|
|
36
|
+
|
|
37
|
+
if suffix == ".mseed":
|
|
38
|
+
if self._mseed is None:
|
|
39
|
+
self._mseed = MSeedBackend()
|
|
40
|
+
return self._mseed
|
|
41
|
+
|
|
42
|
+
return self._soundfile
|
|
43
|
+
|
|
44
|
+
def info(self, path: PathLike | str) -> tuple[int, int, int]:
|
|
45
|
+
"""Return the sample rate, number of frames and channels of the audio file.
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
path: PathLike | str
|
|
50
|
+
Path to the audio file.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
tuple[int,int,int]:
|
|
55
|
+
Sample rate, number of frames and channels of the audio file.
|
|
56
|
+
|
|
57
|
+
"""
|
|
58
|
+
return self._backend(path).info(path)
|
|
59
|
+
|
|
60
|
+
def read(
|
|
61
|
+
self,
|
|
62
|
+
path: PathLike | str,
|
|
63
|
+
start: int = 0,
|
|
64
|
+
stop: int | None = None,
|
|
65
|
+
) -> np.ndarray:
|
|
66
|
+
"""Read the content of an audio file.
|
|
67
|
+
|
|
68
|
+
If the audio file is not the current opened file,
|
|
69
|
+
the current opened file is switched.
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
path: PathLike | str
|
|
74
|
+
Path to the audio file.
|
|
75
|
+
start: int
|
|
76
|
+
First frame to read.
|
|
77
|
+
stop: int | None
|
|
78
|
+
Frame after the last frame to read.
|
|
79
|
+
|
|
80
|
+
Returns
|
|
81
|
+
-------
|
|
82
|
+
np.ndarray:
|
|
83
|
+
A ``(channel * frames)`` array containing the audio data.
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
_, frames, _ = self.info(path)
|
|
87
|
+
|
|
88
|
+
if stop is None:
|
|
89
|
+
stop = frames
|
|
90
|
+
|
|
91
|
+
if stop is None:
|
|
92
|
+
stop = frames
|
|
93
|
+
|
|
94
|
+
if not 0 <= start < frames:
|
|
95
|
+
msg = "Start should be between 0 and the last frame of the audio file."
|
|
96
|
+
raise ValueError(msg)
|
|
97
|
+
if not 0 <= stop <= frames:
|
|
98
|
+
msg = "Stop should be between 0 and the last frame of the audio file."
|
|
99
|
+
raise ValueError(msg)
|
|
100
|
+
if start > stop:
|
|
101
|
+
msg = "Start should be inferior to Stop."
|
|
102
|
+
raise ValueError(msg)
|
|
103
|
+
|
|
104
|
+
return self._backend(path).read(path=path, start=start, stop=stop)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from os import PathLike
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _require_obspy() -> None:
|
|
7
|
+
try:
|
|
8
|
+
import obspy # noqa: PLC0415, F401
|
|
9
|
+
except ImportError as e:
|
|
10
|
+
msg = "MSEED support requires the optional dependency 'obspy' "
|
|
11
|
+
"Install with: ``pip install osekit[mseed]``. "
|
|
12
|
+
"If you're on windows and don't use conda, may the force be with you."
|
|
13
|
+
raise ImportError(msg) from e
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MSeedBackend:
|
|
17
|
+
"""Backend for reading sismology MSEED files."""
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
"""Initialize the MSEED backend."""
|
|
21
|
+
_require_obspy()
|
|
22
|
+
|
|
23
|
+
def close(self) -> None:
|
|
24
|
+
"""Close the currently opened file. No use in MSEED files."""
|
|
25
|
+
|
|
26
|
+
def info(self, path: PathLike | str) -> tuple[int, int, int]:
|
|
27
|
+
"""Return the sample rate, number of frames and channels of the MSEED file.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
path: PathLike | str
|
|
32
|
+
Path to the audio file.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
tuple[int,int,int]:
|
|
37
|
+
Sample rate, number of frames and channels of the MSEED file.
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
_require_obspy()
|
|
41
|
+
import obspy # type: ignore[import-not-found]
|
|
42
|
+
|
|
43
|
+
metadata = obspy.read(pathname_or_url=path, headonly=True)
|
|
44
|
+
sample_rate = {trace.meta.sampling_rate for trace in metadata.traces}
|
|
45
|
+
if len(sample_rate) != 1:
|
|
46
|
+
msg = "Inconsistent sampling rates in MSEED file."
|
|
47
|
+
raise ValueError(msg)
|
|
48
|
+
|
|
49
|
+
frames = sum(trace.meta.npts for trace in metadata.traces)
|
|
50
|
+
return (
|
|
51
|
+
int(sample_rate.pop()),
|
|
52
|
+
frames,
|
|
53
|
+
1,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def read(
|
|
57
|
+
self,
|
|
58
|
+
path: PathLike | str,
|
|
59
|
+
start: int = 0,
|
|
60
|
+
stop: int | None = None,
|
|
61
|
+
) -> np.ndarray:
|
|
62
|
+
"""Read the content of a MSEED file.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
path: PathLike | str
|
|
67
|
+
Path to the audio file.
|
|
68
|
+
start: int
|
|
69
|
+
First frame to read.
|
|
70
|
+
stop: int
|
|
71
|
+
Frame after the last frame to read.
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
np.ndarray:
|
|
76
|
+
A ``(channel * frames)`` array containing the MSEED data.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
_require_obspy()
|
|
80
|
+
import obspy # type: ignore[import-not-found] # noqa: PLC0415
|
|
81
|
+
|
|
82
|
+
file_content = obspy.read(path)
|
|
83
|
+
|
|
84
|
+
data = np.concatenate([trace.data for trace in file_content])
|
|
85
|
+
return data[start:stop]
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from os import PathLike
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import soundfile as sf
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SoundFileBackend:
|
|
8
|
+
"""Backend for reading conventional audio files (WAV, FLAC, MP3...)."""
|
|
9
|
+
|
|
10
|
+
def __init__(self) -> None:
|
|
11
|
+
"""Instantiate a SoundFileBackend."""
|
|
12
|
+
self._file: sf.SoundFile | None = None
|
|
13
|
+
|
|
14
|
+
def close(self) -> None:
|
|
15
|
+
"""Close the currently opened file."""
|
|
16
|
+
if self._file is None:
|
|
17
|
+
return
|
|
18
|
+
self._file.close()
|
|
19
|
+
self._file = None
|
|
20
|
+
|
|
21
|
+
def info(self, path: PathLike | str) -> tuple[int, int, int]:
|
|
22
|
+
"""Return the sample rate, number of frames and channels of the audio file.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
path: PathLike | str
|
|
27
|
+
Path to the audio file.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
tuple[int,int,int]:
|
|
32
|
+
Sample rate, number of frames and channels of the audio file.
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
self._switch(path)
|
|
36
|
+
return (
|
|
37
|
+
self._file.samplerate,
|
|
38
|
+
self._file.frames,
|
|
39
|
+
self._file.channels,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def read(
|
|
43
|
+
self,
|
|
44
|
+
path: PathLike | str,
|
|
45
|
+
start: int = 0,
|
|
46
|
+
stop: int | None = None,
|
|
47
|
+
) -> np.ndarray:
|
|
48
|
+
"""Read the content of an audio file.
|
|
49
|
+
|
|
50
|
+
If the audio file is not the current opened file,
|
|
51
|
+
the current opened file is switched.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
path: PathLike | str
|
|
56
|
+
Path to the audio file.
|
|
57
|
+
start: int
|
|
58
|
+
First frame to read.
|
|
59
|
+
stop: int
|
|
60
|
+
Frame after the last frame to read.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
np.ndarray:
|
|
65
|
+
A ``(channel * frames)`` array containing the audio data.
|
|
66
|
+
|
|
67
|
+
"""
|
|
68
|
+
self._switch(path)
|
|
69
|
+
self._file.seek(start)
|
|
70
|
+
return self._file.read(stop - start)
|
|
71
|
+
|
|
72
|
+
def _close(self) -> None:
|
|
73
|
+
if self._file is None:
|
|
74
|
+
return
|
|
75
|
+
self._file.close()
|
|
76
|
+
self._file = None
|
|
77
|
+
|
|
78
|
+
def _open(self, path: PathLike | str) -> None:
|
|
79
|
+
self._file = sf.SoundFile(path, "r")
|
|
80
|
+
|
|
81
|
+
def _switch(self, path: PathLike | str) -> None:
|
|
82
|
+
if self._file is None or self._file.name != str(path):
|
|
83
|
+
self._close()
|
|
84
|
+
self._open(path)
|
osekit/config.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import stat
|
|
3
|
+
|
|
4
|
+
from osekit.logging_context import LoggingContext
|
|
5
|
+
|
|
6
|
+
TIMESTAMP_FORMAT_AUDIO_FILE = "%Y-%m-%dT%H:%M:%S.%f%z"
|
|
7
|
+
TIMESTAMP_FORMAT_EXPORTED_FILES_UNLOCALIZED = "%Y_%m_%d_%H_%M_%S_%f"
|
|
8
|
+
TIMESTAMP_FORMAT_EXPORTED_FILES_LOCALIZED = "%Y_%m_%d_%H_%M_%S_%f%z"
|
|
9
|
+
TIMESTAMP_FORMATS_EXPORTED_FILES = [
|
|
10
|
+
TIMESTAMP_FORMAT_EXPORTED_FILES_LOCALIZED,
|
|
11
|
+
TIMESTAMP_FORMAT_EXPORTED_FILES_UNLOCALIZED,
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
FPDEFAULT = 0o664 # Default file permissions
|
|
15
|
+
DPDEFAULT = stat.S_ISGID | 0o775 # Default directory permissions
|
|
16
|
+
|
|
17
|
+
global_logging_context = LoggingContext()
|
|
18
|
+
print_logger = logging.getLogger("printer")
|
|
19
|
+
|
|
20
|
+
resample_quality_settings = {
|
|
21
|
+
"downsample": "QQ",
|
|
22
|
+
"upsample": "MQ",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
multiprocessing = {
|
|
26
|
+
"is_active": False,
|
|
27
|
+
"nb_processes": None,
|
|
28
|
+
}
|