spectre-core 0.0.11__py3-none-any.whl → 0.0.12__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 (45) hide show
  1. spectre_core/_file_io/file_handlers.py +12 -12
  2. spectre_core/batches/__init__.py +22 -0
  3. spectre_core/batches/_base.py +146 -0
  4. spectre_core/batches/_batches.py +197 -0
  5. spectre_core/batches/_factory.py +27 -0
  6. spectre_core/{chunks → batches}/_register.py +5 -5
  7. spectre_core/{chunks → batches}/library/_callisto.py +31 -33
  8. spectre_core/{chunks → batches}/library/_fixed_center_frequency.py +43 -38
  9. spectre_core/{chunks → batches}/library/_swept_center_frequency.py +22 -20
  10. spectre_core/capture_configs/_capture_templates.py +6 -6
  11. spectre_core/capture_configs/_parameters.py +3 -6
  12. spectre_core/capture_configs/_ptemplates.py +3 -3
  13. spectre_core/capture_configs/_pvalidators.py +4 -4
  14. spectre_core/config/__init__.py +2 -2
  15. spectre_core/config/_paths.py +5 -5
  16. spectre_core/config/_time_formats.py +5 -3
  17. spectre_core/exceptions.py +2 -2
  18. spectre_core/logging/_configure.py +1 -1
  19. spectre_core/logging/_log_handlers.py +1 -1
  20. spectre_core/plotting/_panels.py +1 -1
  21. spectre_core/post_processing/__init__.py +2 -2
  22. spectre_core/post_processing/_base.py +5 -5
  23. spectre_core/post_processing/_factory.py +3 -3
  24. spectre_core/post_processing/_post_processor.py +5 -5
  25. spectre_core/post_processing/library/_fixed_center_frequency.py +24 -25
  26. spectre_core/post_processing/library/_swept_center_frequency.py +68 -83
  27. spectre_core/receivers/gr/_base.py +1 -1
  28. spectre_core/receivers/gr/_rsp1a.py +3 -3
  29. spectre_core/receivers/gr/_rspduo.py +4 -4
  30. spectre_core/receivers/gr/_test.py +3 -3
  31. spectre_core/receivers/library/_test.py +3 -3
  32. spectre_core/spectrograms/_analytical.py +0 -6
  33. spectre_core/spectrograms/_spectrogram.py +113 -79
  34. spectre_core/spectrograms/_transform.py +19 -36
  35. spectre_core/wgetting/_callisto.py +20 -24
  36. {spectre_core-0.0.11.dist-info → spectre_core-0.0.12.dist-info}/METADATA +1 -1
  37. spectre_core-0.0.12.dist-info/RECORD +64 -0
  38. spectre_core/chunks/__init__.py +0 -22
  39. spectre_core/chunks/_base.py +0 -116
  40. spectre_core/chunks/_chunks.py +0 -200
  41. spectre_core/chunks/_factory.py +0 -25
  42. spectre_core-0.0.11.dist-info/RECORD +0 -64
  43. {spectre_core-0.0.11.dist-info → spectre_core-0.0.12.dist-info}/LICENSE +0 -0
  44. {spectre_core-0.0.11.dist-info → spectre_core-0.0.12.dist-info}/WHEEL +0 -0
  45. {spectre_core-0.0.11.dist-info → spectre_core-0.0.12.dist-info}/top_level.txt +0 -0
@@ -12,7 +12,7 @@ import numpy as np
12
12
  from astropy.io import fits
13
13
 
14
14
  from spectre_core.capture_configs import CaptureConfig, PNames
