spectre-core 0.0.9__py3-none-any.whl → 0.0.11__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 (109) hide show
  1. spectre_core/__init__.py +0 -3
  2. spectre_core/_file_io/__init__.py +15 -0
  3. spectre_core/_file_io/file_handlers.py +128 -0
  4. spectre_core/capture_configs/__init__.py +29 -0
  5. spectre_core/capture_configs/_capture_config.py +85 -0
  6. spectre_core/capture_configs/_capture_templates.py +222 -0
  7. spectre_core/capture_configs/_parameters.py +110 -0
  8. spectre_core/capture_configs/_pconstraints.py +82 -0
  9. spectre_core/capture_configs/_ptemplates.py +450 -0
  10. spectre_core/capture_configs/_pvalidators.py +171 -0
  11. spectre_core/chunks/__init__.py +17 -201
  12. spectre_core/chunks/{base.py → _base.py} +15 -60
  13. spectre_core/chunks/_chunks.py +200 -0
  14. spectre_core/chunks/{factory.py → _factory.py} +6 -7
  15. spectre_core/chunks/library/{callisto/chunk.py → _callisto.py} +4 -7
  16. spectre_core/chunks/library/{fixed/chunk.py → _fixed_center_frequency.py} +7 -64
  17. spectre_core/chunks/library/_swept_center_frequency.py +103 -0
  18. spectre_core/config/__init__.py +20 -0
  19. spectre_core/config/_paths.py +77 -0
  20. spectre_core/config/_time_formats.py +15 -0
  21. spectre_core/exceptions.py +4 -5
  22. spectre_core/logging/__init__.py +11 -0
  23. spectre_core/logging/_configure.py +35 -0
  24. spectre_core/logging/_decorators.py +19 -0
  25. spectre_core/{logging.py → logging/_log_handlers.py} +13 -58
  26. spectre_core/plotting/__init__.py +7 -1
  27. spectre_core/plotting/{base.py → _base.py} +40 -20
  28. spectre_core/plotting/_format.py +18 -0
  29. spectre_core/plotting/{panel_stack.py → _panel_stack.py} +48 -48
  30. spectre_core/plotting/_panels.py +234 -0
  31. spectre_core/post_processing/__init__.py +10 -2
  32. spectre_core/post_processing/_base.py +119 -0
  33. spectre_core/post_processing/{factory.py → _factory.py} +7 -6
  34. spectre_core/post_processing/{post_processor.py → _post_processor.py} +3 -3
  35. spectre_core/post_processing/library/_fixed_center_frequency.py +115 -0
  36. spectre_core/post_processing/library/_swept_center_frequency.py +382 -0
  37. spectre_core/receivers/__init__.py +13 -2
  38. spectre_core/receivers/_base.py +180 -0
  39. spectre_core/receivers/{factory.py → _factory.py} +2 -2
  40. spectre_core/receivers/_spec_names.py +20 -0
  41. spectre_core/receivers/gr/__init__.py +3 -0
  42. spectre_core/receivers/gr/_base.py +33 -0
  43. spectre_core/receivers/gr/_rsp1a.py +158 -0
  44. spectre_core/receivers/gr/_rspduo.py +227 -0
  45. spectre_core/receivers/gr/_test.py +123 -0
  46. spectre_core/receivers/library/_rsp1a.py +61 -0
  47. spectre_core/receivers/library/_rspduo.py +69 -0
  48. spectre_core/receivers/library/_sdrplay_receiver.py +185 -0
  49. spectre_core/receivers/library/_test.py +221 -0
  50. spectre_core/spectrograms/__init__.py +18 -0
  51. spectre_core/spectrograms/{analytical.py → _analytical.py} +29 -27
  52. spectre_core/spectrograms/{array_operations.py → _array_operations.py} +47 -1
  53. spectre_core/spectrograms/{spectrogram.py → _spectrogram.py} +62 -35
  54. spectre_core/spectrograms/{transform.py → _transform.py} +76 -89
  55. spectre_core/{post_processing/library → wgetting}/__init__.py +4 -5
  56. spectre_core/wgetting/_callisto.py +155 -0
  57. {spectre_core-0.0.9.dist-info → spectre_core-0.0.11.dist-info}/METADATA +1 -1
  58. spectre_core-0.0.11.dist-info/RECORD +64 -0
  59. spectre_core/cfg.py +0 -116
  60. spectre_core/chunks/library/__init__.py +0 -8
  61. spectre_core/chunks/library/callisto/__init__.py +0 -0
  62. spectre_core/chunks/library/fixed/__init__.py +0 -0
  63. spectre_core/chunks/library/sweep/__init__.py +0 -0
  64. spectre_core/chunks/library/sweep/chunk.py +0 -400
  65. spectre_core/dynamic_imports.py +0 -22
  66. spectre_core/file_handlers/base.py +0 -68
  67. spectre_core/file_handlers/configs.py +0 -271
  68. spectre_core/file_handlers/json.py +0 -40
  69. spectre_core/file_handlers/text.py +0 -21
  70. spectre_core/plotting/factory.py +0 -26
  71. spectre_core/plotting/format.py +0 -19
  72. spectre_core/plotting/library/__init__.py +0 -7
  73. spectre_core/plotting/library/frequency_cuts/panel.py +0 -74
  74. spectre_core/plotting/library/integral_over_frequency/panel.py +0 -34
  75. spectre_core/plotting/library/spectrogram/panel.py +0 -92
  76. spectre_core/plotting/library/time_cuts/panel.py +0 -77
  77. spectre_core/plotting/panel_register.py +0 -13
  78. spectre_core/post_processing/base.py +0 -132
  79. spectre_core/post_processing/library/fixed/__init__.py +0 -0
  80. spectre_core/post_processing/library/fixed/event_handler.py +0 -40
  81. spectre_core/post_processing/library/sweep/event_handler.py +0 -54
  82. spectre_core/receivers/base.py +0 -422
  83. spectre_core/receivers/library/__init__.py +0 -7
  84. spectre_core/receivers/library/rsp1a/__init__.py +0 -0
  85. spectre_core/receivers/library/rsp1a/gr/__init__.py +0 -0
  86. spectre_core/receivers/library/rsp1a/gr/fixed.py +0 -104
  87. spectre_core/receivers/library/rsp1a/gr/sweep.py +0 -129
  88. spectre_core/receivers/library/rsp1a/receiver.py +0 -68
  89. spectre_core/receivers/library/rspduo/__init__.py +0 -0
  90. spectre_core/receivers/library/rspduo/gr/__init__.py +0 -0
  91. spectre_core/receivers/library/rspduo/gr/tuner_1_fixed.py +0 -114
  92. spectre_core/receivers/library/rspduo/gr/tuner_1_sweep.py +0 -131
  93. spectre_core/receivers/library/rspduo/gr/tuner_2_fixed.py +0 -120
  94. spectre_core/receivers/library/rspduo/gr/tuner_2_sweep.py +0 -119
  95. spectre_core/receivers/library/rspduo/receiver.py +0 -97
  96. spectre_core/receivers/library/test/__init__.py +0 -0
  97. spectre_core/receivers/library/test/gr/__init__.py +0 -0
  98. spectre_core/receivers/library/test/gr/cosine_signal_1.py +0 -83
  99. spectre_core/receivers/library/test/gr/tagged_staircase.py +0 -93
  100. spectre_core/receivers/library/test/receiver.py +0 -203
  101. spectre_core/receivers/validators.py +0 -231
  102. spectre_core/web_fetch/callisto.py +0 -101
  103. spectre_core-0.0.9.dist-info/RECORD +0 -74
  104. /spectre_core/chunks/{chunk_register.py → _register.py} +0 -0
  105. /spectre_core/post_processing/{event_handler_register.py → _register.py} +0 -0
  106. /spectre_core/receivers/{receiver_register.py → _register.py} +0 -0
  107. {spectre_core-0.0.9.dist-info → spectre_core-0.0.11.dist-info}/LICENSE +0 -0
  108. {spectre_core-0.0.9.dist-info → spectre_core-0.0.11.dist-info}/WHEEL +0 -0
  109. {spectre_core-0.0.9.dist-info → spectre_core-0.0.11.dist-info}/top_level.txt +0 -0
