spectre-core 0.0.12__py3-none-any.whl → 0.0.13__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 (88) hide show
  1. spectre_core/_file_io/__init__.py +1 -3
  2. spectre_core/_file_io/file_handlers.py +163 -58
  3. spectre_core/batches/__init__.py +10 -11
  4. spectre_core/batches/_base.py +170 -78
  5. spectre_core/batches/_batches.py +149 -99
  6. spectre_core/batches/_factory.py +56 -14
  7. spectre_core/batches/_register.py +23 -8
  8. spectre_core/batches/plugins/_batch_keys.py +16 -0
  9. spectre_core/batches/plugins/_callisto.py +183 -0
  10. spectre_core/batches/plugins/_iq_stream.py +354 -0
  11. spectre_core/capture_configs/__init__.py +17 -13
  12. spectre_core/capture_configs/_capture_config.py +93 -34
  13. spectre_core/capture_configs/_capture_modes.py +22 -0
  14. spectre_core/capture_configs/_capture_templates.py +207 -122
  15. spectre_core/capture_configs/_parameters.py +115 -42
  16. spectre_core/capture_configs/_pconstraints.py +86 -35
  17. spectre_core/capture_configs/_pnames.py +49 -0
  18. spectre_core/capture_configs/_ptemplates.py +389 -346
  19. spectre_core/capture_configs/_pvalidators.py +117 -73
  20. spectre_core/config/__init__.py +6 -8
  21. spectre_core/config/_paths.py +65 -25
  22. spectre_core/config/_time_formats.py +15 -10
  23. spectre_core/exceptions.py +2 -4
  24. spectre_core/jobs/__init__.py +14 -0
  25. spectre_core/jobs/_jobs.py +111 -0
  26. spectre_core/jobs/_workers.py +171 -0
  27. spectre_core/logs/__init__.py +17 -0
  28. spectre_core/logs/_configure.py +67 -0
  29. spectre_core/logs/_decorators.py +33 -0
  30. spectre_core/logs/_logs.py +228 -0
  31. spectre_core/logs/_process_types.py +14 -0
  32. spectre_core/plotting/__init__.py +4 -2
  33. spectre_core/plotting/_base.py +204 -102
  34. spectre_core/plotting/_format.py +17 -4
  35. spectre_core/plotting/_panel_names.py +18 -0
  36. spectre_core/plotting/_panel_stack.py +167 -53
  37. spectre_core/plotting/_panels.py +341 -141
  38. spectre_core/post_processing/__init__.py +8 -6
  39. spectre_core/post_processing/_base.py +70 -44
  40. spectre_core/post_processing/_factory.py +42 -12
  41. spectre_core/post_processing/_post_processor.py +24 -26
  42. spectre_core/post_processing/_register.py +22 -6
  43. spectre_core/post_processing/plugins/_event_handler_keys.py +16 -0
  44. spectre_core/post_processing/plugins/_fixed_center_frequency.py +129 -0
  45. spectre_core/post_processing/{library → plugins}/_swept_center_frequency.py +215 -143
  46. spectre_core/py.typed +0 -0
  47. spectre_core/receivers/__init__.py +10 -7
  48. spectre_core/receivers/_base.py +220 -69
  49. spectre_core/receivers/_factory.py +53 -7
  50. spectre_core/receivers/_register.py +30 -9
  51. spectre_core/receivers/_spec_names.py +26 -15
  52. spectre_core/receivers/plugins/__init__.py +0 -0
  53. spectre_core/receivers/plugins/_receiver_names.py +16 -0
  54. spectre_core/receivers/plugins/_rsp1a.py +59 -0
  55. spectre_core/receivers/plugins/_rspduo.py +67 -0
  56. spectre_core/receivers/plugins/_sdrplay_receiver.py +190 -0
  57. spectre_core/receivers/plugins/_test.py +218 -0
  58. spectre_core/receivers/plugins/gr/_base.py +80 -0
  59. spectre_core/receivers/{gr → plugins/gr}/_rsp1a.py +42 -52
  60. spectre_core/receivers/{gr → plugins/gr}/_rspduo.py +61 -74
  61. spectre_core/receivers/{gr → plugins/gr}/_test.py +33 -31
  62. spectre_core/spectrograms/__init__.py +5 -3
  63. spectre_core/spectrograms/_analytical.py +121 -66
  64. spectre_core/spectrograms/_array_operations.py +103 -36
  65. spectre_core/spectrograms/_spectrogram.py +380 -207
  66. spectre_core/spectrograms/_transform.py +197 -169
  67. spectre_core/wgetting/__init__.py +4 -2
  68. spectre_core/wgetting/_callisto.py +173 -118
  69. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/METADATA +14 -7
  70. spectre_core-0.0.13.dist-info/RECORD +75 -0
  71. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/WHEEL +1 -1
  72. spectre_core/batches/library/_callisto.py +0 -96
  73. spectre_core/batches/library/_fixed_center_frequency.py +0 -133
  74. spectre_core/batches/library/_swept_center_frequency.py +0 -105
  75. spectre_core/logging/__init__.py +0 -11
  76. spectre_core/logging/_configure.py +0 -35
  77. spectre_core/logging/_decorators.py +0 -19
  78. spectre_core/logging/_log_handlers.py +0 -176
  79. spectre_core/post_processing/library/_fixed_center_frequency.py +0 -114
  80. spectre_core/receivers/gr/_base.py +0 -33
  81. spectre_core/receivers/library/_rsp1a.py +0 -61
  82. spectre_core/receivers/library/_rspduo.py +0 -69
  83. spectre_core/receivers/library/_sdrplay_receiver.py +0 -185
  84. spectre_core/receivers/library/_test.py +0 -221
  85. spectre_core-0.0.12.dist-info/RECORD +0 -64
  86. /spectre_core/receivers/{gr → plugins/gr}/__init__.py +0 -0
  87. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/LICENSE +0 -0
  88. {spectre_core-0.0.12.dist-info → spectre_core-0.0.13.dist-info}/top_level.txt +0 -0
