spectre-core 0.0.10__tar.gz → 0.0.12__tar.gz

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 (75) hide show
  1. {spectre_core-0.0.10 → spectre_core-0.0.12}/PKG-INFO +1 -1
  2. {spectre_core-0.0.10 → spectre_core-0.0.12}/pyproject.toml +1 -1
  3. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/_file_io/file_handlers.py +12 -12
  4. spectre_core-0.0.12/src/spectre_core/batches/__init__.py +22 -0
  5. spectre_core-0.0.12/src/spectre_core/batches/_base.py +146 -0
  6. spectre_core-0.0.12/src/spectre_core/batches/_batches.py +197 -0
  7. spectre_core-0.0.12/src/spectre_core/batches/_factory.py +27 -0
  8. {spectre_core-0.0.10/src/spectre_core/chunks → spectre_core-0.0.12/src/spectre_core/batches}/_register.py +5 -5
  9. {spectre_core-0.0.10/src/spectre_core/chunks → spectre_core-0.0.12/src/spectre_core/batches}/library/_callisto.py +31 -33
  10. {spectre_core-0.0.10/src/spectre_core/chunks → spectre_core-0.0.12/src/spectre_core/batches}/library/_fixed_center_frequency.py +43 -38
  11. {spectre_core-0.0.10/src/spectre_core/chunks → spectre_core-0.0.12/src/spectre_core/batches}/library/_swept_center_frequency.py +22 -20
  12. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/capture_configs/_capture_templates.py +6 -6
  13. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/capture_configs/_parameters.py +3 -6
  14. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/capture_configs/_ptemplates.py +3 -3
  15. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/capture_configs/_pvalidators.py +6 -8
  16. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/config/__init__.py +2 -2
  17. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/config/_paths.py +5 -5
  18. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/config/_time_formats.py +5 -3
  19. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/exceptions.py +2 -2
  20. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/logging/_configure.py +1 -1
  21. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/logging/_log_handlers.py +1 -1
  22. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/plotting/_panels.py +1 -1
  23. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/post_processing/__init__.py +2 -2
  24. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/post_processing/_base.py +6 -6
  25. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/post_processing/_factory.py +3 -3
  26. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/post_processing/_post_processor.py +5 -5
  27. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/post_processing/library/_fixed_center_frequency.py +24 -25
  28. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/post_processing/library/_swept_center_frequency.py +68 -83
  29. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/receivers/__init__.py +1 -0
  30. spectre_core-0.0.12/src/spectre_core/receivers/_base.py +180 -0
  31. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/receivers/gr/_base.py +1 -1
  32. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/receivers/gr/_rsp1a.py +8 -8
  33. spectre_core-0.0.12/src/spectre_core/receivers/gr/_rspduo.py +227 -0
  34. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/receivers/gr/_test.py +5 -5
  35. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/receivers/library/_rsp1a.py +4 -4
  36. spectre_core-0.0.12/src/spectre_core/receivers/library/_rspduo.py +69 -0
  37. spectre_core-0.0.10/src/spectre_core/receivers/_base.py → spectre_core-0.0.12/src/spectre_core/receivers/library/_sdrplay_receiver.py +6 -173
  38. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/receivers/library/_test.py +3 -3
  39. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/spectrograms/_analytical.py +0 -6
  40. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/spectrograms/_spectrogram.py +113 -79
  41. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/spectrograms/_transform.py +19 -36
  42. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/wgetting/_callisto.py +20 -24
  43. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core.egg-info/PKG-INFO +1 -1
  44. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core.egg-info/SOURCES.txt +9 -8
  45. spectre_core-0.0.10/src/spectre_core/chunks/__init__.py +0 -22
  46. spectre_core-0.0.10/src/spectre_core/chunks/_base.py +0 -116
  47. spectre_core-0.0.10/src/spectre_core/chunks/_chunks.py +0 -200
  48. spectre_core-0.0.10/src/spectre_core/chunks/_factory.py +0 -25
  49. spectre_core-0.0.10/src/spectre_core/receivers/gr/_rspduo.py +0 -0
  50. spectre_core-0.0.10/src/spectre_core/receivers/library/_rspduo.py +0 -0
  51. {spectre_core-0.0.10 → spectre_core-0.0.12}/LICENSE +0 -0
  52. {spectre_core-0.0.10 → spectre_core-0.0.12}/README.md +0 -0
  53. {spectre_core-0.0.10 → spectre_core-0.0.12}/setup.cfg +0 -0
  54. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/__init__.py +0 -0
  55. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/_file_io/__init__.py +0 -0
  56. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/capture_configs/__init__.py +0 -0
  57. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/capture_configs/_capture_config.py +0 -0
  58. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/capture_configs/_pconstraints.py +0 -0
  59. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/logging/__init__.py +0 -0
  60. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/logging/_decorators.py +0 -0
  61. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/plotting/__init__.py +0 -0
  62. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/plotting/_base.py +0 -0
  63. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/plotting/_format.py +0 -0
  64. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/plotting/_panel_stack.py +0 -0
  65. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/post_processing/_register.py +0 -0
  66. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/receivers/_factory.py +0 -0
  67. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/receivers/_register.py +0 -0
  68. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/receivers/_spec_names.py +0 -0
  69. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/receivers/gr/__init__.py +0 -0
  70. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/spectrograms/__init__.py +0 -0
  71. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/spectrograms/_array_operations.py +0 -0
  72. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core/wgetting/__init__.py +0 -0
  73. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core.egg-info/dependency_links.txt +0 -0
  74. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core.egg-info/requires.txt +0 -0
  75. {spectre_core-0.0.10 → spectre_core-0.0.12}/src/spectre_core.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spectre-core