@@ -2,205 +2,21 @@
2
2
  # This file is part of SPECTRE
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
- from logging import getLogger
6
- _LOGGER = getLogger(__name__)
5
+ # decorators run on import
6
+ from .library._fixed_center_frequency import _Chunk
7
+ from .library._swept_center_frequency import _Chunk
8
+ from .library._callisto import _Chunk
9
+
10
+ from ._base import BaseChunk, ChunkFile
11
+ from ._factory import get_chunk_from_tag
12
+ from ._chunks import Chunks
13
+ from .library._swept_center_frequency import SweepMetadata
14
+
15
+ __all__ = [
16
+ "BaseChunk",
17
+ "ChunkFile",
18
+ "get_chunk_from_tag",
19
+ "Chunks",
20
+ "SweepMetadata"
21
+ ]
7
22
 
8
- import os
9
- from typing import Optional
10
- from collections import OrderedDict
11
- import warnings
12
- from datetime import datetime
13
-
14
- # dynamically import all chunks
15
- import spectre_core.chunks.library
16
- from spectre_core.chunks.factory import get_chunk_from_tag
17
- from spectre_core.chunks.base import BaseChunk
18
- from spectre_core.spectrograms.spectrogram import Spectrogram
19
- from spectre_core.spectrograms import transform
20
- from spectre_core.cfg import (
21
- DEFAULT_DATETIME_FORMAT,
22
- get_chunks_dir_path
23
- )
24
- from spectre_core.exceptions import (
25
- SpectrogramNotFoundError,
26
- ChunkNotFoundError
27
- )
28
-
29
- class Chunks:
30
- def __init__(self,
31
- tag: str,
32
- year: Optional[int] = None,
33
- month: Optional[int] = None,
34
- day: Optional[int] = None):
35
- self._tag = tag
36
- self._Chunk = get_chunk_from_tag(tag)
37
- self._chunk_map: dict[str, BaseChunk] = OrderedDict()
38
- self._chunk_list: list[BaseChunk] = []
39
- self._chunk_names: list[str] = []
40
- self.set_date(year, month, day)
41
-
42
-
43
- @property
44
- def year(self) -> int:
45
- return self._year
46
-
47
-
48
- @property
49
- def month(self) -> int:
50
- return self._month
51
-
52
-
53
- @property
54
- def day(self) -> int:
55
- return self._day
56
-
57
-
58
- @property
59
- def chunks_dir_path(self) -> str:
60
- return get_chunks_dir_path(self.year, self.month, self.day)
61
-
62
-
63
- @property
64
- def chunk_map(self) -> dict[str, BaseChunk]:
65
- return self._chunk_map
66
-
67
-
68
- @property
69
- def chunk_list(self) -> list[BaseChunk]:
70
- return self._chunk_list
71
-
72
-
73
- @property
74
- def chunk_names(self) -> list[str]:
75
- return self._chunk_names
76
-
77
-
78
- @property
79
- def num_chunks(self) -> int:
80
- return len(self.chunk_list)
81
-
82
-
83
- def set_date(self,
84
- year: Optional[int],
85
- month: Optional[int],
86
- day: Optional[int]) -> None:
87
- self._year = year
88
- self._month = month
89
- self._day = day
90
- self._update_chunk_map()
91
-
92
-
93
- def _update_chunk_map(self) -> None:
94
- self._chunk_map = OrderedDict() # reset cache
95
- self._chunk_list = [] # reset cache
96
- self._chunk_names = [] # reset cache
97
-
98
- chunk_files = [f for (_, _, files) in os.walk(self.chunks_dir_path) for f in files]
99
-
100
- if len(chunk_files) == 0:
101
- warning_message = "No chunks found, setting chunk map with empty dictionary."
102
- _LOGGER.warning(warning_message)
103
- warnings.warn(warning_message)
104
- return
105
-
106
- for chunk_file in chunk_files:
107
- file_name, _ = os.path.splitext(chunk_file)
108
- chunk_start_time, tag = file_name.split("_", 1)
109
- if tag == self._tag:
110
- self._chunk_map[chunk_start_time] = self._Chunk(chunk_start_time, tag)
111
-
112
- self._chunk_map = OrderedDict(sorted(self._chunk_map.items()))
113
- self._chunk_names = list(self._chunk_map.keys())
114
- self._chunk_list = list(self._chunk_map.values())
115
-
116
-
117
- def update(self) -> None:
118
- """Public alias for setting chunk map"""
119
- self._update_chunk_map()
120
-
121
-
122
- def __iter__(self):
123
- yield from self.chunk_list
124
-
125
-
126
- def _get_chunk_by_chunk_start_time(self,
127
- chunk_start_time: str) -> BaseChunk:
128
- try:
129
- return self.chunk_map[chunk_start_time]
130
- except KeyError:
131
- raise ChunkNotFoundError(f"Chunk with chunk start time {chunk_start_time} could not be found within {self.chunks_dir_path}")
132
-
133
-
134
- def _get_chunk_by_index(self,
135
- chunk_index: int) -> BaseChunk:
136
- num_chunks = len(self.chunk_map)
137
- if num_chunks == 0:
138
- raise ChunkNotFoundError("No chunks are available")
139
- index = chunk_index % num_chunks # Use modulo to make the index wrap around. Allows the user to iterate over all the chunks via index cyclically.
140
- return self.chunk_list[index]
141
-
142
-
143
- def __getitem__(self, subscript: str | int):
144
- if isinstance(subscript, str):
145
- return self._get_chunk_by_chunk_start_time(subscript)
146
- elif isinstance(subscript, int):
147
- return self._get_chunk_by_index(subscript)
148
-
149
-
150
- def get_index_by_chunk(self,
151
- chunk_to_match: BaseChunk) -> int:
152
- for i, chunk in enumerate(self):
153
- if chunk.chunk_start_time == chunk_to_match.chunk_start_time:
154
- return i
155
- raise ChunkNotFoundError(f"No matching chunk found for chunk {chunk_to_match.chunk_name}")
156
-
157
-
158
- def count_chunk_files(self,
159
- extension: str) -> int:
160
- return sum(1 for chunk_file in self if chunk_file.has_file(extension))
161
-
162
-
163
- def get_spectrogram_from_range(self,
164
- start_time: str,
165
- end_time: str) -> Spectrogram:
166
- # Convert input strings to datetime objects
167
- start_datetime = datetime.strptime(start_time, DEFAULT_DATETIME_FORMAT)
168
- end_datetime = datetime.strptime(end_time, DEFAULT_DATETIME_FORMAT)
169
-
170
- if start_datetime.day != end_datetime.day:
171
- warning_message = "Joining spectrograms across multiple days"
172
- _LOGGER.warning(warning_message)
173
- warnings.warn(warning_message, RuntimeWarning)
174
-
175
- spectrograms = []
176
- num_fits_chunks = self.count_chunk_files("fits")
177
-
178
- for i, chunk in enumerate(self):
179
- # skip chunks without fits files
180
- if not chunk.has_file("fits"):
181
- continue
182
-
183
- # rather than reading all files to evaluate the actual upper bound to their time range (slow)
184
- # place an upper bound by using the chunk start datetime for the next chunk
185
- # this assumes that the chunks are non-overlapping (reasonable assumption)
186
- lower_bound = chunk.chunk_start_datetime
187
- if i < num_fits_chunks:
188
- next_chunk = self[i + 1]
189
- upper_bound = next_chunk.chunk_start_datetime
190
- # if there is no "next chunk" then we do have to read the file
191
- else:
192
- fits_chunk = chunk.get_file("fits")
193
- upper_bound = fits_chunk.datetimes[-1]
194
-
195
- # if the chunk overlaps with the input time range, then read the fits file
196
- if start_datetime <= upper_bound and lower_bound <= end_datetime:
197
- spectrogram = chunk.read_file("fits")
198
- spectrogram = transform.time_chop(spectrogram, start_time, end_time)
199
- # if we have a non-empty spectrogram, append it to the list of spectrograms
200
- if spectrogram:
201
- spectrograms.append(spectrogram)
202
-
203
- if spectrograms:
204
- return transform.join_spectrograms(spectrograms)
205
- else:
206
- raise SpectrogramNotFoundError("No spectrogram data found for the given time range")
@@ -3,16 +3,10 @@
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
5
  from datetime import datetime