@@ -3,28 +3,38 @@
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
5
  import numpy as np
6
+ import numpy.typing as npt
6
7
  from datetime import datetime, timedelta
7
8
  from typing import Optional
8
9
  from math import floor
9
10
 
10
- from spectre_core.config import TimeFormats
11
- from ._array_operations import find_closest_index, average_array
11
+ from spectre_core.config import TimeFormat
12
+ from ._array_operations import find_closest_index, average_array, time_elapsed
12
13
  from ._spectrogram import Spectrogram
13
14
 
14
-
15
- def frequency_chop(input_spectrogram: Spectrogram,
16
- start_frequency: float | int,
17
- end_frequency: float | int) -> Optional[Spectrogram]:
18
-
19
- is_entirely_below_frequency_range = (start_frequency < input_spectrogram.frequencies[0] and end_frequency < input_spectrogram.frequencies[0])
20
- is_entirely_above_frequency_range = (start_frequency > input_spectrogram.frequencies[-1] and end_frequency > input_spectrogram.frequencies[-1])
21
- # if the requested frequency range is out of bounds for the spectrogram return None
15
+ def frequency_chop(
16
+ spectrogram: Spectrogram,
17
+ start_frequency : float,
18
+ end_frequency : float
19
+ ) -> Spectrogram:
20
+ """
21
+ Extracts a portion of the spectrogram within the specified frequency range.
22
+
23
+ :param spectrogram: The input spectrogram to process.
24
+ :param start_frequency: The starting frequency of the desired range (Hz).
25
+ :param end_frequency: The ending frequency of the desired range (Hz).
26
+ :raises ValueError: If the specified frequency range is entirely outside the spectrogram's frequency range.
27
+ :raises ValueError: If the start and end indices for the frequency range are identical.
28
+ :return: A new spectrogram containing only the specified frequency range.
29
+ """
30
+ is_entirely_below_frequency_range = (start_frequency < spectrogram.frequencies[0] and end_frequency < spectrogram.frequencies[0])
31
+ is_entirely_above_frequency_range = (start_frequency > spectrogram.frequencies[-1] and end_frequency > spectrogram.frequencies[-1])
22
32
  if is_entirely_below_frequency_range or is_entirely_above_frequency_range:
23
- return None
33
+ raise ValueError(f"The requested frequency interval is entirely out of range of the input spectrogram.")
24
34
 
25
- #find the index of the nearest matching frequency bins in the spectrogram
26
- start_index = find_closest_index(start_frequency, input_spectrogram.frequencies)
27
- end_index = find_closest_index(end_frequency, input_spectrogram.frequencies)
35
+ # find the index of the nearest matching frequency bins in the spectrogram
36
+ start_index = find_closest_index(np.float32(start_frequency), spectrogram.frequencies)
37
+ end_index = find_closest_index(np.float32(end_frequency) , spectrogram.frequencies)
28
38
 
29
39
  # enforce distinct start and end indices
30
40
  if start_index == end_index:
@@ -35,163 +45,193 @@ def frequency_chop(input_spectrogram: Spectrogram,
35
45
  start_index, end_index = end_index, start_index
36
46
 
37
47
  # chop the spectrogram accordingly
38
- transformed_dynamic_spectra = input_spectrogram.dynamic_spectra[start_index:end_index+1, :]
39
- transformed_frequencies = input_spectrogram.frequencies[start_index:end_index+1]
48
+ transformed_dynamic_spectra = spectrogram.dynamic_spectra[start_index:end_index+1, :]
49
+ transformed_frequencies = spectrogram.frequencies[start_index:end_index+1]
40
50
 
41
- # return the spectrogram instance
42
51
  return Spectrogram(transformed_dynamic_spectra,
43
- input_spectrogram.times,
52
+ spectrogram.times,
44
53
  transformed_frequencies,
45
- input_spectrogram.tag,
46
- input_spectrogram.start_datetime,
47
- input_spectrogram.spectrum_type)
48
-
49
-
50
- def time_chop(input_spectrogram: Spectrogram,
51
- start_time: str,
52
- end_time: str,
53
- time_format: str = TimeFormats.DATETIME) -> Optional[Spectrogram]:
54
-
55
- # parse the strings as datetimes
56
- start_datetime = datetime.strptime(start_time, time_format)
57
- end_datetime = datetime.strptime(end_time, time_format)
58
-
59
- # if the requested time range is out of bounds for the spectrogram return None
60
- is_entirely_below_time_range = (start_datetime < input_spectrogram.datetimes[0] and end_datetime < input_spectrogram.datetimes[0])
61
- is_entirely_above_time_range = (start_datetime > input_spectrogram.datetimes[-1] and end_datetime > input_spectrogram.datetimes[-1])
54
+ spectrogram.tag,
55
+ spectrogram.spectrum_unit,
56
+ spectrogram.start_datetime)
57
+
58
+
59
+ def time_chop(
60
+ spectrogram: Spectrogram,
61
+ start_time: str,
62
+ end_time: str
63
+ ) -> Spectrogram:
64
+ """
65
+ Extracts a portion of the spectrogram within the specified time range.
66
+
67
+ :param spectrogram: The input spectrogram to process.
68
+ :param start_time: The starting time of the desired range.
69
+ :param end_time: The ending time of the desired range.
70
+ :raises ValueError: If the specified time range is entirely outside the spectrogram's time range.
71
+ :raises ValueError: If the start and end indices for the time range are identical.
72
+ :return: A new spectrogram containing only the specified time range.
73
+ """
74
+ start_datetime = np.datetime64( datetime.strptime(start_time, TimeFormat.DATETIME) )
75
+ end_datetime = np.datetime64( datetime.strptime(end_time , TimeFormat.DATETIME) )
76
+
77
+ is_entirely_below_time_range = (start_datetime < spectrogram.datetimes[0] and end_datetime < spectrogram.datetimes[0])
78
+ is_entirely_above_time_range = (start_datetime > spectrogram.datetimes[-1] and end_datetime > spectrogram.datetimes[-1])
62
79
  if is_entirely_below_time_range or is_entirely_above_time_range:
63
- return None
80
+ raise ValueError(f"Requested time interval is entirely out of range of the input spectrogram.")
64
81
 
65
- start_index = find_closest_index(start_datetime, input_spectrogram.datetimes)
66
- end_index = find_closest_index(end_datetime, input_spectrogram.datetimes)
82
+ # find the index of the nearest matching spectrums in the spectrogram.
83
+ start_index = find_closest_index(start_datetime, spectrogram.datetimes)
84
+ end_index = find_closest_index(end_datetime, spectrogram.datetimes)
67
85
 
86
+ # enforce distinct start and end indices
68
87
  if start_index == end_index:
69
88
  raise ValueError(f"Start and end indices are equal! Got start_index: {start_index} and end_index: {end_index}")
70
89
 
90
+ # if start index is more than end index, swap the ordering so to enforce start_index <= end_index
71
91
  if start_index > end_index:
72
92
  start_index, end_index = end_index, start_index
73
93
 
74
- # chop the spectrogram
75
- transformed_dynamic_spectra = input_spectrogram.dynamic_spectra[:, start_index:end_index+1]
76
-
77
- # compute the new start datetime following the time chop
78
- transformed_start_datetime = input_spectrogram.datetimes[start_index]
94
+ # chop the spectrogram accordingly
95
+ transformed_dynamic_spectra = spectrogram.dynamic_spectra[:, start_index:end_index+1]
96
+ transformed_start_datetime = spectrogram.datetimes[start_index]
79
97
 
80
- # chop the times array
81
- transformed_times = input_spectrogram.times[start_index:end_index+1]
82
- # assign the first spectrum to t=0 [s]
98
+ # chop the times array and translate such that the first spectrum to t=0 [s]
99
+ transformed_times = spectrogram.times[start_index:end_index+1]
83
100
  transformed_times -= transformed_times[0]
84
101
 
85
102
  return Spectrogram(transformed_dynamic_spectra,
86
103
  transformed_times,
87
- input_spectrogram.frequencies,
88
- input_spectrogram.tag,
89
- start_time = transformed_start_datetime,
90
- spectrum_type = input_spectrogram.spectrum_type)
104
+ spectrogram.frequencies,
105
+ spectrogram.tag,
106
+ spectrogram.spectrum_unit,
107
+ transformed_start_datetime)
108
+
109
+
110
+ def _validate_and_compute_average_over(
111
+ original_resolution: float,
112
+ resolution: Optional[float],
113
+ average_over: int
114
+ ) -> int:
115
+ """
116
+ Validates the input parameters and computes `average_over` if `resolution` is specified.
117
+
118
+ :param resolution: The desired resolution for averaging. Mutually exclusive with `average_over`.
119
+ :param average_over: The number of consecutive spectrums to average over. Mutually exclusive with `resolution`.
120
+ :param original_resolution: The original resolution (e.g., time or frequency).
121
+ :raises ValueError: If neither or both `resolution` and `average_over` are specified.
122
+ :return: The computed or validated `average_over` value.
123
+ """
124
+ if (resolution is None) and (average_over == 1):
125
+ return average_over
126
+
127
+ if not (resolution is not None) ^ (average_over != 1):
128
+ raise ValueError("Exactly one of 'resolution' or 'average_over' must be specified.")
91
129
 