15
- from spectre_core.config import get_chunks_dir_path, TimeFormats
15
+ from spectre_core.config import get_batches_dir_path, TimeFormats
16
16
  from ._array_operations import (
17
17
  find_closest_index,
18
18
  normalise_peak_intensity,
@@ -21,16 +21,10 @@ from ._array_operations import (
21
21
  subtract_background,
22
22
  )
23
23
 
24
- __all__ = [
25
- "FrequencyCut",
26
- "TimeCut",
27
- "TimeTypes",
28
- "SpectrumTypes",
29
- "Spectrogram"
30
- ]
31
24
 
32
25
  @dataclass
33
26
  class FrequencyCut:
27
+ """A container to hold a cut of a dynamic spectra at a particular instant of time."""
34
28
  time: float | datetime
35
29
  frequencies: np.ndarray
36
30
  cut: np.ndarray
@@ -39,6 +33,7 @@ class FrequencyCut:
39
33
 
40
34
  @dataclass
41
35
  class TimeCut:
36
+ """A container to hold a cut of a dynamic spectra at a particular frequency."""
42
37
  frequency: float
43
38
  times: np.ndarray
44
39
  cut: np.ndarray
@@ -47,178 +42,211 @@ class TimeCut:
47
42
 
48
43
  @dataclass(frozen=True)
49
44
  class TimeTypes:
45
+ """Container to hold the different types of time we can assign to each spectrum in the dynamic spectra.
46
+
47
+ 'SECONDS' is equivalent to 'seconds elapsed since the first spectrum'.
48
+ 'DATETIMES' is equivalent to 'the datetime associated with each spectrum'.
49
+ """
50
50
  SECONDS : str = "seconds"
51
51
  DATETIMES: str = "datetimes"
52
52
 
53
53
 
54
54
  @dataclass(frozen=True)
55
55
  class SpectrumTypes:
56
+ """A container for defined units of dynamic spectra."""
56
57
  AMPLITUDE: str = "amplitude"
57
58
  POWER : str = "power"
58
59
  DIGITS : str = "digits"
59
60
 
60
61
 
61
62
  class Spectrogram:
63
+ """A convenient, standardised wrapper for spectrogram data."""
62
64
  def __init__(self,
63
- dynamic_spectra: np.ndarray, # holds the spectrogram data
64
- times: np.ndarray, # holds the time stamp [s] for each spectrum
65
- frequencies: np.ndarray, # physical frequencies [Hz] for each spectral component
65
+ dynamic_spectra: np.ndarray,
66
+ times: np.ndarray,
67
+ frequencies: np.ndarray,
66
68
  tag: str,
67
- chunk_start_time: Optional[str] = None,
68
- microsecond_correction: int = 0,
69
- spectrum_type: Optional[str] = None,
70
- start_background: Optional[str] = None,
71
- end_background: Optional[str] = None):
69
+ start_datetime: Optional[datetime] = None,
70
+ spectrum_type: Optional[str] = None):
72
71
 
73
72
  # dynamic spectra
74
73
  self._dynamic_spectra = dynamic_spectra
75
- self._dynamic_spectra_as_dBb: Optional[np.ndarray] = None # cache
74
+ self._dynamic_spectra_dBb: Optional[np.ndarray] = None # cache
76
75
 
77
76
  # assigned times and frequencies
78
77
  if times[0] != 0:
79
- raise ValueError(f"The first spectrum must correspond to t=0 [s]")
78
+ raise ValueError(f"The first spectrum must correspond to t=0")
80
79
 
81
80
  self._times = times
82
- self._datetimes: Optional[list[datetime]] = None # cache
83
81
  self._frequencies = frequencies
84
82
 
85
83
  # general metadata
86
84
  self._tag = tag
87
- self._chunk_start_time = chunk_start_time
88
- self._chunk_start_datetime: Optional[datetime] = None # cache
89
- self._microsecond_correction = microsecond_correction
90
85
  self._spectrum_type = spectrum_type
91
-
86
+
87
+ # datetime information
88
+ self._start_datetime = start_datetime
89
+ self._datetimes: Optional[list[datetime]] = None # cache
90
+
92
91
  # background metadata
93
92
  self._background_spectrum: Optional[np.ndarray] = None # cache
94
- self._start_background = start_background
95
- self._end_background = end_background
96
- self._start_background_index = 0 # by default
97
- self._end_background_index = self.num_times # by default
93
+ self._start_background_index = 0
94
+ self._end_background_index = self.num_times
95
+ # background interval can be set after instanitation.
96
+ self._start_background = None
97
+ self._end_background = None
98
+
99
+ # finally check that the spectrogram arrays are matching in shape
98
100
  self._check_shapes()
99
101
 
100
102
 
101
103
  @property
102
104
  def dynamic_spectra(self) -> np.ndarray:
105
+ """The dynamic spectra."""
103
106
  return self._dynamic_spectra