6
- from abc import abstractmethod
7
6
  from typing import Optional
8
7
 
9
- from scipy.signal import ShortTimeFFT, get_window
10
-
11
- from spectre_core.file_handlers.base import BaseFileHandler
12
- from spectre_core.cfg import get_chunks_dir_path
13
- from spectre_core.file_handlers.configs import CaptureConfig
14
- from spectre_core.spectrograms.spectrogram import Spectrogram
15
- from spectre_core.cfg import DEFAULT_DATETIME_FORMAT
8
+ from spectre_core._file_io import BaseFileHandler
9
+ from spectre_core.config import get_chunks_dir_path, TimeFormats
16
10
  from spectre_core.exceptions import ChunkFileNotFoundError
17
11
 
18
12
  class ChunkFile(BaseFileHandler):
@@ -35,15 +29,15 @@ class ChunkFile(BaseFileHandler):
35
29
 
36
30
 
37
31
  @property
38
- def chunk_start_datetime(self) -> datetime:
39
- if self._chunk_start_datetime is None:
40
- self._chunk_start_datetime = datetime.strptime(self.chunk_start_time, DEFAULT_DATETIME_FORMAT)
41
- return self._chunk_start_datetime
32
+ def tag(self) -> str:
33
+ return self._tag
42
34
 