3
- Version: 0.0.10
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spectre-core"
7
- version = "0.0.10"
7
+ version = "0.0.12"
8
8
  maintainers = [
9
9
  { name="Jimmy Fitzpatrick", email="jcfitzpatrick12@gmail.com" },
10
10
  ]
@@ -10,10 +10,10 @@ from typing import Any, Optional
10
10
 
11
11
  class BaseFileHandler(ABC):
12
12
  def __init__(self,
13
- parent_path: str,
13
+ parent_dir_path: str,
14
14
  base_file_name: str,
15
15
  extension: Optional[str] = None):
16
- self._parent_path = parent_path
16
+ self._parent_dir_path = parent_dir_path
17
17
  self._base_file_name = base_file_name
18
18
  self._extension = extension
19
19
 
@@ -24,8 +24,8 @@ class BaseFileHandler(ABC):
24
24
 
25
25
 
26
26
  @property
27
- def parent_path(self) -> str:
28
- return self._parent_path
27
+ def parent_dir_path(self) -> str:
28
+ return self._parent_dir_path
29
29
 
30
30
 
31
31
  @property
@@ -45,7 +45,7 @@ class BaseFileHandler(ABC):
45
45
 
46
46
  @property
47
47
  def file_path(self) -> str:
48
- return os.path.join(self._parent_path, self.file_name)
48
+ return os.path.join(self._parent_dir_path, self.file_name)
49
49
 
50
50
 
51
51
  @property
@@ -53,8 +53,8 @@ class BaseFileHandler(ABC):
53
53
  return os.path.exists(self.file_path)
54
54
 
55
55
 
56
- def make_parent_path(self) -> None:
57
- os.makedirs(self.parent_path, exist_ok=True)
56
+ def make_parent_dir_path(self) -> None:
57
+ os.makedirs(self.parent_dir_path, exist_ok=True)
58
58
 
59
59
 