104
107
 
105
108
 
106
109
  @property
107
110
  def times(self) -> np.ndarray:
111
+ """The physical time assigned to each spectrum.
112
+
113
+ Equivalent to the 'seconds elapsed since the first spectrum'. So, by convention, the
114
+ first spectrum is at t=0.
115
+ """
108
116
  return self._times
109
117
 
110
118
 
111
119
  @property
112
120
  def num_times(self) -> int:
121
+ """The size of the times array. Equivalent to the number of spectrums in the spectrogram."""
113
122
  return len(self._times)
114
123
 
115
124
 
116
125
  @property
117
126
  def time_resolution(self) -> float:
127
+ """The time resolution of the dynamic spectra."""
118
128
  return compute_resolution(self._times)
119
129
 
120
130
 
121
131
  @property
122
132
  def time_range(self) -> float:
133
+ """The time range of the dynamic spectra."""
123
134
  return compute_range(self._times)
124
135
 
125
136
 
126
- @property
127
- def datetimes(self) -> list[datetime]:
128
- if self._datetimes is None:
129
- self._datetimes = [self.chunk_start_datetime + timedelta(seconds=(t + self.microsecond_correction*1e-6)) for t in self._times]
130
- return self._datetimes
131
-
132
-
133
137
  @property
134
138
  def frequencies(self) -> np.ndarray:
139
+ """The physical frequency assigned to each spectral component."""
135
140
  return self._frequencies
136
141
 
137
142
 
138
143
  @property
139
144
  def num_frequencies(self) -> int:
145
+ """The number of spectral components."""
140
146
  return len(self._frequencies)
141
147
 
142
148
 
143
149
  @property
144
150
  def frequency_resolution(self) -> float:
151
+ """The frequency resolution of the dynamic spectra."""
145
152
  return compute_resolution(self._frequencies)
146
153
 
147
154
 
148
155
  @property
149
156
  def frequency_range(self) -> float:
157
+ """The frequency range covered by the dynamic spectra."""
150
158
  return compute_range(self._frequencies)
151
159
 
152
160
 
153
161
  @property
154
162
  def tag(self) -> str:
163
+ """The tag identifier corresponding to the dynamic spectra."""
155
164
  return self._tag
156
165
 
157
166
 
158
167
  @property
159
- def chunk_start_time(self) -> str:
160
- if self._chunk_start_time is None:
161
- raise AttributeError(f"Chunk start time has not been set.")
162
- return self._chunk_start_time
168
+ def start_datetime_is_set(self) -> bool:
169
+ """Returns true if the start datetime for the spectrogram has been set."""
170
+ return (self._start_datetime is not None)
171
+
163
172
 
164
-
165
173
  @property
166
- def chunk_start_datetime(self) -> datetime:
167
- if self._chunk_start_datetime is None:
168
- self._chunk_start_datetime = datetime.strptime(self.chunk_start_time, TimeFormats.DATETIME)
169
- return self._chunk_start_datetime
170
-
171
-
174
+ def start_datetime(self) -> datetime:
175
+ """The datetime assigned to the first spectrum in the dynamic spectra."""
176
+ if self._start_datetime is None:
177
+ raise AttributeError(f"A start time has not been set.")
178
+ return self._start_datetime
179
+
180
+
181
+ def format_start_time(self,
182
+ precise: bool = False) -> str:
183
+ """The datetime assigned to the first spectrum in the dynamic spectra, formatted as a string."""
184
+ if precise:
185
+ return datetime.strftime(self.start_datetime, TimeFormats.PRECISE_DATETIME)
186
+ return datetime.strftime(self.start_datetime, TimeFormats.DATETIME)
187
+
188
+
172
189
  @property
173
- def microsecond_correction(self) -> int:
174
- return self._microsecond_correction
190
+ def datetimes(self) -> list[datetime]:
191
+ """The datetimes associated with each spectrum in the dynamic spectra."""
192
+ if self._datetimes is None:
193
+ self._datetimes = [self.start_datetime + timedelta( seconds=(float(t)) ) for t in self._times]
194
+ return self._datetimes
175
195
 
176
196
 
177
197
  @property
178
198
  def spectrum_type(self) -> Optional[str]:
199
+ """The units of the dynamic spectra."""
179
200
  return self._spectrum_type
180
201
 
181
202
 
182
203
  @property
183
204
  def start_background(self) -> Optional[str]:
205
+ """The start of the background interval, as a datetime string up to seconds precision."""
184
206
  return self._start_background
185
207
 
186
208
 
187
209
  @property
188
210
  def end_background(self) -> Optional[str]:
211
+ """The end of the background interval, as a datetime string up to seconds precision."""
189
212
  return self._end_background
190
213
 
191
214
 
192
215
  @property
193
216
  def background_spectrum(self) -> np.ndarray:
217
+ """The background spectrum, computed by averaging the dynamic spectra according to the specified background interval.
218
+
219
+ By default, the entire dynamic spectra is averaged over.
220
+ """
194
221
  if self._background_spectrum is None:
195
222
  self._background_spectrum = np.nanmean(self._dynamic_spectra[:, self._start_background_index:self._end_background_index+1],
196
- axis=-1)
223
+ axis=-1)
197
224
  return self._background_spectrum
198
225
 
199
226
 
200
227
  @property
201
- def dynamic_spectra_as_dBb(self) -> np.ndarray:
202
- if self._dynamic_spectra_as_dBb is None:
228
+ def dynamic_spectra_dBb(self) -> np.ndarray:
229
+ """The dynamic spectra in units of decibels above the background spectrum."""
230
+ if self._dynamic_spectra_dBb is None:
203
231
  # Create an artificial spectrogram where each spectrum is identically the background spectrum
204
232
  background_spectra = self.background_spectrum[:, np.newaxis]
205
233
  # Suppress divide by zero and invalid value warnings for this block of code
206
234
  with np.errstate(divide='ignore'):
207
235
  # Depending on the spectrum type, compute the dBb values differently
208
236
  if self._spectrum_type == SpectrumTypes.AMPLITUDE or self._spectrum_type == SpectrumTypes.DIGITS:
209
- self._dynamic_spectra_as_dBb = 10 * np.log10(self._dynamic_spectra / background_spectra)
237
+ self._dynamic_spectra_dBb = 10 * np.log10(self._dynamic_spectra / background_spectra)
210
238
  elif self._spectrum_type == SpectrumTypes.POWER:
211
- self._dynamic_spectra_as_dBb = 20 * np.log10(self._dynamic_spectra / background_spectra)
239
+ self._dynamic_spectra_dBb = 20 * np.log10(self._dynamic_spectra / background_spectra)
212
240
  else:
213
241
  raise NotImplementedError(f"{self.spectrum_type} unrecognised, uncertain decibel conversion!")
214
- return self._dynamic_spectra_as_dBb
242
+ return self._dynamic_spectra_dBb
215
243
 
216
244
 
217
245
  def set_background(self,
218
246
  start_background: str,
219
247
  end_background: str) -> None:
220
248
  """Public setter for start and end of the background"""
221
- self._dynamic_spectra_as_dBb = None # reset cache
249
+ self._dynamic_spectra_dBb = None # reset cache
222
250
  self._background_spectrum = None # reset cache
223
251
  self._start_background = start_background
224
252
  self._end_background = end_background
@@ -228,14 +256,13 @@ class Spectrogram:
228
256
 
229
257
  def _update_background_indices_from_interval(self) -> None:
230
258
  start_background = datetime.strptime(self._start_background, TimeFormats.DATETIME)
259
+ end_background = datetime.strptime(self._end_background, TimeFormats.DATETIME)
231
260
  self._start_background_index = find_closest_index(start_background,
232
261
  self.datetimes,
233
262
  enforce_strict_bounds=True)
234
-
235
- end_background = datetime.strptime(self._end_background, TimeFormats.DATETIME)
236
- self._end_background_index = find_closest_index(end_background,
237
- self.datetimes,
238
- enforce_strict_bounds=True)
263
+ self._end_background_index = find_closest_index(end_background,
264
+ self.datetimes,
265
+ enforce_strict_bounds=True)
239
266
 
240
267
 
241
268
  def _check_shapes(self) -> None:
@@ -253,19 +280,14 @@ class Spectrogram:
253
280
 
254
281
 
255
282
  def save(self) -> None:
256
- chunk_parent_path = get_chunks_dir_path(year = self.chunk_start_datetime.year,
257
- month = self.chunk_start_datetime.month,
258
- day = self.chunk_start_datetime.day)
259
- file_name = f"{self.chunk_start_time}_{self._tag}.fits"
260
- write_path = os.path.join(chunk_parent_path,
261
- file_name)
262
- _save_spectrogram(write_path, self)
283
+ """Save the spectrogram as a fits file."""
284
+ _save_spectrogram(self)
263
285
 
264
286
 
265
287
  def integrate_over_frequency(self,
266
288
  correct_background: bool = False,
267
289
  peak_normalise: bool = False) -> np.ndarray[np.float32]:
268
-
290
+ """Return the dynamic spectra, numerically integrated over frequency."""
269
291
  # integrate over frequency
270
292
  I = np.trapz(self._dynamic_spectra, self._frequencies, axis=0)
271
293
 
@@ -282,10 +304,12 @@ class Spectrogram:
282
304
  at_time: float | str,
283
305
  dBb: bool = False,
284
306
  peak_normalise: bool = False) -> FrequencyCut:
285
-
286
- # it is important to note that the "at time" specified by the user likely does not correspond
287
- # exactly to one of the times assigned to each spectrogram. So, we compute the nearest achievable,
288
- # and return it from the function as output too.
307
+ """Get a cut of the dynamic spectra at a particular instant of time.
308
+
309
+ It is important to note that the 'at_time' as specified at input may not correspond exactly
310
+ to one of the times assigned to each spectrogram.
311
+ """
312
+
289
313
  if isinstance(at_time, str):
290
314
  at_time = datetime.strptime(at_time, TimeFormats.DATETIME)
291
315
  index_of_cut = find_closest_index(at_time,
@@ -303,7 +327,7 @@ class Spectrogram:
303
327
  raise ValueError(f"Type of at_time is unsupported: {type(at_time)}")
304
328
 
305
329
  if dBb:
306
- ds = self.dynamic_spectra_as_dBb
330
+ ds = self.dynamic_spectra_dBb
307
331
  else:
308
332
  ds = self._dynamic_spectra
309
333
 
@@ -328,10 +352,11 @@ class Spectrogram:
328
352
  peak_normalise = False,
329
353
  correct_background = False,
330
354
  return_time_type: str = TimeTypes.SECONDS) -> TimeCut:
331
-
332
- # it is important to note that the "at frequency" specified by the user likely does not correspond
333
- # exactly to one of the physical frequencies assigned to each spectral component. So, we compute the nearest achievable,
334
- # and return it from the function as output too.
355
+ """Get a cut of the dynamic spectra at a particular frequency.
356
+
357
+ It is important to note that the 'at_frequency' as specified at input may not correspond exactly
358
+ to one of the times assigned to each spectrogram.
359
+ """
335
360
  index_of_cut = find_closest_index(at_frequency,
336
361
  self._frequencies,
337
362
  enforce_strict_bounds = True)
@@ -346,7 +371,7 @@ class Spectrogram:
346
371
 
347
372
  # dependent on the requested cut type, we return the dynamic spectra in the preferred units
348
373
  if dBb:
349
- ds = self.dynamic_spectra_as_dBb
374
+ ds = self.dynamic_spectra_dBb
350
375
  else:
351
376
  ds = self.dynamic_spectra
352
377
 
@@ -379,9 +404,18 @@ def _seconds_of_day(dt: datetime) -> float:
379
404
 
380
405
 
381
406
  # Function to create a FITS file with the specified structure
382
- def _save_spectrogram(write_path: str,
383
- spectrogram: Spectrogram) -> None:
407
+ def _save_spectrogram(spectrogram: Spectrogram) -> None:
408
+
409
+ # making the write path
410
+ batch_parent_path = get_batches_dir_path(year = spectrogram.start_datetime.year,
411
+ month = spectrogram.start_datetime.month,
412
+ day = spectrogram.start_datetime.day)
413
+ # file name formatted as a batch file
414
+ file_name = f"{spectrogram.format_start_time()}_{spectrogram.tag}.fits"
415
+ write_path = os.path.join(batch_parent_path,
416
+ file_name)
384
417
 
