jolly-roger 0.1.0__tar.gz → 0.2.0__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.
Potentially problematic release.
This version of jolly-roger might be problematic. Click here for more details.
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/.gitignore +2 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/PKG-INFO +3 -1
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/jolly_roger/_version.py +2 -2
- jolly_roger-0.2.0/jolly_roger/delays.py +107 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/jolly_roger/hour_angles.py +7 -8
- jolly_roger-0.2.0/jolly_roger/plots.py +171 -0
- jolly_roger-0.2.0/jolly_roger/tractor.py +808 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/jolly_roger/uvws.py +55 -5
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/pyproject.toml +3 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/.copier-answers.yml +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/.git_archival.txt +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/.gitattributes +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/.github/CONTRIBUTING.md +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/.github/actions/setup-deps/action.yml +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/.github/dependabot.yml +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/.github/release.yml +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/.github/workflows/cd.yml +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/.github/workflows/ci.yml +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/.pre-commit-config.yaml +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/.readthedocs.yaml +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/LICENSE +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/README.md +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/docs/conf.py +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/docs/index.md +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/jolly_roger/__init__.py +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/jolly_roger/_version.pyi +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/jolly_roger/baselines.py +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/jolly_roger/flagger.py +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/jolly_roger/logging.py +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/jolly_roger/py.typed +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/noxfile.py +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/tests/test_hour_angles.py +0 -0
- {jolly_roger-0.1.0 → jolly_roger-0.2.0}/tests/test_package.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jolly-roger
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: The pirate flagger
|
|
5
5
|
Project-URL: Homepage, https://github.com/flint-crew/jolly-roger
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/flint-crew/jolly-roger/issues
|
|
@@ -23,6 +23,8 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
23
23
|
Classifier: Typing :: Typed
|
|
24
24
|
Requires-Python: >=3.11
|
|
25
25
|
Requires-Dist: astropy
|
|
26
|
+
Requires-Dist: dask-ms
|
|
27
|
+
Requires-Dist: matplotlib
|
|
26
28
|
Requires-Dist: numpy>=2.0.0
|
|
27
29
|
Requires-Dist: python-casacore>=3.6.0
|
|
28
30
|
Requires-Dist: tqdm
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Utilities and structures around the delay calculations"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
import astropy.units as u
|
|
9
|
+
import numpy as np
|
|
10
|
+
from numpy.typing import NDArray
|
|
11
|
+
|
|
12
|
+
from jolly_roger.logging import logger
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
# avoid circular imports
|
|
16
|
+
from jolly_roger.tractor import BaselineData, DataChunk
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class DelayTime:
|
|
21
|
+
"""Container for delay time and associated metadata."""
|
|
22
|
+
|
|
23
|
+
delay_time: NDArray[np.complexfloating]
|
|
24
|
+
""" The delay vs time data. shape=(time, delay, pol)"""
|
|
25
|
+
delay: u.Quantity
|
|
26
|
+
"""The delay values corresponding to the delay time data."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def data_to_delay_time(data: BaselineData | DataChunk) -> DelayTime:
|
|
30
|
+
logger.debug("Converting freq-time to delay-time")
|
|
31
|
+
delay_time = np.fft.fftshift(
|
|
32
|
+
np.fft.fft(data.masked_data.filled(0 + 0j), axis=1), axes=1
|
|
33
|
+
)
|
|
34
|
+
delay = np.fft.fftshift(
|
|
35
|
+
np.fft.fftfreq(
|
|
36
|
+
n=len(data.freq_chan),
|
|
37
|
+
d=np.diff(data.freq_chan).mean(),
|
|
38
|
+
).decompose()
|
|
39
|
+
)
|
|
40
|
+
return DelayTime(
|
|
41
|
+
delay_time=delay_time,
|
|
42
|
+
delay=delay,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def delay_time_to_data(
|
|
47
|
+
delay_time: DelayTime,
|
|
48
|
+
original_data: DataChunk,
|
|
49
|
+
) -> DataChunk:
|
|
50
|
+
"""Convert delay time data back to the original data format."""
|
|
51
|
+
logger.debug("Converting delay-time to freq-time")
|
|
52
|
+
new_data = np.fft.ifft(
|
|
53
|
+
np.fft.ifftshift(delay_time.delay_time, axes=1),
|
|
54
|
+
axis=1,
|
|
55
|
+
)
|
|
56
|
+
new_data_masked = np.ma.masked_array(
|
|
57
|
+
new_data,
|
|
58
|
+
mask=original_data.masked_data.mask,
|
|
59
|
+
)
|
|
60
|
+
new_data = original_data
|
|
61
|
+
new_data.masked_data = new_data_masked
|
|
62
|
+
return new_data
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class DelayRate:
|
|
67
|
+
"""Container for delay rate and associated metadata."""
|
|
68
|
+
|
|
69
|
+
delay_rate: np.ndarray
|
|
70
|
+
"""The delay rate vs time data. shape=(rate, delay, pol)"""
|
|
71
|
+
delay: u.Quantity
|
|
72
|
+
"""The delay values corresponding to the delay rate data."""
|
|
73
|
+
rate: u.Quantity
|
|
74
|
+
"""The delay rate values corresponding to the delay rate data."""
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def data_to_delay_rate(
|
|
78
|
+
baseline_data: BaselineData,
|
|
79
|
+
) -> DelayRate:
|
|
80
|
+
"""Convert baseline data to delay rate."""
|
|
81
|
+
# This only makes sense when running on time data. Hence
|
|
82
|
+
# asserting the type of BaelineData
|
|
83
|
+
|
|
84
|
+
assert isinstance(baseline_data, BaselineData), (
|
|
85
|
+
f"baseline_data is type={type(baseline_data)}, but needs to be BaselineData"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
logger.info("Converting freq-time to delay-rate")
|
|
89
|
+
delay_rate = np.fft.fftshift(np.fft.fft2(baseline_data.masked_data.filled(0 + 0j)))
|
|
90
|
+
delay = np.fft.fftshift(
|
|
91
|
+
np.fft.fftfreq(
|
|
92
|
+
n=len(baseline_data.freq_chan),
|
|
93
|
+
d=np.diff(baseline_data.freq_chan).mean(),
|
|
94
|
+
).decompose()
|
|
95
|
+
)
|
|
96
|
+
rate = np.fft.fftshift(
|
|
97
|
+
np.fft.fftfreq(
|
|
98
|
+
n=len(baseline_data.time),
|
|
99
|
+
d=np.diff(baseline_data.time.mjd * u.day).mean(),
|
|
100
|
+
).decompose()
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return DelayRate(
|
|
104
|
+
delay_rate=delay_rate,
|
|
105
|
+
delay=delay,
|
|
106
|
+
rate=rate,
|
|
107
|
+
)
|
|
@@ -4,7 +4,6 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Literal
|
|
8
7
|
|
|
9
8
|
import astropy.units as u
|
|
10
9
|
import numpy as np
|
|
@@ -26,13 +25,13 @@ class PositionHourAngles:
|
|
|
26
25
|
|
|
27
26
|
hour_angle: u.rad
|
|
28
27
|
"""The hour angle across sampled time intervales of a source for a Earth location"""
|
|
29
|
-
time_mjds:
|
|
28
|
+
time_mjds: u.Quantity
|
|
30
29
|
"""The MJD time in seconds from which other quantities are evalauted against. Should be drawn from a measurement set."""
|
|
31
30
|
location: EarthLocation
|
|
32
31
|
"""The location these quantities have been derived from."""
|
|
33
32
|
position: SkyCoord
|
|
34
33
|
"""The sky-position that is being used to calculate quantities towards"""
|
|
35
|
-
elevation:
|
|
34
|
+
elevation: u.Quantity
|
|
36
35
|
"""The elevation of the ``position` direction across time"""
|
|
37
36
|
time: Time
|
|
38
37
|
"""Representation of the `time_mjds` attribute"""
|
|
@@ -43,7 +42,7 @@ class PositionHourAngles:
|
|
|
43
42
|
|
|
44
43
|
|
|
45
44
|
def _process_position(
|
|
46
|
-
position: SkyCoord |
|
|
45
|
+
position: SkyCoord | str | None = None,
|
|
47
46
|
ms_path: Path | None = None,
|
|
48
47
|
times: Time | None = None,
|
|
49
48
|
) -> SkyCoord:
|
|
@@ -54,7 +53,7 @@ def _process_position(
|
|
|
54
53
|
set
|
|
55
54
|
|
|
56
55
|
Args:
|
|
57
|
-
position (SkyCoord |
|
|
56
|
+
position (SkyCoord | str | None, optional): The position to be considered. Defaults to None.
|
|
58
57
|
ms_path (Path | None, optional): The path with the PHASE_DIR to use should `position` be None. Defaults to None.
|
|
59
58
|
times (Time | None, optional): Times to used if they are required in the lookup. Defaults to None.
|
|
60
59
|
|
|
@@ -71,8 +70,8 @@ def _process_position(
|
|
|
71
70
|
if times is None:
|
|
72
71
|
msg = f"{times=}, but needs to be set when position is a name"
|
|
73
72
|
raise ValueError(msg)
|
|
74
|
-
if position == "sun":
|
|
75
|
-
logger.info("Getting sky-position of the
|
|
73
|
+
if position.lower() == "sun":
|
|
74
|
+
logger.info("Getting sky-position of the Sun")
|
|
76
75
|
position = get_sun(times)
|
|
77
76
|
else:
|
|
78
77
|
logger.info(f"Getting sky-position of {position=}")
|
|
@@ -142,7 +141,7 @@ def make_hour_angles_for_ms(
|
|
|
142
141
|
lst = times.sidereal_time("apparent", longitude=location.lon)
|
|
143
142
|
hour_angle = (lst - sky_position.ra).wrap_at(12 * u.hourangle)
|
|
144
143
|
|
|
145
|
-
logger.info("
|
|
144
|
+
logger.info("Creating elevation curve")
|
|
146
145
|
altaz = sky_position.transform_to(AltAz(obstime=times, location=location))
|
|
147
146
|
|
|
148
147
|
return PositionHourAngles(
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Routines around plotting"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from jolly_roger.uvws import WDelays
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from jolly_roger.delays import DelayTime
|
|
15
|
+
from jolly_roger.tractor import BaselineData
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def plot_baseline_data(
|
|
19
|
+
baseline_data: BaselineData,
|
|
20
|
+
output_dir: Path,
|
|
21
|
+
suffix: str = "",
|
|
22
|
+
) -> None:
|
|
23
|
+
from astropy.visualization import quantity_support, time_support
|
|
24
|
+
|
|
25
|
+
with quantity_support(), time_support():
|
|
26
|
+
data_masked = baseline_data.masked_data
|
|
27
|
+
data_xx = data_masked[..., 0]
|
|
28
|
+
data_yy = data_masked[..., -1]
|
|
29
|
+
data_stokesi = (data_xx + data_yy) / 2
|
|
30
|
+
amp_stokesi = np.abs(data_stokesi)
|
|
31
|
+
|
|
32
|
+
fig, ax = plt.subplots()
|
|
33
|
+
im = ax.pcolormesh(
|
|
34
|
+
baseline_data.time,
|
|
35
|
+
baseline_data.freq_chan,
|
|
36
|
+
amp_stokesi.T,
|
|
37
|
+
)
|
|
38
|
+
fig.colorbar(im, ax=ax, label="Stokes I Amplitude / Jy")
|
|
39
|
+
ax.set(
|
|
40
|
+
ylabel=f"Frequency / {baseline_data.freq_chan.unit:latex_inline}",
|
|
41
|
+
title=f"Ant {baseline_data.ant_1} - Ant {baseline_data.ant_2}",
|
|
42
|
+
)
|
|
43
|
+
output_path = (
|
|
44
|
+
output_dir
|
|
45
|
+
/ f"baseline_data_{baseline_data.ant_1}_{baseline_data.ant_2}{suffix}.png"
|
|
46
|
+
)
|
|
47
|
+
fig.savefig(output_path)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def plot_baseline_comparison_data(
|
|
51
|
+
before_baseline_data: BaselineData,
|
|
52
|
+
after_baseline_data: BaselineData,
|
|
53
|
+
before_delays: DelayTime,
|
|
54
|
+
after_delays: DelayTime,
|
|
55
|
+
output_dir: Path,
|
|
56
|
+
suffix: str = "",
|
|
57
|
+
w_delays: WDelays | None = None,
|
|
58
|
+
) -> Path:
|
|
59
|
+
from astropy.visualization import (
|
|
60
|
+
ImageNormalize,
|
|
61
|
+
LogStretch,
|
|
62
|
+
MinMaxInterval,
|
|
63
|
+
SqrtStretch,
|
|
64
|
+
ZScaleInterval,
|
|
65
|
+
quantity_support,
|
|
66
|
+
time_support,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
with quantity_support(), time_support():
|
|
70
|
+
before_amp_stokesi = np.abs(
|
|
71
|
+
(
|
|
72
|
+
before_baseline_data.masked_data[..., 0]
|
|
73
|
+
+ before_baseline_data.masked_data[..., -1]
|
|
74
|
+
)
|
|
75
|
+
/ 2
|
|
76
|
+
)
|
|
77
|
+
after_amp_stokesi = np.abs(
|
|
78
|
+
(
|
|
79
|
+
after_baseline_data.masked_data[..., 0]
|
|
80
|
+
+ after_baseline_data.masked_data[..., -1]
|
|
81
|
+
)
|
|
82
|
+
/ 2
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
norm = ImageNormalize(
|
|
86
|
+
after_amp_stokesi, interval=ZScaleInterval(), stretch=SqrtStretch()
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(
|
|
90
|
+
2, 2, figsize=(10, 10), sharex=True, sharey="row"
|
|
91
|
+
)
|
|
92
|
+
im = ax1.pcolormesh(
|
|
93
|
+
before_baseline_data.time,
|
|
94
|
+
before_baseline_data.freq_chan,
|
|
95
|
+
before_amp_stokesi.T,
|
|
96
|
+
norm=norm,
|
|
97
|
+
)
|
|
98
|
+
ax1.set(
|
|
99
|
+
ylabel=f"Frequency / {before_baseline_data.freq_chan.unit:latex_inline}",
|
|
100
|
+
title="Before",
|
|
101
|
+
)
|
|
102
|
+
ax2.pcolormesh(
|
|
103
|
+
after_baseline_data.time,
|
|
104
|
+
after_baseline_data.freq_chan,
|
|
105
|
+
after_amp_stokesi.T,
|
|
106
|
+
norm=norm,
|
|
107
|
+
)
|
|
108
|
+
ax2.set(
|
|
109
|
+
ylabel=f"Frequency / {after_baseline_data.freq_chan.unit:latex_inline}",
|
|
110
|
+
title="After",
|
|
111
|
+
)
|
|
112
|
+
fig.colorbar(im, ax=ax2, label="Stokes I Amplitude / Jy")
|
|
113
|
+
|
|
114
|
+
# TODO: Move these delay calculations outside of the plotting function
|
|
115
|
+
# And here we calculate the delay information
|
|
116
|
+
|
|
117
|
+
before_delays_i = np.abs(
|
|
118
|
+
(before_delays.delay_time[:, :, 0] + before_delays.delay_time[:, :, -1]) / 2
|
|
119
|
+
)
|
|
120
|
+
after_delays_i = np.abs(
|
|
121
|
+
(after_delays.delay_time[:, :, 0] + after_delays.delay_time[:, :, -1]) / 2
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
delay_norm = ImageNormalize(
|
|
125
|
+
before_delays_i, interval=MinMaxInterval(), stretch=LogStretch()
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
im = ax3.pcolormesh(
|
|
129
|
+
before_baseline_data.time,
|
|
130
|
+
before_delays.delay,
|
|
131
|
+
before_delays_i.T,
|
|
132
|
+
norm=delay_norm,
|
|
133
|
+
)
|
|
134
|
+
ax3.set(ylabel="Delay / s", title="Before")
|
|
135
|
+
ax4.pcolormesh(
|
|
136
|
+
after_baseline_data.time,
|
|
137
|
+
after_delays.delay,
|
|
138
|
+
after_delays_i.T,
|
|
139
|
+
norm=delay_norm,
|
|
140
|
+
)
|
|
141
|
+
ax4.set(ylabel="Delay / s", title="After")
|
|
142
|
+
fig.colorbar(im, ax=ax4, label="Stokes I Amplitude / Jy")
|
|
143
|
+
|
|
144
|
+
if w_delays is not None:
|
|
145
|
+
for ax, baseline_data in zip( # type:ignore[call-overload]
|
|
146
|
+
(ax3, ax4),
|
|
147
|
+
(before_baseline_data, after_baseline_data),
|
|
148
|
+
strict=True,
|
|
149
|
+
):
|
|
150
|
+
ant_1, ant_2 = baseline_data.ant_1, baseline_data.ant_2
|
|
151
|
+
b_idx = w_delays.b_map[ant_1, ant_2]
|
|
152
|
+
ax.plot(
|
|
153
|
+
baseline_data.time,
|
|
154
|
+
w_delays.w_delays[b_idx],
|
|
155
|
+
color="k",
|
|
156
|
+
linestyle="--",
|
|
157
|
+
label=f"Delay for {w_delays.object_name}",
|
|
158
|
+
)
|
|
159
|
+
ax.legend()
|
|
160
|
+
|
|
161
|
+
output_path = (
|
|
162
|
+
output_dir
|
|
163
|
+
/ f"baseline_data_{before_baseline_data.ant_1}_{before_baseline_data.ant_2}{suffix}.png"
|
|
164
|
+
)
|
|
165
|
+
fig.suptitle(
|
|
166
|
+
f"Ant {after_baseline_data.ant_1} - Ant {after_baseline_data.ant_2}"
|
|
167
|
+
)
|
|
168
|
+
fig.tight_layout()
|
|
169
|
+
fig.savefig(output_path)
|
|
170
|
+
|
|
171
|
+
return output_path
|