43
35
 
44
36
  @property
45
- def tag(self) -> str:
46
- return self._tag
37
+ def chunk_start_datetime(self) -> datetime:
38
+ if self._chunk_start_datetime is None:
39
+ self._chunk_start_datetime = datetime.strptime(self.chunk_start_time, TimeFormats.DATETIME)
40
+ return self._chunk_start_datetime
47
41
 
48
42
 
49
43
 
@@ -64,18 +58,18 @@ class BaseChunk:
64
58
  @property
65
59
  def chunk_start_time(self) -> str:
66
60
  return self._chunk_start_time
67
-
68
61
 
69
- @property
70
- def chunk_start_datetime(self) -> datetime:
71
- if self._chunk_start_datetime is None:
72
- self._chunk_start_datetime = datetime.strptime(self.chunk_start_time, DEFAULT_DATETIME_FORMAT)
73
- return self._chunk_start_datetime
74
-
75
62
 
76
63
  @property
77
64
  def tag(self) -> str:
78
65
  return self._tag
66
+
67
+
68
+ @property
69
+ def chunk_start_datetime(self) -> datetime:
70
+ if self._chunk_start_datetime is None:
71
+ self._chunk_start_datetime = datetime.strptime(self.chunk_start_time, TimeFormats.DATETIME)
72
+ return self._chunk_start_datetime
79
73
 
