jolly-roger 0.1.0__tar.gz → 0.3.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.

Files changed (33) hide show
  1. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/.gitignore +2 -0
  2. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/PKG-INFO +3 -1
  3. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/jolly_roger/_version.py +2 -2
  4. jolly_roger-0.3.0/jolly_roger/delays.py +107 -0
  5. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/jolly_roger/hour_angles.py +7 -8
  6. jolly_roger-0.3.0/jolly_roger/plots.py +178 -0
  7. jolly_roger-0.3.0/jolly_roger/tractor.py +809 -0
  8. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/jolly_roger/uvws.py +55 -5
  9. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/pyproject.toml +3 -0
  10. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/.copier-answers.yml +0 -0
  11. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/.git_archival.txt +0 -0
  12. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/.gitattributes +0 -0
  13. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/.github/CONTRIBUTING.md +0 -0
  14. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/.github/actions/setup-deps/action.yml +0 -0
  15. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/.github/dependabot.yml +0 -0
  16. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/.github/release.yml +0 -0
  17. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/.github/workflows/cd.yml +0 -0
  18. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/.github/workflows/ci.yml +0 -0
  19. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/.pre-commit-config.yaml +0 -0
  20. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/.readthedocs.yaml +0 -0
  21. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/LICENSE +0 -0
  22. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/README.md +0 -0
  23. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/docs/conf.py +0 -0
  24. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/docs/index.md +0 -0
  25. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/jolly_roger/__init__.py +0 -0
  26. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/jolly_roger/_version.pyi +0 -0
  27. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/jolly_roger/baselines.py +0 -0
  28. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/jolly_roger/flagger.py +0 -0
  29. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/jolly_roger/logging.py +0 -0
  30. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/jolly_roger/py.typed +0 -0
  31. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/noxfile.py +0 -0
  32. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/tests/test_hour_angles.py +0 -0
  33. {jolly_roger-0.1.0 → jolly_roger-0.3.0}/tests/test_package.py +0 -0
@@ -157,3 +157,5 @@ Thumbs.db
157
157
  *~
158
158
  *.swp
159
159
  jolly_roger/_version.py
160
+ subtractor_uvw.ipynb
161
+ subtractor.ipynb
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jolly-roger
3
- Version: 0.1.0
3
+ Version: 0.3.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
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.1.0'
21
- __version_tuple__ = version_tuple = (0, 1, 0)
20
+ __version__ = version = '0.3.0'
21
+ __version_tuple__ = version_tuple = (0, 3, 0)
@@ -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: np.ndarray
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: np.ndarray
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 | Literal["sun"] | None = None,
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 | Literal["sun"] | None, optional): The position to be considered. Defaults to None.
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 sun")
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("Creatring elevation curve")
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,178 @@
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
+ cmap = plt.cm.viridis
89
+
90
+ fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(
91
+ 2, 2, figsize=(12, 10), sharex=True, sharey="row"
92
+ )
93
+ im = ax1.pcolormesh(
94
+ before_baseline_data.time,
95
+ before_baseline_data.freq_chan,
96
+ before_amp_stokesi.T,
97
+ norm=norm,
98
+ cmap=cmap,
99
+ )
100
+ ax1.set(
101
+ ylabel=f"Frequency / {before_baseline_data.freq_chan.unit:latex_inline}",
102
+ title="Before",
103
+ )
104
+ ax2.pcolormesh(
105
+ after_baseline_data.time,
106
+ after_baseline_data.freq_chan,
107
+ after_amp_stokesi.T,
108
+ norm=norm,
109
+ cmap=cmap,
110
+ )
111
+ ax2.set(
112
+ ylabel=f"Frequency / {after_baseline_data.freq_chan.unit:latex_inline}",
113
+ title="After",
114
+ )
115
+ for ax in (ax1, ax2):
116
+ fig.colorbar(im, ax=ax, label="Stokes I Amplitude / Jy")
117
+
118
+ # TODO: Move these delay calculations outside of the plotting function
119
+ # And here we calculate the delay information
120
+
121
+ before_delays_i = np.abs(
122
+ (before_delays.delay_time[:, :, 0] + before_delays.delay_time[:, :, -1]) / 2
123
+ )
124
+ after_delays_i = np.abs(
125
+ (after_delays.delay_time[:, :, 0] + after_delays.delay_time[:, :, -1]) / 2
126
+ )
127
+
128
+ delay_norm = ImageNormalize(
129
+ before_delays_i, interval=MinMaxInterval(), stretch=LogStretch()
130
+ )
131
+
132
+ im = ax3.pcolormesh(
133
+ before_baseline_data.time,
134
+ before_delays.delay,
135
+ before_delays_i.T,
136
+ norm=delay_norm,
137
+ cmap=cmap,
138
+ )
139
+ ax3.set(ylabel="Delay / s", title="Before")
140
+ ax4.pcolormesh(
141
+ after_baseline_data.time,
142
+ after_delays.delay,
143
+ after_delays_i.T,
144
+ norm=delay_norm,
145
+ cmap=cmap,
146
+ )
147
+ ax4.set(ylabel="Delay / s", title="After")
148
+ for ax in (ax3, ax4):
149
+ fig.colorbar(im, ax=ax, label="Stokes I Amplitude / Jy")
150
+
151
+ if w_delays is not None:
152
+ for ax, baseline_data in zip( # type:ignore[call-overload]
153
+ (ax3, ax4),
154
+ (before_baseline_data, after_baseline_data),
155
+ strict=True,
156
+ ):
157
+ ant_1, ant_2 = baseline_data.ant_1, baseline_data.ant_2
158
+ b_idx = w_delays.b_map[ant_1, ant_2]
159
+ ax.plot(
160
+ baseline_data.time,
161
+ w_delays.w_delays[b_idx],
162
+ color="tab:red",
163
+ linestyle="-",
164
+ label=f"Delay for {w_delays.object_name}",
165
+ )
166
+ ax.legend()
167
+
168
+ output_path = (
169
+ output_dir
170
+ / f"baseline_data_{before_baseline_data.ant_1}_{before_baseline_data.ant_2}{suffix}.png"
171
+ )
172
+ fig.suptitle(
173
+ f"Ant {after_baseline_data.ant_1} - Ant {after_baseline_data.ant_2}"
174
+ )
175
+ fig.tight_layout()
176
+ fig.savefig(output_path)
177
+
178
+ return output_path