Skip to content

Commit f585ce4

Browse files
committed
huge structural overhaul for clarity
1 parent fad6e41 commit f585ce4

32 files changed

Lines changed: 2536 additions & 1088 deletions

setigen/_frame/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
Private implementation modules for the public ``setigen.frame`` facade.
3+
"""

setigen/_frame/construction.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
from __future__ import annotations
2+
3+
import copy
4+
from dataclasses import dataclass
5+
import pathlib
6+
import time
7+
8+
import numpy as np
9+
10+
from astropy import units as u
11+
from astropy.time import Time
12+
13+
from blimpy import Waterfall
14+
15+
from .. import waterfall_utils
16+
from .. import unit_utils
17+
18+
19+
@dataclass(frozen=True)
20+
class _SyntheticFrameSpec:
21+
df: float
22+
dt: float
23+
fch1: float
24+
ascending: bool
25+
shape: tuple[int, int]
26+
data: np.ndarray
27+
t_start: float
28+
source_name: str
29+
30+
31+
@dataclass(frozen=True)
32+
class _WaterfallLoadSpec:
33+
waterfall: Waterfall
34+
header: object
35+
df: float
36+
dt: float
37+
fch1: float
38+
ascending: bool
39+
shape: tuple[int, int]
40+
data: np.ndarray
41+
t_start: float
42+
source_name: str
43+
44+
45+
def _is_synthetic_init(*, fchans=None, tchans=None, data=None, kwargs=None):
46+
kwargs = {} if kwargs is None else kwargs
47+
return None not in [fchans, tchans] or "shape" in kwargs or data is not None
48+
49+
50+
def _normalize_synthetic_init(*,
51+
fchans=None,
52+
tchans=None,
53+
df=2.7939677238464355 * u.Hz,
54+
dt=18.253611008 * u.s,
55+
fch1=6 * u.GHz,
56+
ascending=False,
57+
data=None,
58+
kwargs=None):
59+
kwargs = {} if kwargs is None else kwargs
60+
61+
normalized_df = unit_utils.get_value(abs(df), u.Hz)
62+
normalized_dt = unit_utils.get_value(dt, u.s)
63+
normalized_fch1 = unit_utils.get_value(fch1, u.Hz)
64+
65+
mjd = kwargs.get("mjd")
66+
if mjd is not None:
67+
t_start = Time(mjd, format="mjd").unix
68+
else:
69+
t_start = kwargs.get("t_start", time.time())
70+
source_name = kwargs.get("source_name", "Synthetic")
71+
72+
if "shape" in kwargs:
73+
shape = tuple(kwargs["shape"])
74+
elif data is not None:
75+
shape = data.shape
76+
else:
77+
shape = (int(unit_utils.get_value(tchans, u.pixel)),
78+
int(unit_utils.get_value(fchans, u.pixel)))
79+
80+
if data is not None:
81+
if data.shape != shape:
82+
raise ValueError(f"Data shape {data.shape} does not match frame shape {shape}.")
83+
frame_data = np.copy(data)
84+
else:
85+
frame_data = np.zeros(shape)
86+
87+
return _SyntheticFrameSpec(df=normalized_df,
88+
dt=normalized_dt,
89+
fch1=normalized_fch1,
90+
ascending=ascending,
91+
shape=shape,
92+
data=frame_data,
93+
t_start=t_start,
94+
source_name=source_name)
95+
96+
97+
def _normalize_waterfall_init(*, waterfall, kwargs=None):
98+
kwargs = {} if kwargs is None else kwargs
99+
100+
if isinstance(waterfall, pathlib.PurePath):
101+
waterfall = str(waterfall)
102+
if isinstance(waterfall, str):
103+
waterfall = Waterfall(waterfall,
104+
f_start=kwargs.get("f_start"),
105+
f_stop=kwargs.get("f_stop"))
106+
elif not isinstance(waterfall, Waterfall):
107+
raise FileNotFoundError(f"Unsupported data type: {type(waterfall)}")
108+
109+
header = waterfall.header
110+
tchans, _, fchans = waterfall.container.selection_shape
111+
shape = (tchans, fchans)
112+
113+
df = unit_utils.cast_value(abs(header["foff"]), u.MHz).to(u.Hz).value
114+
dt = unit_utils.get_value(header["tsamp"], u.s)
115+
116+
ascending = header["foff"] > 0
117+
if ascending:
118+
fch1 = waterfall.container.f_start
119+
else:
120+
fch1 = waterfall.container.f_stop
121+
fch1 = unit_utils.cast_value(fch1, u.MHz).to(u.Hz).value
122+
123+
t_start = Time(header["tstart"], format="mjd").unix
124+
source_name = header["source_name"]
125+
126+
data = waterfall_utils.get_data(waterfall)
127+
if not ascending:
128+
data = data[:, ::-1]
129+
130+
return _WaterfallLoadSpec(waterfall=waterfall,
131+
header=header,
132+
df=df,
133+
dt=dt,
134+
fch1=fch1,
135+
ascending=ascending,
136+
shape=shape,
137+
data=data,
138+
t_start=t_start,
139+
source_name=source_name)
140+
141+
142+
def _normalize_frame_init(*,
143+
waterfall=None,
144+
fchans=None,
145+
tchans=None,
146+
df=2.7939677238464355 * u.Hz,
147+
dt=18.253611008 * u.s,
148+
fch1=6 * u.GHz,
149+
ascending=False,
150+
data=None,
151+
kwargs=None):
152+
kwargs = {} if kwargs is None else kwargs
153+
if _is_synthetic_init(fchans=fchans,
154+
tchans=tchans,
155+
data=data,
156+
kwargs=kwargs):
157+
return _normalize_synthetic_init(fchans=fchans,
158+
tchans=tchans,
159+
df=df,
160+
dt=dt,
161+
fch1=fch1,
162+
ascending=ascending,
163+
data=data,
164+
kwargs=kwargs)
165+
if waterfall is not None:
166+
return _normalize_waterfall_init(waterfall=waterfall, kwargs=kwargs)
167+
raise ValueError("Frame must be provided dimensions or an existing filterbank file.")
168+
169+
170+
def _initialize_frame_from_spec(frame, spec):
171+
frame.df = spec.df
172+
frame.dt = spec.dt
173+
frame.fch1 = spec.fch1
174+
frame.ascending = spec.ascending
175+
frame.t_start = spec.t_start
176+
frame.source_name = spec.source_name
177+
frame.shape = spec.shape
178+
frame.tchans, frame.fchans = spec.shape
179+
frame.data = spec.data
180+
181+
if isinstance(spec, _WaterfallLoadSpec):
182+
frame.waterfall = spec.waterfall
183+
frame.header = spec.header
184+
else:
185+
frame.waterfall = None
186+
frame.header = None
187+
188+
189+
def _attach_loaded_waterfall(frame, waterfall):
190+
if waterfall is None:
191+
return
192+
try:
193+
del waterfall.container.h5
194+
except AttributeError:
195+
pass
196+
frame.waterfall = copy.deepcopy(waterfall)

setigen/_frame/io.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
from __future__ import annotations
2+
3+
import pathlib
4+
import pickle
5+
6+
import numpy as np
7+
8+
from blimpy import Waterfall
9+
from blimpy.io import sigproc
10+
11+
12+
def _create_synthetic_waterfall(frame, *, max_load=1):
13+
path = pathlib.Path(__file__).resolve().parents[1] / "assets" / "sample.fil"
14+
waterfall = Waterfall(str(path), max_load=max_load)
15+
waterfall.header["source_name"] = frame.source_name
16+
waterfall.header["rawdatafile"] = "Synthetic"
17+
18+
container_attr = {
19+
"t_begin": 0,
20+
"t_end": frame.tchans,
21+
"file_size_bytes": frame.tchans * frame.fchans * waterfall.header["nbits"] / 8,
22+
"n_channels_in_file": frame.fchans,
23+
"n_ints_in_file": frame.tchans,
24+
"file_shape": (frame.tchans, 1, frame.fchans),
25+
"f_end": frame.fmax * 1e-6,
26+
"f_begin": frame.fmin * 1e-6,
27+
"f_stop": frame.fmax * 1e-6,
28+
"f_start": frame.fmin * 1e-6,
29+
"t_start": 0,
30+
"t_stop": frame.tchans,
31+
"selection_shape": (frame.tchans, 1, frame.fchans),
32+
"chan_start_idx": 0,
33+
"chan_stop_idx": frame.fchans,
34+
}
35+
for key, value in container_attr.items():
36+
setattr(waterfall.container, key, value)
37+
38+
wat_attr = {
39+
"n_channels_in_file": frame.fchans,
40+
"n_ints_in_file": frame.tchans,
41+
"file_shape": (frame.tchans, 1, frame.fchans),
42+
"file_size_bytes": frame.tchans * frame.fchans * waterfall.header["nbits"] / 8,
43+
"selection_shape": (frame.tchans, 1, frame.fchans),
44+
}
45+
for key, value in wat_attr.items():
46+
setattr(waterfall, key, value)
47+
48+
return waterfall
49+
50+
51+
def _update_waterfall(frame, *, filename=None, max_load=1):
52+
if frame.waterfall is None:
53+
frame.waterfall = _create_synthetic_waterfall(frame, max_load=max_load)
54+
55+
frame.waterfall.data = frame.data[:, np.newaxis, :]
56+
if not frame.ascending:
57+
frame.waterfall.data = frame.waterfall.data[:, :, ::-1]
58+
59+
header_attr = {
60+
"tsamp": frame.dt,
61+
"tstart": frame.mjd,
62+
"nchans": frame.fchans,
63+
"fch1": frame.fch1 * 1e-6,
64+
}
65+
if frame.ascending:
66+
header_attr["foff"] = frame.df * 1e-6
67+
else:
68+
header_attr["foff"] = frame.df * -1e-6
69+
frame.waterfall.header.update(header_attr)
70+
frame.waterfall.file_header.update(header_attr)
71+
72+
if filename is not None:
73+
frame.waterfall.container.filename = str(pathlib.Path(filename).resolve())
74+
frame.waterfall.container.idx_data = len(sigproc.generate_sigproc_header(frame.waterfall))
75+
76+
77+
def _encode_bytestrings(frame):
78+
for key in ["source_name", "rawdatafile"]:
79+
if key in frame.waterfall.header and not isinstance(frame.waterfall.header[key], bytes):
80+
frame.waterfall.header[key] = frame.waterfall.header[key].encode()
81+
82+
83+
def _decode_bytestrings(frame):
84+
for key in ["source_name", "rawdatafile"]:
85+
if key in frame.waterfall.header and isinstance(frame.waterfall.header[key], bytes):
86+
frame.waterfall.header[key] = frame.waterfall.header[key].decode()
87+
88+
89+
def _get_waterfall(frame):
90+
_update_waterfall(frame)
91+
return frame.waterfall
92+
93+
94+
def _check_waterfall(frame):
95+
if frame.waterfall is None:
96+
return None
97+
return _get_waterfall(frame)
98+
99+
100+
def _save_fil(frame, filename, *, max_load=1):
101+
_update_waterfall(frame, filename=filename, max_load=max_load)
102+
_encode_bytestrings(frame)
103+
frame.waterfall.write_to_fil(filename)
104+
_decode_bytestrings(frame)
105+
106+
107+
def _save_hdf5(frame, filename, *, max_load=1):
108+
_update_waterfall(frame, filename=filename, max_load=max_load)
109+
_encode_bytestrings(frame)
110+
frame.waterfall.write_to_hdf5(filename)
111+
_decode_bytestrings(frame)
112+
113+
114+
def _save_npy(frame, filename):
115+
np.save(filename, frame.data)
116+
117+
118+
def _load_npy(frame, filename):
119+
frame.data = np.load(filename)
120+
121+
122+
def _save_pickle(frame, filename):
123+
with open(filename, "wb") as f:
124+
pickle.dump(frame, f)
125+
126+
127+
def _load_pickle(filename):
128+
with open(filename, "rb") as f:
129+
return pickle.load(f)

0 commit comments

Comments
 (0)