80
74
 
81
75
  @property
@@ -120,42 +114,3 @@ class BaseChunk:
120
114
  return False
121
115
 
122
116
 
123
- class SPECTREChunk(BaseChunk):
124
- def __init__(self, *args, **kwargs):
125
- super().__init__(*args, **kwargs)
126
-
127
- self._capture_config = CaptureConfig(self._tag)
128
- self._SFT = None # cache
129
-
130
-
131
- @abstractmethod
132
- def build_spectrogram(self) -> Spectrogram:
133
- """Create a spectrogram object derived from chunk files for this chunk."""
134
- pass
135
-
136
-
137
- @property
138
- def capture_config(self) -> CaptureConfig:
139
- return self._capture_config
140
-
141
-
142
- @property
143
- def SFT(self) -> ShortTimeFFT:
144
- if self._SFT is None:
145
- self._SFT = self.__get_SFT_instance()
146
- return self._SFT
147
-
148
-
149
- def __get_SFT_instance(self) -> ShortTimeFFT:
150
- hop = self.capture_config.get("hop")
151
- window_type = self.capture_config.get("window_type")
152
- window_params = self.capture_config.get("window_kwargs").values()
153
- window_size = self.capture_config.get("window_size")
154
- window = get_window((window_type,
155
- *window_params),
156
- window_size)
157
- samp_rate = self.capture_config.get("samp_rate")
158
- return ShortTimeFFT(window,
159
- hop,
160
- samp_rate,
161
- fft_mode = "centered")
@@ -0,0 +1,200 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ from logging import getLogger
6
+ _LOGGER = getLogger(__name__)
7
+
8
+ import os
9
+ from typing import Optional
10
+ from collections import OrderedDict
11
+ import warnings
12
+ from datetime import datetime
13
+
14
+ from spectre_core.spectrograms import Spectrogram, time_chop, join_spectrograms
15
+ from spectre_core.config import get_chunks_dir_path, TimeFormats
16
+ from spectre_core.exceptions import (
17
+ SpectrogramNotFoundError,
18
+ ChunkNotFoundError
19
+ )
20
+ from ._base import BaseChunk
21
+ from ._factory import get_chunk_from_tag
22
+
23
+ class Chunks:
24
+ def __init__(self,
25
+ tag: str,
26
+ year: Optional[int] = None,
27
+ month: Optional[int] = None,
28
+ day: Optional[int] = None):
29
+ self._tag = tag
30
+ self._Chunk = get_chunk_from_tag(tag)
31
+ self._chunk_map: dict[str, BaseChunk] = OrderedDict()
32
+ self._chunk_list: list[BaseChunk] = []
33
+ self._chunk_names: list[str] = []
34
+ self.set_date(year, month, day)
35
+
36
+
37
+ @property
38
+ def year(self) -> int:
39
+ return self._year
40
+
41
+
42
+ @property
43
+ def month(self) -> int:
44
+ return self._month
45
+
46
+
47
+ @property
48
+ def day(self) -> int:
49
+ return self._day
50
+
51
+
52
+ @property
53
+ def chunks_dir_path(self) -> str:
54
+ return get_chunks_dir_path(self.year, self.month, self.day)
55
+
56
+
57
+ @property
58
+ def chunk_map(self) -> dict[str, BaseChunk]:
59
+ return self._chunk_map
60
+
61
+
62
+ @property
63
+ def chunk_list(self) -> list[BaseChunk]:
64
+ return self._chunk_list
65
+
66
+
67
+ @property
68
+ def chunk_names(self) -> list[str]:
69
+ return self._chunk_names
70
+
71
+
72
+ @property
73
+ def num_chunks(self) -> int:
74
+ return len(self.chunk_list)
75
+
76
+
77
+ def set_date(self,
78
+ year: Optional[int],
79
+ month: Optional[int],
80
+ day: Optional[int]) -> None:
81
+ self._year = year
82
+ self._month = month
83
+ self._day = day
84
+ self._update_chunk_map()
85
+
86
+
87
+ def _update_chunk_map(self) -> None:
88
+ self._chunk_map = OrderedDict() # reset cache
89
+ self._chunk_list = [] # reset cache
90
+ self._chunk_names = [] # reset cache
91
+
92
+ chunk_files = [f for (_, _, files) in os.walk(self.chunks_dir_path) for f in files]
93
+
94
+ if len(chunk_files) == 0:
95
+ warning_message = "No chunks found, setting chunk map with empty dictionary."
96
+ _LOGGER.warning(warning_message)
97
+ warnings.warn(warning_message)
98
+ return
99
+
100
+ for chunk_file in chunk_files:
101
+ file_name, _ = os.path.splitext(chunk_file)
102
+ chunk_start_time, tag = file_name.split("_", 1)
103
+ if tag == self._tag:
104
+ self._chunk_map[chunk_start_time] = self._Chunk(chunk_start_time, tag)
105
+
106
+ self._chunk_map = OrderedDict(sorted(self._chunk_map.items()))
107
+ self._chunk_names = list(self._chunk_map.keys())
108
+ self._chunk_list = list(self._chunk_map.values())
109
+
110
+
111
+ def update(self) -> None:
112
+ """Public alias for setting chunk map"""
113
+ self._update_chunk_map()
114
+
115
+
116
+ def __iter__(self):
117
+ yield from self.chunk_list
118
+
119
+
120
+ def _get_chunk_by_chunk_start_time(self,
121
+ chunk_start_time: str) -> BaseChunk:
122
+ try:
123
+ return self.chunk_map[chunk_start_time]
124
+ except KeyError:
125
+ raise ChunkNotFoundError(f"Chunk with chunk start time {chunk_start_time} could not be found within {self.chunks_dir_path}")
126
+
127
+
128
+ def _get_chunk_by_index(self,
129
+ chunk_index: int) -> BaseChunk:
130
+ num_chunks = len(self.chunk_map)
131
+ if num_chunks == 0:
132
+ raise ChunkNotFoundError("No chunks are available")
133
+ index = chunk_index % num_chunks # Use modulo to make the index wrap around. Allows the user to iterate over all the chunks via index cyclically.
134
+ return self.chunk_list[index]
135
+
136
+
137
+ def __getitem__(self, subscript: str | int):
138
+ if isinstance(subscript, str):
139
+ return self._get_chunk_by_chunk_start_time(subscript)
140
+ elif isinstance(subscript, int):
141
+ return self._get_chunk_by_index(subscript)
142
+
143
+
144
+ def get_index_by_chunk(self,
145
+ chunk_to_match: BaseChunk) -> int:
146
+ for i, chunk in enumerate(self):
147
+ if chunk.chunk_start_time == chunk_to_match.chunk_start_time:
148
+ return i
149
+ raise ChunkNotFoundError(f"No matching chunk found for chunk {chunk_to_match.chunk_name}")
150
+
151
+
152
+ def count_chunk_files(self,
153
+ extension: str) -> int:
154
+ return sum(1 for chunk_file in self if chunk_file.has_file(extension))
155
+
156
+
157
+ def get_spectrogram_from_range(self,
158
+ start_time: str,
159
+ end_time: str) -> Spectrogram:
160
+ # Convert input strings to datetime objects
161
+ start_datetime = datetime.strptime(start_time, TimeFormats.DATETIME)
162
+ end_datetime = datetime.strptime(end_time, TimeFormats.DATETIME)
163
+
164
+ if start_datetime.day != end_datetime.day:
165
+ warning_message = "Joining spectrograms across multiple days"
166
+ _LOGGER.warning(warning_message)
167
+ warnings.warn(warning_message, RuntimeWarning)
168
+
169
+ spectrograms = []
170
+ num_fits_chunks = self.count_chunk_files("fits")
171
+
172
+ for i, chunk in enumerate(self):
173
+ # skip chunks without fits files
174
+ if not chunk.has_file("fits"):
175
+ continue
176
+
177
+ # rather than reading all files to evaluate the actual upper bound to their time range (slow)
178
+ # place an upper bound by using the chunk start datetime for the next chunk
179
+ # this assumes that the chunks are non-overlapping (reasonable assumption)
180
+ lower_bound = chunk.chunk_start_datetime
181
+ if i < num_fits_chunks:
182
+ next_chunk = self[i + 1]
183
+ upper_bound = next_chunk.chunk_start_datetime
184
+ # if there is no "next chunk" then we do have to read the file
185
+ else:
186
+ fits_chunk = chunk.get_file("fits")
187
+ upper_bound = fits_chunk.datetimes[-1]
188
+
189
+ # if the chunk overlaps with the input time range, then read the fits file
190
+ if start_datetime <= upper_bound and lower_bound <= end_datetime:
191
+ spectrogram = chunk.read_file("fits")
192
+ spectrogram = time_chop(spectrogram, start_time, end_time)
193
+ # if we have a non-empty spectrogram, append it to the list of spectrograms
194
+ if spectrogram:
195
+ spectrograms.append(spectrogram)
196
+
197
+ if spectrograms:
198
+ return join_spectrograms(spectrograms)
199
+ else:
200
+ raise SpectrogramNotFoundError("No spectrogram data found for the given time range")
@@ -2,13 +2,12 @@
2
2
  # This file is part of SPECTRE
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
- # after we decorate all chunks, we can import the chunk_key -> chunk maps
6
- from spectre_core.chunks.base import BaseChunk
7
- from spectre_core.file_handlers.configs import CaptureConfig
5
+ from spectre_core.capture_configs import CaptureConfig, PNames
8
6
  from spectre_core.exceptions import ChunkNotFoundError
