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.
Files changed (44) hide show
  1. osekit/__init__.py +51 -0
  2. osekit/audio_backend/audio_backend.py +49 -0
  3. osekit/audio_backend/audio_file_manager.py +104 -0
  4. osekit/audio_backend/mseed_backend.py +85 -0
  5. osekit/audio_backend/soundfile_backend.py +84 -0
  6. osekit/config.py +28 -0
  7. osekit/core_api/__init__.py +3 -0
  8. osekit/core_api/audio_data.py +581 -0
  9. osekit/core_api/audio_dataset.py +396 -0
  10. osekit/core_api/audio_file.py +134 -0
  11. osekit/core_api/audio_item.py +66 -0
  12. osekit/core_api/base_data.py +370 -0
  13. osekit/core_api/base_dataset.py +606 -0
  14. osekit/core_api/base_file.py +180 -0
  15. osekit/core_api/base_item.py +84 -0
  16. osekit/core_api/event.py +228 -0
  17. osekit/core_api/frequency_scale.py +232 -0
  18. osekit/core_api/instrument.py +141 -0
  19. osekit/core_api/json_serializer.py +156 -0
  20. osekit/core_api/ltas_data.py +309 -0
  21. osekit/core_api/ltas_dataset.py +105 -0
  22. osekit/core_api/spectro_data.py +1009 -0
  23. osekit/core_api/spectro_dataset.py +776 -0
  24. osekit/core_api/spectro_file.py +169 -0
  25. osekit/core_api/spectro_item.py +77 -0
  26. osekit/logging_config.yaml +36 -0
  27. osekit/logging_context.py +59 -0
  28. osekit/public_api/__init__.py +0 -0
  29. osekit/public_api/analysis.py +181 -0
  30. osekit/public_api/dataset.py +842 -0
  31. osekit/public_api/export_analysis.py +342 -0
  32. osekit/utils/__init__.py +0 -0
  33. osekit/utils/audio_utils.py +204 -0
  34. osekit/utils/core_utils.py +230 -0
  35. osekit/utils/formatting_utils.py +179 -0
  36. osekit/utils/job.py +565 -0
  37. osekit/utils/multiprocess_utils.py +63 -0
  38. osekit/utils/path_utils.py +53 -0
  39. osekit/utils/timestamp_utils.py +256 -0
  40. osekit-0.4.3.dist-info/METADATA +84 -0
  41. osekit-0.4.3.dist-info/RECORD +44 -0
  42. osekit-0.4.3.dist-info/WHEEL +4 -0
  43. osekit-0.4.3.dist-info/entry_points.txt +2 -0
  44. 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
+ }
@@ -0,0 +1,3 @@
1
+ from osekit.audio_backend.audio_file_manager import AudioFileManager
2
+
3
+ audio_file_manager = AudioFileManager()