60
60
  def delete(self,
@@ -71,13 +71,13 @@ class BaseFileHandler(ABC):
71
71
 
72
72
  class JsonHandler(BaseFileHandler):
73
73
  def __init__(self,
74
- parent_path: str,
74
+ parent_dir_path: str,
75
75
  base_file_name: str,
76
76
  extension: str = "json",
77
77
  **kwargs):
78
78
 
79
79
  self._dict = None # cache
80
- super().__init__(parent_path,
80
+ super().__init__(parent_dir_path,
81
81
  base_file_name,
82
82
  extension,
83
83
  **kwargs)
@@ -91,7 +91,7 @@ class JsonHandler(BaseFileHandler):
91
91
  def save(self,
92
92
  d: dict,
93
93
  force: bool = False) -> None:
94
- self.make_parent_path()
94
+ self.make_parent_dir_path()
95
95
 
96
96
  if self.exists:
97
97
  if force:
@@ -113,11 +113,11 @@ class JsonHandler(BaseFileHandler):
113
113
 
114
114
  class TextHandler(BaseFileHandler):
115
115
  def __init__(self,
116
- parent_path: str,
116
+ parent_dir_path: str,
117
117
  base_file_name: str,
118
118
  extension: str = "txt",
119
119
  **kwargs):
120
- super().__init__(parent_path,
120
+ super().__init__(parent_dir_path,
121
121
  base_file_name,
122
122
  extension,
123
123
  **kwargs)
@@ -0,0 +1,22 @@
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
+ # register decorators are actioned on import
6
+ from .library._fixed_center_frequency import _Batch
7
+ from .library._swept_center_frequency import _Batch
8
+ from .library._callisto import _Batch
9
+
10
+ from ._base import BaseBatch, BatchFile
11
+ from ._factory import get_batch_cls_from_tag
12
+ from ._batches import Batches
13
+ from .library._swept_center_frequency import SweepMetadata
14
+
15
+ __all__ = [
16
+ "BaseBatch",
17
+ "BatchFile",
18
+ "get_batch_cls_from_tag",
19
+ "Batches",
20
+ "SweepMetadata"
21
+ ]
22
+
@@ -0,0 +1,146 @@
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 datetime import datetime
6
+ from typing import Optional
7
+
8
+ from spectre_core._file_io import BaseFileHandler
9
+ from spectre_core.config import get_batches_dir_path, TimeFormats
10
+ from spectre_core.exceptions import BatchFileNotFoundError
11
+
12
+
13
+ class BatchFile(BaseFileHandler):
14
+ """A specific file in a given batch, uniquely identified in the batch by the file extension."""
15
+ def __init__(self,
16
+ batch_parent_dir_path: str,
17
+ batch_name: str,
18
+ extension: str):
19
+ super().__init__(batch_parent_dir_path,
20
+ batch_name,
21
+ extension)
22
+ self._start_time, self._tag = batch_name.split("_")
23
+ # computed if required
24
+ self._start_datetime: Optional[datetime] = None
25
+
26
+
27
+ @property
28
+ def start_time(self) -> str:
29
+ """The start time of the batch file, up to seconds precision."""
30
+ return self._start_time
31
+
32
+
33
+ @property
34
+ def start_datetime(self) -> datetime:
35
+ """The datetime of the batch file, up to seconds precision."""
36
+ if self._start_datetime is None:
37
+ self._start_datetime = datetime.strptime(self.start_time, TimeFormats.DATETIME)
38
+ return self._start_datetime
39
+
40
+
41
+ @property
42
+ def tag(self) -> str:
43
+ """The tag identifier for the batch file."""
44
+ return self._tag
45
+
46
+
47
+
48
+ class BaseBatch:
49
+ """A group of one or more files which share a common start time and a tag identifier.
50
+
51
+ All files belonging to the same batch will share a batch name, and differ
52
+ only in their file extension.
53
+ """
54
+ def __init__(self,
55
+ start_time: str,
56
+ tag: str):
57
+ self._start_time = start_time
58
+ self._tag: str = tag
59
+ self._batch_files: dict[str, BatchFile] = {}
60
+ self._start_datetime = datetime.strptime(self.start_time, TimeFormats.DATETIME)
61
+ self._parent_dir_path = get_batches_dir_path(year = self.start_datetime.year,
62
+ month = self.start_datetime.month,
63
+ day = self.start_datetime.day)
64
+
65
+
66
+ @property
67
+ def start_time(self) -> str:
68
+ """The start time of the batch, up to seconds precision."""
69
+ return self._start_time
70
+
71
+
72
+ @property
73
+ def tag(self) -> str:
74
+ """The tag identifier of for the batch."""
75
+ return self._tag
76
+
77
+
78
+ @property
79
+ def start_datetime(self) -> datetime:
80
+ """The datetime of the batch file, up to seconds precision."""
81
+ return self._start_datetime
82
+
83
+
84
+ @property
85
+ def parent_dir_path(self) -> str:
86
+ """The parent directory for the batch."""
87
+ return self._parent_dir_path
88
+
89
+
90
+ @property
91
+ def name(self) -> str:
92
+ """The name of the batch."""
93
+ return f"{self._start_time}_{self._tag}"
94
+
95
+
96
+ @property
97
+ def extensions(self) -> list[str]:
98
+ """All defined file extensions for the batch."""
99
+ return list(self._batch_files.keys())
100
+
101
+ @property
102
+ def batch_files(self) -> dict[str, BatchFile]:
103
+ """Map each file extension in the batch to the corresponding batch file instance."""
104
+ return self._batch_files
105
+
106
+
107
+ def add_file(self, batch_file: BatchFile) -> None:
108
+ """Add an instance of a batch file to the batch."""
109
+ self._batch_files[batch_file.extension] = batch_file
110
+
111
+
112
+ def get_file(self, extension: str) -> BatchFile:
113
+ """Get a batch file instance from the batch, according to the file extension."""
114
+ try:
115
+ return self._batch_files[extension]
116
+ except KeyError:
117
+ raise BatchFileNotFoundError(f"No batch file found with extension '{extension}'")
118
+
119
+
120
+ def read_file(self, extension: str):
121
+ """Read a file from the batch, according to the file extension."""
122
+ batch_file = self.get_file(extension)
123
+ return batch_file.read()
124
+
125
+
126
+ def delete_file(self, extension: str) -> None:
127
+ """Delete a file from the batch, according to the file extension."""
128
+ batch_file = self.get_file(extension)
129
+ try:
130
+ batch_file.delete()
131
+ except FileNotFoundError as e:
132
+ raise BatchFileNotFoundError(str(e))
133
+
134
+
135
+ def has_file(self, extension: str) -> bool:
136
+ """Return true if a file exists in the batch with the input file extension."""
137
+ try:
138
+ # only return true if both
139
+ # -> the batch has the extension defined
140
+ # -> the file with that extension exists in the batch parent directory
141
+ batch_file = self.get_file(extension)
142
+ return batch_file.exists
143
+ except BatchFileNotFoundError:
144
+ return False
145
+
146
+
@@ -0,0 +1,197 @@
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_batches_dir_path, TimeFormats
16
+ from spectre_core.exceptions import (
17
+ SpectrogramNotFoundError,
18
+ BatchNotFoundError
19
+ )
20
+ from ._base import BaseBatch
21
+ from ._factory import get_batch_cls_from_tag
22
+
23
+ class Batches:
24
+ """A collection of batches for a given day of the year."""
25
+ def __init__(self,
26
+ tag: str,
27
+ year: Optional[int] = None,
28
+ month: Optional[int] = None,
29
+ day: Optional[int] = None):
30
+ self._tag = tag
31
+ self._Batch = get_batch_cls_from_tag(tag)
32
+ self._batch_map: dict[str, BaseBatch] = OrderedDict()
33
+ self.set_date(year, month, day)
34
+
35
+
36
+ @property
37
+ def tag(self) -> str:
38
+ """Tag identifier for each batch."""
39
+ return self._tag
40
+
41
+
42
+ @property
43
+ def year(self) -> int:
44
+ """The numeric year."""
45
+ return self._year
46
+
47
+
48
+ @property
49
+ def month(self) -> int:
50
+ """The numeric month of the year."""
51
+ return self._month
52
+
53
+
54
+ @property
55
+ def day(self) -> int:
56
+ """The numeric day of the year."""
57
+ return self._day
58
+
59
+
60
+ @property
61
+ def batches_dir_path(self) -> str:
62
+ """The parent directory for all the batches."""
63
+ return get_batches_dir_path(self.year, self.month, self.day)
64
+
65
+
66
+ @property
67
+ def batch_list(self) -> list[BaseBatch]:
68
+ """A list of all the batch instances."""
69
+ return list(self._batch_map.values())
70
+
71
+
72
+ @property
73
+ def start_times(self) -> list[str]:
74
+ """The start times of each batch."""
75
+ return list(self._batch_map.keys())
76
+
77
+
78
+ @property
79
+ def num_batches(self) -> int:
80
+ """The number of batches in the batch parent directory."""
81
+ return len(self.batch_list)
82
+
83
+
84
+ def set_date(self,
85
+ year: Optional[int],
86
+ month: Optional[int],
87
+ day: Optional[int]) -> None:
88
+ """Update the parent directory for the batches according to the numeric date."""
89
+ self._year = year
90
+ self._month = month
91
+ self._day = day
92
+ self._update_batch_map()
93
+
94
+
95
+ def _update_batch_map(self) -> None:
96
+ # reset cache
97
+ self._batch_map = OrderedDict()
98
+
99
+ # get a list of all batch file names in the batches directory path
100
+ batch_file_names = [f for (_, _, files) in os.walk(self.batches_dir_path) for f in files]
101
+ for batch_file_name in batch_file_names:
102
+ # strip the extension
103
+ batch_name, _ = os.path.splitext(batch_file_name)
104
+ start_time, tag = batch_name.split("_", 1)
105
+ if tag == self._tag:
106
+ self._batch_map[start_time] = self._Batch(start_time, tag)
107
+
108
+ self._batch_map = OrderedDict(sorted(self._batch_map.items()))
109
+
110
+
111
+ def update(self) -> None:
112
+ """Public alias for setting batch map"""
113
+ self._update_batch_map()
114
+
115
+
116
+ def __iter__(self):
117
+ """Iterate over the stored batch instances."""
118
+ yield from self.batch_list
119
+
120
+
121
+ def _get_from_start_time(self,
122
+ start_time: str) -> BaseBatch:
123
+ """Get the batch according to the input start time."""
124
+ try:
125
+ return self._batch_map[start_time]
126
+ except KeyError:
127
+ raise BatchNotFoundError(f"Batch with start time {start_time} could not be found within {self.batches_dir_path}")
128
+
129
+
130
+ def _get_from_index(self,
131
+ index: int) -> BaseBatch:
132
+ """Get the batch according to its index, where the batches are ordered in time."""
133
+ num_batches = len(self.batch_list)
134
+ if num_batches == 0:
135
+ raise BatchNotFoundError("No batches are available")
136
+ index = index % num_batches # Use modulo to make the index wrap around. Allows the user to iterate over all the batches via index cyclically.
137
+ return self.batch_list[index]
138
+
139
+
140
+ def __getitem__(self, subscript: str | int):
141
+ if isinstance(subscript, str):
142
+ return self._get_from_start_time(subscript)
143
+ elif isinstance(subscript, int):
144
+ return self._get_from_index(subscript)
145
+
146
+
147
+ def num_batch_files(self,
148
+ extension: str) -> int:
149
+ """Get the number of existing batch files with the given extension."""
150
+ return sum(1 for batch_file in self if batch_file.has_file(extension))
151
+
152
+
153
+ def get_spectrogram_from_range(self,
154
+ start_time: str,
155
+ end_time: str) -> Spectrogram:
156
+ """Return a spectrogram over the input time range."""
157
+ # Convert input strings to datetime objects
158
+ start_datetime = datetime.strptime(start_time, TimeFormats.DATETIME)
159
+ end_datetime = datetime.strptime(end_time, TimeFormats.DATETIME)
160
+
161
+ if start_datetime.day != end_datetime.day:
162
+ warning_message = "Joining spectrograms across multiple days"
163
+ _LOGGER.warning(warning_message)
164
+ warnings.warn(warning_message, RuntimeWarning)
165
+
166
+ spectrograms = []
167
+ num_fits_batch_files = self.num_batch_files("fits")
168
+
169
+ for i, batch in enumerate(self):
170
+ # skip batches without fits files
171
+ if not batch.has_file("fits"):
172
+ continue
173
+
174
+ # rather than reading all files to evaluate the actual upper bound to their time range (slow)
175
+ # place an upper bound by using the start datetime for the next batch
176
+ # this assumes that the batches are non-overlapping (reasonable assumption)
177
+ lower_bound = batch.start_datetime
178
+ if i < num_fits_batch_files:
179
+ next_batch = self[i + 1]
180
+ upper_bound = next_batch.start_datetime
181
+ # if there is no "next batch" then we do have to read the file
182
+ else:
183
+ fits_batch = batch.get_file("fits")
184
+ upper_bound = fits_batch.datetimes[-1]
185
+
186
+ # if the batch overlaps with the input time range, then read the fits file
187
+ if start_datetime <= upper_bound and lower_bound <= end_datetime:
188
+ spectrogram = batch.read_file("fits")
189
+ spectrogram = time_chop(spectrogram, start_time, end_time)
190
+ # if we have a non-empty spectrogram, append it to the list of spectrograms
191
+ if spectrogram:
192
+ spectrograms.append(spectrogram)
193
+
194
+ if spectrograms:
195
+ return join_spectrograms(spectrograms)
196
+ else:
197
+ raise SpectrogramNotFoundError("No spectrogram data found for the given time range")
@@ -0,0 +1,27 @@
1
+ # SPDX-FileCopyrightText: © 2024 Jimmy Fitzpatrick <jcfitzpatrick12@gmail.com>
2
+ # This file is part of SPECTRE
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ from spectre_core.capture_configs import CaptureConfig, PNames
6
+ from spectre_core.exceptions import BatchNotFoundError
7
+ from ._register import batch_map
8
+ from ._base import BaseBatch
9
+
10
+
11
+ def _get_batch_cls(batch_key: str) -> BaseBatch:
12
+ Batch = batch_map.get(batch_key)
13
+ if Batch is None:
14
+ valid_batch_keys = list(batch_map.keys())
15
+ raise BatchNotFoundError(f"No batch found for the batch key: {batch_key}. Valid batch keys are: {valid_batch_keys}")
16
+ return Batch
17
+
18
+
19
+ def get_batch_cls_from_tag(tag: str) -> BaseBatch:
20
+ # if we are dealing with a callisto batch, the batch key is equal to the tag
21
+ if "callisto" in tag:
22
+ batch_key = "callisto"
23
+ # otherwise, we fetch the batch key from the capture config
24
+ else:
25
+ capture_config= CaptureConfig(tag)
26
+ batch_key = capture_config.get_parameter_value(PNames.BATCH_KEY)
27
+ return _get_batch_cls(batch_key)
@@ -3,13 +3,13 @@
3
3
  # SPDX-License-Identifier: GPL-3.0-or-later
4
4
 
5
5
  # Global dictionaries to hold the mappings
6
- chunk_map = {}
6
+ batch_map = {}
7
7
 
8
- # classes decorated with @register_chunk([CHUNK_KEY])
9
- # will be added to chunk_map
10
- def register_chunk(chunk_key: str):
8
+ # classes decorated with @register_batch([BATCH_KEY])
9
+ # will be added to batch_map
10
+ def register_batch(batch_key: str):
11
11
  def decorator(cls):
12
- chunk_map[chunk_key] = cls
12
+ batch_map[batch_key] = cls
13
13
  return cls
14
14
  return decorator
15
15
 
@@ -11,43 +11,46 @@ 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.spectrograms import Spectrogram
15
- from .._register import register_chunk
16
- from .._base import BaseChunk, ChunkFile
14
+ from spectre_core.spectrograms import Spectrogram, SpectrumTypes
15
+ from .._register import register_batch
16
+ from .._base import BaseBatch, BatchFile
17
17
 
18
18
 
19
- @register_chunk('callisto')
20
- class _Chunk(BaseChunk):
21
- def __init__(self, chunk_start_time: str, tag: str):
22
- super().__init__(chunk_start_time, tag)
23
- self.add_file(FitsChunk(self.chunk_parent_path, self.chunk_name))
19
+ @register_batch('callisto')
20
+ class _Batch(BaseBatch):
21
+ def __init__(self,
22
+ start_time: str,
23
+ tag: str):
24
+ super().__init__(start_time, tag)
25
+ self.add_file( FitsFile(self.parent_dir_path, self.name) )
24
26
 
25
27
 
26
- class FitsChunk(ChunkFile):
27
- def __init__(self, chunk_parent_path: str, chunk_name: str):
28
- super().__init__(chunk_parent_path, chunk_name, "fits")
28
+ class FitsFile(BatchFile):
29
+ def __init__(self,
30
+ parent_dir_path: str,
31
+ base_file_name: str):
32
+ super().__init__(parent_dir_path, base_file_name, "fits")
29
33
 
30
34
 
31
35
  def read(self) -> Spectrogram:
32
36
  with fits.open(self.file_path, mode='readonly') as hdulist:
33
- primary_hdu = self._get_primary_hdu(hdulist)
34
- dynamic_spectra = self._get_dynamic_spectra(primary_hdu)
35
- microsecond_correction = self._get_microsecond_correction(primary_hdu)
36
- bintable_hdu = self._get_bintable_hdu(hdulist)
37
- times, frequencies = self._get_time_and_frequency(bintable_hdu)
38
- spectrum_type = self._get_spectrum_type(primary_hdu)
39
-
40
- if spectrum_type == "digits":
37
+ primary_hdu = self._get_primary_hdu(hdulist)
38
+ dynamic_spectra = self._get_dynamic_spectra(primary_hdu)
39
+ spectrogram_start_datetime = self._get_spectrogram_start_datetime(primary_hdu)
40
+ bintable_hdu = self._get_bintable_hdu(hdulist)
41
+ times, frequencies = self._get_time_and_frequency(bintable_hdu)
42
+ spectrum_type = self._get_spectrum_type(primary_hdu)
43
+
44
+ if spectrum_type == SpectrumTypes.DIGITS:
41
45
  dynamic_spectra_linearised = self._convert_units_to_linearised(dynamic_spectra)
42
46
  return Spectrogram(dynamic_spectra_linearised[::-1, :], # reverse the spectra along the frequency axis
43
47
  times,
44
48
  frequencies[::-1], # sort the frequencies in ascending order
45
49
  self.tag,
46
- chunk_start_time=self.chunk_start_time,
47
- microsecond_correction = microsecond_correction,
48
- spectrum_type = spectrum_type)
50
+ spectrogram_start_datetime,
51
+ spectrum_type)
49
52
  else:
50
- raise NotImplementedError(f"SPECTRE does not currently support spectrum type with BUNITS {spectrum_type}")
53
+ raise NotImplementedError(f"SPECTRE does not currently support spectrum type with BUNITS '{spectrum_type}'")
51
54
 
52
55
 
53
56
  @property
@@ -55,7 +58,7 @@ class FitsChunk(ChunkFile):
55
58
  with fits.open(self.file_path, mode='readonly') as hdulist:
56
59
  bintable_data = hdulist[1].data
57
60
  times = bintable_data['TIME'][0]
58
- return [self.chunk_start_datetime + timedelta(seconds=t) for t in times]
61
+ return [self.start_datetime + timedelta(seconds=t) for t in times]
59
62
 
60
63
 
61
64
  def _get_primary_hdu(self, hdulist: HDUList) -> PrimaryHDU:
@@ -66,11 +69,10 @@ class FitsChunk(ChunkFile):
66
69
  return primary_hdu.data
67
70
 
68
71
 
69
- def _get_microsecond_correction(self, primary_hdu: PrimaryHDU) -> int:
70
- date_obs = primary_hdu.header.get('DATE-OBS', None)
71
- time_obs = primary_hdu.header.get('TIME-OBS', None)
72
- datetime_obs = datetime.strptime(f"{date_obs}T{time_obs}", "%Y/%m/%dT%H:%M:%S.%f")
73
- return datetime_obs.microsecond
72
+ def _get_spectrogram_start_datetime(self, primary_hdu: PrimaryHDU) -> datetime:
73
+ date_obs = primary_hdu.header['DATE-OBS']
74
+ time_obs = primary_hdu.header['TIME-OBS']
75
+ return datetime.strptime(f"{date_obs}T{time_obs}", "%Y-%m-%dT%H:%M:%S.%f")
74
76
 
75
77
 
76
78
  def _get_bintable_hdu(self, hdulist: HDUList) -> BinTableHDU:
@@ -85,10 +87,6 @@ class FitsChunk(ChunkFile):
85
87
  return times, frequencies
86
88
 
87
89
 
88
- def _get_spectrum_type(self, primary_hdu: PrimaryHDU) -> str:
89
- return primary_hdu.header.get('BUNIT', None)
90
-
91
-
92
90
  def _convert_units_to_linearised(self, dynamic_spectra: np.ndarray) -> np.ndarray:
93
91
  digits_floats = np.array(dynamic_spectra, dtype='float')
94
92
  # conversion as per ADC specs [see email from C. Monstein]