9
- from spectre_core.chunks.chunk_register import chunk_map
7
+ from ._register import chunk_map
8
+ from ._base import BaseChunk
10
9
 
11
- def get_chunk(chunk_key: str) -> BaseChunk:
10
+ def _get_chunk(chunk_key: str) -> BaseChunk:
12
11
  Chunk = chunk_map.get(chunk_key)
13
12
  if Chunk is None:
14
13
  valid_chunk_keys = list(chunk_map.keys())
@@ -22,5 +21,5 @@ def get_chunk_from_tag(tag: str) -> BaseChunk:
22
21
  # otherwise, we fetch the chunk key from the capture config
23
22
  else:
24
23
  capture_config= CaptureConfig(tag)
25
- chunk_key = capture_config.get('chunk_key')
26
- return get_chunk(chunk_key)
24
+ chunk_key = capture_config.get_parameter_value(PNames.CHUNK_KEY)
25
+ return _get_chunk(chunk_key)
@@ -11,16 +11,13 @@ from astropy.io.fits.hdu.image import PrimaryHDU
11
11
  from astropy.io.fits.hdu.table import BinTableHDU
12
12
  from astropy.io.fits.hdu.hdulist import HDUList
13
13
 
14
- from spectre_core.chunks.chunk_register import register_chunk
15
- from spectre_core.spectrograms.spectrogram import Spectrogram
16
- from spectre_core.chunks.base import (
17
- BaseChunk,
18
- ChunkFile
19
- )
14
+ from spectre_core.spectrograms import Spectrogram
15
+ from .._register import register_chunk
16
+ from .._base import BaseChunk, ChunkFile
20
17
 
