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.
Files changed (72) hide show
  1. spectre_core/__init__.py +3 -0
  2. spectre_core/cfg.py +116 -0
  3. spectre_core/chunks/__init__.py +206 -0
  4. spectre_core/chunks/base.py +160 -0
  5. spectre_core/chunks/chunk_register.py +15 -0
  6. spectre_core/chunks/factory.py +26 -0
  7. spectre_core/chunks/library/__init__.py +8 -0
  8. spectre_core/chunks/library/callisto/__init__.py +0 -0
  9. spectre_core/chunks/library/callisto/chunk.py +101 -0
  10. spectre_core/chunks/library/fixed/__init__.py +0 -0
  11. spectre_core/chunks/library/fixed/chunk.py +185 -0
  12. spectre_core/chunks/library/sweep/__init__.py +0 -0
  13. spectre_core/chunks/library/sweep/chunk.py +400 -0
  14. spectre_core/dynamic_imports.py +22 -0
  15. spectre_core/exceptions.py +17 -0
  16. spectre_core/file_handlers/base.py +94 -0
  17. spectre_core/file_handlers/configs.py +269 -0
  18. spectre_core/file_handlers/json.py +36 -0
  19. spectre_core/file_handlers/text.py +21 -0
  20. spectre_core/logging.py +222 -0
  21. spectre_core/plotting/__init__.py +5 -0
  22. spectre_core/plotting/base.py +194 -0
  23. spectre_core/plotting/factory.py +26 -0
  24. spectre_core/plotting/format.py +19 -0
  25. spectre_core/plotting/library/__init__.py +7 -0
  26. spectre_core/plotting/library/frequency_cuts/panel.py +74 -0
  27. spectre_core/plotting/library/integral_over_frequency/panel.py +34 -0
  28. spectre_core/plotting/library/spectrogram/panel.py +92 -0
  29. spectre_core/plotting/library/time_cuts/panel.py +77 -0
  30. spectre_core/plotting/panel_register.py +13 -0
  31. spectre_core/plotting/panel_stack.py +148 -0
  32. spectre_core/receivers/__init__.py +6 -0
  33. spectre_core/receivers/base.py +415 -0
  34. spectre_core/receivers/factory.py +19 -0
  35. spectre_core/receivers/library/__init__.py +7 -0
  36. spectre_core/receivers/library/rsp1a/__init__.py +0 -0
  37. spectre_core/receivers/library/rsp1a/gr/__init__.py +0 -0
  38. spectre_core/receivers/library/rsp1a/gr/fixed.py +104 -0
  39. spectre_core/receivers/library/rsp1a/gr/sweep.py +129 -0
  40. spectre_core/receivers/library/rsp1a/receiver.py +68 -0
  41. spectre_core/receivers/library/rspduo/__init__.py +0 -0
  42. spectre_core/receivers/library/rspduo/gr/__init__.py +0 -0
  43. spectre_core/receivers/library/rspduo/gr/tuner_1_fixed.py +110 -0
  44. spectre_core/receivers/library/rspduo/gr/tuner_1_sweep.py +135 -0
  45. spectre_core/receivers/library/rspduo/receiver.py +68 -0
  46. spectre_core/receivers/library/test/__init__.py +0 -0
  47. spectre_core/receivers/library/test/gr/__init__.py +0 -0
  48. spectre_core/receivers/library/test/gr/cosine_signal_1.py +83 -0
  49. spectre_core/receivers/library/test/gr/tagged_staircase.py +93 -0
  50. spectre_core/receivers/library/test/receiver.py +174 -0
  51. spectre_core/receivers/receiver_register.py +22 -0
  52. spectre_core/receivers/validators.py +205 -0
  53. spectre_core/spectrograms/__init__.py +3 -0
  54. spectre_core/spectrograms/analytical.py +205 -0
  55. spectre_core/spectrograms/array_operations.py +77 -0
  56. spectre_core/spectrograms/spectrogram.py +461 -0
  57. spectre_core/spectrograms/transform.py +267 -0
  58. spectre_core/watchdog/__init__.py +6 -0
  59. spectre_core/watchdog/base.py +105 -0
  60. spectre_core/watchdog/event_handler_register.py +15 -0
  61. spectre_core/watchdog/factory.py +22 -0
  62. spectre_core/watchdog/library/__init__.py +10 -0
  63. spectre_core/watchdog/library/fixed/__init__.py +0 -0
  64. spectre_core/watchdog/library/fixed/event_handler.py +41 -0
  65. spectre_core/watchdog/library/sweep/event_handler.py +55 -0
  66. spectre_core/watchdog/watcher.py +50 -0
  67. spectre_core/web_fetch/callisto.py +101 -0
  68. spectre_core-0.0.1.dist-info/LICENSE +674 -0
  69. spectre_core-0.0.1.dist-info/METADATA +40 -0
  70. spectre_core-0.0.1.dist-info/RECORD +72 -0
  71. spectre_core-0.0.1.dist-info/WHEEL +5 -0
  72. 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()
@@ -0,0 +1,6 @@
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
+ # dynamically import all receiver
6
+ import spectre_core.receivers.library