418
+ # get optional metadata from the capture config
385
419
  capture_config = CaptureConfig(spectrogram.tag)
386
420
  ORIGIN = capture_config.get_parameter_value(PNames.ORIGIN)
387
421
  INSTRUME = capture_config.get_parameter_value(PNames.INSTRUMENT)
@@ -11,13 +11,6 @@ from spectre_core.config import TimeFormats
11
11
  from ._array_operations import find_closest_index, average_array
12
12
  from ._spectrogram import Spectrogram
13
13
 
14
- __all__ = [
15
- "frequency_chop",
16
- "time_chop",
17
- "frequency_average",
18
- "time_average",
19
- "join_spectrograms"
20
- ]
21
14
 
22
15
  def frequency_chop(input_spectrogram: Spectrogram,
23
16
  start_frequency: float | int,
@@ -50,9 +43,8 @@ def frequency_chop(input_spectrogram: Spectrogram,
50
43
  input_spectrogram.times,
51
44
  transformed_frequencies,
52
45
  input_spectrogram.tag,
53
- chunk_start_time = input_spectrogram.chunk_start_time,
54
- microsecond_correction = input_spectrogram.microsecond_correction,
55
- spectrum_type = input_spectrogram.spectrum_type)
46
+ input_spectrogram.start_datetime,
47
+ input_spectrogram.spectrum_type)
56
48
 
57
49
 
58
50
  def time_chop(input_spectrogram: Spectrogram,
@@ -84,9 +76,6 @@ def time_chop(input_spectrogram: Spectrogram,
84
76
 
85
77
  # compute the new start datetime following the time chop
86
78
  transformed_start_datetime = input_spectrogram.datetimes[start_index]
87
- # compute the microsecond correction, and chunk start time
88
- transformed_chunk_start_time = datetime.strftime(transformed_start_datetime, TimeFormats.DATETIME)
89
- transformed_microsecond_correction = transformed_start_datetime.microsecond
90
79
 
91
80
  # chop the times array
92
81
  transformed_times = input_spectrogram.times[start_index:end_index+1]
@@ -97,8 +86,7 @@ def time_chop(input_spectrogram: Spectrogram,
97
86
  transformed_times,
98
87
  input_spectrogram.frequencies,
99
88
  input_spectrogram.tag,
100
- chunk_start_time = transformed_chunk_start_time,
101
- microsecond_correction = transformed_microsecond_correction,
89
+ start_time = transformed_start_datetime,
102
90
  spectrum_type = input_spectrogram.spectrum_type)
103
91
 
104
92
 
@@ -107,8 +95,8 @@ def time_average(input_spectrogram: Spectrogram,
107
95
  average_over: Optional[int] = None) -> Spectrogram:
108
96
 
109
97
  # spectre does not currently support averaging of non-datetime assigned spectrograms
110
- if input_spectrogram.chunk_start_time is None:
111
- raise ValueError(f"Input spectrogram is missing chunk start time. Averaging is not yet supported for 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.")
112
100
 
113
101
  # if nothing is specified, do nothing
114
102
  if (resolution is None) and (average_over is None):
@@ -130,28 +118,26 @@ def time_average(input_spectrogram: Spectrogram,
130
118
 
131
119
  # average the dynamic spectra array
132
120
  transformed_dynamic_spectra = average_array(input_spectrogram.dynamic_spectra,
133
- average_over,
134
- axis=1)
121
+ average_over,
122
+ axis=1)
135
123
 
136
124
  # We need to assign timestamps to the averaged spectrums in the spectrograms.
137
125
  # The natural way to do this is to assign the i'th averaged spectrogram
138
126
  # to the i'th averaged time
139
127
  transformed_times = average_array(input_spectrogram.times, average_over)
140
128
 
141
- # find the new chunk start time, which we will assign to the first spectrum after averaging
142
- corrected_start_datetime = input_spectrogram.datetimes[0] + timedelta(seconds = float(transformed_times[0]))
143
- transformed_chunk_start_time = corrected_start_datetime.strftime(TimeFormats.DATETIME)
144
- transformed_microsecond_correction = corrected_start_datetime.microsecond
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]))
145
131
 
146
132
  # finally, translate the averaged time seconds to begin at t=0 [s]
