spectre-core 0.0.8__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} +50 -48
- spectre_core/plotting/_panels.py +234 -0
- spectre_core/post_processing/__init__.py +14 -0
- spectre_core/post_processing/_base.py +119 -0
- spectre_core/post_processing/_factory.py +23 -0
- spectre_core/post_processing/_post_processor.py +40 -0
- 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/{receivers/library → wgetting}/__init__.py +4 -2
- spectre_core/wgetting/_callisto.py +155 -0
- {spectre_core-0.0.8.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/receivers/base.py +0 -415
- 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 -178
- spectre_core/receivers/validators.py +0 -193
- spectre_core/watchdog/__init__.py +0 -6
- spectre_core/watchdog/base.py +0 -105
- spectre_core/watchdog/factory.py +0 -22
- spectre_core/watchdog/library/__init__.py +0 -10
- spectre_core/watchdog/library/fixed/__init__.py +0 -0
- spectre_core/watchdog/library/fixed/event_handler.py +0 -41
- spectre_core/watchdog/library/sweep/event_handler.py +0 -55
- spectre_core/watchdog/post_processor.py +0 -50
- spectre_core/web_fetch/callisto.py +0 -101
- spectre_core-0.0.8.dist-info/RECORD +0 -74
- /spectre_core/chunks/{chunk_register.py → _register.py} +0 -0
- /spectre_core/{watchdog/event_handler_register.py → post_processing/_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.8.dist-info → spectre_core-0.0.10.dist-info}/LICENSE +0 -0
- {spectre_core-0.0.8.dist-info → spectre_core-0.0.10.dist-info}/WHEEL +0 -0
- {spectre_core-0.0.8.dist-info → spectre_core-0.0.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
#
|
5
|
+
# SPDX-License-Identifier: GPL-3.0
|
6
|
+
#
|
7
|
+
# GNU Radio Python Flow Graph
|
8
|
+
# Title: Test receiver
|
9
|
+
# GNU Radio version: 3.10.1.1
|
10
|
+
|
11
|
+
# SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
12
|
+
# This file is part of SPECTRE
|
13
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
14
|
+
|
15
|
+
#
|
16
|
+
# Test receiver top blocks
|
17
|
+
#
|
18
|
+
|
19
|
+
from functools import partial
|
20
|
+
from dataclasses import dataclass
|
21
|
+
|
22
|
+
from gnuradio import gr
|
23
|
+
from gnuradio import blocks
|
24
|
+
from gnuradio import spectre
|
25
|
+
from gnuradio import analog
|
26
|
+
|
27
|
+
from spectre_core.capture_configs import Parameters, PNames
|
28
|
+
from spectre_core.config import get_chunks_dir_path
|
29
|
+
from ._base import capture
|
30
|
+
|
31
|
+
|
32
|
+
class _cosine_signal_1(gr.top_block):
|
33
|
+
def __init__(self,
|
34
|
+
tag: str,
|
35
|
+
parameters: Parameters):
|
36
|
+
gr.top_block.__init__(self, "cosine-signal-1", catch_exceptions=True)
|
37
|
+
|
38
|
+
##################################################
|
39
|
+
# Unpack capture config
|
40
|
+
##################################################
|
41
|
+
samp_rate = parameters.get_parameter_value(PNames.SAMPLE_RATE)
|
42
|
+
batch_size = parameters.get_parameter_value(PNames.BATCH_SIZE)
|
43
|
+
frequency = parameters.get_parameter_value(PNames.FREQUENCY)
|
44
|
+
amplitude = parameters.get_parameter_value(PNames.AMPLITUDE)
|
45
|
+
|
46
|
+
##################################################
|
47
|
+
# Blocks
|
48
|
+
##################################################
|
49
|
+
self.spectre_batched_file_sink_0 = spectre.batched_file_sink(get_chunks_dir_path(),
|
50
|
+
tag,
|
51
|
+
batch_size,
|
52
|
+
samp_rate)
|
53
|
+
self.blocks_throttle_0_1 = blocks.throttle(gr.sizeof_float*1,
|
54
|
+
samp_rate,
|
55
|
+
True)
|
56
|
+
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1,
|
57
|
+
samp_rate,
|
58
|
+
True)
|
59
|
+
self.blocks_null_source_1 = blocks.null_source(gr.sizeof_float*1)
|
60
|
+
self.blocks_float_to_complex_1 = blocks.float_to_complex(1)
|
61
|
+
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate,
|
62
|
+
analog.GR_COS_WAVE,
|
63
|
+
frequency,
|
64
|
+
amplitude,
|
65
|
+
0,
|
66
|
+
0)
|
67
|
+
|
68
|
+
|
69
|
+
##################################################
|
70
|
+
# Connections
|
71
|
+
##################################################
|
72
|
+
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))
|
73
|
+
self.connect((self.blocks_float_to_complex_1, 0), (self.spectre_batched_file_sink_0, 0))
|
74
|
+
self.connect((self.blocks_null_source_1, 0), (self.blocks_throttle_0_1, 0))
|
75
|
+
self.connect((self.blocks_throttle_0, 0), (self.blocks_float_to_complex_1, 0))
|
76
|
+
self.connect((self.blocks_throttle_0_1, 0), (self.blocks_float_to_complex_1, 1))
|
77
|
+
|
78
|
+
|
79
|
+
class _tagged_staircase(gr.top_block):
|
80
|
+
def __init__(self,
|
81
|
+
tag: str,
|
82
|
+
parameters: Parameters):
|
83
|
+
gr.top_block.__init__(self, "tagged-staircase", catch_exceptions=True)
|
84
|
+
|
85
|
+
##################################################
|
86
|
+
# Unpack capture config
|
87
|
+
##################################################
|
88
|
+
step_increment = parameters.get_parameter_value(PNames.STEP_INCREMENT)
|
89
|
+
samp_rate = parameters.get_parameter_value(PNames.SAMPLE_RATE)
|
90
|
+
min_samples_per_step = parameters.get_parameter_value(PNames.MIN_SAMPLES_PER_STEP)
|
91
|
+
max_samples_per_step = parameters.get_parameter_value(PNames.MAX_SAMPLES_PER_STEP)
|
92
|
+
frequency_step = parameters.get_parameter_value(PNames.FREQUENCY_STEP)
|
93
|
+
batch_size = parameters.get_parameter_value(PNames.BATCH_SIZE)
|
94
|
+
|
95
|
+
##################################################
|
96
|
+
# Blocks
|
97
|
+
##################################################
|
98
|
+
self.spectre_tagged_staircase_0 = spectre.tagged_staircase(min_samples_per_step,
|
99
|
+
max_samples_per_step,
|
100
|
+
frequency_step,
|
101
|
+
step_increment,
|
102
|
+
samp_rate)
|
103
|
+
self.spectre_batched_file_sink_0 = spectre.batched_file_sink(get_chunks_dir_path(),
|
104
|
+
tag,
|
105
|
+
batch_size,
|
106
|
+
samp_rate,
|
107
|
+
True,
|
108
|
+
'rx_freq',
|
109
|
+
0) # zero means the center frequency is unset
|
110
|
+
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_gr_complex*1, samp_rate, True)
|
111
|
+
|
112
|
+
|
113
|
+
##################################################
|
114
|
+
# Connections
|
115
|
+
##################################################
|
116
|
+
self.connect((self.blocks_throttle_0, 0), (self.spectre_batched_file_sink_0, 0))
|
117
|
+
self.connect((self.spectre_tagged_staircase_0, 0), (self.blocks_throttle_0, 0))
|
118
|
+
|
119
|
+
|
120
|
+
@dataclass(frozen=True)
|
121
|
+
class CaptureMethods:
|
122
|
+
cosine_signal_1 = partial(capture, top_block_cls=_cosine_signal_1)
|
123
|
+
tagged_staircase = partial(capture, top_block_cls=_tagged_staircase)
|
@@ -0,0 +1,61 @@
|
|
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 dataclasses import dataclass
|
7
|
+
from functools import partial
|
8
|
+
|
9
|
+
from spectre_core.capture_configs import (
|
10
|
+
CaptureModes
|
11
|
+
)
|
12
|
+
from ..gr._rsp1a import CaptureMethods
|
13
|
+
from .._spec_names import SpecNames
|
14
|
+
from .._base import SDRPlayReceiver
|
15
|
+
from .._register import register_receiver
|
16
|
+
|
17
|
+
@dataclass
|
18
|
+
class Modes:
|
19
|
+
FIXED_CENTER_FREQUENCY = CaptureModes.FIXED_CENTER_FREQUENCY
|
20
|
+
SWEPT_CENTER_FREQUENCY = CaptureModes.SWEPT_CENTER_FREQUENCY
|
21
|
+
|
22
|
+
@register_receiver("rsp1a")
|
23
|
+
class _Receiver(SDRPlayReceiver):
|
24
|
+
def __init__(self,
|
25
|
+
name: str,
|
26
|
+
mode: Optional[str]):
|
27
|
+
super().__init__(name,
|
28
|
+
mode)
|
29
|
+
|
30
|
+
|
31
|
+
def _add_specs(self) -> None:
|
32
|
+
self.add_spec( SpecNames.SAMPLE_RATE_LOWER_BOUND, 200e3 ) # Hz
|
33
|
+
self.add_spec( SpecNames.SAMPLE_RATE_UPPER_BOUND, 10e6 ) # Hz
|
34
|
+
self.add_spec( SpecNames.FREQUENCY_LOWER_BOUND , 1e3 ) # Hz
|
35
|
+
self.add_spec( SpecNames.FREQUENCY_UPPER_BOUND , 2e9 ) # Hz
|
36
|
+
self.add_spec( SpecNames.IF_GAIN_UPPER_BOUND , -20 ) # dB
|
37
|
+
self.add_spec( SpecNames.RF_GAIN_UPPER_BOUND , 0 ) # dB
|
38
|
+
self.add_spec( SpecNames.API_RETUNING_LATENCY , 50 * 1e-3 ) # s
|
39
|
+
self.add_spec( SpecNames.BANDWIDTH_OPTIONS,
|
40
|
+
[200000, 300000, 600000, 1536000, 5000000, 6000000, 7000000, 8000000])
|
41
|
+
|
42
|
+
|
43
|
+
def _add_capture_methods(self) -> None:
|
44
|
+
self.add_capture_method(Modes.FIXED_CENTER_FREQUENCY,
|
45
|
+
CaptureMethods.fixed_center_frequency)
|
46
|
+
self.add_capture_method(Modes.SWEPT_CENTER_FREQUENCY,
|
47
|
+
CaptureMethods._swept_center_frequency)
|
48
|
+
|
49
|
+
|
50
|
+
def _add_capture_templates(self):
|
51
|
+
self.add_capture_template(Modes.FIXED_CENTER_FREQUENCY,
|
52
|
+
self._get_capture_template_fixed_center_frequency())
|
53
|
+
self.add_capture_template(Modes.SWEPT_CENTER_FREQUENCY,
|
54
|
+
self._get_capture_template_swept_center_frequency())
|
55
|
+
|
56
|
+
|
57
|
+
def _add_pvalidators(self):
|
58
|
+
self.add_pvalidator(CaptureModes.FIXED_CENTER_FREQUENCY,
|
59
|
+
self._get_pvalidator_fixed_center_frequency())
|
60
|
+
self.add_pvalidator(CaptureModes.SWEPT_CENTER_FREQUENCY,
|
61
|
+
self._get_pvalidator_swept_center_frequency())
|
@@ -0,0 +1,221 @@
|
|
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
|
+
from typing import Optional, Callable
|
7
|
+
|
8
|
+
from spectre_core.capture_configs import (
|
9
|
+
CaptureTemplate, CaptureModes, Parameters, Bound, PValidators, PNames,
|
10
|
+
get_base_capture_template, make_base_capture_template, get_base_ptemplate
|
11
|
+
)
|
12
|
+
from ..gr._test import CaptureMethods
|
13
|
+
from .._spec_names import SpecNames
|
14
|
+
from .._base import BaseReceiver
|
15
|
+
from .._register import register_receiver
|
16
|
+
|
17
|
+
|
18
|
+
@dataclass
|
19
|
+
class Modes:
|
20
|
+
COSINE_SIGNAL_1 = "cosine-signal-1"
|
21
|
+
TAGGED_STAIRCASE = "tagged-staircase"
|
22
|
+
|
23
|
+
|
24
|
+
@register_receiver("test")
|
25
|
+
class _Receiver(BaseReceiver):
|
26
|
+
def __init__(self,
|
27
|
+
name: str,
|
28
|
+
mode: Optional[str]):
|
29
|
+
super().__init__(name,
|
30
|
+
mode)
|
31
|
+
|
32
|
+
|
33
|
+
def _add_specs(self) -> None:
|
34
|
+
self.add_spec( SpecNames.SAMPLE_RATE_LOWER_BOUND, 64000 )
|
35
|
+
self.add_spec( SpecNames.SAMPLE_RATE_UPPER_BOUND, 640000 )
|
36
|
+
self.add_spec( SpecNames.FREQUENCY_LOWER_BOUND , 16000 )
|
37
|
+
self.add_spec( SpecNames.FREQUENCY_UPPER_BOUND , 160000 )
|
38
|
+
|
39
|
+
|
40
|
+
def _add_capture_methods(self) -> None:
|
41
|
+
self.add_capture_method( Modes.COSINE_SIGNAL_1 , CaptureMethods.cosine_signal_1 )
|
42
|
+
self.add_capture_method( Modes.TAGGED_STAIRCASE, CaptureMethods.tagged_staircase)
|
43
|
+
|
44
|
+
|
45
|
+
def _add_pvalidators(self) -> None:
|
46
|
+
self.add_pvalidator( Modes.COSINE_SIGNAL_1 , self.__get_pvalidator_cosine_signal_1() )
|
47
|
+
self.add_pvalidator( Modes.TAGGED_STAIRCASE, self.__get_pvalidator_tagged_staircase() )
|
48
|
+
|
49
|
+
|
50
|
+
def _add_capture_templates(self) -> None:
|
51
|
+
self.add_capture_template( Modes.COSINE_SIGNAL_1 , self.__get_capture_template_cosine_signal_1() )
|
52
|
+
self.add_capture_template( Modes.TAGGED_STAIRCASE, self.__get_capture_template_tagged_staircase() )
|
53
|
+
|
54
|
+
|
55
|
+
def __get_capture_template_cosine_signal_1(self) -> CaptureTemplate:
|
56
|
+
#
|
57
|
+
# Create the base template
|
58
|
+
#
|
59
|
+
capture_template = get_base_capture_template( CaptureModes.FIXED_CENTER_FREQUENCY )
|
60
|
+
capture_template.add_ptemplate( get_base_ptemplate(PNames.AMPLITUDE) )
|
61
|
+
capture_template.add_ptemplate( get_base_ptemplate(PNames.FREQUENCY) )
|
62
|
+
|
63
|
+
#
|
64
|
+
# Update the defaults
|
65
|
+
#
|
66
|
+
capture_template.set_defaults(
|
67
|
+
(PNames.BATCH_SIZE, 3.0),
|
68
|
+
(PNames.CENTER_FREQUENCY, 16000),
|
69
|
+
(PNames.AMPLITUDE, 2.0),
|
70
|
+
(PNames.FREQUENCY, 32000),
|
71
|
+
(PNames.SAMPLE_RATE, 128000),
|
72
|
+
(PNames.WINDOW_HOP, 512),
|
73
|
+
(PNames.WINDOW_SIZE, 512),
|
74
|
+
(PNames.WINDOW_TYPE, "boxcar")
|
75
|
+
)
|
76
|
+
|
77
|
+
#
|
78
|
+
# Enforce defaults
|
79
|
+
#
|
80
|
+
capture_template.enforce_defaults(
|
81
|
+
PNames.TIME_RESOLUTION,
|
82
|
+
PNames.TIME_RANGE,
|
83
|
+
PNames.FREQUENCY_RESOLUTION,
|
84
|
+
PNames.WINDOW_TYPE
|
85
|
+
)
|
86
|
+
|
87
|
+
|
88
|
+
#
|
89
|
+
# Adding pconstraints
|
90
|
+
#
|
91
|
+
capture_template.add_pconstraint(
|
92
|
+
PNames.SAMPLE_RATE,
|
93
|
+
[
|
94
|
+
Bound(
|
95
|
+
lower_bound=self.get_spec(SpecNames.SAMPLE_RATE_LOWER_BOUND),
|
96
|
+
upper_bound=self.get_spec(SpecNames.SAMPLE_RATE_UPPER_BOUND)
|
97
|
+
)
|
98
|
+
]
|
99
|
+
)
|
100
|
+
capture_template.add_pconstraint(
|
101
|
+
PNames.FREQUENCY,
|
102
|
+
[
|
103
|
+
Bound(
|
104
|
+
lower_bound=self.get_spec(SpecNames.FREQUENCY_LOWER_BOUND),
|
105
|
+
upper_bound=self.get_spec(SpecNames.FREQUENCY_UPPER_BOUND)
|
106
|
+
)
|
107
|
+
]
|
108
|
+
)
|
109
|
+
return capture_template
|
110
|
+
|
111
|
+
|
112
|
+
def __get_capture_template_tagged_staircase(self) -> CaptureTemplate:
|
113
|
+
#
|
114
|
+
# Make the base template
|
115
|
+
#
|
116
|
+
capture_template = make_base_capture_template(
|
117
|
+
PNames.TIME_RESOLUTION,
|
118
|
+
PNames.FREQUENCY_RESOLUTION,
|
119
|
+
PNames.TIME_RANGE,
|
120
|
+
PNames.SAMPLE_RATE,
|
121
|
+
PNames.BATCH_SIZE,
|
122
|
+
PNames.WINDOW_TYPE,
|
123
|
+
PNames.WINDOW_HOP,
|
124
|
+
PNames.WINDOW_SIZE,
|
125
|
+
PNames.EVENT_HANDLER_KEY,
|
126
|
+
PNames.CHUNK_KEY,
|
127
|
+
PNames.WATCH_EXTENSION,
|
128
|
+
PNames.MIN_SAMPLES_PER_STEP,
|
129
|
+
PNames.MAX_SAMPLES_PER_STEP,
|
130
|
+
PNames.FREQUENCY_STEP,
|
131
|
+
PNames.STEP_INCREMENT,
|
132
|
+
PNames.OBS_ALT,
|
133
|
+
PNames.OBS_LAT,
|
134
|
+
PNames.OBS_LON,
|
135
|
+
PNames.OBJECT,
|
136
|
+
PNames.ORIGIN,
|
137
|
+
PNames.TELESCOPE,
|
138
|
+
PNames.INSTRUMENT
|
139
|
+
)
|
140
|
+
|
141
|
+
#
|
142
|
+
# Update the defaults
|
143
|
+
#
|
144
|
+
capture_template.set_defaults(
|
145
|
+
(PNames.BATCH_SIZE, 3.0),
|
146
|
+
(PNames.FREQUENCY_STEP, 128000),
|
147
|
+
(PNames.MAX_SAMPLES_PER_STEP, 5000),
|
148
|
+
(PNames.MIN_SAMPLES_PER_STEP, 4000),
|
149
|
+
(PNames.SAMPLE_RATE, 128000),
|
150
|
+
(PNames.STEP_INCREMENT, 200),
|
151
|
+
(PNames.WINDOW_HOP, 512),
|
152
|
+
(PNames.WINDOW_SIZE, 512),
|
153
|
+
(PNames.WINDOW_TYPE, "boxcar"),
|
154
|
+
(PNames.EVENT_HANDLER_KEY, CaptureModes.SWEPT_CENTER_FREQUENCY),
|
155
|
+
(PNames.CHUNK_KEY, CaptureModes.SWEPT_CENTER_FREQUENCY),
|
156
|
+
(PNames.WATCH_EXTENSION, "bin")
|
157
|
+
)
|
158
|
+
|
159
|
+
|
160
|
+
#
|
161
|
+
# Enforce defaults
|
162
|
+
#
|
163
|
+
capture_template.enforce_defaults(
|
164
|
+
PNames.TIME_RESOLUTION,
|
165
|
+
PNames.TIME_RANGE,
|
166
|
+
PNames.FREQUENCY_RESOLUTION,
|
167
|
+
PNames.WINDOW_TYPE,
|
168
|
+
PNames.EVENT_HANDLER_KEY,
|
169
|
+
PNames.CHUNK_KEY,
|
170
|
+
PNames.WATCH_EXTENSION
|
171
|
+
)
|
172
|
+
|
173
|
+
return capture_template
|
174
|
+
|
175
|
+
|
176
|
+
def __get_pvalidator_cosine_signal_1(self) -> Callable:
|
177
|
+
def pvalidator(parameters: Parameters):
|
178
|
+
PValidators.window(parameters)
|
179
|
+
|
180
|
+
sample_rate = parameters.get_parameter_value(PNames.SAMPLE_RATE)
|
181
|
+
frequency = parameters.get_parameter_value(PNames.FREQUENCY)
|
182
|
+
window_size = parameters.get_parameter_value(PNames.WINDOW_SIZE)
|
183
|
+
|
184
|
+
# check that the sample rate is an integer multiple of the underlying signal frequency
|
185
|
+
if sample_rate % frequency != 0:
|
186
|
+
raise ValueError("The sampling rate must be some integer multiple of frequency")
|
187
|
+
|
188
|
+
a = sample_rate/frequency
|
189
|
+
if a < 2:
|
190
|
+
raise ValueError((f"The ratio of sampling rate over frequency must be greater than two. "
|
191
|
+
f"Got {a}"))
|
192
|
+
|
193
|
+
# analytical requirement
|
194
|
+
# if p is the number of sampled cycles, we can find that p = window_size / a
|
195
|
+
# the number of sampled cycles must be a positive natural number.
|
196
|
+
p = window_size / a
|
197
|
+
if window_size % a != 0:
|
198
|
+
raise ValueError((f"The number of sampled cycles must be a positive natural number. "
|
199
|
+
f"Computed that p={p}"))
|
200
|
+
return pvalidator
|
201
|
+
|
202
|
+
|
203
|
+
def __get_pvalidator_tagged_staircase(self) -> None:
|
204
|
+
def pvalidator(parameters: Parameters):
|
205
|
+
PValidators.window(parameters)
|
206
|
+
|
207
|
+
freq_step = parameters.get_parameter_value(PNames.FREQUENCY_STEP)
|
208
|
+
sample_rate = parameters.get_parameter_value(PNames.SAMPLE_RATE)
|
209
|
+
min_samples_per_step = parameters.get_parameter_value(PNames.MIN_SAMPLES_PER_STEP)
|
210
|
+
max_samples_per_step = parameters.get_parameter_value(PNames.MAX_SAMPLES_PER_STEP)
|
211
|
+
|
212
|
+
if freq_step != sample_rate:
|
213
|
+
raise ValueError(f"The frequency step must be equal to the sampling rate")
|
214
|
+
|
215
|
+
|
216
|
+
if min_samples_per_step > max_samples_per_step:
|
217
|
+
raise ValueError((f"Minimum samples per step cannot be greater than the maximum samples per step. "
|
218
|
+
f"Got {min_samples_per_step}, which is greater than {max_samples_per_step}"))
|
219
|
+
|
220
|
+
return pvalidator
|
221
|
+
|
@@ -1,3 +1,21 @@
|
|
1
1
|
# SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
|
+
|
5
|
+
from ._analytical import (
|
6
|
+
get_analytical_spectrogram, validate_analytically, TestResults
|
7
|
+
)
|
8
|
+
from ._spectrogram import (
|
9
|
+
Spectrogram, FrequencyCut, TimeCut, SpectrumTypes, TimeTypes
|
10
|
+
)
|
11
|
+
from ._transform import (
|
12
|
+
frequency_chop, time_chop, frequency_average, time_average,
|
13
|
+
join_spectrograms
|
14
|
+
)
|
15
|
+
|
16
|
+
__all__ = [
|
17
|
+
"get_analytical_spectrogram", "validate_analytically", "TestResults",
|
18
|
+
"Spectrogram", "FrequencyCut", "TimeCut", "SpectrumTypes", "frequency_chop",
|
19
|
+
"time_chop", "frequency_average","time_average", "join_spectrograms",
|
20
|
+
"TimeTypes"
|
21
|
+
]
|
@@ -2,17 +2,21 @@
|
|
2
2
|
# This file is part of SPECTRE
|
3
3
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
4
4
|
|
5
|
-
from typing import Callable
|
5
|
+
from typing import Callable
|
6
6
|
from dataclasses import dataclass
|
7
7
|
|
8
8
|
import numpy as np
|
9
9
|
|
10
|
-
from spectre_core.
|
11
|
-
from spectre_core.spectrograms.spectrogram import Spectrogram
|
12
|
-
from spectre_core.spectrograms.array_operations import is_close
|
10
|
+
from spectre_core.capture_configs import CaptureConfig, PNames
|
13
11
|
from spectre_core.exceptions import ModeNotFoundError
|
12
|
+
from ._spectrogram import Spectrogram
|
13
|
+
from ._array_operations import is_close
|
14
14
|
|
15
|
-
|
15
|
+
__all__ = [
|
16
|
+
"get_analytical_spectrogram",
|
17
|
+
"validate_analytically",
|
18
|
+
"TestResults"
|
19
|
+
]
|
16
20
|
|
17
21
|
@dataclass
|
18
22
|
class TestResults:
|
@@ -36,7 +40,7 @@ class TestResults:
|
|
36
40
|
return len(self.spectrum_validated) - self.num_validated_spectrums
|
37
41
|
|
38
42
|
|
39
|
-
def
|
43
|
+
def to_dict(self) -> dict[str, bool | dict[float, bool]]:
|
40
44
|
return {
|
41
45
|
"times_validated": self.times_validated,
|
42
46
|
"frequencies_validated": self.frequencies_validated,
|
@@ -72,14 +76,13 @@ class _AnalyticalFactory:
|
|
72
76
|
by parameters in the corresponding capture config and the number of spectrums
|
73
77
|
in the output spectrogram.
|
74
78
|
"""
|
75
|
-
receiver_name, test_mode = capture_config['receiver'], capture_config['mode']
|
76
79
|
|
77
|
-
if receiver_name != "test":
|
80
|
+
if capture_config.receiver_name != "test":
|
78
81
|
raise ValueError(f"Input capture config must correspond to the test receiver")
|
79
82
|
|
80
|
-
builder_method = self.builders.get(
|
83
|
+
builder_method = self.builders.get(capture_config.receiver_mode)
|
81
84
|
if builder_method is None:
|
82
|
-
raise ModeNotFoundError(f"Test mode not found. Expected one of {self.test_modes}, but received {
|
85
|
+
raise ModeNotFoundError(f"Test mode not found. Expected one of {self.test_modes}, but received {capture_config.receiver_mode}")
|
83
86
|
return builder_method(num_spectrums,
|
84
87
|
capture_config)
|
85
88
|
|
@@ -88,14 +91,14 @@ class _AnalyticalFactory:
|
|
88
91
|
num_spectrums: int,
|
89
92
|
capture_config: CaptureConfig) -> Spectrogram:
|
90
93
|
# Extract necessary parameters from the capture configuration.
|
91
|
-
window_size
|
92
|
-
|
93
|
-
amplitude
|
94
|
-
frequency
|
95
|
-
|
96
|
-
|
94
|
+
window_size = capture_config.get_parameter_value(PNames.WINDOW_SIZE)
|
95
|
+
sample_rate = capture_config.get_parameter_value(PNames.SAMPLE_RATE)
|
96
|
+
amplitude = capture_config.get_parameter_value(PNames.AMPLITUDE)
|
97
|
+
frequency = capture_config.get_parameter_value(PNames.FREQUENCY)
|
98
|
+
window_hop = capture_config.get_parameter_value(PNames.WINDOW_HOP)
|
99
|
+
center_frequency = capture_config.get_parameter_value(PNames.CENTER_FREQUENCY)
|
97
100
|
# Calculate derived parameters a (sampling rate ratio) and p (sampled periods).
|
98
|
-
a = int(
|
101
|
+
a = int(sample_rate / frequency)
|
99
102
|
p = int(window_size / a)
|
100
103
|
|
101
104
|
# Create the analytical spectrum, which is constant in time.
|
@@ -111,11 +114,11 @@ class _AnalyticalFactory:
|
|
111
114
|
analytical_dynamic_spectra = np.ones((window_size, num_spectrums)) * spectrum[:, np.newaxis]
|
112
115
|
|
113
116
|
# Compute time array.
|
114
|
-
sampling_interval = 1 /
|
115
|
-
times = np.arange(num_spectrums) *
|
117
|
+
sampling_interval = 1 / sample_rate
|
118
|
+
times = np.arange(num_spectrums) * window_hop * sampling_interval
|
116
119
|
|
117
120
|
# compute the frequency array.
|
118
|
-
frequencies = np.fft.fftshift(np.fft.fftfreq(window_size, sampling_interval))
|
121
|
+
frequencies = np.fft.fftshift(np.fft.fftfreq(window_size, sampling_interval)) + center_frequency
|
119
122
|
|
120
123
|
# Return the spectrogram.
|
121
124
|
return Spectrogram(analytical_dynamic_spectra,
|
@@ -129,11 +132,11 @@ class _AnalyticalFactory:
|
|
129
132
|
num_spectrums: int,
|
130
133
|
capture_config: CaptureConfig) -> Spectrogram:
|
131
134
|
# Extract necessary parameters from the capture configuration.
|
132
|
-
window_size
|
133
|
-
min_samples_per_step = capture_config
|
134
|
-
max_samples_per_step = capture_config
|
135
|
-
step_increment
|
136
|
-
samp_rate
|
135
|
+
window_size = capture_config.get_parameter_value(PNames.WINDOW_SIZE)
|
136
|
+
min_samples_per_step = capture_config.get_parameter_value(PNames.MIN_SAMPLES_PER_STEP)
|
137
|
+
max_samples_per_step = capture_config.get_parameter_value(PNames.MAX_SAMPLES_PER_STEP)
|
138
|
+
step_increment = capture_config.get_parameter_value(PNames.STEP_INCREMENT)
|
139
|
+
samp_rate = capture_config.get_parameter_value(PNames.SAMPLE_RATE)
|
137
140
|
|
138
141
|
# Calculate step sizes and derived parameters.
|
139
142
|
num_samples_per_step = np.arange(min_samples_per_step, max_samples_per_step + 1, step_increment)
|
@@ -152,11 +155,10 @@ class _AnalyticalFactory:
|
|
152
155
|
|
153
156
|
# Compute time array
|
154
157
|
num_samples_per_sweep = sum(num_samples_per_step)
|
155
|
-
midpoint_sample = sum(num_samples_per_step) // 2
|
156
158
|
sampling_interval = 1 / samp_rate
|
157
159
|
# compute the sample index we are "assigning" to each spectrum
|
158
160
|
# and multiply by the sampling interval to get the equivalent physical time
|
159
|
-
times = np.array([
|
161
|
+
times = np.array([(i * num_samples_per_sweep) for i in range(num_spectrums) ]) * sampling_interval
|
160
162
|
|
161
163
|
# Compute the frequency array
|
162
164
|
baseband_frequencies = np.fft.fftshift(np.fft.fftfreq(window_size, sampling_interval))
|
@@ -6,6 +6,53 @@ from datetime import datetime
|
|
6
6
|
|
7
7
|
import numpy as np
|
8
8
|
|
9
|
+
def average_array(array: np.ndarray, average_over: int, axis=0) -> np.ndarray:
|
10
|
+
|
11
|
+
# Check if average_over is an integer
|
12
|
+
if type(average_over) != int:
|
13
|
+
raise TypeError(f"average_over must be an integer. Got {type(average_over)}")
|
14
|
+
|
15
|
+
# Get the size of the specified axis which we will average over
|
16
|
+
axis_size = array.shape[axis]
|
17
|
+
# Check if average_over is within the valid range
|
18
|
+
if not 1 <= average_over <= axis_size:
|
19
|
+
raise ValueError(f"average_over must be between 1 and the length of the axis ({axis_size})")
|
20
|
+
|
21
|
+
max_axis_index = len(np.shape(array)) - 1
|
22
|
+
if axis > max_axis_index: # zero indexing on specifying axis, so minus one
|
23
|
+
raise ValueError(f"Requested axis is out of range of array dimensions. Axis: {axis}, max axis index: {max_axis_index}")
|
24
|
+
|
25
|
+
# find the number of elements in the requested axis
|
26
|
+
num_elements = array.shape[axis]
|
27
|
+
|
28
|
+
# find the number of "full blocks" to average over
|
29
|
+
num_full_blocks = num_elements // average_over
|
30
|
+
# if num_elements is not exactly divisible by average_over, we will have some elements left over
|
31
|
+
# these remaining elements will be padded with nans to become another full block
|
32
|
+
remainder = num_elements % average_over
|
33
|
+
|
34
|
+
# if there exists a remainder, pad the last block
|
35
|
+
if remainder != 0:
|
36
|
+
# initialise an array to hold the padding shape
|
37
|
+
padding_shape = [(0, 0)] * array.ndim
|
38
|
+
# pad after the last column in the requested axis
|
39
|
+
padding_shape[axis] = (0, average_over - remainder)
|
40
|
+
# pad with nan values (so to not contribute towards the mean computation)
|
41
|
+
array = np.pad(array, padding_shape, mode='constant', constant_values=np.nan)
|
42
|
+
|
43
|
+
# initalise a list to hold the new shape
|
44
|
+
new_shape = list(array.shape)
|
45
|
+
# update the shape on the requested access (to the number of blocks we will average over)
|
46
|
+
new_shape[axis] = num_full_blocks + (1 if remainder else 0)
|
47
|
+
# insert a new dimension, with the size of each block
|
48
|
+
new_shape.insert(axis + 1, average_over)
|
49
|
+
# and reshape the array to sort the array into the relevant blocks.
|
50
|
+
reshaped_array = array.reshape(new_shape)
|
51
|
+
# average over the newly created axis, essentially averaging over the blocks.
|
52
|
+
averaged_array = np.nanmean(reshaped_array, axis=axis + 1)
|
53
|
+
# return the averaged array
|
54
|
+
return averaged_array
|
55
|
+
|
9
56
|
|
10
57
|
def is_close(ar: np.ndarray,
|
11
58
|
ar_comparison: np.ndarray,
|
@@ -15,7 +62,6 @@ def is_close(ar: np.ndarray,
|
|
15
62
|
ar_comparison,
|
16
63
|
atol=absolute_tolerance))
|
17
64
|
|
18
|
-
|
19
65
|
def find_closest_index(
|
20
66
|
target_value: float | datetime,
|
21
67
|
array: np.ndarray,
|