92
-
93
- def time_average(input_spectrogram: Spectrogram,
94
- resolution: Optional[float] = None,
95
- average_over: Optional[int] = None) -> Spectrogram:
96
-
97
- # spectre does not currently support averaging of non-datetime assigned spectrograms
98
- if not input_spectrogram.start_datetime_is_set:
99
- raise NotImplementedError(f"Time averaging is not yet supported for spectrograms without an assigned datetime.")
100
-
101
- # if nothing is specified, do nothing
102
- if (resolution is None) and (average_over is None):
103
- average_over = 1
104
-
105
- if not (resolution is not None) ^ (average_over is not None):
106
- raise ValueError(f"Exactly one of 'resolution' or 'average_over' "
107
- f"must be specified.")
108
-
109
- # if the resolution is specified, compute the appropriate number of spectrums to average over
110
- # and recall the same function
111
130
  if resolution is not None:
112
- average_over = max(1, floor(resolution / input_spectrogram.time_resolution))
113
- return time_average(input_spectrogram, average_over=average_over)
114
-
115
- # No averaging is required, if we have to average over every one spectrum
116
- if average_over == 1:
117
- return input_spectrogram
118
-
119
- # average the dynamic spectra array
120
- transformed_dynamic_spectra = average_array(input_spectrogram.dynamic_spectra,
121
- average_over,
122
- axis=1)
123
-
124
- # We need to assign timestamps to the averaged spectrums in the spectrograms.
125
- # The natural way to do this is to assign the i'th averaged spectrogram
126
- # to the i'th averaged time
127
- transformed_times = average_array(input_spectrogram.times, average_over)
128
-
129
- # find the new batch start time, which we will assign to the first spectrum after averaging
130
- transformed_start_datetime = input_spectrogram.datetimes[0] + timedelta(seconds = float(transformed_times[0]))
131
-
132
- # finally, translate the averaged time seconds to begin at t=0 [s]
131
+ return max(1, floor(resolution / original_resolution))
132
+
133
+ else:
134
+ return average_over
135
+
136
+
137
+ def time_average(
138
+ spectrogram: Spectrogram,
139
+ resolution: Optional[float] = None,
140
+ average_over: int = 1,
141
+ ) -> Spectrogram:
142
+ """
143
+ Performs time averaging on the spectrogram data.
144
+
145
+ :param spectrogram: The input spectrogram to process.
146
+ :param resolution: The desired time resolution for averaging (seconds). Mutually exclusive with `average_over`.
147
+ :param average_over: The number of consecutive time points to average. Mutually exclusive with `resolution`.
148
+ :raises NotImplementedError: If the spectrogram lacks a defined start datetime.
149
+ :raises ValueError: If neither or both `resolution` and `average_over` are specified.
150
+ :return: A new spectrogram with time-averaged data.
151
+ """
152
+ if not spectrogram.start_datetime_is_set:
153
+ raise NotImplementedError(
154
+ "Time averaging is not supported for spectrograms without an assigned start datetime."
155
+ )
156
+
157
+ average_over = _validate_and_compute_average_over(
158
+ spectrogram.time_resolution,
159
+ resolution,
160
+ average_over
161
+ )
162
+
163
+ # Perform averaging
164
+ transformed_dynamic_spectra = average_array(
165
+ spectrogram.dynamic_spectra, average_over, axis=1
166
+ )
167
+ transformed_times = average_array(spectrogram.times, average_over)
168
+
169
+ # Update start datetime and adjust times to start at t=0
170
+ transformed_start_datetime = (
171
+ spectrogram.datetimes[0] + (transformed_times[0]*1e6).astype("timedelta64[us]")
172
+ )
133
173
  transformed_times -= transformed_times[0]
