spectre-core 0.0.9__py3-none-any.whl → 0.0.10__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/__init__.py +0 -3
- spectre_core/_file_io/__init__.py +15 -0
- spectre_core/_file_io/file_handlers.py +128 -0
- spectre_core/capture_configs/__init__.py +29 -0
- spectre_core/capture_configs/_capture_config.py +85 -0
- spectre_core/capture_configs/_capture_templates.py +222 -0
- spectre_core/capture_configs/_parameters.py +110 -0
- spectre_core/capture_configs/_pconstraints.py +82 -0
- spectre_core/capture_configs/_ptemplates.py +450 -0
- spectre_core/capture_configs/_pvalidators.py +173 -0
- spectre_core/chunks/__init__.py +17 -201
- spectre_core/chunks/{base.py → _base.py} +15 -60
- spectre_core/chunks/_chunks.py +200 -0
- spectre_core/chunks/{factory.py → _factory.py} +6 -7
- spectre_core/chunks/library/{callisto/chunk.py → _callisto.py} +4 -7
- spectre_core/chunks/library/{fixed/chunk.py → _fixed_center_frequency.py} +7 -64
- spectre_core/chunks/library/_swept_center_frequency.py +103 -0
- spectre_core/config/__init__.py +20 -0
- spectre_core/config/_paths.py +77 -0
- spectre_core/config/_time_formats.py +15 -0
- spectre_core/exceptions.py +4 -5
- spectre_core/logging/__init__.py +11 -0
- spectre_core/logging/_configure.py +35 -0
- spectre_core/logging/_decorators.py +19 -0
- spectre_core/{logging.py → logging/_log_handlers.py} +13 -58
- spectre_core/plotting/__init__.py +7 -1
- spectre_core/plotting/{base.py → _base.py} +40 -20
- spectre_core/plotting/_format.py +18 -0
- spectre_core/plotting/{panel_stack.py → _panel_stack.py} +48 -48
- spectre_core/plotting/_panels.py +234 -0
- spectre_core/post_processing/__init__.py +10 -2
- spectre_core/post_processing/_base.py +119 -0
- spectre_core/post_processing/{factory.py → _factory.py} +7 -6
- spectre_core/post_processing/{post_processor.py → _post_processor.py} +3 -3
- spectre_core/post_processing/library/_fixed_center_frequency.py +115 -0
- spectre_core/post_processing/library/_swept_center_frequency.py +382 -0
- spectre_core/receivers/__init__.py +12 -2
- spectre_core/receivers/_base.py +352 -0
- spectre_core/receivers/{factory.py → _factory.py} +2 -2
- spectre_core/receivers/_spec_names.py +20 -0
- spectre_core/receivers/gr/__init__.py +3 -0
- spectre_core/receivers/gr/_base.py +33 -0
- spectre_core/receivers/gr/_rsp1a.py +158 -0
- spectre_core/receivers/gr/_test.py +123 -0
- spectre_core/receivers/library/_rsp1a.py +61 -0
- spectre_core/receivers/library/_test.py +221 -0
- spectre_core/spectrograms/__init__.py +18 -0
- spectre_core/spectrograms/{analytical.py → _analytical.py} +29 -27
- spectre_core/spectrograms/{array_operations.py → _array_operations.py} +47 -1
- spectre_core/spectrograms/{spectrogram.py → _spectrogram.py} +62 -35
- spectre_core/spectrograms/{transform.py → _transform.py} +76 -89
- spectre_core/{post_processing/library → wgetting}/__init__.py +4 -5
- spectre_core/wgetting/_callisto.py +155 -0
- {spectre_core-0.0.9.dist-info → spectre_core-0.0.10.dist-info}/METADATA +1 -1
- spectre_core-0.0.10.dist-info/RECORD +63 -0
- spectre_core/cfg.py +0 -116
- spectre_core/chunks/library/__init__.py +0 -8
- spectre_core/chunks/library/sweep/__init__.py +0 -0
- spectre_core/chunks/library/sweep/chunk.py +0 -400
- spectre_core/dynamic_imports.py +0 -22
- spectre_core/file_handlers/base.py +0 -68
- spectre_core/file_handlers/configs.py +0 -271
- spectre_core/file_handlers/json.py +0 -40
- spectre_core/file_handlers/text.py +0 -21
- spectre_core/plotting/factory.py +0 -26
- spectre_core/plotting/format.py +0 -19
- spectre_core/plotting/library/__init__.py +0 -7
- spectre_core/plotting/library/frequency_cuts/panel.py +0 -74
- spectre_core/plotting/library/integral_over_frequency/panel.py +0 -34
- spectre_core/plotting/library/spectrogram/panel.py +0 -92
- spectre_core/plotting/library/time_cuts/panel.py +0 -77
- spectre_core/plotting/panel_register.py +0 -13
- spectre_core/post_processing/base.py +0 -132
- spectre_core/post_processing/library/fixed/__init__.py +0 -0
- spectre_core/post_processing/library/fixed/event_handler.py +0 -40
- spectre_core/post_processing/library/sweep/event_handler.py +0 -54
- spectre_core/receivers/base.py +0 -422
- spectre_core/receivers/library/__init__.py +0 -7
- spectre_core/receivers/library/rsp1a/__init__.py +0 -0
- spectre_core/receivers/library/rsp1a/gr/__init__.py +0 -0
- spectre_core/receivers/library/rsp1a/gr/fixed.py +0 -104
- spectre_core/receivers/library/rsp1a/gr/sweep.py +0 -129
- spectre_core/receivers/library/rsp1a/receiver.py +0 -68
- spectre_core/receivers/library/rspduo/__init__.py +0 -0
- spectre_core/receivers/library/rspduo/gr/__init__.py +0 -0
- spectre_core/receivers/library/rspduo/gr/tuner_1_fixed.py +0 -114
- spectre_core/receivers/library/rspduo/gr/tuner_1_sweep.py +0 -131
- spectre_core/receivers/library/rspduo/gr/tuner_2_fixed.py +0 -120
- spectre_core/receivers/library/rspduo/gr/tuner_2_sweep.py +0 -119
- spectre_core/receivers/library/rspduo/receiver.py +0 -97
- spectre_core/receivers/library/test/__init__.py +0 -0
- spectre_core/receivers/library/test/gr/__init__.py +0 -0
- spectre_core/receivers/library/test/gr/cosine_signal_1.py +0 -83
- spectre_core/receivers/library/test/gr/tagged_staircase.py +0 -93
- spectre_core/receivers/library/test/receiver.py +0 -203
- spectre_core/receivers/validators.py +0 -231
- spectre_core/web_fetch/callisto.py +0 -101
- spectre_core-0.0.9.dist-info/RECORD +0 -74
- /spectre_core/chunks/{chunk_register.py → _register.py} +0 -0
- /spectre_core/post_processing/{event_handler_register.py → _register.py} +0 -0
- /spectre_core/receivers/{receiver_register.py → _register.py} +0 -0
- /spectre_core/{chunks/library/callisto/__init__.py → receivers/gr/_rspduo.py} +0 -0
- /spectre_core/{chunks/library/fixed/__init__.py → receivers/library/_rspduo.py} +0 -0
- {spectre_core-0.0.9.dist-info → spectre_core-0.0.10.dist-info}/LICENSE +0 -0
- {spectre_core-0.0.9.dist-info → spectre_core-0.0.10.dist-info}/WHEEL +0 -0
- {spectre_core-0.0.9.dist-info → spectre_core-0.0.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,103 @@
|
|
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 typing import Tuple
|
6
|
+
from dataclasses import dataclass
|
7
|
+
|
8
|
+
import numpy as np
|
9
|
+
|
10
|
+
from spectre_core.exceptions import InvalidSweepMetadataError
|
11
|
+
from spectre_core.capture_configs import CaptureModes
|
12
|
+
from ._fixed_center_frequency import BinChunk, FitsChunk
|
13
|
+
from .._register import register_chunk
|
14
|
+
from .._base import BaseChunk, ChunkFile
|
15
|
+
|
16
|
+
|
17
|
+
@dataclass
|
18
|
+
class SweepMetadata:
|
19
|
+
"""Wrapper for metadata required to assign center frequencies to each IQ sample in the chunk.
|
20
|
+
|
21
|
+
center_frequencies is an ordered list containing all the center frequencies that the IQ samples
|
22
|
+
were collected at. Typically, these will be ordered in "steps", where each step corresponds to
|
23
|
+
IQ samples collected at a constant center frequency:
|
24
|
+
|
25
|
+
(freq_0, freq_1, ..., freq_M, freq_0, freq_1, ..., freq_M, ...), freq_0 < freq_1 < ... < freq_M
|
26
|
+
|
27
|
+
The n'th element of the num_samples list, tells us how many samples were collected at the n'th
|
28
|
+
element of center_frequencies:
|
29
|
+
chunks.library.fixed_center_frequency.chunk import (
|
30
|
+
BinChunk, FitsChunk
|
31
|
+
)
|
32
|
+
Number of samples: (num_samples_at_freq_0, num_samples_at_freq_1, ...)
|
33
|
+
|
34
|
+
Both these lists together allow us to map for each IQ sample, the center frequency it was collected at.
|
35
|
+
"""
|
36
|
+
center_frequencies: np.ndarray
|
37
|
+
num_samples: np.ndarray
|
38
|
+
|
39
|
+
|
40
|
+
@register_chunk(CaptureModes.SWEPT_CENTER_FREQUENCY)
|
41
|
+
class _Chunk(BaseChunk):
|
42
|
+
def __init__(self, chunk_start_time, tag):
|
43
|
+
super().__init__(chunk_start_time, tag)
|
44
|
+
|
45
|
+
self.add_file(BinChunk(self.chunk_parent_path, self.chunk_name))
|
46
|
+
self.add_file(FitsChunk(self.chunk_parent_path, self.chunk_name))
|
47
|
+
self.add_file(HdrChunk(self.chunk_parent_path, self.chunk_name))
|
48
|
+
|
49
|
+
|
50
|
+
class HdrChunk(ChunkFile):
|
51
|
+
def __init__(self, chunk_parent_path: str, chunk_name: str):
|
52
|
+
super().__init__(chunk_parent_path, chunk_name, "hdr")
|
53
|
+
|
54
|
+
def read(self) -> Tuple[int, SweepMetadata]:
|
55
|
+
hdr_contents = self._read_file_contents()
|
56
|
+
millisecond_correction = self._get_millisecond_correction(hdr_contents)
|
57
|
+
center_frequencies = self._get_center_frequencies(hdr_contents)
|
58
|
+
num_samples = self._get_num_samples(hdr_contents)
|
59
|
+
self._validate_frequencies_and_samples(center_frequencies,
|
60
|
+
num_samples)
|
61
|
+
return millisecond_correction, SweepMetadata(center_frequencies, num_samples)
|
62
|
+
|
63
|
+
|
64
|
+
def _read_file_contents(self) -> np.ndarray:
|
65
|
+
with open(self.file_path, "rb") as fh:
|
66
|
+
return np.fromfile(fh, dtype=np.float32)
|
67
|
+
|
68
|
+
|
69
|
+
def _get_millisecond_correction(self, hdr_contents: np.ndarray) -> int:
|
70
|
+
''' Millisecond correction is an integral quantity, but stored in the detached header as a 32-bit float.'''
|
71
|
+
millisecond_correction_as_float = float(hdr_contents[0])
|
72
|
+
|
73
|
+
if not millisecond_correction_as_float.is_integer():
|
74
|
+
raise TypeError(f"Expected integer value for millisecond correction, but got {millisecond_correction_as_float}")
|
75
|
+
|
76
|
+
return int(millisecond_correction_as_float)
|
77
|
+
|
78
|
+
|
79
|
+
def _get_center_frequencies(self, hdr_contents: np.ndarray) -> np.ndarray:
|
80
|
+
'''
|
81
|
+
Detached header contents are stored in (center_freq_i, num_samples_at_center_freq_i) pairs
|
82
|
+
Return only a list of center frequencies, by skipping over file contents in twos.
|
83
|
+
'''
|
84
|
+
return hdr_contents[1::2]
|
85
|
+
|
86
|
+
|
87
|
+
def _get_num_samples(self, hdr_contents: np.ndarray) -> np.ndarray:
|
88
|
+
'''
|
89
|
+
Detached header contents are stored in (center_freq_i, num_samples_at_center_freq_i) pairs
|
90
|
+
Return only the number of samples at each center frequency, by skipping over file contents in twos.
|
91
|
+
Number of samples is an integral quantity, but stored in the detached header as a 32-bit float.
|
92
|
+
Types are checked before return.
|
93
|
+
'''
|
94
|
+
num_samples_as_float = hdr_contents[2::2]
|
95
|
+
if not all(num_samples_as_float == num_samples_as_float.astype(int)):
|
96
|
+
raise InvalidSweepMetadataError("Number of samples per frequency is expected to describe an integer")
|
97
|
+
return num_samples_as_float.astype(int)
|
98
|
+
|
99
|
+
|
100
|
+
def _validate_frequencies_and_samples(self, center_frequencies: np.ndarray, num_samples: np.ndarray) -> None:
|
101
|
+
"""Validates that the center frequencies and the number of samples arrays have the same length."""
|
102
|
+
if len(center_frequencies) != len(num_samples):
|
103
|
+
raise InvalidSweepMetadataError("Center frequencies and number of samples arrays are not the same length")
|
@@ -0,0 +1,20 @@
|
|
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 ._paths import (
|
6
|
+
get_spectre_data_dir_path, get_chunks_dir_path, get_configs_dir_path, get_logs_dir_path
|
7
|
+
)
|
8
|
+
from ._time_formats import (
|
9
|
+
TimeFormats
|
10
|
+
)
|
11
|
+
|
12
|
+
__all__ = [
|
13
|
+
"get_spectre_data_dir_path",
|
14
|
+
"get_chunks_dir_path",
|
15
|
+
"get_configs_dir_path",
|
16
|
+
"get_logs_dir_path",
|
17
|
+
"DEFAULT_DATE_FORMAT",
|
18
|
+
"DEFAULT_TIME_FORMAT",
|
19
|
+
"DEFAULT_DATETIME_FORMAT"
|
20
|
+
]
|
@@ -0,0 +1,77 @@
|
|
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 data paths.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import os
|
10
|
+
|
11
|
+
_SPECTRE_DATA_DIR_PATH = os.environ.get("SPECTRE_DATA_DIR_PATH")
|
12
|
+
if _SPECTRE_DATA_DIR_PATH is None:
|
13
|
+
raise ValueError("The environment variable SPECTRE_DATA_DIR_PATH has not been set")
|
14
|
+
|
15
|
+
_CHUNKS_DIR_PATH = os.environ.get("SPECTRE_CHUNKS_DIR_PATH",
|
16
|
+
os.path.join(_SPECTRE_DATA_DIR_PATH, 'chunks'))
|
17
|
+
os.makedirs(_CHUNKS_DIR_PATH,
|
18
|
+
exist_ok=True)
|
19
|
+
|
20
|
+
_LOGS_DIR_PATH = os.environ.get("SPECTRE_LOGS_DIR_PATH",
|
21
|
+
os.path.join(_SPECTRE_DATA_DIR_PATH, 'logs'))
|
22
|
+
os.makedirs(_LOGS_DIR_PATH,
|
23
|
+
exist_ok=True)
|
24
|
+
|
25
|
+
_CONFIGS_DIR_PATH = os.environ.get("SPECTRE_CONFIGS_DIR_PATH",
|
26
|
+
os.path.join(_SPECTRE_DATA_DIR_PATH, "configs"))
|
27
|
+
os.makedirs(_CONFIGS_DIR_PATH,
|
28
|
+
exist_ok=True)
|
29
|
+
|
30
|
+
|
31
|
+
def get_spectre_data_dir_path(
|
32
|
+
) -> str:
|
33
|
+
return _SPECTRE_DATA_DIR_PATH
|
34
|
+
|
35
|
+
|
36
|
+
def _get_date_based_dir_path(base_dir: str, year: int = None,
|
37
|
+
month: int = None, day: int = None
|
38
|
+
) -> str:
|
39
|
+
if day and not (year and month):
|
40
|
+
raise ValueError("A day requires both a month and a year")
|
41
|
+
if month and not year:
|
42
|
+
raise ValueError("A month requires a year")
|
43
|
+
|
44
|
+
date_dir_components = []
|
45
|
+
if year:
|
46
|
+
date_dir_components.append(f"{year:04}")
|
47
|
+
if month:
|
48
|
+
date_dir_components.append(f"{month:02}")
|
49
|
+
if day:
|
50
|
+
date_dir_components.append(f"{day:02}")
|
51
|
+
|
52
|
+
return os.path.join(base_dir, *date_dir_components)
|
53
|
+
|
54
|
+
|
55
|
+
def get_chunks_dir_path(year: int = None,
|
56
|
+
month: int = None,
|
57
|
+
day: int = None
|
58
|
+
) -> str:
|
59
|
+
return _get_date_based_dir_path(_CHUNKS_DIR_PATH,
|
60
|
+
year,
|
61
|
+
month,
|
62
|
+
day)
|
63
|
+
|
64
|
+
|
65
|
+
def get_logs_dir_path(year: int = None,
|
66
|
+
month: int = None,
|
67
|
+
day: int = None
|
68
|
+
) -> str:
|
69
|
+
return _get_date_based_dir_path(_LOGS_DIR_PATH,
|
70
|
+
year,
|
71
|
+
month,
|
72
|
+
day)
|
73
|
+
|
74
|
+
|
75
|
+
def get_configs_dir_path(
|
76
|
+
) -> str:
|
77
|
+
return _CONFIGS_DIR_PATH
|
@@ -0,0 +1,15 @@
|
|
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
|
+
Package-wide default datetime formats.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from dataclasses import dataclass
|
10
|
+
|
11
|
+
@dataclass(frozen=True)
|
12
|
+
class TimeFormats:
|
13
|
+
TIME = "%H:%M:%S"
|
14
|
+
DATE = "%Y-%m-%d"
|
15
|
+
DATETIME = f"{DATE}T{TIME}"
|
spectre_core/exceptions.py
CHANGED
@@ -2,16 +2,15 @@
|
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
|
+
"""
|
6
|
+
SPECTRE custom exceptions.
|
7
|
+
"""
|
8
|
+
|
5
9
|
class ChunkNotFoundError(FileNotFoundError): ...
|
6
10
|
class ChunkFileNotFoundError(FileNotFoundError): ...
|
7
11
|
class SpectrogramNotFoundError(FileNotFoundError): ...
|
8
|
-
|
9
12
|
class ModeNotFoundError(KeyError): ...
|
10
13
|
class EventHandlerNotFoundError(KeyError): ...
|
11
14
|
class ReceiverNotFoundError(KeyError): ...
|
12
|
-
class TemplateNotFoundError(KeyError): ...
|
13
|
-
class SpecificationNotFoundError(KeyError): ...
|
14
|
-
class PanelNotFoundError(KeyError): ...
|
15
|
-
|
16
15
|
class InvalidTagError(ValueError): ...
|
17
16
|
class InvalidSweepMetadataError(ValueError): ...
|
@@ -0,0 +1,11 @@
|
|
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 ._decorators import log_call
|
6
|
+
from ._configure import configure_root_logger
|
7
|
+
from ._log_handlers import LogHandler, LogHandlers
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"log_call", "configure_root_logger", "LogHandler", "LogHandlers"
|
11
|
+
]
|
@@ -0,0 +1,35 @@
|
|
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 datetime import datetime
|
8
|
+
|
9
|
+
from spectre_core.config import TimeFormats
|
10
|
+
from ._log_handlers import LogHandler
|
11
|
+
|
12
|
+
def configure_root_logger(process_type: str,
|
13
|
+
level: int = logging.INFO
|
14
|
+
) -> LogHandler:
|
15
|
+
system_datetime = datetime.now()
|
16
|
+
datetime_stamp = system_datetime.strftime(TimeFormats.DATETIME)
|
17
|
+
pid = os.getpid()
|
18
|
+
log_handler = LogHandler(datetime_stamp, pid, process_type)
|
19
|
+
log_handler.make_parent_path()
|
20
|
+
|
21
|
+
# configure the root logger
|
22
|
+
logger = logging.getLogger()
|
23
|
+
logger.setLevel(level)
|
24
|
+
# Remove any existing handlers to avoid duplicate logs
|
25
|
+
for handler in logger.handlers:
|
26
|
+
logger.removeHandler(handler)
|
27
|
+
# Set up file handler with specific filename
|
28
|
+
file_handler = logging.FileHandler(log_handler.file_path)
|
29
|
+
file_handler.setLevel(level)
|
30
|
+
formatter = logging.Formatter("[%(asctime)s] [%(levelname)8s] --- %(message)s (%(name)s:%(lineno)s)")
|
31
|
+
file_handler.setFormatter(formatter)
|
32
|
+
# and add it to the root logger
|
33
|
+
logger.addHandler(file_handler)
|
34
|
+
|
35
|
+
return log_handler
|
@@ -0,0 +1,19 @@
|
|
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
|
7
|
+
from functools import wraps
|
8
|
+
|
9
|
+
def log_call(func: Callable) -> Callable:
|
10
|
+
@wraps(func)
|
11
|
+
def wrapper(*args, **kwargs):
|
12
|
+
logger = logging.getLogger(func.__module__)
|
13
|
+
try:
|
14
|
+
logger.info(f"Calling the function: {func.__name__}")
|
15
|
+
return func(*args, **kwargs)
|
16
|
+
except Exception as e:
|
17
|
+
logger.error(f"Error in function: {func.__name__}", exc_info=True)
|
18
|
+
raise
|
19
|
+
return wrapper
|
@@ -8,26 +8,21 @@ _LOGGER = getLogger(__name__)
|
|
8
8
|
|
9
9
|
import os
|
10
10
|
import logging
|
11
|
-
from
|
11
|
+
from dataclasses import dataclass
|
12
|
+
from typing import Optional
|
12
13
|
import warnings
|
13
14
|
from collections import OrderedDict
|
14
15
|
from datetime import datetime
|
15
|
-
from functools import wraps
|
16
16
|
|
17
|
-
from spectre_core.
|
18
|
-
from spectre_core.
|
19
|
-
LOGS_DIR_PATH,
|
20
|
-
DEFAULT_DATETIME_FORMAT,
|
21
|
-
get_logs_dir_path
|
22
|
-
)
|
17
|
+
from spectre_core._file_io import TextHandler
|
18
|
+
from spectre_core.config import get_logs_dir_path, TimeFormats
|
23
19
|
|
24
20
|
PROCESS_TYPES = [
|
25
21
|
"user",
|
26
22
|
"worker"
|
27
23
|
]
|
28
24
|
|
29
|
-
|
30
|
-
def validate_process_type(process_type: str
|
25
|
+
def _validate_process_type(process_type: str
|
31
26
|
) -> None:
|
32
27
|
if process_type not in PROCESS_TYPES:
|
33
28
|
raise ValueError(f"Invalid process type: {process_type}. Expected one of {PROCESS_TYPES}")
|
@@ -40,12 +35,11 @@ class LogHandler(TextHandler):
|
|
40
35
|
process_type: str):
|
41
36
|
self._datetime_stamp = datetime_stamp
|
42
37
|
self._pid = pid
|
43
|
-
|
38
|
+
_validate_process_type(process_type)
|
44
39
|
self._process_type = process_type
|
45
40
|
|
46
|
-
dt = datetime.strptime(datetime_stamp,
|
47
|
-
|
48
|
-
parent_path = os.path.join(LOGS_DIR_PATH, date_dir)
|
41
|
+
dt = datetime.strptime(datetime_stamp, TimeFormats.DATETIME)
|
42
|
+
parent_path = get_logs_dir_path(dt.year, dt.month, dt.day)
|
49
43
|
base_file_name = f"{datetime_stamp}_{pid}_{process_type}"
|
50
44
|
|
51
45
|
super().__init__(parent_path, base_file_name, extension = "log")
|
@@ -164,8 +158,8 @@ class LogHandlers:
|
|
164
158
|
yield from self.log_handler_list
|
165
159
|
|
166
160
|
|
167
|
-
def
|
168
|
-
|
161
|
+
def get_from_file_name(self,
|
162
|
+
file_name: str) -> LogHandler:
|
169
163
|
# auto strip the extension if present
|
170
164
|
file_name, _ = os.path.splitext(file_name)
|
171
165
|
try:
|
@@ -174,48 +168,9 @@ class LogHandlers:
|
|
174
168
|
raise FileNotFoundError(f"Log handler for file name '{file_name}' not found in log map")
|
175
169
|
|
176
170
|
|
177
|
-
def
|
178
|
-
|
171
|
+
def get_from_pid(self,
|
172
|
+
pid: str) -> LogHandler:
|
179
173
|
for log_handler in self.log_handler_list:
|
180
174
|
if log_handler.pid == pid:
|
181
175
|
return log_handler
|
182
|
-
raise FileNotFoundError(f"Log handler for PID '{pid}' not found in log map")
|
183
|
-
|
184
|
-
|
185
|
-
def configure_root_logger(process_type: str,
|
186
|
-
level: int = logging.INFO
|
187
|
-
) -> LogHandler:
|
188
|
-
system_datetime = datetime.now()
|
189
|
-
datetime_stamp = system_datetime.strftime(DEFAULT_DATETIME_FORMAT)
|
190
|
-
pid = os.getpid()
|
191
|
-
log_handler = LogHandler(datetime_stamp, pid, process_type)
|
192
|
-
log_handler.make_parent_path()
|
193
|
-
|
194
|
-
# configure the root logger
|
195
|
-
logger = logging.getLogger()
|
196
|
-
logger.setLevel(level)
|
197
|
-
# Remove any existing handlers to avoid duplicate logs
|
198
|
-
for handler in logger.handlers:
|
199
|
-
logger.removeHandler(handler)
|
200
|
-
# Set up file handler with specific filename
|
201
|
-
file_handler = logging.FileHandler(log_handler.file_path)
|
202
|
-
file_handler.setLevel(level)
|
203
|
-
formatter = logging.Formatter("[%(asctime)s] [%(levelname)8s] --- %(message)s (%(name)s:%(lineno)s)")
|
204
|
-
file_handler.setFormatter(formatter)
|
205
|
-
# and add it to the root logger
|
206
|
-
logger.addHandler(file_handler)
|
207
|
-
|
208
|
-
return log_handler
|
209
|
-
|
210
|
-
|
211
|
-
def log_call(func: Callable) -> Callable:
|
212
|
-
@wraps(func)
|
213
|
-
def wrapper(*args, **kwargs):
|
214
|
-
logger = logging.getLogger(func.__module__) # Automatically get module-level logger
|
215
|
-
try:
|
216
|
-
logger.info(f"Calling the function: {func.__name__}")
|
217
|
-
return func(*args, **kwargs)
|
218
|
-
except Exception as e:
|
219
|
-
logger.error(f"Error in function: {func.__name__}", exc_info=True)
|
220
|
-
raise
|
221
|
-
return wrapper
|
176
|
+
raise FileNotFoundError(f"Log handler for PID '{pid}' not found in log map")
|
@@ -2,4 +2,10 @@
|
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
|
-
import
|
5
|
+
from ._format import PanelFormat
|
6
|
+
from ._panels import Panels
|
7
|
+
from ._panel_stack import PanelStack
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
"PanelFormat", "Panels", "PanelStack"
|
11
|
+
]
|
@@ -5,34 +5,40 @@
|
|
5
5
|
from abc import ABC, abstractmethod
|
6
6
|
from typing import Optional
|
7
7
|
import numpy as np
|
8
|
+
from dataclasses import dataclass
|
8
9
|
|
9
10
|
from matplotlib import cm
|
10
11
|
import matplotlib.dates as mdates
|
11
12
|
from matplotlib.axes import Axes
|
12
13
|
from matplotlib.figure import Figure
|
13
14
|
|
14
|
-
from spectre_core.spectrograms
|
15
|
+
from spectre_core.spectrograms import Spectrogram, TimeTypes
|
16
|
+
from ._format import PanelFormat
|
17
|
+
|
18
|
+
|
19
|
+
@dataclass(frozen=True)
|
20
|
+
class XAxisTypes:
|
21
|
+
TIME : str = "time"
|
22
|
+
FREQUENCY: str = "frequency"
|
15
23
|
|
16
|
-
TIME_X_AXIS = "time"
|
17
|
-
FREQUENCY_X_AXIS = "frequency"
|
18
24
|
|
19
25
|
class BasePanel(ABC):
|
20
26
|
def __init__(self,
|
21
27
|
name: str,
|
22
|
-
spectrogram: Spectrogram
|
23
|
-
time_type: str = "seconds"):
|
28
|
+
spectrogram: Spectrogram):
|
24
29
|
self._name = name
|
25
30
|
self._spectrogram = spectrogram
|
26
31
|
|
27
|
-
self._validate_time_type(time_type)
|
28
|
-
self._time_type = time_type
|
29
|
-
|
30
32
|
self._x_axis_type: Optional[str] = None
|
31
33
|
self._set_x_axis_type()
|
32
34
|
|
33
|
-
|
34
|
-
self.
|
35
|
-
self.
|
35
|
+
# defined while stacking
|
36
|
+
self._panel_format: Optional[PanelFormat] = None
|
37
|
+
self._time_type: Optional[str] = None
|
38
|
+
self._ax: Optional[Axes] = None
|
39
|
+
self._fig: Optional[Figure] = None
|
40
|
+
# defined if specified by the user
|
41
|
+
self._identifier: Optional[str] = None
|
36
42
|
|
37
43
|
|
38
44
|
@abstractmethod
|
@@ -71,6 +77,12 @@ class BasePanel(ABC):
|
|
71
77
|
return self._time_type
|
72
78
|
|
73
79
|
|
80
|
+
@time_type.setter
|
81
|
+
def time_type(self, value: str) -> None:
|
82
|
+
self._validate_time_type(value)
|
83
|
+
self._time_type = value
|
84
|
+
|
85
|
+
|
74
86
|
@property
|
75
87
|
def name(self) -> str:
|
76
88
|
if self._name is None:
|
@@ -83,6 +95,18 @@ class BasePanel(ABC):
|
|
83
95
|
self._name = value
|
84
96
|
|
85
97
|
|
98
|
+
@property
|
99
|
+
def panel_format(self) -> PanelFormat:
|
100
|
+
if self._panel_format is None:
|
101
|
+
raise AttributeError(f"Panel format has not yet been specified for this panel")
|
102
|
+
return self._panel_format
|
103
|
+
|
104
|
+
|
105
|
+
@panel_format.setter
|
106
|
+
def panel_format(self, value: PanelFormat):
|
107
|
+
self._panel_format = value
|
108
|
+
|
109
|
+
|
86
110
|
@property
|
87
111
|
def ax(self) -> Axes:
|
88
112
|
if self._ax is None:
|
@@ -126,7 +150,7 @@ class BasePanel(ABC):
|
|
126
150
|
|
127
151
|
def _validate_time_type(self,
|
128
152
|
time_type: str):
|
129
|
-
valid_time_types = [
|
153
|
+
valid_time_types = [TimeTypes.SECONDS, TimeTypes.DATETIMES]
|
130
154
|
if time_type not in valid_time_types:
|
131
155
|
raise ValueError(f"Invalid time type. "
|
132
156
|
f"Expected one of {valid_time_types} "
|
@@ -156,11 +180,11 @@ class BaseTimeSeriesPanel(BasePanel):
|
|
156
180
|
|
157
181
|
@property
|
158
182
|
def times(self):
|
159
|
-
return self.spectrogram.times if self.time_type ==
|
183
|
+
return self.spectrogram.times if self.time_type == TimeTypes.SECONDS else self.spectrogram.datetimes
|
160
184
|
|
161
185
|
|
162
186
|
def annotate_x_axis(self):
|
163
|
-
if self.time_type ==
|
187
|
+
if self.time_type == TimeTypes.SECONDS:
|
164
188
|
self.ax.set_xlabel('Time [s]')
|
165
189
|
else:
|
166
190
|
self.ax.set_xlabel('Time [UTC]')
|
@@ -168,7 +192,7 @@ class BaseTimeSeriesPanel(BasePanel):
|
|
168
192
|
|
169
193
|
|
170
194
|
def _set_x_axis_type(self):
|
171
|
-
self._x_axis_type =
|
195
|
+
self._x_axis_type = XAxisTypes.TIME
|
172
196
|
|
173
197
|
|
174
198
|
|
@@ -187,8 +211,4 @@ class BaseSpectrumPanel(BasePanel):
|
|
187
211
|
|
188
212
|
|
189
213
|
def _set_x_axis_type(self):
|
190
|
-
self._x_axis_type =
|
191
|
-
|
192
|
-
|
193
|
-
class CutsPanel(BasePanel):
|
194
|
-
"""Convenience parent class for cuts"""
|
214
|
+
self._x_axis_type = XAxisTypes.FREQUENCY
|
@@ -0,0 +1,18 @@
|
|
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 dataclasses import dataclass
|
6
|
+
|
7
|
+
@dataclass
|
8
|
+
class PanelFormat:
|
9
|
+
small_size : int = 18
|
10
|
+
medium_size : int = 21
|
11
|
+
large_size : int = 24
|
12
|
+
line_width : int = 3
|
13
|
+
style : str = "dark_background"
|
14
|
+
spectrogram_cmap: str = "gnuplot2"
|
15
|
+
cuts_cmap : str = "winter"
|
16
|
+
integral_color : str = "lime"
|
17
|
+
|
18
|
+
DEFAULT_FORMAT = PanelFormat()
|