21
18
 
22
19
  @register_chunk('callisto')
23
- class Chunk(BaseChunk):
20
+ class _Chunk(BaseChunk):
24
21
  def __init__(self, chunk_start_time: str, tag: str):
25
22
  super().__init__(chunk_start_time, tag)
26
23
  self.add_file(FitsChunk(self.chunk_parent_path, self.chunk_name))
@@ -11,15 +11,13 @@ from astropy.io.fits.hdu.image import PrimaryHDU
11
11
  from astropy.io.fits.hdu.table import BinTableHDU
12
12
  from astropy.io.fits.hdu.hdulist import HDUList
13
13
 
14
- from spectre_core.chunks.chunk_register import register_chunk
15
- from spectre_core.spectrograms.spectrogram import Spectrogram
16
- from spectre_core.chunks.base import (
17
- SPECTREChunk,
18
- ChunkFile
19
- )
20
-
21
- @register_chunk("fixed")
22
- class Chunk(SPECTREChunk):
14
+ from spectre_core.spectrograms import Spectrogram
15
+ from spectre_core.capture_configs import CaptureModes
16
+ from .._register import register_chunk
17
+ from .._base import BaseChunk, ChunkFile
18
+
19
+ @register_chunk(CaptureModes.FIXED_CENTER_FREQUENCY)
20
+ class _Chunk(BaseChunk):
23
21
  def __init__(self, *args, **kwargs):