147
133
  transformed_times -= transformed_times[0]
134
+
148
135
  return Spectrogram(transformed_dynamic_spectra,
149
136
  transformed_times,
150
137
  input_spectrogram.frequencies,
151
138
  input_spectrogram.tag,
152
- chunk_start_time = transformed_chunk_start_time,
153
- microsecond_correction = transformed_microsecond_correction,
154
- spectrum_type = input_spectrogram.spectrum_type)
139
+ transformed_start_datetime,
140
+ input_spectrogram.spectrum_type)
155
141
 
156
142
 
157
143
 
@@ -189,9 +175,8 @@ def frequency_average(input_spectrogram: Spectrogram,
189
175
  input_spectrogram.times,
190
176
  transformed_frequencies,
191
177
  input_spectrogram.tag,
192
- chunk_start_time = input_spectrogram.chunk_start_time,
193
- microsecond_correction = input_spectrogram.microsecond_correction,
194
- spectrum_type = input_spectrogram.spectrum_type)
178
+ input_spectrogram.start_datetime,
179
+ input_spectrogram.spectrum_type)
195
180
 
196
181
 
197
182
  def _time_elapsed(datetimes: np.ndarray) -> np.ndarray:
@@ -203,7 +188,7 @@ def _time_elapsed(datetimes: np.ndarray) -> np.ndarray:
203
188
  return np.array(elapsed_time, dtype=np.float32)
204
189
 
205
190
 
206
- # we assume that the spectrogram list is ORDERED chronologically
191
+ # we assume that the spectrogram list is ordered chronologically
207
192
  # we assume there is no time overlap in any of the spectrograms in the list
208
193
  def join_spectrograms(spectrograms: list[Spectrogram]) -> Spectrogram:
209
194
 
@@ -224,9 +209,8 @@ def join_spectrograms(spectrograms: list[Spectrogram]) -> Spectrogram:
224
209
  raise ValueError(f"All tags must be equal for each spectrogram in the input list!")
225
210
  if spectrogram.spectrum_type != reference_spectrogram.spectrum_type:
226
211
  raise ValueError(f"All units must be equal for each spectrogram in the input list!")
227
- if spectrogram.chunk_start_time is None:
228
- raise ValueError(f"All spectrograms must have chunk_start_time set. Received {spectrogram.chunk_start_time}")
229
-
212
+ if not spectrogram.start_datetime_is_set:
213
+ raise ValueError(f"All spectrograms must have their start datetime set.")
230
214
 
231
215
  # build a list of the time array of each spectrogram in the list
232
216
  conc_datetimes = np.concatenate([s.datetimes for s in spectrograms])
@@ -249,6 +233,5 @@ def join_spectrograms(spectrograms: list[Spectrogram]) -> Spectrogram:
249
233
  transformed_times,
250
234
  reference_spectrogram.frequencies,
251
235
  reference_spectrogram.tag,
252
- chunk_start_time = reference_spectrogram.chunk_start_time,
253
- microsecond_correction = reference_spectrogram.microsecond_correction,
254
- spectrum_type = reference_spectrogram.spectrum_type)
236
+ reference_spectrogram.start_datetime,
237
+ reference_spectrogram.spectrum_type)
@@ -9,7 +9,7 @@ import gzip
9
9
  from datetime import datetime
10
10
  from typing import Optional
11
11
 
12
- from spectre_core.config import get_spectre_data_dir_path, get_chunks_dir_path, TimeFormats
12
+ from spectre_core.config import get_spectre_data_dir_path, get_batches_dir_path, TimeFormats
13
13
 