134
-
135
- return Spectrogram(transformed_dynamic_spectra,
136
- transformed_times,
137
- input_spectrogram.frequencies,
138
- input_spectrogram.tag,
139
- transformed_start_datetime,
140
- input_spectrogram.spectrum_type)
141
-
142
-
143
-
144
- def frequency_average(input_spectrogram: Spectrogram,
145
- resolution: Optional[float] = None,
146
- average_over: Optional[int] = None) -> Spectrogram:
147
-
148
- # if nothing is specified, do nothing
149
- if (resolution is None) and (average_over is None):
150
- average_over = 1
151
-
152
- if not (resolution is not None) ^ (average_over is not None):
153
- raise ValueError(f"Exactly one of 'resolution' or 'average_over' "
154
- f"must be specified.")
155
-
156
- # if the resolution is specified, compute the appropriate number of spectrums to average over
157
- # and recall the same function
158
- if resolution is not None:
159
- average_over = max(1, floor(resolution / input_spectrogram.frequency_resolution))
160
- return frequency_average(input_spectrogram, average_over=average_over)
161
-
162
- # No averaging is required, if we have to average over every one spectrum
163
- if average_over == 1:
164
- return input_spectrogram
165
-
166
- # We need to assign physical frequencies to the averaged spectrums in the spectrograms.
167
- # is to assign the i'th averaged spectral component to the i'th averaged frequency.
168
- # average the dynamic spectra array
169
- transformed_dynamic_spectra = average_array(input_spectrogram.dynamic_spectra,
170
- average_over,
171
- axis=0)
172
- transformed_frequencies = average_array(input_spectrogram.frequencies, average_over)
173
-
174
- return Spectrogram(transformed_dynamic_spectra,
175
- input_spectrogram.times,
176
- transformed_frequencies,
177
- input_spectrogram.tag,
178
- input_spectrogram.start_datetime,
179
- input_spectrogram.spectrum_type)
180
-
181
-
182
- def _time_elapsed(datetimes: np.ndarray) -> np.ndarray:
183
- # Extract the first datetime to use as the reference point
184
- base_time = datetimes[0]
185
- # Calculate elapsed time in seconds for each datetime in the list
186
- elapsed_time = [(dt - base_time).total_seconds() for dt in datetimes]
187
- # Convert the list of seconds to a NumPy array of type float32
188
- return np.array(elapsed_time, dtype=np.float32)
189
-
190
-
191
- # we assume that the spectrogram list is ordered chronologically
192
- # we assume there is no time overlap in any of the spectrograms in the list
193
- def join_spectrograms(spectrograms: list[Spectrogram]) -> Spectrogram:
194
174
 
175
+ return Spectrogram(
176
+ transformed_dynamic_spectra,
177
+ transformed_times,
178
+ spectrogram.frequencies,
179
+ spectrogram.tag,
180
+ spectrogram.spectrum_unit,
181
+ transformed_start_datetime,
182
+ )
183
+
184
+
185
+ def frequency_average(
186
+ spectrogram: Spectrogram,
187
+ resolution: Optional[float] = None,
188
+ average_over: int = 1,
189
+ ) -> Spectrogram:
190
+ """
191
+ Performs frequency averaging on the spectrogram data.
192
+
193
+ :param spectrogram: The input spectrogram to process.
194
+ :param resolution: The desired frequency resolution for averaging (Hz). Mutually exclusive with `average_over`.
195
+ :param average_over: The number of consecutive frequency bins to average. Mutually exclusive with `resolution`.
196
+ :raises ValueError: If neither or both `resolution` and `average_over` are specified.
197
+ :return: A new spectrogram with frequency-averaged data.
198
+ """
199
+ average_over = _validate_and_compute_average_over(
200
+ spectrogram.frequency_resolution,
201
+ resolution,
202
+ average_over
203
+ )
204
+
205
+ # Perform averaging
206
+ transformed_dynamic_spectra = average_array(
207
+ spectrogram.dynamic_spectra, average_over, axis=0
208
+ )
209
+ transformed_frequencies = average_array(spectrogram.frequencies, average_over)
210
+
211
+ return Spectrogram(
212
+ transformed_dynamic_spectra,
213
+ spectrogram.times,
214
+ transformed_frequencies,
215
+ spectrogram.tag,
216
+ spectrogram.spectrum_unit,
217
+ spectrogram.start_datetime,
218
+ )
219
+
220
+
221
+ def join_spectrograms(
222
+ spectrograms: list[Spectrogram]
223
+ ) -> Spectrogram:
224
+ """
225
+ Joins multiple spectrograms into a single spectrogram along the time axis.
226
+
227
+ :param spectrograms: A list of spectrograms to combine.
228
+ :raises ValueError: If the input list is empty.
229
+ :raises ValueError: If spectrograms have mismatched frequency ranges.
230
+ :raises ValueError: If spectrograms have different tags.
231
+ :raises ValueError: If spectrograms have differing spectrum units.
232
+ :raises ValueError: If any spectrogram lacks a defined start datetime.
233
+ :return: A new spectrogram combining all input spectrograms along the time axis.
234
+ """
195
235
  # check that the length of the list is non-zero