24
22
  super().__init__(*args, **kwargs)
25
23
 
@@ -28,61 +26,6 @@ class Chunk(SPECTREChunk):
28
26
  self.add_file(HdrChunk(self.chunk_parent_path, self.chunk_name))
29
27
 
30
28
 
31
- def build_spectrogram(self) -> Spectrogram:
32
- """Create a spectrogram by performing a Short Time FFT on the IQ samples for this chunk."""
33
- iq_data = self.read_file("bin")
34
- millisecond_correction = self.read_file("hdr")
35
-
36
- # units conversion
37
- microsecond_correction = millisecond_correction * 1e3
38
-
39
- times, frequencies, dynamic_spectra = self.__do_STFFT(iq_data)
40
-
41
- # explicitly type cast data arrays to 32-bit floats
42
- times = np.array(times, dtype = 'float32')
43
- frequencies = np.array(frequencies, dtype = 'float32')
44
- dynamic_spectra = np.array(dynamic_spectra, dtype = 'float32')
45
-
46
- return Spectrogram(dynamic_spectra,
47
- times,
48
- frequencies,
49
- self.tag,
50
- chunk_start_time = self.chunk_start_time,
51
- microsecond_correction = microsecond_correction,
52
- spectrum_type = "amplitude")
53
-
54
-
55
- def __do_STFFT(self,
56
- iq_data: np.array) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
57
- """For reference: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.ShortTimeFFT.html"""
58
-
59
- # set p0=0, since by convention in the STFFT docs, p=0 corresponds to the slice centred at t=0
60
- p0=0
61
-
62
- # set p1 to the index of the first slice where the "midpoint" of the window is still inside the signal
63
- num_samples = len(iq_data)
64
- p1 = self.SFT.upper_border_begin(num_samples)[1]
65
-
66
- # compute a ShortTimeFFT on the IQ samples
67
- complex_spectra = self.SFT.stft(iq_data,
68
- p0 = p0,
69
- p1 = p1)
70
-
71
- # compute the magnitude of each spectral component
72
- dynamic_spectra = np.abs(complex_spectra)
73
-
74
-
75
- # assign a physical time to each spectrum
76
- times = self.SFT.t(num_samples,
77
- p0 = 0,
78
- p1 = p1)
79
-
80
- # assign physical frequencies to each spectral component
81
- frequencies = self.SFT.f + self.capture_config.get('center_freq', 0.0)
82
-
83
- return times, frequencies, dynamic_spectra
84
-
85
-
86
29
  class BinChunk(ChunkFile):
87
30
  def __init__(self, chunk_parent_path: str, chunk_name: str):
88
31
  super().__init__(chunk_parent_path, chunk_name, "bin")