spectre-core 0.0.12__py3-none-any.whl → 0.0.14__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.
- spectre_core/_file_io/__init__.py +1 -3
- spectre_core/_file_io/file_handlers.py +163 -58
- spectre_core/batches/__init__.py +10 -11
- spectre_core/batches/_base.py +170 -78
- spectre_core/batches/_batches.py +149 -99
- spectre_core/batches/_factory.py +56 -14
- spectre_core/batches/_register.py +23 -8
- spectre_core/batches/plugins/_batch_keys.py +16 -0
- spectre_core/batches/plugins/_callisto.py +183 -0
- spectre_core/batches/plugins/_iq_stream.py +354 -0
- spectre_core/capture_configs/__init__.py +17 -13
- spectre_core/capture_configs/_capture_config.py +93 -34
- spectre_core/capture_configs/_capture_modes.py +22 -0
- spectre_core/capture_configs/_capture_templates.py +207 -122
- spectre_core/capture_configs/_parameters.py +115 -42
- spectre_core/capture_configs/_pconstraints.py +86 -35
- spectre_core/capture_configs/_pnames.py +49 -0
- spectre_core/capture_configs/_ptemplates.py +389 -346
- spectre_core/capture_configs/_pvalidators.py +117 -73
- spectre_core/config/__init__.py +6 -8
- spectre_core/config/_paths.py +65 -25
- spectre_core/config/_time_formats.py +15 -10
- spectre_core/exceptions.py +2 -4
- spectre_core/jobs/__init__.py +14 -0
- spectre_core/jobs/_jobs.py +111 -0
- spectre_core/jobs/_workers.py +171 -0
- spectre_core/logs/__init__.py +17 -0
- spectre_core/logs/_configure.py +67 -0
- spectre_core/logs/_decorators.py +33 -0
- spectre_core/logs/_logs.py +228 -0
- spectre_core/logs/_process_types.py +14 -0
- spectre_core/plotting/__init__.py +4 -2
- spectre_core/plotting/_base.py +204 -102
- spectre_core/plotting/_format.py +17 -4
- spectre_core/plotting/_panel_names.py +18 -0
- spectre_core/plotting/_panel_stack.py +167 -53
- spectre_core/plotting/_panels.py +341 -141
- spectre_core/post_processing/__init__.py +8 -6
- spectre_core/post_processing/_base.py +70 -44
- spectre_core/post_processing/_factory.py +42 -12
- spectre_core/post_processing/_post_processor.py +24 -26
- spectre_core/post_processing/_register.py +22 -6
- spectre_core/post_processing/plugins/_event_handler_keys.py +16 -0
- spectre_core/post_processing/plugins/_fixed_center_frequency.py +129 -0
- spectre_core/post_processing/{library → plugins}/_swept_center_frequency.py +215 -143
- spectre_core/py.typed +0 -0
- spectre_core/receivers/__init__.py +10 -7
- spectre_core/receivers/_base.py +220 -69
- spectre_core/receivers/_factory.py +53 -7
- spectre_core/receivers/_register.py +30 -9
- spectre_core/receivers/_spec_names.py +26 -15
- spectre_core/receivers/plugins/__init__.py +0 -0
- spectre_core/receivers/plugins/_receiver_names.py +16 -0
- spectre_core/receivers/plugins/_rsp1a.py +59 -0
- spectre_core/receivers/plugins/_rspduo.py +67 -0
- spectre_core/receivers/plugins/_sdrplay_receiver.py +190 -0
- spectre_core/receivers/plugins/_test.py +218 -0
- spectre_core/receivers/plugins/gr/_base.py +80 -0
- spectre_core/receivers/{gr → plugins/gr}/_rsp1a.py +42 -52
- spectre_core/receivers/{gr → plugins/gr}/_rspduo.py +61 -74
- spectre_core/receivers/{gr → plugins/gr}/_test.py +33 -31
- spectre_core/spectrograms/__init__.py +5 -3
- spectre_core/spectrograms/_analytical.py +121 -66
- spectre_core/spectrograms/_array_operations.py +103 -36
- spectre_core/spectrograms/_spectrogram.py +380 -207
- spectre_core/spectrograms/_transform.py +197 -169
- spectre_core/wgetting/__init__.py +4 -2
- spectre_core/wgetting/_callisto.py +173 -118
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.14.dist-info}/METADATA +14 -7
- spectre_core-0.0.14.dist-info/RECORD +75 -0
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.14.dist-info}/WHEEL +1 -1
- spectre_core/batches/library/_callisto.py +0 -96
- spectre_core/batches/library/_fixed_center_frequency.py +0 -133
- spectre_core/batches/library/_swept_center_frequency.py +0 -105
- spectre_core/logging/__init__.py +0 -11
- spectre_core/logging/_configure.py +0 -35
- spectre_core/logging/_decorators.py +0 -19
- spectre_core/logging/_log_handlers.py +0 -176
- spectre_core/post_processing/library/_fixed_center_frequency.py +0 -114
- spectre_core/receivers/gr/_base.py +0 -33
- spectre_core/receivers/library/_rsp1a.py +0 -61
- spectre_core/receivers/library/_rspduo.py +0 -69
- spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
- spectre_core/receivers/library/_test.py +0 -221
- spectre_core-0.0.12.dist-info/RECORD +0 -64
- /spectre_core/receivers/{gr → plugins/gr}/__init__.py +0 -0
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.14.dist-info}/LICENSE +0 -0
- {spectre_core-0.0.12.dist-info → spectre_core-0.0.14.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,171 @@
|
|
1
|
+
# SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
2
|
+
# This file is part of SPECTRE
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
|
+
|
5
|
+
from logging import getLogger
|
6
|
+
_LOGGER = getLogger(__name__)
|
7
|
+
|
8
|
+
from functools import wraps
|
9
|
+
import time
|
10
|
+
from typing import Callable, TypeVar, ParamSpec
|
11
|
+
import multiprocessing
|
12
|
+
|
13
|
+
from spectre_core.logs import configure_root_logger, log_call, ProcessType
|
14
|
+
from spectre_core.capture_configs import CaptureConfig
|
15
|
+
from spectre_core.receivers import get_receiver, ReceiverName
|
16
|
+
from spectre_core.post_processing import start_post_processor
|
17
|
+
|
18
|
+
|
19
|
+
def _make_daemon_process(
|
20
|
+
name: str,
|
21
|
+
target: Callable[[], None]
|
22
|
+
) -> multiprocessing.Process:
|
23
|
+
"""
|
24
|
+
Creates and returns a daemon `multiprocessing.Process` instance.
|
25
|
+
|
26
|
+
:param name: The name to assign to the process.
|
27
|
+
:param target: The function to execute in the process.
|
28
|
+
:return: A `multiprocessing.Process` instance configured as a daemon.
|
29
|
+
"""
|
30
|
+
return multiprocessing.Process(target=target,
|
31
|
+
name=name,
|
32
|
+
daemon=True)
|
33
|
+
|
34
|
+
|
35
|
+
class Worker:
|
36
|
+
"""A lightweight wrapper for a `multiprocessing.Process` daemon.
|
37
|
+
|
38
|
+
Provides a very simple API to start, and restart a multiprocessing process.
|
39
|
+
"""
|
40
|
+
def __init__(
|
41
|
+
self,
|
42
|
+
name: str,
|
43
|
+
target: Callable[[], None]
|
44
|
+
) -> None:
|
45
|
+
"""Initialise a `Worker` instance.
|
46
|
+
|
47
|
+
:param name: The name assigned to the process.
|
48
|
+
:param target: The callable to be executed by the worker process.
|
49
|
+
"""
|
50
|
+
self._name = name
|
51
|
+
self._target = target
|
52
|
+
self._process = _make_daemon_process(name, target)
|
53
|
+
|
54
|
+
|
55
|
+
@property
|
56
|
+
def name(
|
57
|
+
self
|
58
|
+
) -> str:
|
59
|
+
"""Get the name of the worker process.
|
60
|
+
|
61
|
+
:return: The name of the multiprocessing process.
|
62
|
+
"""
|
63
|
+
return self._process.name
|
64
|
+
|
65
|
+
|
66
|
+
@property
|
67
|
+
def process(
|
68
|
+
self
|
69
|
+
) -> multiprocessing.Process:
|
70
|
+
"""Access the underlying multiprocessing process.
|
71
|
+
|
72
|
+
:return: The wrapped `multiprocessing.Process` instance.
|
73
|
+
"""
|
74
|
+
return self._process
|
75
|
+
|
76
|
+
|
77
|
+
def start(
|
78
|
+
self
|
79
|
+
) -> None:
|
80
|
+
"""Start the worker process.
|
81
|
+
|
82
|
+
This method runs the `target` in the background as a daemon.
|
83
|
+
"""
|
84
|
+
self._process.start()
|
85
|
+
|
86
|
+
|
87
|
+
def restart(
|
88
|
+
self
|
89
|
+
) -> None:
|
90
|
+
"""Restart the worker process.
|
91
|
+
|
92
|
+
Terminates the existing process if it is alive and then starts a new process
|
93
|
+
after a brief pause.
|
94
|
+
"""
|
95
|
+
_LOGGER.info(f"Restarting {self.name} worker")
|
96
|
+
if self._process.is_alive():
|
97
|
+
# forcibly stop if it is still alive
|
98
|
+
self._process.terminate()
|
99
|
+
self._process.join()
|
100
|
+
# a moment of respite
|
101
|
+
time.sleep(1)
|
102
|
+
# make a new process, as we can't start the same process again.
|
103
|
+
self._process = _make_daemon_process(self._name, self._target)
|
104
|
+
self.start()
|
105
|
+
|
106
|
+
|
107
|
+
P = ParamSpec("P")
|
108
|
+
T = TypeVar("T", bound=Callable[..., None])
|
109
|
+
def make_worker(
|
110
|
+
name: str
|
111
|
+
) -> Callable[[Callable[P, None]], Callable[P, Worker]]:
|
112
|
+
"""
|
113
|
+
Turns a function into a worker.
|
114
|
+
|
115
|
+
This decorator wraps a function, allowing it to run in a separate process
|
116
|
+
managed by a `Worker` object. Use it to easily create long-running or
|
117
|
+
isolated tasks without directly handling multiprocessing.
|
118
|
+
|
119
|
+
:param name: A human-readable name for the worker process.
|
120
|
+
:return: A decorator that creates a `Worker` to run the function in its own process.
|
121
|
+
"""
|
122
|
+
|
123
|
+
def decorator(
|
124
|
+
func: Callable[P, None]
|
125
|
+
) -> Callable[P, Worker]:
|
126
|
+
@wraps(func)
|
127
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> Worker:
|
128
|
+
# Worker target funcs must have no arguments
|
129
|
+
def target():
|
130
|
+
configure_root_logger(ProcessType.WORKER)
|
131
|
+
func(*args, **kwargs)
|
132
|
+
return Worker(name, target)
|
133
|
+
return wrapper
|
134
|
+
return decorator
|
135
|
+
|
136
|
+
|
137
|
+
@make_worker("capture")
|
138
|
+
@log_call
|
139
|
+
def do_capture(
|
140
|
+
tag: str,
|
141
|
+
) -> None:
|
142
|
+
"""Start capturing data from an SDR in real time.
|
143
|
+
|
144
|
+
:param tag: The capture config tag.
|
145
|
+
"""
|
146
|
+
_LOGGER.info((f"Reading capture config with tag '{tag}'"))
|
147
|
+
|
148
|
+
# load the receiver and mode from the capture config file
|
149
|
+
capture_config = CaptureConfig(tag)
|
150
|
+
|
151
|
+
_LOGGER.info((f"Starting capture with the receiver '{capture_config.receiver_name}' "
|
152
|
+
f"operating in mode '{capture_config.receiver_mode}' "
|
153
|
+
f"with tag '{tag}'"))
|
154
|
+
|
155
|
+
name = ReceiverName( capture_config.receiver_name )
|
156
|
+
receiver = get_receiver(name,
|
157
|
+
capture_config.receiver_mode)
|
158
|
+
receiver.start_capture(tag)
|
159
|
+
|
160
|
+
|
161
|
+
@make_worker("post_processing")
|
162
|
+
@log_call
|
163
|
+
def do_post_processing(
|
164
|
+
tag: str,
|
165
|
+
) -> None:
|
166
|
+
"""Start post processing SDR data into spectrograms in real time.
|
167
|
+
|
168
|
+
:param tag: The capture config tag.
|
169
|
+
"""
|
170
|
+
_LOGGER.info(f"Starting post processor with tag '{tag}'")
|
171
|
+
start_post_processor(tag)
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
2
|
+
# This file is part of SPECTRE
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
|
+
|
5
|
+
|
6
|
+
"""`spectre` logging configurations."""
|
7
|
+
|
8
|
+
from ._process_types import ProcessType
|
9
|
+
from ._decorators import log_call
|
10
|
+
from ._configure import configure_root_logger, get_root_logger_state
|
11
|
+
from ._logs import Log, Logs
|
12
|
+
|
13
|
+
|
14
|
+
__all__ = [
|
15
|
+
"log_call", "configure_root_logger", "Log", "Logs", "ProcessType",
|
16
|
+
"get_root_logger_state"
|
17
|
+
]
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
2
|
+
# This file is part of SPECTRE
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
|
+
|
5
|
+
import os
|
6
|
+
import logging
|
7
|
+
from typing import Tuple
|
8
|
+
from datetime import datetime
|
9
|
+
|
10
|
+
from spectre_core.config import TimeFormat
|
11
|
+
from ._logs import Log
|
12
|
+
from ._process_types import ProcessType
|
13
|
+
|
14
|
+
|
15
|
+
def configure_root_logger(
|
16
|
+
process_type: ProcessType,
|
17
|
+
level: int = logging.INFO
|
18
|
+
) -> str:
|
19
|
+
"""Configures the root logger to write logs to a file named based on
|
20
|
+
the process type, process ID, and the current system time.
|
21
|
+
|
22
|
+
:param process_type: Indicates the type of process, as defined by `ProcessType`.
|
23
|
+
:param level: The logging level, as defined in Python's `logging` module. Defaults to `logging.INFO`.
|
24
|
+
:return: The file path of the created log file.
|
25
|
+
"""
|
26
|
+
# create a `spectre` log handler instance, to represent the log file.
|
27
|
+
# get the star time of the log
|
28
|
+
system_datetime = datetime.now()
|
29
|
+
start_time = system_datetime.strftime(TimeFormat.DATETIME)
|
30
|
+
|
31
|
+
# extract the process identifier, and cast as a string
|
32
|
+
pid = str( os.getpid() )
|
33
|
+
|
34
|
+
# create a file handler representing the log file
|
35
|
+
log = Log(start_time,
|
36
|
+
pid,
|
37
|
+
process_type)
|
38
|
+
log.make_parent_dir_path()
|
39
|
+
|
40
|
+
# get the root logger and set its level.
|
41
|
+
logger = logging.getLogger()
|
42
|
+
logger.setLevel(level)
|
43
|
+
|
44
|
+
# remove existing handlers
|
45
|
+
for handler in logger.handlers:
|
46
|
+
logger.removeHandler(handler)
|
47
|
+
|
48
|
+
# Set up a file handler and add it to the root logger
|
49
|
+
file_handler = logging.FileHandler(log.file_path)
|
50
|
+
file_handler.setLevel(level)
|
51
|
+
formatter = logging.Formatter("[%(asctime)s] [%(levelname)8s] --- %(message)s (%(name)s:%(lineno)s)")
|
52
|
+
file_handler.setFormatter(formatter)
|
53
|
+
logger.addHandler(file_handler)
|
54
|
+
|
55
|
+
return log.file_path
|
56
|
+
|
57
|
+
|
58
|
+
def get_root_logger_state(
|
59
|
+
) -> Tuple[bool, int]:
|
60
|
+
"""Get the state of the root logger.
|
61
|
+
|
62
|
+
:return: Whether the root logger has any handlers, and the level of the root logger.
|
63
|
+
"""
|
64
|
+
root_logger = logging.getLogger()
|
65
|
+
if root_logger.handlers:
|
66
|
+
return True, root_logger.level
|
67
|
+
return False, logging.NOTSET
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
2
|
+
# This file is part of SPECTRE
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
|
+
|
5
|
+
import logging
|
6
|
+
from typing import Callable, TypeVar, ParamSpec
|
7
|
+
from functools import wraps
|
8
|
+
|
9
|
+
# ParamSpec for capturing the argument types of the function
|
10
|
+
P = ParamSpec("P")
|
11
|
+
# TypeVar for capturing the return type of the function
|
12
|
+
RT = TypeVar("RT")
|
13
|
+
def log_call(
|
14
|
+
func: Callable[P, RT]
|
15
|
+
) -> Callable[P, RT]:
|
16
|
+
"""Decorator to log the execution of a function.
|
17
|
+
|
18
|
+
Logs an informational message when the decorated function is called,
|
19
|
+
and an error message if the function raises an exception.
|
20
|
+
|
21
|
+
:param func: The function to be decorated.
|
22
|
+
:return: The decorated function with added logging behaviour.
|
23
|
+
"""
|
24
|
+
@wraps(func)
|
25
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> RT:
|
26
|
+
logger = logging.getLogger(func.__module__)
|
27
|
+
try:
|
28
|
+
logger.info(f"Calling the function: {func.__name__}")
|
29
|
+
return func(*args, **kwargs)
|
30
|
+
except Exception as e:
|
31
|
+
logger.error(f"Error in function: {func.__name__}", exc_info=True)
|
32
|
+
raise
|
33
|
+
return wrapper
|
@@ -0,0 +1,228 @@
|
|
1
|
+
# SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
2
|
+
# This file is part of SPECTRE
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
|
+
|
5
|
+
from logging import getLogger
|
6
|
+
_LOGGER = getLogger(__name__)
|
7
|
+
|
8
|
+
import os
|
9
|
+
from typing import Optional, Iterator
|
10
|
+
from collections import OrderedDict
|
11
|
+
from datetime import datetime
|
12
|
+
|
13
|
+
from spectre_core._file_io import TextHandler
|
14
|
+
from spectre_core.config import get_logs_dir_path, TimeFormat
|
15
|
+
from ._process_types import ProcessType
|
16
|
+
|
17
|
+
class Log(TextHandler):
|
18
|
+
"""Interface to read log files generated by `spectre`."""
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
start_time: str,
|
22
|
+
pid: str,
|
23
|
+
process_type: ProcessType
|
24
|
+
) -> None:
|
25
|
+
"""Initialise a `Log` instance.
|
26
|
+
|
27
|
+
:param start_time: The timestamp when the log file was created.
|
28
|
+
:param pid: The ID of the process writing to the log file.
|
29
|
+
:param process_type: Indicates the type of process, as defined by `ProcessType`.
|
30
|
+
"""
|
31
|
+
self._start_time = start_time
|
32
|
+
self._pid = pid
|
33
|
+
self._process_type = process_type.value
|
34
|
+
|
35
|
+
dt = datetime.strptime(start_time, TimeFormat.DATETIME)
|
36
|
+
parent_path = get_logs_dir_path(dt.year, dt.month, dt.day)
|
37
|
+
base_file_name = f"{start_time}_{pid}_{process_type.value}"
|
38
|
+
|
39
|
+
super().__init__(parent_path, base_file_name, "log")
|
40
|
+
|
41
|
+
|
42
|
+
@property
|
43
|
+
def start_time(
|
44
|
+
self
|
45
|
+
) -> str:
|
46
|
+
"""The system time when the log was created."""
|
47
|
+
return self._start_time
|
48
|
+
|
49
|
+
|
50
|
+
@property
|
51
|
+
def pid(
|
52
|
+
self
|
53
|
+
) -> str:
|
54
|
+
"""The ID of the process writing to the log file."""
|
55
|
+
return self._pid
|
56
|
+
|
57
|
+
|
58
|
+
@property
|
59
|
+
def process_type(
|
60
|
+
self
|
61
|
+
) -> str:
|
62
|
+
"""Indicates the type of process, as defined by `ProcessType`."""
|
63
|
+
return self._process_type
|
64
|
+
|
65
|
+
|
66
|
+
class Logs:
|
67
|
+
"""Filter and read a collection of logs generated by `spectre`."""
|
68
|
+
def __init__(
|
69
|
+
self,
|
70
|
+
process_type: Optional[ProcessType] = None,
|
71
|
+
year: Optional[int] = None,
|
72
|
+
month: Optional[int] = None,
|
73
|
+
day: Optional[int] = None
|
74
|
+
) -> None:
|
75
|
+
"""Initialise a `Logs` instance.
|
76
|
+
|
77
|
+
:param process_type: Filter by the process type. Defaults to None.
|
78
|
+
:param year: Filter by the numeric year. Defaults to None.
|
79
|
+
:param month: Filter by the numeric month. Defaults to None.
|
80
|
+
:param day: Filter by the numeric day. Defaults to None.
|
81
|
+
"""
|
82
|
+
self._process_type = process_type.value if process_type is not None else None
|
83
|
+
|
84
|
+
self._log_map: dict[str, Log] = OrderedDict()
|
85
|
+
self.set_date(year, month, day)
|
86
|
+
|
87
|
+
|
88
|
+
@property
|
89
|
+
def process_type(
|
90
|
+
self
|
91
|
+
) -> Optional[str]:
|
92
|
+
"""Indicates the type of process, as defined by `ProcessType`."""
|
93
|
+
return self._process_type
|
94
|
+
|
95
|
+
|
96
|
+
@property
|
97
|
+
def year(
|
98
|
+
self
|
99
|
+
) -> Optional[int]:
|
100
|
+
"""Filter by the numeric year."""
|
101
|
+
return self._year
|
102
|
+
|
103
|
+
|
104
|
+
@property
|
105
|
+
def month(
|
106
|
+
self
|
107
|
+
) -> Optional[int]:
|
108
|
+
"""Filter by the numeric month."""
|
109
|
+
return self._month
|
110
|
+
|
111
|
+
|
112
|
+
@property
|
113
|
+
def day(
|
114
|
+
self
|
115
|
+
) -> Optional[int]:
|
116
|
+
"""Filter by the numeric day."""
|
117
|
+
return self._day
|
118
|
+
|
119
|
+
|
120
|
+
@property
|
121
|
+
def logs_dir_path(
|
122
|
+
self
|
123
|
+
) -> str:
|
124
|
+
"""The shared ancestral path for all the log files. `Logs` recursively searches
|
125
|
+
this directory to find all log files according to the date and process type."""
|
126
|
+
return get_logs_dir_path(self.year, self.month, self.day)
|
127
|
+
|
128
|
+
|
129
|
+
@property
|
130
|
+
def log_list(
|
131
|
+
self
|
132
|
+
) -> list[Log]:
|
133
|
+
"""A list of all log handlers representing files found within `logs_dir_path`."""
|
134
|
+
return list(self._log_map.values())
|
135
|
+
|
136
|
+
|
137
|
+
@property
|
138
|
+
def num_logs(
|
139
|
+
self
|
140
|
+
) -> int:
|
141
|
+
"""The number of log files found within `logs_dir_path`."""
|
142
|
+
return len(self.log_list)
|
143
|
+
|
144
|
+
|
145
|
+
@property
|
146
|
+
def file_names(
|
147
|
+
self
|
148
|
+
) -> list[str]:
|
149
|
+
"""A list of all log file names found within `logs_dir_path`."""
|
150
|
+
return list(self._log_map.keys())
|
151
|
+
|
152
|
+
|
153
|
+
def set_date(
|
154
|
+
self,
|
155
|
+
year: Optional[int],
|
156
|
+
month: Optional[int],
|
157
|
+
day: Optional[int]
|
158
|
+
) -> None:
|
159
|
+
"""Reset `logs_dir_path` according to the numeric date, and refresh the list
|
160
|
+
of available log files.
|
161
|
+
|
162
|
+
:param year: The numeric year.
|
163
|
+
:param month: The numeric month of the year.
|
164
|
+
:param day: The numeric day of the month.
|
165
|
+
"""
|
166
|
+
self._year = year
|
167
|
+
self._month = month
|
168
|
+
self._day = day
|
169
|
+
self.update()
|
170
|
+
|
171
|
+
|
172
|
+
def update(
|
173
|
+
self
|
174
|
+
) -> None:
|
175
|
+
"""Perform a fresh search of all files in `logs_dir_path` for log files
|
176
|
+
according to the date and process type."""
|
177
|
+
log_files = [f for (_, _, files) in os.walk(self.logs_dir_path) for f in files]
|
178
|
+
|
179
|
+
for log_file in log_files:
|
180
|
+
file_name, _ = os.path.splitext(log_file)
|
181
|
+
log_start_time, pid, process_type = file_name.split("_")
|
182
|
+
|
183
|
+
if self.process_type and process_type != self.process_type:
|
184
|
+
continue
|
185
|
+
|
186
|
+
self._log_map[file_name] = Log(log_start_time, pid, ProcessType(process_type))
|
187
|
+
|
188
|
+
self._log_map = OrderedDict(sorted(self._log_map.items()))
|
189
|
+
|
190
|
+
|
191
|
+
def __iter__(
|
192
|
+
self
|
193
|
+
) -> Iterator[Log]:
|
194
|
+
yield from self.log_list
|
195
|
+
|
196
|
+
|
197
|
+
def get_from_file_name(
|
198
|
+
self,
|
199
|
+
file_name: str
|
200
|
+
) -> Log:
|
201
|
+
"""Retrieve a `Log` instance based on the log file name.
|
202
|
+
|
203
|
+
:param file_name: The name of the log file (with or without extension).
|
204
|
+
:raises FileNotFoundError: If the log file name is not found.
|
205
|
+
:return: The `Log` instance corresponding to the file name.
|
206
|
+
"""
|
207
|
+
# auto strip the extension if present
|
208
|
+
file_name, _ = os.path.splitext(file_name)
|
209
|
+
try:
|
210
|
+
return self._log_map[file_name]
|
211
|
+
except KeyError:
|
212
|
+
raise FileNotFoundError(f"Log handler for file name '{file_name}' not found in log map")
|
213
|
+
|
214
|
+
|
215
|
+
def get_from_pid(
|
216
|
+
self,
|
217
|
+
pid: str
|
218
|
+
) -> Log:
|
219
|
+
"""Retrieve a `Log` instance based on the process ID.
|
220
|
+
|
221
|
+
:param pid: The process ID to search for.
|
222
|
+
:raises FileNotFoundError: If a log file corresponding to the process ID is not found.
|
223
|
+
:return: The `Log` instance corresponding to the process ID.
|
224
|
+
"""
|
225
|
+
for log in self.log_list:
|
226
|
+
if log.pid == pid:
|
227
|
+
return log
|
228
|
+
raise FileNotFoundError(f"Log handler for PID '{pid}' not found in log map")
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
2
|
+
# This file is part of SPECTRE
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
|
+
|
5
|
+
from enum import Enum
|
6
|
+
|
7
|
+
class ProcessType(Enum):
|
8
|
+
"""The origin of a `spectre` process.
|
9
|
+
|
10
|
+
:ivar USER: A process is one initiated directly by the user, or part of the main user session.
|
11
|
+
:ivar WORKER: A process is one which is created and managed internally by `spectre`.
|
12
|
+
"""
|
13
|
+
USER = "user"
|
14
|
+
WORKER = "worker"
|
@@ -2,10 +2,12 @@
|
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
|
+
"""An intuitive API for plotting spectrogram data."""
|
6
|
+
|
5
7
|
from ._format import PanelFormat
|
6
|
-
from ._panels import
|
8
|
+
from ._panels import SpectrogramPanel, FrequencyCutsPanel, TimeCutsPanel, IntegralOverFrequencyPanel
|
7
9
|
from ._panel_stack import PanelStack
|
8
10
|
|
9
11
|
__all__ = [
|
10
|
-
"PanelFormat", "
|
12
|
+
"PanelFormat", "PanelStack", "SpectrogramPanel", "FrequencyCutsPanel", "TimeCutsPanel", "IntegralOverFrequencyPanel"
|
11
13
|
]
|