196
236
  num_spectrograms = len(spectrograms)
197
237
  if num_spectrograms == 0:
@@ -207,31 +247,19 @@ def join_spectrograms(spectrograms: list[Spectrogram]) -> Spectrogram:
207
247
  raise ValueError(f"All spectrograms must have identical frequency ranges")
208
248
  if spectrogram.tag != reference_spectrogram.tag:
209
249
  raise ValueError(f"All tags must be equal for each spectrogram in the input list!")
210
- if spectrogram.spectrum_type != reference_spectrogram.spectrum_type:
250
+ if spectrogram.spectrum_unit != reference_spectrogram.spectrum_unit:
211
251
  raise ValueError(f"All units must be equal for each spectrogram in the input list!")
212
252
  if not spectrogram.start_datetime_is_set:
213
253
  raise ValueError(f"All spectrograms must have their start datetime set.")
214
254
 
215
- # build a list of the time array of each spectrogram in the list
216
- conc_datetimes = np.concatenate([s.datetimes for s in spectrograms])
217
- # find the total number of time stamps
218
- num_total_time_bins = len(conc_datetimes)
219
- # find the total number of frequency bins (we can safely now use the first)
220
- num_total_freq_bins = len(reference_spectrogram.frequencies)
221
- # create an empty numpy array to hold the joined spectrograms
222
- transformed_dynamic_spectra = np.empty((num_total_freq_bins, num_total_time_bins))
223
-
224
- start_index = 0
225
- for spectrogram in spectrograms:
226
- end_index = start_index + len(spectrogram.times)
227
- transformed_dynamic_spectra[:, start_index:end_index] = spectrogram.dynamic_spectra
228
- start_index = end_index
229
-
230
- transformed_times = _time_elapsed(conc_datetimes)
255
+ # Concatenate all dynamic spectra directly along the time axis
256
+ transformed_dynamic_spectra = np.hstack([spectrogram.dynamic_spectra for spectrogram in spectrograms])
257
+
258
+ transformed_times = time_elapsed( np.concatenate([s.datetimes for s in spectrograms]) )
231
259
 
232
260
  return Spectrogram(transformed_dynamic_spectra,
233
261
  transformed_times,
234
262
  reference_spectrogram.frequencies,
235
263
  reference_spectrogram.tag,
236
- reference_spectrogram.start_datetime,
237
- reference_spectrogram.spectrum_type)
264
+ reference_spectrogram.spectrum_unit,
265
+ reference_spectrogram.start_datetime)
@@ -2,8 +2,10 @@
2
2
  # This file is part of SPECTRE
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
- from ._callisto import download_callisto_data, CALLISTO_INSTRUMENT_CODES
5
+ """Download third-party spectrogram data."""
6
+
7
+ from ._callisto import download_callisto_data, CallistoInstrumentCode
6
8
 
7
9
  __all__ = [
8
- "download_callisto_data", "CALLISTO_INSTRUMENT_CODES"
10
+ "download_callisto_data", "CallistoInstrumentCode"
9
11
  ]