14
14
  CALLISTO_INSTRUMENT_CODES = [
15
15
  "ALASKA-ANCHORAGE",
@@ -70,13 +70,13 @@ CALLISTO_INSTRUMENT_CODES = [
70
70
  _temp_dir = os.path.join(get_spectre_data_dir_path(), "temp")
71
71
 
72
72
 
73
- def _get_chunk_name(station: str, date: str, time: str, instrument_code: str) -> str:
73
+ def _get_batch_name(station: str, date: str, time: str, instrument_code: str) -> str:
74
74
  dt = datetime.strptime(f"{date}T{time}", '%Y%m%dT%H%M%S')
75
75
  formatted_time = dt.strftime(TimeFormats.DATETIME)
76
76
  return f"{formatted_time}_callisto-{station.lower()}-{instrument_code}.fits"
77
77
 
78
78
 
79
- def _get_chunk_components(gz_path: str):
79
+ def _get_batch_components(gz_path: str):
80
80
  file_name = os.path.basename(gz_path)
81
81
  if not file_name.endswith(".fit.gz"):
82
82
  raise ValueError(f"Unexpected file extension in {file_name}. Expected .fit.gz")
@@ -89,29 +89,29 @@ def _get_chunk_components(gz_path: str):
89
89
  return parts
90
90
 
91
91
 
92
- def _get_chunk_path(gz_path: str) -> str:
93
- station, date, time, instrument_code = _get_chunk_components(gz_path)
94
- fits_chunk_name = _get_chunk_name(station, date, time, instrument_code)
95
- chunk_start_time = fits_chunk_name.split('_')[0]
96
- chunk_start_datetime = datetime.strptime(chunk_start_time, TimeFormats.DATETIME)
97
- chunk_parent_path = get_chunks_dir_path(year = chunk_start_datetime.year,
98
- month = chunk_start_datetime.month,
99
- day = chunk_start_datetime.day)
100
- if not os.path.exists(chunk_parent_path):
101
- os.makedirs(chunk_parent_path)
102
- return os.path.join(chunk_parent_path, fits_chunk_name)
92
+ def _get_batch_path(gz_path: str) -> str:
93
+ station, date, time, instrument_code = _get_batch_components(gz_path)
94
+ fits_batch_name = _get_batch_name(station, date, time, instrument_code)
95
+ batch_start_time = fits_batch_name.split('_')[0]
96
+ batch_start_datetime = datetime.strptime(batch_start_time, TimeFormats.DATETIME)
97
+ batch_parent_path = get_batches_dir_path(year = batch_start_datetime.year,
98
+ month = batch_start_datetime.month,
99
+ day = batch_start_datetime.day)
100
+ if not os.path.exists(batch_parent_path):
101
+ os.makedirs(batch_parent_path)
102
+ return os.path.join(batch_parent_path, fits_batch_name)
103
103
 
104
104
 
105
- def _unzip_file_to_chunks(gz_path: str):
106
- fits_path = _get_chunk_path(gz_path)
105
+ def _unzip_file_to_batches(gz_path: str):
106
+ fits_path = _get_batch_path(gz_path)
107
107
  with gzip.open(gz_path, 'rb') as f_in, open(fits_path, 'wb') as f_out:
108
108
  shutil.copyfileobj(f_in, f_out)
109
109
 
110
110
 
111
- def _unzip_to_chunks():
111
+ def _unzip_to_batches():
112
112
  for entry in os.scandir(_temp_dir):
113
113
  if entry.is_file() and entry.name.endswith('.gz'):
114
- _unzip_file_to_chunks(entry.path)
114
+ _unzip_file_to_batches(entry.path)
115
115
  os.remove(entry.path)
116
116
 
117
117
 
@@ -128,11 +128,7 @@ def _wget_callisto_data(instrument_code: str,
128
128
  '-P', _temp_dir,
129
129
  base_url
130
130
  ]
131
-
132
- try:
133
- subprocess.run(command, check=True)
134
- except subprocess.CalledProcessError as e:
135
- print(f"An error occurred: {e}")
131
+ subprocess.run(command, check=True)
136
132
 
137
133
 
138
134
  def download_callisto_data(instrument_code: Optional[str],
@@ -151,5 +147,5 @@ def download_callisto_data(instrument_code: Optional[str],
151
147
  raise ValueError(f"No match found for '{instrument_code}'. Expected one of {CALLISTO_INSTRUMENT_CODES}")
152
148
 
153
149
  _wget_callisto_data(instrument_code, year, month, day)
154
- _unzip_to_chunks()
150
+ _unzip_to_batches()
155
151
  shutil.rmtree(_temp_dir)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spectre-core
3
- Version: 0.0.11
3
+ Version: 0.0.12
4
4
  Summary: The core Python package used by the spectre program.
5
5
  Maintainer-email: Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
6
6
  License: GNU GENERAL PUBLIC LICENSE