jolly-roger 0.2.0__tar.gz → 0.4.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.2.0 → jolly_roger-0.4.0}/PKG-INFO +4 -1
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/README.md +3 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/jolly_roger/_version.py +2 -2
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/jolly_roger/plots.py +12 -5
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/jolly_roger/tractor.py +109 -33
- jolly_roger-0.4.0/jolly_roger/utils.py +28 -0
- jolly_roger-0.4.0/jolly_roger/wrap.py +51 -0
- jolly_roger-0.4.0/logo.png +0 -0
- jolly_roger-0.4.0/tests/test_wrap.py +64 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/.copier-answers.yml +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/.git_archival.txt +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/.gitattributes +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/.github/CONTRIBUTING.md +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/.github/actions/setup-deps/action.yml +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/.github/dependabot.yml +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/.github/release.yml +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/.github/workflows/cd.yml +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/.github/workflows/ci.yml +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/.gitignore +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/.pre-commit-config.yaml +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/.readthedocs.yaml +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/LICENSE +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/docs/conf.py +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/docs/index.md +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/jolly_roger/__init__.py +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/jolly_roger/_version.pyi +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/jolly_roger/baselines.py +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/jolly_roger/delays.py +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/jolly_roger/flagger.py +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/jolly_roger/hour_angles.py +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/jolly_roger/logging.py +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/jolly_roger/py.typed +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/jolly_roger/uvws.py +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/noxfile.py +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/pyproject.toml +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.0}/tests/test_hour_angles.py +0 -0
- {jolly_roger-0.2.0 → jolly_roger-0.4.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.4.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
|
|
@@ -60,6 +60,9 @@ Description-Content-Type: text/markdown
|
|
|
60
60
|
|
|
61
61
|
The pirate flagger!
|
|
62
62
|
|
|
63
|
+
<img src="logo.png" alt="The Jolly Roger Flag" style="width:400px;"/>
|
|
64
|
+
|
|
65
|
+
|
|
63
66
|
# Installation
|
|
64
67
|
|
|
65
68
|
`pip install jolly-roger`
|
|
@@ -85,15 +85,17 @@ def plot_baseline_comparison_data(
|
|
|
85
85
|
norm = ImageNormalize(
|
|
86
86
|
after_amp_stokesi, interval=ZScaleInterval(), stretch=SqrtStretch()
|
|
87
87
|
)
|
|
88
|
+
cmap = plt.cm.viridis
|
|
88
89
|
|
|
89
90
|
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(
|
|
90
|
-
2, 2, figsize=(
|
|
91
|
+
2, 2, figsize=(12, 10), sharex=True, sharey="row"
|
|
91
92
|
)
|
|
92
93
|
im = ax1.pcolormesh(
|
|
93
94
|
before_baseline_data.time,
|
|
94
95
|
before_baseline_data.freq_chan,
|
|
95
96
|
before_amp_stokesi.T,
|
|
96
97
|
norm=norm,
|
|
98
|
+
cmap=cmap,
|
|
97
99
|
)
|
|
98
100
|
ax1.set(
|
|
99
101
|
ylabel=f"Frequency / {before_baseline_data.freq_chan.unit:latex_inline}",
|
|
@@ -104,12 +106,14 @@ def plot_baseline_comparison_data(
|
|
|
104
106
|
after_baseline_data.freq_chan,
|
|
105
107
|
after_amp_stokesi.T,
|
|
106
108
|
norm=norm,
|
|
109
|
+
cmap=cmap,
|
|
107
110
|
)
|
|
108
111
|
ax2.set(
|
|
109
112
|
ylabel=f"Frequency / {after_baseline_data.freq_chan.unit:latex_inline}",
|
|
110
113
|
title="After",
|
|
111
114
|
)
|
|
112
|
-
|
|
115
|
+
for ax in (ax1, ax2):
|
|
116
|
+
fig.colorbar(im, ax=ax, label="Stokes I Amplitude / Jy")
|
|
113
117
|
|
|
114
118
|
# TODO: Move these delay calculations outside of the plotting function
|
|
115
119
|
# And here we calculate the delay information
|
|
@@ -130,6 +134,7 @@ def plot_baseline_comparison_data(
|
|
|
130
134
|
before_delays.delay,
|
|
131
135
|
before_delays_i.T,
|
|
132
136
|
norm=delay_norm,
|
|
137
|
+
cmap=cmap,
|
|
133
138
|
)
|
|
134
139
|
ax3.set(ylabel="Delay / s", title="Before")
|
|
135
140
|
ax4.pcolormesh(
|
|
@@ -137,9 +142,11 @@ def plot_baseline_comparison_data(
|
|
|
137
142
|
after_delays.delay,
|
|
138
143
|
after_delays_i.T,
|
|
139
144
|
norm=delay_norm,
|
|
145
|
+
cmap=cmap,
|
|
140
146
|
)
|
|
141
147
|
ax4.set(ylabel="Delay / s", title="After")
|
|
142
|
-
|
|
148
|
+
for ax in (ax3, ax4):
|
|
149
|
+
fig.colorbar(im, ax=ax, label="Stokes I Amplitude / Jy")
|
|
143
150
|
|
|
144
151
|
if w_delays is not None:
|
|
145
152
|
for ax, baseline_data in zip( # type:ignore[call-overload]
|
|
@@ -152,8 +159,8 @@ def plot_baseline_comparison_data(
|
|
|
152
159
|
ax.plot(
|
|
153
160
|
baseline_data.time,
|
|
154
161
|
w_delays.w_delays[b_idx],
|
|
155
|
-
color="
|
|
156
|
-
linestyle="
|
|
162
|
+
color="tab:red",
|
|
163
|
+
linestyle="-",
|
|
157
164
|
label=f"Delay for {w_delays.object_name}",
|
|
158
165
|
)
|
|
159
166
|
ax.legend()
|
|
@@ -19,7 +19,9 @@ from tqdm.auto import tqdm
|
|
|
19
19
|
from jolly_roger.delays import data_to_delay_time, delay_time_to_data
|
|
20
20
|
from jolly_roger.logging import logger
|
|
21
21
|
from jolly_roger.plots import plot_baseline_comparison_data
|
|
22
|
+
from jolly_roger.utils import log_dataclass_attributes, log_jolly_roger_version
|
|
22
23
|
from jolly_roger.uvws import WDelays, get_object_delay_for_ms
|
|
24
|
+
from jolly_roger.wrap import calculate_nyquist_zone, symmetric_domain_wrap
|
|
23
25
|
|
|
24
26
|
|
|
25
27
|
@dataclass(frozen=True)
|
|
@@ -73,9 +75,12 @@ def tukey_taper(
|
|
|
73
75
|
|
|
74
76
|
if tukey_x_offset is not None:
|
|
75
77
|
x_freq = x_freq[:, None] - tukey_x_offset[None, :]
|
|
78
|
+
from jolly_roger.wrap import symmetric_domain_wrap
|
|
79
|
+
|
|
80
|
+
x_freq = symmetric_domain_wrap(values=x_freq, upper_limit=np.pi)
|
|
76
81
|
|
|
77
82
|
taper = np.ones_like(x_freq)
|
|
78
|
-
logger.debug(f"{x_freq.shape=} {type(x_freq)=}")
|
|
83
|
+
# logger.debug(f"{x_freq.shape=} {type(x_freq)=}")
|
|
79
84
|
# Fully zero region
|
|
80
85
|
taper[np.abs(x_freq) > outer_width] = 0
|
|
81
86
|
|
|
@@ -202,17 +207,16 @@ def _get_data_chunk_from_main_table(
|
|
|
202
207
|
logger.debug(f"Length of open table: {table_length} rows")
|
|
203
208
|
|
|
204
209
|
lower_row = 0
|
|
205
|
-
upper_row = chunk_size
|
|
206
210
|
|
|
207
211
|
while lower_row < table_length:
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
ant_1 =
|
|
215
|
-
ant_2 =
|
|
212
|
+
data = ms_table.getcol(data_column, startrow=lower_row, nrow=chunk_size)
|
|
213
|
+
flags = ms_table.getcol("FLAG", startrow=lower_row, nrow=chunk_size)
|
|
214
|
+
uvws = ms_table.getcol("UVW", startrow=lower_row, nrow=chunk_size)
|
|
215
|
+
time_centroid = ms_table.getcol(
|
|
216
|
+
"TIME_CENTROID", startrow=lower_row, nrow=chunk_size
|
|
217
|
+
)
|
|
218
|
+
ant_1 = ms_table.getcol("ANTENNA1", startrow=lower_row, nrow=chunk_size)
|
|
219
|
+
ant_2 = ms_table.getcol("ANTENNA2", startrow=lower_row, nrow=chunk_size)
|
|
216
220
|
|
|
217
221
|
yield DataChunkArray(
|
|
218
222
|
data=data,
|
|
@@ -226,7 +230,6 @@ def _get_data_chunk_from_main_table(
|
|
|
226
230
|
)
|
|
227
231
|
|
|
228
232
|
lower_row += chunk_size
|
|
229
|
-
upper_row += chunk_size
|
|
230
233
|
|
|
231
234
|
|
|
232
235
|
def get_data_chunks(
|
|
@@ -457,6 +460,7 @@ def make_plot_results(
|
|
|
457
460
|
before_delays = data_to_delay_time(data=before_baseline_data)
|
|
458
461
|
after_delays = data_to_delay_time(data=after_baseline_data)
|
|
459
462
|
|
|
463
|
+
logger.info("Creating figure")
|
|
460
464
|
# TODO: the baseline data and delay times could be put into a single
|
|
461
465
|
# structure to pass around easier.
|
|
462
466
|
plot_path = plot_baseline_comparison_data(
|
|
@@ -525,23 +529,32 @@ def _tukey_tractor(
|
|
|
525
529
|
"""
|
|
526
530
|
|
|
527
531
|
delay_time = data_to_delay_time(data=data_chunk)
|
|
532
|
+
flags_to_return: None | NDArray[np.bool] = None
|
|
528
533
|
|
|
529
|
-
#
|
|
534
|
+
# Set up the offsets. By default we will be tapering around the field,
|
|
535
|
+
# but should w_delays be specified these will be modified to direct
|
|
536
|
+
# towards the nominated object in the if below
|
|
530
537
|
tukey_x_offset: u.Quantity = np.zeros_like(delay_time.delay)
|
|
531
538
|
|
|
532
539
|
if w_delays is not None:
|
|
533
540
|
baseline_idx, time_idx = _get_baseline_time_indicies(
|
|
534
541
|
w_delays=w_delays, data_chunk=data_chunk
|
|
535
542
|
)
|
|
536
|
-
|
|
537
|
-
|
|
543
|
+
original_tukey_x_offset = w_delays.w_delays[baseline_idx, time_idx]
|
|
544
|
+
|
|
545
|
+
# Make a copy for later use post wrapping
|
|
546
|
+
tukey_x_offset = original_tukey_x_offset.copy()
|
|
547
|
+
|
|
548
|
+
tukey_x_offset = symmetric_domain_wrap(
|
|
549
|
+
values=tukey_x_offset.value, upper_limit=np.max(delay_time.delay).value
|
|
550
|
+
)
|
|
538
551
|
|
|
539
552
|
# need to scale the x offsert to the -pi to pi
|
|
540
553
|
# The delay should be symmetric
|
|
541
554
|
tukey_x_offset = (
|
|
542
555
|
tukey_x_offset / (np.max(delay_time.delay) / np.pi).decompose()
|
|
543
556
|
).value
|
|
544
|
-
# logger.info(f"{tukey_x_offset=}")
|
|
557
|
+
# # logger.info(f"{tukey_x_offset=}")
|
|
545
558
|
|
|
546
559
|
taper = tukey_taper(
|
|
547
560
|
x=delay_time.delay,
|
|
@@ -549,28 +562,70 @@ def _tukey_tractor(
|
|
|
549
562
|
tukey_width=tukey_tractor_options.tukey_width,
|
|
550
563
|
tukey_x_offset=tukey_x_offset,
|
|
551
564
|
)
|
|
552
|
-
|
|
565
|
+
|
|
566
|
+
if w_delays is None:
|
|
567
|
+
# This is the easy case
|
|
568
|
+
taper = taper[None, :, None]
|
|
569
|
+
|
|
570
|
+
else:
|
|
571
|
+
# TODO: This pirate reckons that merging the masks together
|
|
572
|
+
# into a single mask throughout may make things easier to
|
|
573
|
+
# manage and visualise.
|
|
574
|
+
|
|
553
575
|
# The use of the `tukey_x_offset` changes the
|
|
554
576
|
# shape of the output array. The internals of that
|
|
555
577
|
# function returns a different shape via the broadcasting
|
|
556
578
|
taper = np.swapaxes(taper[:, :, None], 0, 1)
|
|
557
579
|
|
|
558
|
-
# Since we want to dampen the target object we invert the taper
|
|
580
|
+
# Since we want to dampen the target object we invert the taper.
|
|
581
|
+
# By default the taper dampers outside the inner region.
|
|
559
582
|
taper = 1.0 - taper
|
|
560
583
|
|
|
584
|
+
# apply the flags to ignore the tapering if the object is larger
|
|
585
|
+
# than one wrap away
|
|
586
|
+
# Calculate the offset account of nyquist sampling
|
|
587
|
+
no_wraps_for_offset = calculate_nyquist_zone(
|
|
588
|
+
values=original_tukey_x_offset.value,
|
|
589
|
+
upper_limit=np.max(delay_time.delay).value,
|
|
590
|
+
)
|
|
591
|
+
ignore_wrapping_for = (
|
|
592
|
+
no_wraps_for_offset > tukey_tractor_options.ignore_nyquist_zone
|
|
593
|
+
)
|
|
594
|
+
taper[ignore_wrapping_for, :, :] = 1.0
|
|
595
|
+
|
|
561
596
|
# Delay with the elevation of the target object
|
|
562
|
-
elevation_mask = w_delays.elevation <
|
|
597
|
+
elevation_mask = w_delays.elevation < tukey_tractor_options.elevation_cut
|
|
563
598
|
taper[elevation_mask[time_idx], :, :] = 1.0
|
|
564
599
|
|
|
565
|
-
#
|
|
566
|
-
|
|
567
|
-
#
|
|
568
|
-
#
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
600
|
+
# Compute flags to ignore the objects delay crossing 0, Do
|
|
601
|
+
# This by computing the taper towards the field and
|
|
602
|
+
# see if there are any components of the two sets of tapers
|
|
603
|
+
# that are not 1 (where 1 is 'no change').
|
|
604
|
+
field_taper = tukey_taper(
|
|
605
|
+
x=delay_time.delay,
|
|
606
|
+
outer_width=tukey_tractor_options.outer_width / 4,
|
|
607
|
+
tukey_width=tukey_tractor_options.tukey_width,
|
|
608
|
+
tukey_x_offset=None,
|
|
609
|
+
)
|
|
610
|
+
# We need to account for no broadcasting when offset is None
|
|
611
|
+
# as the returned shape is different
|
|
612
|
+
field_taper = field_taper[None, :, None]
|
|
613
|
+
field_taper = 1.0 - field_taper
|
|
614
|
+
intersecting_taper = np.any(
|
|
615
|
+
np.reshape((taper != 1) & (field_taper != 1), (taper.shape[0], -1)), axis=1
|
|
616
|
+
)
|
|
617
|
+
# # Should the data need to be modified in conjunction with the flags
|
|
618
|
+
# taper[
|
|
619
|
+
# intersecting_taper &
|
|
620
|
+
# ~elevation_mask[time_idx] &
|
|
621
|
+
# ~ignore_wrapping_for
|
|
622
|
+
# ] = 0.0
|
|
623
|
+
# Update flags
|
|
624
|
+
flags_to_return = np.zeros_like(data_chunk.masked_data.mask)
|
|
625
|
+
flags_to_return[intersecting_taper] = True
|
|
626
|
+
flags_to_return = (
|
|
627
|
+
~np.isfinite(data_chunk.masked_data.filled(np.nan)) | flags_to_return
|
|
628
|
+
)
|
|
574
629
|
|
|
575
630
|
# Delay-time is a 3D array: (time, delay, pol)
|
|
576
631
|
# Taper is 1D: (delay,)
|
|
@@ -588,7 +643,7 @@ def _tukey_tractor(
|
|
|
588
643
|
)
|
|
589
644
|
logger.debug(f"{tapered_data.masked_data.shape=} {tapered_data.masked_data.dtype}")
|
|
590
645
|
|
|
591
|
-
return tapered_data
|
|
646
|
+
return tapered_data, flags_to_return
|
|
592
647
|
|
|
593
648
|
|
|
594
649
|
@dataclass
|
|
@@ -610,7 +665,7 @@ class TukeyTractorOptions:
|
|
|
610
665
|
dry_run: bool = False
|
|
611
666
|
"""Indicates whether the data will be written back to the measurement set"""
|
|
612
667
|
make_plots: bool = False
|
|
613
|
-
"""Create a small set of diagnostic plots"""
|
|
668
|
+
"""Create a small set of diagnostic plots. This can be slow."""
|
|
614
669
|
overwrite: bool = False
|
|
615
670
|
"""If the output column exists it will be overwritten"""
|
|
616
671
|
chunk_size: int = 1000
|
|
@@ -619,6 +674,10 @@ class TukeyTractorOptions:
|
|
|
619
674
|
"""apply the taper using the delay towards the target object."""
|
|
620
675
|
target_object: str = "Sun"
|
|
621
676
|
"""The target object to apply the delay towards."""
|
|
677
|
+
elevation_cut: u.Quantity = -1 * u.deg
|
|
678
|
+
"""The elevation cut-off for the target object. Defaults to 0 degrees."""
|
|
679
|
+
ignore_nyquist_zone: int = 2
|
|
680
|
+
"""Do not apply the tukey taper if object is beyond this Nyquist zone"""
|
|
622
681
|
|
|
623
682
|
|
|
624
683
|
def tukey_tractor(
|
|
@@ -634,8 +693,10 @@ def tukey_tractor(
|
|
|
634
693
|
Args:
|
|
635
694
|
tukey_tractor_options (TukeyTractorOptions): The settings to use during the taper, and measurement set to apply them to.
|
|
636
695
|
"""
|
|
637
|
-
|
|
638
|
-
|
|
696
|
+
log_jolly_roger_version()
|
|
697
|
+
log_dataclass_attributes(
|
|
698
|
+
to_log=tukey_tractor_options, class_name="TukeyTaperOptions"
|
|
699
|
+
)
|
|
639
700
|
|
|
640
701
|
# acquire all the tables necessary to get unit information and data from
|
|
641
702
|
open_ms_tables = get_open_ms_tables(
|
|
@@ -670,7 +731,7 @@ def tukey_tractor(
|
|
|
670
731
|
chunk_size=tukey_tractor_options.chunk_size,
|
|
671
732
|
data_column=tukey_tractor_options.data_column,
|
|
672
733
|
):
|
|
673
|
-
taper_data_chunk = _tukey_tractor(
|
|
734
|
+
taper_data_chunk, flags_to_apply = _tukey_tractor(
|
|
674
735
|
data_chunk=data_chunk,
|
|
675
736
|
tukey_tractor_options=tukey_tractor_options,
|
|
676
737
|
w_delays=w_delays,
|
|
@@ -685,6 +746,13 @@ def tukey_tractor(
|
|
|
685
746
|
startrow=taper_data_chunk.row_start,
|
|
686
747
|
nrow=taper_data_chunk.chunk_size,
|
|
687
748
|
)
|
|
749
|
+
if flags_to_apply is not None:
|
|
750
|
+
open_ms_tables.main_table.putcol(
|
|
751
|
+
columnname="FLAG",
|
|
752
|
+
value=flags_to_apply,
|
|
753
|
+
startrow=taper_data_chunk.row_start,
|
|
754
|
+
nrow=taper_data_chunk.chunk_size,
|
|
755
|
+
)
|
|
688
756
|
|
|
689
757
|
if tukey_tractor_options.make_plots:
|
|
690
758
|
plot_paths = make_plot_results(
|
|
@@ -751,7 +819,7 @@ def get_parser() -> ArgumentParser:
|
|
|
751
819
|
tukey_parser.add_argument(
|
|
752
820
|
"--make-plots",
|
|
753
821
|
action="store_true",
|
|
754
|
-
help="If set, the Tukey tractor will make plots of the results",
|
|
822
|
+
help="If set, the Tukey tractor will make plots of the results. This can be slow.",
|
|
755
823
|
)
|
|
756
824
|
tukey_parser.add_argument(
|
|
757
825
|
"--overwrite",
|
|
@@ -775,6 +843,12 @@ def get_parser() -> ArgumentParser:
|
|
|
775
843
|
action="store_true",
|
|
776
844
|
help="Whether the tukey taper is applied towards the target object (e.g. the Sun). If not set, the taper is applied towards large delays.",
|
|
777
845
|
)
|
|
846
|
+
tukey_parser.add_argument(
|
|
847
|
+
"--ignore-nyquist-zone",
|
|
848
|
+
type=int,
|
|
849
|
+
default=2,
|
|
850
|
+
help="Do not apply the taper if the objects delays beyond this Nyquist zone",
|
|
851
|
+
)
|
|
778
852
|
|
|
779
853
|
return parser
|
|
780
854
|
|
|
@@ -798,7 +872,9 @@ def cli() -> None:
|
|
|
798
872
|
chunk_size=args.chunk_size,
|
|
799
873
|
target_object=args.target_object,
|
|
800
874
|
apply_towards_object=args.apply_towards_object,
|
|
875
|
+
ignore_nyquist_zone=args.ignore_nyquist_zone,
|
|
801
876
|
)
|
|
877
|
+
|
|
802
878
|
tukey_tractor(tukey_tractor_options=tukey_tractor_options)
|
|
803
879
|
else:
|
|
804
880
|
parser.print_help()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Small helper utility functions"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import is_dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from jolly_roger import __version__
|
|
9
|
+
from jolly_roger.logging import logger
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def log_jolly_roger_version() -> None:
|
|
13
|
+
"""Write out a jolly roger header to output"""
|
|
14
|
+
logger.info("Jolly-Roger: Flagging and Tapering")
|
|
15
|
+
logger.info(f"Version: {__version__}")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def log_dataclass_attributes(to_log: Any, class_name: str | None = None) -> None:
|
|
19
|
+
"""Log the attributes and values from an Dataclass"""
|
|
20
|
+
if not is_dataclass(to_log):
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
if class_name:
|
|
24
|
+
logger.info(f"Settings for {class_name}")
|
|
25
|
+
|
|
26
|
+
for attribute in to_log.__annotations__:
|
|
27
|
+
value = to_log.__dict__[attribute]
|
|
28
|
+
logger.info(f"{attribute:<30} = {value}")
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Helper utilities to deal with cyclic boundaries
|
|
2
|
+
on data"""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from numpy.typing import NDArray
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def symmetric_domain_wrap(
|
|
11
|
+
values: NDArray[np.floating], upper_limit: float
|
|
12
|
+
) -> NDArray[np.floating]:
|
|
13
|
+
"""Place a set of values into a cyclic domain that is
|
|
14
|
+
symmetric around zero.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
values (NDArray[np.floating]): Values that need to be mapped into the cyclic domain
|
|
18
|
+
upper_limit (float): The upper bound of the symmetric cyclic domain
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
NDArray[np.floating]: Values that have been mapped to the -upper_limit to upper_limit domain
|
|
22
|
+
"""
|
|
23
|
+
# Calculate an appropriate domain mapping
|
|
24
|
+
# The natural domain is going to be mapped
|
|
25
|
+
# to -pi to pi.
|
|
26
|
+
domain_mapping = np.pi / upper_limit
|
|
27
|
+
|
|
28
|
+
real = np.cos(values * domain_mapping)
|
|
29
|
+
imag = np.sin(values * domain_mapping)
|
|
30
|
+
|
|
31
|
+
wrapped_values = real + 1j * imag
|
|
32
|
+
|
|
33
|
+
return np.angle(wrapped_values) / domain_mapping
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def calculate_nyquist_zone(
|
|
37
|
+
values: NDArray[np.floating], upper_limit: float
|
|
38
|
+
) -> NDArray[np.int_]:
|
|
39
|
+
"""Return the nyquist zone a value is in for a symmetric
|
|
40
|
+
set of bounds around zero
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
values (NDArray[np.floating]): The values to calculate the zone for
|
|
44
|
+
upper_limit (float): The upper bound to the symmetric domain around zero
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
NDArray[np.int_]: The zones values correspond to
|
|
48
|
+
"""
|
|
49
|
+
return np.array(
|
|
50
|
+
np.floor((upper_limit + np.abs(values)) / (2.0 * upper_limit)) + 1, dtype=int
|
|
51
|
+
)
|
|
Binary file
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Tests to ensure correctness of the
|
|
2
|
+
wrapping utilities"""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from jolly_roger.wrap import calculate_nyquist_zone, symmetric_domain_wrap
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_symmetric_domain_wrap_with_2d_arrays() -> None:
|
|
12
|
+
"""Ensure mapping values to a periodic domain works for arrays"""
|
|
13
|
+
|
|
14
|
+
values = np.array([np.linspace(10, 40, 10), np.linspace(10, 40, 10)])
|
|
15
|
+
assert values.shape == (2, 10)
|
|
16
|
+
|
|
17
|
+
wrapped_values = symmetric_domain_wrap(values=values, upper_limit=60)
|
|
18
|
+
assert wrapped_values.shape == values.shape
|
|
19
|
+
assert np.all(np.isclose(wrapped_values, values))
|
|
20
|
+
|
|
21
|
+
# The addition of 120 is the size of the domain here
|
|
22
|
+
# and they should be wrapped back to the main map
|
|
23
|
+
larger_values = np.array(
|
|
24
|
+
[np.linspace(10, 40, 10), np.linspace(10 + 120, 40 + 120, 10)]
|
|
25
|
+
)
|
|
26
|
+
assert values.shape == (2, 10)
|
|
27
|
+
|
|
28
|
+
wrapped_values = symmetric_domain_wrap(values=larger_values, upper_limit=60)
|
|
29
|
+
assert wrapped_values.shape == values.shape
|
|
30
|
+
assert np.all(np.isclose(wrapped_values, values))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_symmetric_domain_wrap() -> None:
|
|
34
|
+
"""Ensure mapping values to a periodic domain works"""
|
|
35
|
+
|
|
36
|
+
values = np.linspace(10, 40, 10)
|
|
37
|
+
|
|
38
|
+
wrapped_values = symmetric_domain_wrap(values=values, upper_limit=60)
|
|
39
|
+
assert np.all(np.isclose(values, wrapped_values))
|
|
40
|
+
|
|
41
|
+
values = np.linspace(10, 40, 10)
|
|
42
|
+
|
|
43
|
+
# Domain size would be 120 values given the upper limit of 60
|
|
44
|
+
wrapped_values = symmetric_domain_wrap(values=values + 120, upper_limit=60)
|
|
45
|
+
assert np.all(np.isclose(values, wrapped_values))
|
|
46
|
+
|
|
47
|
+
values = np.linspace(10, 40, 10)
|
|
48
|
+
|
|
49
|
+
# Domain size would be 120 values given the upper limit of 60
|
|
50
|
+
wrapped_values = symmetric_domain_wrap(values=values + 2 * 120, upper_limit=60)
|
|
51
|
+
assert np.all(np.isclose(values, wrapped_values))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_calculate_nyquist_zone() -> None:
|
|
55
|
+
"""Match to the right zone"""
|
|
56
|
+
assert calculate_nyquist_zone(values=30, upper_limit=60) == 1
|
|
57
|
+
assert calculate_nyquist_zone(values=90, upper_limit=60) == 2
|
|
58
|
+
assert calculate_nyquist_zone(values=-30, upper_limit=60) == 1
|
|
59
|
+
assert calculate_nyquist_zone(values=-90, upper_limit=60) == 2
|
|
60
|
+
|
|
61
|
+
assert np.all(
|
|
62
|
+
calculate_nyquist_zone(values=np.array([-90, -30, 0, 30, 90]), upper_limit=60)
|
|
63
|
+
== np.array([2, 1, 1, 1, 2])
|
|
64
|
+
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|