spectre-core 0.0.1__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 +3 -0
- spectre_core/cfg.py +116 -0
- spectre_core/chunks/__init__.py +206 -0
- spectre_core/chunks/base.py +160 -0
- spectre_core/chunks/chunk_register.py +15 -0
- spectre_core/chunks/factory.py +26 -0
- spectre_core/chunks/library/__init__.py +8 -0
- spectre_core/chunks/library/callisto/__init__.py +0 -0
- spectre_core/chunks/library/callisto/chunk.py +101 -0
- spectre_core/chunks/library/fixed/__init__.py +0 -0
- spectre_core/chunks/library/fixed/chunk.py +185 -0
- spectre_core/chunks/library/sweep/__init__.py +0 -0
- spectre_core/chunks/library/sweep/chunk.py +400 -0
- spectre_core/dynamic_imports.py +22 -0
- spectre_core/exceptions.py +17 -0
- spectre_core/file_handlers/base.py +94 -0
- spectre_core/file_handlers/configs.py +269 -0
- spectre_core/file_handlers/json.py +36 -0
- spectre_core/file_handlers/text.py +21 -0
- spectre_core/logging.py +222 -0
- spectre_core/plotting/__init__.py +5 -0
- spectre_core/plotting/base.py +194 -0
- spectre_core/plotting/factory.py +26 -0
- spectre_core/plotting/format.py +19 -0
- spectre_core/plotting/library/__init__.py +7 -0
- spectre_core/plotting/library/frequency_cuts/panel.py +74 -0
- spectre_core/plotting/library/integral_over_frequency/panel.py +34 -0
- spectre_core/plotting/library/spectrogram/panel.py +92 -0
- spectre_core/plotting/library/time_cuts/panel.py +77 -0
- spectre_core/plotting/panel_register.py +13 -0
- spectre_core/plotting/panel_stack.py +148 -0
- spectre_core/receivers/__init__.py +6 -0
- spectre_core/receivers/base.py +415 -0
- spectre_core/receivers/factory.py +19 -0
- spectre_core/receivers/library/__init__.py +7 -0
- 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 +104 -0
- spectre_core/receivers/library/rsp1a/gr/sweep.py +129 -0
- spectre_core/receivers/library/rsp1a/receiver.py +68 -0
- 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 +110 -0
- spectre_core/receivers/library/rspduo/gr/tuner_1_sweep.py +135 -0
- spectre_core/receivers/library/rspduo/receiver.py +68 -0
- 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 +83 -0
- spectre_core/receivers/library/test/gr/tagged_staircase.py +93 -0
- spectre_core/receivers/library/test/receiver.py +174 -0
- spectre_core/receivers/receiver_register.py +22 -0
- spectre_core/receivers/validators.py +205 -0
- spectre_core/spectrograms/__init__.py +3 -0
- spectre_core/spectrograms/analytical.py +205 -0
- spectre_core/spectrograms/array_operations.py +77 -0
- spectre_core/spectrograms/spectrogram.py +461 -0
- spectre_core/spectrograms/transform.py +267 -0
- spectre_core/watchdog/__init__.py +6 -0
- spectre_core/watchdog/base.py +105 -0
- spectre_core/watchdog/event_handler_register.py +15 -0
- spectre_core/watchdog/factory.py +22 -0
- spectre_core/watchdog/library/__init__.py +10 -0
- spectre_core/watchdog/library/fixed/__init__.py +0 -0
- spectre_core/watchdog/library/fixed/event_handler.py +41 -0
- spectre_core/watchdog/library/sweep/event_handler.py +55 -0
- spectre_core/watchdog/watcher.py +50 -0
- spectre_core/web_fetch/callisto.py +101 -0
- spectre_core-0.0.1.dist-info/LICENSE +674 -0
- spectre_core-0.0.1.dist-info/METADATA +40 -0
- spectre_core-0.0.1.dist-info/RECORD +72 -0
- spectre_core-0.0.1.dist-info/WHEEL +5 -0
- spectre_core-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,194 @@
|
|
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 abc import ABC, abstractmethod
|
6
|
+
from typing import Optional
|
7
|
+
import numpy as np
|
8
|
+
|
9
|
+
from matplotlib import cm
|
10
|
+
import matplotlib.dates as mdates
|
11
|
+
from matplotlib.axes import Axes
|
12
|
+
from matplotlib.figure import Figure
|
13
|
+
|
14
|
+
from spectre_core.spectrograms.spectrogram import Spectrogram
|
15
|
+
|
16
|
+
TIME_X_AXIS = "time"
|
17
|
+
FREQUENCY_X_AXIS = "frequency"
|
18
|
+
|
19
|
+
class BasePanel(ABC):
|
20
|
+
def __init__(self,
|
21
|
+
name: str,
|
22
|
+
spectrogram: Spectrogram,
|
23
|
+
time_type: str = "seconds"):
|
24
|
+
self._name = name
|
25
|
+
self._spectrogram = spectrogram
|
26
|
+
|
27
|
+
self._validate_time_type(time_type)
|
28
|
+
self._time_type = time_type
|
29
|
+
|
30
|
+
self._x_axis_type: Optional[str] = None
|
31
|
+
self._set_x_axis_type()
|
32
|
+
|
33
|
+
self._ax: Optional[Axes] = None # defined while stacking
|
34
|
+
self._fig: Optional[Figure] = None # defined while stacking
|
35
|
+
self._identifier: Optional[str] = None # defined if specified by the user
|
36
|
+
|
37
|
+
|
38
|
+
@abstractmethod
|
39
|
+
def draw(self):
|
40
|
+
pass
|
41
|
+
|
42
|
+
|
43
|
+
@abstractmethod
|
44
|
+
def annotate_x_axis(self):
|
45
|
+
pass
|
46
|
+
|
47
|
+
|
48
|
+
@abstractmethod
|
49
|
+
def annotate_y_axis(self):
|
50
|
+
pass
|
51
|
+
|
52
|
+
|
53
|
+
@abstractmethod
|
54
|
+
def _set_x_axis_type(self):
|
55
|
+
"""Required to allow for axes sharing in the stack"""
|
56
|
+
pass
|
57
|
+
|
58
|
+
|
59
|
+
@property
|
60
|
+
def spectrogram(self) -> Spectrogram:
|
61
|
+
return self._spectrogram
|
62
|
+
|
63
|
+
|
64
|
+
@property
|
65
|
+
def tag(self) -> str:
|
66
|
+
return self._spectrogram.tag
|
67
|
+
|
68
|
+
|
69
|
+
@property
|
70
|
+
def time_type(self) -> str:
|
71
|
+
return self._time_type
|
72
|
+
|
73
|
+
|
74
|
+
@property
|
75
|
+
def name(self) -> str:
|
76
|
+
if self._name is None:
|
77
|
+
raise AttributeError(f"Name for this panel has not yet been set")
|
78
|
+
return self._name
|
79
|
+
|
80
|
+
|
81
|
+
@name.setter
|
82
|
+
def name(self, value: str) -> None:
|
83
|
+
self._name = value
|
84
|
+
|
85
|
+
|
86
|
+
@property
|
87
|
+
def ax(self) -> Axes:
|
88
|
+
if self._ax is None:
|
89
|
+
raise AttributeError(f"Axes have not yet been set for this panel")
|
90
|
+
return self._ax
|
91
|
+
|
92
|
+
|
93
|
+
@ax.setter
|
94
|
+
def ax(self, value: Axes) -> None:
|
95
|
+
self._ax = value
|
96
|
+
|
97
|
+
|
98
|
+
@property
|
99
|
+
def fig(self) -> Figure:
|
100
|
+
if self._fig is None:
|
101
|
+
raise AttributeError(f"Figure has not yet been set for this panel")
|
102
|
+
return self._fig
|
103
|
+
|
104
|
+
|
105
|
+
@fig.setter
|
106
|
+
def fig(self, value: Figure) -> None:
|
107
|
+
self._fig = value
|
108
|
+
|
109
|
+
|
110
|
+
|
111
|
+
@property
|
112
|
+
def x_axis_type(self) -> str:
|
113
|
+
if self._x_axis_type is None:
|
114
|
+
raise AttributeError(f"x-axis type has not been defined for this panel")
|
115
|
+
return self._x_axis_type
|
116
|
+
|
117
|
+
@property
|
118
|
+
def identifier(self) -> Optional[str]:
|
119
|
+
return self._identifier
|
120
|
+
|
121
|
+
|
122
|
+
@identifier.setter
|
123
|
+
def identifier(self, value: str) -> None:
|
124
|
+
self._identifier = value
|
125
|
+
|
126
|
+
|
127
|
+
def _validate_time_type(self,
|
128
|
+
time_type: str):
|
129
|
+
valid_time_types = ["seconds", "datetimes"]
|
130
|
+
if time_type not in valid_time_types:
|
131
|
+
raise ValueError(f"Invalid time type. "
|
132
|
+
f"Expected one of {valid_time_types} "
|
133
|
+
f"but received {time_type}")
|
134
|
+
|
135
|
+
|
136
|
+
def bind_to_colors(self,
|
137
|
+
values: np.ndarray,
|
138
|
+
cmap: str = "winter"):
|
139
|
+
cmap = cm.get_cmap(cmap)
|
140
|
+
rgbas = cmap(np.linspace(0.1, 0.9, len(values)))
|
141
|
+
return zip(values, rgbas) # assign each RGBA array to each value
|
142
|
+
|
143
|
+
|
144
|
+
def hide_x_axis_labels(self) -> None:
|
145
|
+
self.ax.tick_params(axis='x', labelbottom=False)
|
146
|
+
|
147
|
+
|
148
|
+
def hide_y_axis_labels(self) -> None:
|
149
|
+
self.ax.tick_params(axis='y', labelbottom=False)
|
150
|
+
|
151
|
+
|
152
|
+
class BaseTimeSeriesPanel(BasePanel):
|
153
|
+
def __init__(self, *args, **kwargs):
|
154
|
+
super().__init__(*args, **kwargs)
|
155
|
+
|
156
|
+
|
157
|
+
@property
|
158
|
+
def times(self):
|
159
|
+
return self.spectrogram.times if self.time_type == "seconds" else self.spectrogram.datetimes
|
160
|
+
|
161
|
+
|
162
|
+
def annotate_x_axis(self):
|
163
|
+
if self.time_type == "seconds":
|
164
|
+
self.ax.set_xlabel('Time [s]')
|
165
|
+
else:
|
166
|
+
self.ax.set_xlabel('Time [UTC]')
|
167
|
+
self.ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M:%S'))
|
168
|
+
|
169
|
+
|
170
|
+
def _set_x_axis_type(self):
|
171
|
+
self._x_axis_type = TIME_X_AXIS
|
172
|
+
|
173
|
+
|
174
|
+
|
175
|
+
class BaseSpectrumPanel(BasePanel):
|
176
|
+
def __init__(self, *args, **kwargs):
|
177
|
+
super().__init__(*args, **kwargs)
|
178
|
+
|
179
|
+
|
180
|
+
@property
|
181
|
+
def frequencies(self):
|
182
|
+
return self._spectrogram.frequencies
|
183
|
+
|
184
|
+
|
185
|
+
def annotate_x_axis(self):
|
186
|
+
self.ax.set_xlabel('Frequency [MHz]')
|
187
|
+
|
188
|
+
|
189
|
+
def _set_x_axis_type(self):
|
190
|
+
self._x_axis_type = FREQUENCY_X_AXIS
|
191
|
+
|
192
|
+
|
193
|
+
class CutsPanel(BasePanel):
|
194
|
+
"""Convenience parent class for cuts"""
|
@@ -0,0 +1,26 @@
|
|
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 spectre_core.spectrograms.spectrogram import Spectrogram
|
6
|
+
from spectre_core.plotting.base import BasePanel
|
7
|
+
from spectre_core.plotting.panel_register import panels
|
8
|
+
from spectre_core.exceptions import PanelNotFoundError
|
9
|
+
|
10
|
+
def get_panel(panel_name: str,
|
11
|
+
spectrogram: Spectrogram,
|
12
|
+
time_type: str,
|
13
|
+
*args,
|
14
|
+
**kwargs) -> BasePanel:
|
15
|
+
|
16
|
+
Panel = panels.get(panel_name)
|
17
|
+
if Panel is None:
|
18
|
+
valid_panels = list(panels.keys())
|
19
|
+
raise PanelNotFoundError(f"Could not find panel with name {panel_name}. "
|
20
|
+
f"Expected one of {valid_panels}")
|
21
|
+
|
22
|
+
return Panel(panel_name,
|
23
|
+
spectrogram,
|
24
|
+
time_type,
|
25
|
+
*args,
|
26
|
+
**kwargs)
|
@@ -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
|
+
from dataclasses import dataclass
|
6
|
+
|
7
|
+
@dataclass
|
8
|
+
class DefaultFormats:
|
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_over_frequency_color: str = "lime"
|
17
|
+
|
18
|
+
|
19
|
+
DEFAULT_FORMATS = DefaultFormats()
|
@@ -0,0 +1,7 @@
|
|
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 spectre_core.dynamic_imports import import_target_modules
|
6
|
+
|
7
|
+
import_target_modules(__file__, __name__, "panel")
|
@@ -0,0 +1,74 @@
|
|
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 Optional
|
6
|
+
from datetime import datetime
|
7
|
+
|
8
|
+
from spectre_core.spectrograms.spectrogram import FrequencyCut
|
9
|
+
from spectre_core.spectrograms.spectrogram import Spectrogram
|
10
|
+
from spectre_core.plotting.base import BaseSpectrumPanel, CutsPanel
|
11
|
+
from spectre_core.plotting.panel_register import register_panel
|
12
|
+
from spectre_core.plotting.format import DEFAULT_FORMATS
|
13
|
+
|
14
|
+
FREQUENCY_CUTS_PANEL_NAME = "frequency_cuts"
|
15
|
+
|
16
|
+
@register_panel(FREQUENCY_CUTS_PANEL_NAME)
|
17
|
+
class Panel(BaseSpectrumPanel, CutsPanel):
|
18
|
+
def __init__(self,
|
19
|
+
name: str,
|
20
|
+
spectrogram: Spectrogram,
|
21
|
+
time_type: str = "seconds",
|
22
|
+
*times: list[float | str],
|
23
|
+
dBb: bool = False,
|
24
|
+
peak_normalise: bool = False,
|
25
|
+
cmap: str = DEFAULT_FORMATS.cuts_cmap,
|
26
|
+
**kwargs):
|
27
|
+
super().__init__(name,
|
28
|
+
spectrogram,
|
29
|
+
time_type,
|
30
|
+
**kwargs)
|
31
|
+
self._times = times
|
32
|
+
self._cmap = cmap
|
33
|
+
self._dBb = dBb
|
34
|
+
self._peak_normalise = peak_normalise
|
35
|
+
# map each time cut to the corresponding FrequencyCut dataclass
|
36
|
+
self._frequency_cuts: Optional[dict[float | datetime, FrequencyCut]] = {}
|
37
|
+
|
38
|
+
|
39
|
+
@property
|
40
|
+
def frequency_cuts(self) -> dict[float | str, FrequencyCut]:
|
41
|
+
if not self._frequency_cuts:
|
42
|
+
for time in self._times:
|
43
|
+
frequency_cut = self._spectrogram.get_frequency_cut(time,
|
44
|
+
dBb = self._dBb,
|
45
|
+
peak_normalise = self._peak_normalise)
|
46
|
+
self._frequency_cuts[frequency_cut.time] = frequency_cut
|
47
|
+
return self._frequency_cuts
|
48
|
+
|
49
|
+
|
50
|
+
@property
|
51
|
+
def times(self) -> list[float | datetime]:
|
52
|
+
return list(self.frequency_cuts.keys())
|
53
|
+
|
54
|
+
|
55
|
+
def draw(self):
|
56
|
+
for time, color in self.bind_to_colors():
|
57
|
+
frequency_cut = self.frequency_cuts[time]
|
58
|
+
self.ax.step(self.frequencies*1e-6, # convert to MHz
|
59
|
+
frequency_cut.cut,
|
60
|
+
where='mid',
|
61
|
+
color = color)
|
62
|
+
|
63
|
+
|
64
|
+
def annotate_y_axis(self) -> None:
|
65
|
+
if self._dBb:
|
66
|
+
self.ax.set_ylabel('dBb')
|
67
|
+
elif self._peak_normalise:
|
68
|
+
return # no y-axis label
|
69
|
+
else:
|
70
|
+
self.ax.set_ylabel(f'{self._spectrogram.spectrum_type.capitalize()}')
|
71
|
+
|
72
|
+
|
73
|
+
def bind_to_colors(self):
|
74
|
+
return super().bind_to_colors(self.times, cmap = self._cmap)
|
@@ -0,0 +1,34 @@
|
|
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 spectre_core.plotting.base import BaseTimeSeriesPanel
|
6
|
+
from spectre_core.plotting.panel_register import register_panel
|
7
|
+
from spectre_core.plotting.format import DEFAULT_FORMATS
|
8
|
+
|
9
|
+
INTEGRAL_OVER_FREQUENCY_PANEL_NAME = "integral_over_frequency"
|
10
|
+
|
11
|
+
@register_panel(INTEGRAL_OVER_FREQUENCY_PANEL_NAME)
|
12
|
+
class Panel(BaseTimeSeriesPanel):
|
13
|
+
def __init__(self,
|
14
|
+
*args,
|
15
|
+
peak_normalise: bool = False,
|
16
|
+
background_subtract: bool = False,
|
17
|
+
color: str = DEFAULT_FORMATS.integral_over_frequency_color,
|
18
|
+
**kwargs):
|
19
|
+
super().__init__(*args, **kwargs)
|
20
|
+
self._peak_normalise = peak_normalise
|
21
|
+
self._background_subtract = background_subtract
|
22
|
+
self._color = color
|
23
|
+
|
24
|
+
|
25
|
+
def draw(self):
|
26
|
+
I = self._spectrogram.integrate_over_frequency(correct_background = self._background_subtract,
|
27
|
+
peak_normalise = self._peak_normalise)
|
28
|
+
self.ax.step(self.times, I, where="mid", color = self._color)
|
29
|
+
|
30
|
+
|
31
|
+
def annotate_y_axis(self):
|
32
|
+
return # no y-axis label
|
33
|
+
|
34
|
+
|
@@ -0,0 +1,92 @@
|
|
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 numpy as np
|
6
|
+
from matplotlib.colors import LogNorm
|
7
|
+
from matplotlib.figure import Figure
|
8
|
+
from matplotlib.axes import Axes
|
9
|
+
from warnings import warn
|
10
|
+
|
11
|
+
from spectre_core.plotting.base import BaseTimeSeriesPanel
|
12
|
+
from spectre_core.plotting.panel_register import register_panel
|
13
|
+
from spectre_core.plotting.base import CutsPanel
|
14
|
+
from spectre_core.plotting.library.time_cuts.panel import Panel as TimeCutsPanel
|
15
|
+
from spectre_core.plotting.library.frequency_cuts.panel import Panel as FrequencyCutsPanel
|
16
|
+
from spectre_core.plotting.format import DEFAULT_FORMATS
|
17
|
+
|
18
|
+
SPECTROGRAM_PANEL_NAME = "spectrogram"
|
19
|
+
|
20
|
+
@register_panel(SPECTROGRAM_PANEL_NAME)
|
21
|
+
class Panel(BaseTimeSeriesPanel):
|
22
|
+
def __init__(self,
|
23
|
+
*args,
|
24
|
+
log_norm: bool = False,
|
25
|
+
dBb: bool = False,
|
26
|
+
vmin: float | None = -1,
|
27
|
+
vmax: float | None = 2,
|
28
|
+
cmap: str = DEFAULT_FORMATS.spectrogram_cmap,
|
29
|
+
**kwargs):
|
30
|
+
super().__init__(*args, **kwargs)
|
31
|
+
self._log_norm = log_norm
|
32
|
+
self._dBb = dBb
|
33
|
+
self._vmin = vmin
|
34
|
+
self._vmax = vmax
|
35
|
+
self._cmap = cmap
|
36
|
+
|
37
|
+
|
38
|
+
def draw(self):
|
39
|
+
dynamic_spectra = self._spectrogram.dynamic_spectra_as_dBb if self._dBb else self._spectrogram.dynamic_spectra
|
40
|
+
|
41
|
+
norm = LogNorm(vmin=np.nanmin(dynamic_spectra[dynamic_spectra > 0]),
|
42
|
+
vmax=np.nanmax(dynamic_spectra)) if self._log_norm else None
|
43
|
+
|
44
|
+
|
45
|
+
if self._log_norm and (self._vmin or self._vmax):
|
46
|
+
warn("vmin/vmax will be ignored while using log_norm.")
|
47
|
+
self._vmin = None
|
48
|
+
self._vmax = None
|
49
|
+
|
50
|
+
# Plot the spectrogram with kwargs
|
51
|
+
pcm = self.ax.pcolormesh(self.times,
|
52
|
+
self._spectrogram.frequencies * 1e-6,
|
53
|
+
dynamic_spectra,
|
54
|
+
vmin=self._vmin,
|
55
|
+
vmax=self._vmax,
|
56
|
+
norm=norm,
|
57
|
+
cmap=self._cmap)
|
58
|
+
|
59
|
+
# Add colorbar if dBb is used
|
60
|
+
if self._dBb:
|
61
|
+
cbar = self.fig.colorbar(pcm,
|
62
|
+
ax=self.ax,
|
63
|
+
ticks=np.linspace(self._vmin, self._vmax, 6, dtype=int))
|
64
|
+
cbar.set_label('dBb')
|
65
|
+
|
66
|
+
|
67
|
+
def annotate_y_axis(self) -> None:
|
68
|
+
self.ax.set_ylabel('Frequency [MHz]')
|
69
|
+
return
|
70
|
+
|
71
|
+
|
72
|
+
def overlay_cuts(self, cuts_panel: CutsPanel) -> None:
|
73
|
+
if isinstance(cuts_panel, TimeCutsPanel):
|
74
|
+
self._overlay_time_cuts(cuts_panel)
|
75
|
+
elif isinstance(cuts_panel, FrequencyCutsPanel):
|
76
|
+
self._overlay_frequency_cuts(cuts_panel)
|
77
|
+
|
78
|
+
|
79
|
+
def _overlay_time_cuts(self, time_cuts_panel: TimeCutsPanel) -> None:
|
80
|
+
for frequency, color in time_cuts_panel.bind_to_colors():
|
81
|
+
self.ax.axhline(frequency*1e-6, # convert to MHz
|
82
|
+
color = color,
|
83
|
+
linewidth=DEFAULT_FORMATS.line_width
|
84
|
+
)
|
85
|
+
|
86
|
+
|
87
|
+
def _overlay_frequency_cuts(self, frequency_cuts_panel: FrequencyCutsPanel) -> None:
|
88
|
+
for time, color in frequency_cuts_panel.bind_to_colors():
|
89
|
+
self.ax.axvline(time,
|
90
|
+
color = color,
|
91
|
+
linewidth=DEFAULT_FORMATS.line_width
|
92
|
+
)
|
@@ -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
|
+
from typing import Optional
|
6
|
+
|
7
|
+
from spectre_core.spectrograms.spectrogram import TimeCut
|
8
|
+
from spectre_core.spectrograms.spectrogram import Spectrogram
|
9
|
+
from spectre_core.plotting.base import BaseTimeSeriesPanel, CutsPanel
|
10
|
+
from spectre_core.plotting.format import DEFAULT_FORMATS
|
11
|
+
from spectre_core.plotting.panel_register import register_panel
|
12
|
+
|
13
|
+
TIME_CUTS_PANEL_NAME = "time_cuts"
|
14
|
+
|
15
|
+
@register_panel(TIME_CUTS_PANEL_NAME)
|
16
|
+
class Panel(BaseTimeSeriesPanel, CutsPanel):
|
17
|
+
def __init__(self,
|
18
|
+
name: str,
|
19
|
+
spectrogram: Spectrogram,
|
20
|
+
time_type: str = "seconds",
|
21
|
+
*frequencies: list[float],
|
22
|
+
dBb: bool = False,
|
23
|
+
peak_normalise: bool = False,
|
24
|
+
background_subtract: bool = False,
|
25
|
+
cmap: str = DEFAULT_FORMATS.cuts_cmap,
|
26
|
+
**kwargs):
|
27
|
+
super().__init__(name,
|
28
|
+
spectrogram,
|
29
|
+
time_type,
|
30
|
+
**kwargs)
|
31
|
+
self._frequencies = frequencies
|
32
|
+
self._cmap = cmap
|
33
|
+
self._dBb = dBb
|
34
|
+
self._peak_normalise = peak_normalise
|
35
|
+
self._background_subtract = background_subtract
|
36
|
+
# map each cut frequency to the corresponding TimeCut dataclass
|
37
|
+
self._time_cuts: Optional[dict[float, TimeCut]] = {}
|
38
|
+
|
39
|
+
|
40
|
+
@property
|
41
|
+
def time_cuts(self) -> dict[float, TimeCut]:
|
42
|
+
if not self._time_cuts:
|
43
|
+
for frequency in self._frequencies:
|
44
|
+
time_cut = self._spectrogram.get_time_cut(frequency,
|
45
|
+
dBb = self._dBb,
|
46
|
+
peak_normalise = self._peak_normalise,
|
47
|
+
correct_background = self._background_subtract,
|
48
|
+
return_time_type=self._time_type)
|
49
|
+
self._time_cuts[time_cut.frequency] = time_cut
|
50
|
+
return self._time_cuts
|
51
|
+
|
52
|
+
|
53
|
+
@property
|
54
|
+
def frequencies(self) -> list[float]:
|
55
|
+
return list(self.time_cuts.keys())
|
56
|
+
|
57
|
+
|
58
|
+
def draw(self):
|
59
|
+
for frequency, color in self.bind_to_colors():
|
60
|
+
time_cut = self.time_cuts[frequency]
|
61
|
+
self.ax.step(self.times,
|
62
|
+
time_cut.cut,
|
63
|
+
where='mid',
|
64
|
+
color = color)
|
65
|
+
|
66
|
+
|
67
|
+
def annotate_y_axis(self) -> None:
|
68
|
+
if self._dBb:
|
69
|
+
self.ax.set_ylabel('dBb')
|
70
|
+
elif self._peak_normalise:
|
71
|
+
return # no y-axis label
|
72
|
+
else:
|
73
|
+
self.ax.set_ylabel(f'{self._spectrogram.spectrum_type.capitalize()}')
|
74
|
+
|
75
|
+
|
76
|
+
def bind_to_colors(self):
|
77
|
+
return super().bind_to_colors(self.frequencies, cmap = self._cmap)
|
@@ -0,0 +1,13 @@
|
|
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
|
+
# Global dictionaries to hold the mappings
|
7
|
+
panels = {}
|
8
|
+
|
9
|
+
def register_panel(panel_name: str):
|
10
|
+
def decorator(cls):
|
11
|
+
panels[panel_name] = cls
|
12
|
+
return cls
|
13
|
+
return decorator
|
@@ -0,0 +1,148 @@
|
|
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 numpy as np
|
6
|
+
from typing import List, Optional, Tuple
|
7
|
+
import matplotlib.pyplot as plt
|
8
|
+
from matplotlib.figure import Figure
|
9
|
+
from matplotlib.axes import Axes
|
10
|
+
|
11
|
+
from spectre_core.spectrograms.spectrogram import Spectrogram
|
12
|
+
from spectre_core.plotting.base import BasePanel
|
13
|
+
from spectre_core.plotting.factory import get_panel
|
14
|
+
from spectre_core.plotting.library.time_cuts.panel import Panel as TimeCutsPanel
|
15
|
+
from spectre_core.plotting.library.frequency_cuts.panel import Panel as FrequencyCutsPanel
|
16
|
+
from spectre_core.plotting.library.spectrogram.panel import Panel as SpectrogramPanel
|
17
|
+
from spectre_core.plotting.base import CutsPanel
|
18
|
+
from spectre_core.plotting.format import DEFAULT_FORMATS
|
19
|
+
|
20
|
+
class PanelStack:
|
21
|
+
def __init__(self, time_type: str, figsize: Tuple[int, int] = (10, 10)):
|
22
|
+
self._time_type = time_type
|
23
|
+
self._figsize = figsize
|
24
|
+
self._panels: List[BasePanel] = []
|
25
|
+
self._superimposed_panels: List[BasePanel] = []
|
26
|
+
self._fig: Optional[Figure] = None
|
27
|
+
self._axs: Optional[np.ndarray[Axes]] = None
|
28
|
+
|
29
|
+
|
30
|
+
@property
|
31
|
+
def time_type(self) -> str:
|
32
|
+
return self._time_type
|
33
|
+
|
34
|
+
|
35
|
+
@property
|
36
|
+
def panels(self) -> List[BasePanel]:
|
37
|
+
return sorted(self._panels, key=lambda panel: panel.x_axis_type)
|
38
|
+
|
39
|
+
|
40
|
+
@property
|
41
|
+
def fig(self) -> Optional[Figure]:
|
42
|
+
return self._fig
|
43
|
+
|
44
|
+
|
45
|
+
@property
|
46
|
+
def axs(self) -> Optional[np.ndarray[Axes]]:
|
47
|
+
return np.atleast_1d(self._axs)
|
48
|
+
|
49
|
+
|
50
|
+
@property
|
51
|
+
def num_panels(self) -> int:
|
52
|
+
return len(self._panels)
|
53
|
+
|
54
|
+
|
55
|
+
def add_panel(self,
|
56
|
+
panel_name: str,
|
57
|
+
spectrogram: Spectrogram,
|
58
|
+
*args,
|
59
|
+
identifier: Optional[str] = None,
|
60
|
+
**kwargs) -> None:
|
61
|
+
panel = get_panel(panel_name,
|
62
|
+
spectrogram,
|
63
|
+
self._time_type,
|
64
|
+
*args,
|
65
|
+
**kwargs)
|
66
|
+
if identifier:
|
67
|
+
panel.identifier = identifier
|
68
|
+
self._panels.append(panel)
|
69
|
+
|
70
|
+
|
71
|
+
def superimpose_panel(self,
|
72
|
+
panel_name: str,
|
73
|
+
spectrogram: Spectrogram,
|
74
|
+
*args,
|
75
|
+
identifier: Optional[str] = None,
|
76
|
+
**kwargs) -> None:
|
77
|
+
panel = get_panel(panel_name,
|
78
|
+
spectrogram,
|
79
|
+
self._time_type,
|
80
|
+
*args,
|
81
|
+
**kwargs)
|
82
|
+
if identifier:
|
83
|
+
panel.identifier = identifier
|
84
|
+
self._superimposed_panels.append(panel)
|
85
|
+
|
86
|
+
|
87
|
+
def _init_plot_style(self) -> None:
|
88
|
+
plt.style.use(DEFAULT_FORMATS.style)
|
89
|
+
plt.rc('font', size=DEFAULT_FORMATS.small_size)
|
90
|
+
plt.rc('axes', titlesize=DEFAULT_FORMATS.medium_size,
|
91
|
+
labelsize=DEFAULT_FORMATS.medium_size)
|
92
|
+
plt.rc('xtick', labelsize=DEFAULT_FORMATS.small_size)
|
93
|
+
plt.rc('ytick', labelsize=DEFAULT_FORMATS.small_size)
|
94
|
+
plt.rc('legend', fontsize=DEFAULT_FORMATS.small_size)
|
95
|
+
plt.rc('figure', titlesize=DEFAULT_FORMATS.large_size)
|
96
|
+
|
97
|
+
|
98
|
+
def _create_figure_and_axes(self) -> None:
|
99
|
+
self._fig, self._axs = plt.subplots(self.num_panels, 1, figsize=self._figsize, layout="constrained")
|
100
|
+
|
101
|
+
|
102
|
+
def _assign_axes(self) -> None:
|
103
|
+
shared_axes = {}
|
104
|
+
for i, panel in enumerate(self.panels):
|
105
|
+
panel.ax = self.axs[i]
|
106
|
+
panel.fig = self._fig
|
107
|
+
if panel.x_axis_type in shared_axes:
|
108
|
+
panel.ax.sharex(shared_axes[panel.x_axis_type])
|
109
|
+
else:
|
110
|
+
shared_axes[panel.x_axis_type] = panel.ax
|
111
|
+
|
112
|
+
|
113
|
+
def _overlay_cuts(self, cuts_panel: CutsPanel) -> None:
|
114
|
+
"""Given a cuts panel, finds any corresponding spectrogram panels and adds the appropriate overlay"""
|
115
|
+
for panel in self.panels:
|
116
|
+
is_corresponding_panel = isinstance(panel, SpectrogramPanel) and (panel.tag == cuts_panel.tag)
|
117
|
+
if is_corresponding_panel:
|
118
|
+
panel.overlay_cuts(cuts_panel)
|
119
|
+
|
120
|
+
|
121
|
+
def _overlay_superimposed_panels(self) -> None:
|
122
|
+
for super_panel in self._superimposed_panels:
|
123
|
+
for panel in self._panels:
|
124
|
+
if panel.name == super_panel.name and (panel.identifier == super_panel.identifier): # find the matching panels via panel names
|
125
|
+
super_panel.ax, super_panel.fig = panel.ax, self._fig # and superimpose via axes sharing
|
126
|
+
super_panel.draw()
|
127
|
+
if isinstance(super_panel, CutsPanel):
|
128
|
+
self._overlay_cuts(super_panel)
|
129
|
+
|
130
|
+
|
131
|
+
def show(self) -> None:
|
132
|
+
self._init_plot_style()
|
133
|
+
self._create_figure_and_axes()
|
134
|
+
self._assign_axes()
|
135
|
+
last_panel_per_axis = {panel.x_axis_type: panel for panel in self.panels}
|
136
|
+
for panel in self.panels:
|
137
|
+
panel.draw()
|
138
|
+
panel.annotate_y_axis()
|
139
|
+
if panel == last_panel_per_axis[panel.x_axis_type]:
|
140
|
+
panel.annotate_x_axis()
|
141
|
+
else:
|
142
|
+
panel.hide_x_axis_labels()
|
143
|
+
if isinstance(panel, CutsPanel):
|
144
|
+
self._overlay_cuts(panel)
|
145
|
+
|
146
|
+
|
147
|
+
self._overlay_superimposed_panels()
|
148
|
+
plt.show()
|