tabsim 0.0.1__py3-none-any.whl

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.
tabsim/jax/utils.py ADDED
@@ -0,0 +1,90 @@
1
+ # Progress bar for JAX scan
2
+ # Credit to https://www.jeremiecoullon.com/2021/01/29/jax_progress_bar/
3
+
4
+ from jax import lax, jit
5
+ from jax.experimental import host_callback
6
+ from tqdm import tqdm
7
+ from collections.abc import Callable
8
+
9
+
10
+ def jit_with_doc(func: Callable) -> Callable:
11
+ jit_func = jit(func)
12
+ jit_func.__doc__ = func.__doc__
13
+ return jit_func
14
+
15
+ jit_with_doc.__doc__ = jit.__doc__
16
+
17
+ def progress_bar_scan(num_samples: int, message=None):
18
+ "Progress bar for a JAX scan"
19
+ if message is None:
20
+ message = f"Running for {num_samples:,} iterations"
21
+ tqdm_bars = {}
22
+ if num_samples > 20:
23
+ print_rate = int(num_samples / 20)
24
+ else:
25
+ print_rate = 1 # if you run the sampler for less than 20 iterations
26
+
27
+ remainder = num_samples % print_rate
28
+
29
+ def _define_tqdm(arg, transform):
30
+ tqdm_bars[0] = tqdm(range(num_samples))
31
+ tqdm_bars[0].set_description(message, refresh=False)
32
+
33
+ def _update_tqdm(arg, transform):
34
+ tqdm_bars[0].update(arg)
35
+
36
+ def _update_progress_bar(iter_num):
37
+ "Updates tqdm progress bar of a JAX scan or loop"
38
+ _ = lax.cond(
39
+ iter_num == 0,
40
+ lambda _: host_callback.id_tap(_define_tqdm, None, result=iter_num),
41
+ lambda _: iter_num,
42
+ operand=None,
43
+ )
44
+
45
+ _ = lax.cond(
46
+ # update tqdm every multiple of `print_rate` except at the end
47
+ (iter_num % print_rate == 0) & (iter_num != num_samples - remainder),
48
+ lambda _: host_callback.id_tap(_update_tqdm, print_rate, result=iter_num),
49
+ lambda _: iter_num,
50
+ operand=None,
51
+ )
52
+
53
+ _ = lax.cond(
54
+ # update tqdm by `remainder`
55
+ iter_num == num_samples - remainder,
56
+ lambda _: host_callback.id_tap(_update_tqdm, remainder, result=iter_num),
57
+ lambda _: iter_num,
58
+ operand=None,
59
+ )
60
+
61
+ def _close_tqdm(arg, transform):
62
+ tqdm_bars[0].close()
63
+
64
+ def close_tqdm(result, iter_num):
65
+ return lax.cond(
66
+ iter_num == num_samples - 1,
67
+ lambda _: host_callback.id_tap(_close_tqdm, None, result=result),
68
+ lambda _: result,
69
+ operand=None,
70
+ )
71
+
72
+ def _progress_bar_scan(func):
73
+ """Decorator that adds a progress bar to `body_fun` used in `lax.scan`.
74
+ Note that `body_fun` must either be looping over `np.arange(num_samples)`,
75
+ or be looping over a tuple who's first element is `np.arange(num_samples)`
76
+ This means that `iter_num` is the current iteration number
77
+ """
78
+
79
+ def wrapper_progress_bar(carry, x):
80
+ if type(x) is tuple:
81
+ iter_num, *_ = x
82
+ else:
83
+ iter_num = x
84
+ _update_progress_bar(iter_num)
85
+ result = func(carry, x)
86
+ return close_tqdm(result, iter_num)
87
+
88
+ return wrapper_progress_bar
89
+
90
+ return _progress_bar_scan
tabsim/plot.py ADDED
@@ -0,0 +1,323 @@
1
+ import matplotlib.pyplot as plt
2
+ import numpy as np
3
+ from numpy.typing import ArrayLike
4
+
5
+ import jax.numpy as jnp
6
+
7
+ from tabsim.dask.observation import Observation
8
+ from tabsim.jax.coordinates import alt_az_of_source
9
+ import os
10
+
11
+ plt.rcParams["font.size"] = 18
12
+
13
+
14
+ def time_units(times: ArrayLike) -> tuple:
15
+ """Scale the time axis to hours, minutes or seconds depending on the total range.
16
+
17
+ Parameters
18
+ ----------
19
+ times : ArrayLike
20
+ Times to consider.
21
+
22
+ Returns
23
+ -------
24
+ tuple
25
+ Rescaled times array and the scale unit as a string.
26
+ """
27
+
28
+ time_range = times[-1] - times[0]
29
+ times = times - times[0]
30
+ if time_range > 3600:
31
+ units = "hr"
32
+ times = times / 3600
33
+ elif time_range > 60:
34
+ units = "min"
35
+ times = times / 60
36
+ else:
37
+ units = "s"
38
+
39
+ return times, units
40
+
41
+
42
+ def plot_angular_seps(obs: Observation, save_path: str) -> None:
43
+ """Plot the angular separations between the RFI sources and pointing direction.
44
+
45
+ Parameters
46
+ ----------
47
+ obs : Observation
48
+ Observation object with RFI sources added
49
+ save_path : str
50
+ Path to where to save the plots.
51
+ """
52
+
53
+ times, scale = time_units(obs.times_fine)
54
+ n_time = len(times)
55
+ if n_time > 100:
56
+ idx = np.arange(0, n_time, n_time // 100)
57
+ else:
58
+ idx = np.arange(n_time)
59
+ plt.figure(figsize=(10, 7))
60
+ if obs.n_rfi_satellite > 0:
61
+ ang_seps = np.concatenate(obs.rfi_satellite_ang_sep, axis=0).mean(axis=-1).T
62
+ plt.plot(times[idx], ang_seps[idx], label="Satellite")
63
+ if obs.n_rfi_stationary > 0:
64
+ ang_seps = np.concatenate(obs.rfi_stationary_ang_sep, axis=0).mean(axis=-1).T
65
+ plt.plot(times[idx], ang_seps[idx], label="Stationary")
66
+ if obs.n_rfi_tle_satellite > 0:
67
+ ang_seps = np.concatenate(obs.rfi_tle_satellite_ang_sep, axis=0).mean(axis=-1)
68
+ for ang_sep, n_id in zip(np.atleast_2d(ang_seps), obs.norad_ids):
69
+ plt.plot(times[idx], ang_sep[idx], label=n_id.compute())
70
+ plt.xlabel(f"Time [{scale}]")
71
+ plt.ylabel("Angular Separation [deg]")
72
+ plt.legend()
73
+ plt.savefig(os.path.join(save_path, "AngularSeps.png"), format="png", dpi=200)
74
+
75
+
76
+ def plot_src_alt(obs: Observation, save_path: str) -> None:
77
+ """Plot the target source altitude over the period of the observation.
78
+
79
+ Parameters
80
+ ----------
81
+ obs : Observation
82
+ Observation object.
83
+ save_path : str
84
+ Path to where to save the plot.
85
+ """
86
+
87
+ times, scale = time_units(obs.times)
88
+ lsa = obs.lsa.compute()
89
+ alt = alt_az_of_source(
90
+ lsa[obs.t_idx], *[x.compute() for x in [obs.latitude, obs.ra, obs.dec]]
91
+ )[:, 0]
92
+ plt.figure(figsize=(10, 7))
93
+ plt.plot(times, alt, ".-")
94
+ plt.xlabel(f"Time [{scale}]")
95
+ plt.ylabel("Source Altitude [deg]")
96
+ plt.savefig(os.path.join(save_path, "SourceAltitude.png"), format="png", dpi=200)
97
+
98
+
99
+ def plot_uv(obs: Observation, save_path: str) -> None:
100
+ """Plot the uv coverage of the telescope for the given observation.
101
+
102
+ Parameters
103
+ ----------
104
+ obs : Observation
105
+ Observation object.
106
+ save_path : str
107
+ Path to where to save the plot.
108
+ """
109
+
110
+ plt.figure(figsize=(10, 10))
111
+ if obs.n_time_fine > 100:
112
+ time_step = int(obs.n_time_fine / 100)
113
+ else:
114
+ time_step = 1
115
+ u = obs.bl_uvw[::time_step, :, 0].compute().flatten()
116
+ v = obs.bl_uvw[::time_step, :, 1].compute().flatten()
117
+ max_U = np.max(np.sqrt(u**2 + v**2))
118
+ exp = float(np.floor(np.log10(max_U)))
119
+ mantissa = np.ceil(10 ** (np.log10(max_U) - exp))
120
+ lim = mantissa * 10**exp
121
+ plt.plot(u, v, "k.", ms=1, alpha=0.3)
122
+ plt.plot(-u, -v, "k.", ms=1, alpha=0.3)
123
+ plt.grid()
124
+ plt.xlim(-lim, lim)
125
+ plt.ylim(-lim, lim)
126
+ plt.xlabel("U [m]")
127
+ plt.ylabel("V [m]")
128
+ plt.savefig(os.path.join(save_path, "UV.png"), format="png", dpi=200)
129
+
130
+ plt.rcParams["font.size"] = 16
131
+
132
+
133
+ def plot_comparison(
134
+ ax, times, mean1, mean2, std1, std2, true1, true2, rmse, diff=False
135
+ ):
136
+ times, units = time_units(times)
137
+ for i, a in enumerate(ax):
138
+ a[0].plot(rmse[..., i], "o")
139
+ a[0].set_xlabel("Sample")
140
+
141
+ if diff:
142
+ a[1].plot(times, mean1[i] - true1[i].real, label="Estimate")
143
+ a[1].fill_between(times, -std1[i], std1[i], color="tab:orange", alpha=0.3)
144
+ a[1].fill_between(
145
+ times, -2 * std1[i], 2 * std1[i], color="tab:orange", alpha=0.3
146
+ )
147
+ a[2].plot(times, mean2[i] - true2[i], label="Estimate")
148
+ a[2].fill_between(times, -std2[i], std2[i], color="tab:orange", alpha=0.3)
149
+ a[2].fill_between(
150
+ times, -2 * std2[i], 2 * std2[i], color="tab:orange", alpha=0.3
151
+ )
152
+ else:
153
+ a[1].plot(times, true1[i], label="True")
154
+ a[1].plot(times, mean1[i], label="Estimate")
155
+ a[1].fill_between(
156
+ times,
157
+ mean1[i] - std1[i],
158
+ mean1[i] + std1[i],
159
+ color="tab:orange",
160
+ alpha=0.3,
161
+ )
162
+ a[1].fill_between(
163
+ times,
164
+ mean1[i] - 2 * std1[i],
165
+ mean1[i] + 2 * std1[i],
166
+ color="tab:orange",
167
+ alpha=0.3,
168
+ )
169
+ a[2].plot(times, true2[i], label="True")
170
+ a[2].plot(times, mean2[i], label="Estimate")
171
+ a[2].fill_between(
172
+ times,
173
+ mean2[i] - std2[i],
174
+ mean2[i] + std2[i],
175
+ color="tab:orange",
176
+ alpha=0.3,
177
+ )
178
+ a[2].fill_between(
179
+ times,
180
+ mean2[i] - 2 * std2[i],
181
+ mean2[i] + 2 * std2[i],
182
+ color="tab:orange",
183
+ alpha=0.3,
184
+ )
185
+
186
+ a[1].set_xlabel(f"Time [{units}]")
187
+ a[1].legend()
188
+ a[2].set_xlabel(f"Time [{units}]")
189
+ a[2].legend()
190
+
191
+
192
+ def plot_complex_real_imag(
193
+ times,
194
+ param,
195
+ true,
196
+ rmse,
197
+ name: str,
198
+ save_name: str = None,
199
+ diff: bool = False,
200
+ max_plots: int = 10,
201
+ save_dir: str = "plots/",
202
+ ):
203
+ n_params = min(param.shape[1], max_plots)
204
+ # idx = np.random.permutation(param.shape[1])
205
+ # print(param.shape, true.shape)
206
+ # param = param[:, idx]
207
+ # true = true[idx]
208
+ mean_r = param.real.mean(axis=0)
209
+ mean_i = param.imag.mean(axis=0)
210
+ std_r = param.real.std(axis=0)
211
+ std_i = param.imag.std(axis=0)
212
+
213
+ fig, ax = plt.subplots(n_params, 3, figsize=(18, 4.5 * n_params))
214
+
215
+ ax[0, 0].set_title("Root Mean Squared Error")
216
+ ax[0, 1].set_title(f"{name} Real")
217
+ ax[0, 2].set_title(f"{name} Imag")
218
+
219
+ plot_comparison(
220
+ ax, times, mean_r, mean_i, std_r, std_i, true.real, true.imag, rmse, diff=diff
221
+ )
222
+
223
+ if save_name is not None:
224
+ fig.savefig(
225
+ os.path.join(save_dir, f"{save_name}_real_imag.pdf"),
226
+ format="pdf",
227
+ bbox_inches="tight",
228
+ )
229
+ plt.close(fig)
230
+
231
+
232
+ def plot_complex_amp_phase(
233
+ times,
234
+ param,
235
+ true,
236
+ rmse,
237
+ name: str,
238
+ save_name: str = None,
239
+ diff: bool = False,
240
+ max_plots: int = 10,
241
+ save_dir: str = "plots/",
242
+ ):
243
+ n_params = min(param.shape[1], max_plots)
244
+ # idx = np.random.permutation(param.shape[1])
245
+ # print(param.shape, true.shape)
246
+ # param = param[:, idx]
247
+ # true = true[idx]
248
+ mean_amp = jnp.abs(param).mean(axis=0)
249
+ mean_phase = jnp.rad2deg(jnp.angle(param)).mean(axis=0)
250
+ std_amp = jnp.abs(param).std(axis=0)
251
+ std_phase = jnp.rad2deg(jnp.angle(param)).std(axis=0)
252
+
253
+ fig, ax = plt.subplots(n_params, 3, figsize=(18, 4.5 * n_params))
254
+
255
+ ax[0, 0].set_title("Root Mean Squared Error")
256
+ ax[0, 1].set_title(f"{name} Magnitude")
257
+ ax[0, 2].set_title(f"{name} Phase")
258
+
259
+ plot_comparison(
260
+ ax,
261
+ times,
262
+ mean_amp,
263
+ mean_phase,
264
+ std_amp,
265
+ std_phase,
266
+ jnp.abs(true),
267
+ jnp.rad2deg(jnp.angle(true)),
268
+ rmse,
269
+ diff=diff,
270
+ )
271
+
272
+ if save_name is not None:
273
+ fig.savefig(
274
+ os.path.join(save_dir, f"{save_name}_amp_phase.pdf"),
275
+ format="pdf",
276
+ bbox_inches="tight",
277
+ )
278
+ plt.close(fig)
279
+
280
+
281
+ def plot_predictions(
282
+ times,
283
+ pred,
284
+ args,
285
+ type: str = "",
286
+ model_name: str = "",
287
+ max_plots: int = 10,
288
+ save_dir: str = "plots/",
289
+ ):
290
+ plot_complex_real_imag(
291
+ times=times,
292
+ param=pred["ast_vis"],
293
+ true=args["vis_ast_true"],
294
+ rmse=pred["rmse_ast"],
295
+ name="Ast. Vis.",
296
+ save_name=f"{model_name}_{type}_ast_vis",
297
+ max_plots=max_plots,
298
+ save_dir=save_dir,
299
+ )
300
+
301
+ plot_complex_amp_phase(
302
+ times=times,
303
+ param=pred["rfi_vis"],
304
+ true=args["vis_rfi_true"],
305
+ rmse=pred["rmse_rfi"],
306
+ name="RFI Vis.",
307
+ save_name=f"{model_name}_{type}_rfi_vis",
308
+ diff=False, # True,
309
+ max_plots=max_plots,
310
+ save_dir=save_dir,
311
+ )
312
+
313
+ plot_complex_amp_phase(
314
+ times=times,
315
+ param=pred["gains"],
316
+ true=args["gains_true"],
317
+ rmse=pred["rmse_gains"],
318
+ name="Gains",
319
+ save_name=f"{model_name}_{type}_gains",
320
+ diff=False,
321
+ max_plots=max_plots,
322
+ save_dir=save_dir,
323
+ )
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,56 @@
1
+ from astropy.coordinates import SkyCoord
2
+ import astropy.units as u
3
+ from regions import CircleSkyRegion, RectangleSkyRegion
4
+
5
+ import xarray as xr
6
+ import numpy as np
7
+
8
+ import argparse
9
+
10
+ import os
11
+
12
+ import matplotlib.colors as mcolors
13
+
14
+
15
+ def main():
16
+
17
+ colors = list(mcolors.TABLEAU_COLORS.values())
18
+ # n_c = len(colors)
19
+
20
+ parser = argparse.ArgumentParser(
21
+ description="Extract astronomical positions to ds9 region file."
22
+ )
23
+ parser.add_argument(
24
+ "-z",
25
+ "--zarr_path",
26
+ required=True,
27
+ help="File path to the tabascal zarr simulation file.",
28
+ )
29
+
30
+ args = parser.parse_args()
31
+ zarr_path = args.zarr_path
32
+
33
+ if zarr_path[-1] == "/":
34
+ zarr_path = zarr_path[:-1]
35
+
36
+ region_path = os.path.join(
37
+ os.path.split(zarr_path)[0], "astronomical_positions.ds9"
38
+ )
39
+
40
+ xds = xr.open_zarr(zarr_path)
41
+
42
+ ra, dec = xds.ast_p_radec.data.compute().T
43
+ c = SkyCoord(ra, dec, unit="deg", frame="fk5")
44
+
45
+ max_U = np.max(np.linalg.norm(xds.bl_uvw.data[0], axis=-1))
46
+ syn_bw = (3e8 / (max_U * xds.freq.data.max())).compute()
47
+
48
+ with open(region_path, "w") as fp:
49
+ for i in range(len(ra)):
50
+ fp.write(
51
+ CircleSkyRegion(
52
+ c[i], radius=syn_bw * u.rad, visual={"color": colors[0]}
53
+ ).serialize(format="ds9")
54
+ )
55
+
56
+ print(f"Region file written to : {region_path}")
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env python
2
+
3
+ from daskms import xds_from_ms, xds_to_table
4
+
5
+ import xarray as xr
6
+ import dask.array as da
7
+ import dask
8
+ import os
9
+
10
+ import argparse
11
+ import subprocess
12
+ import shutil
13
+
14
+ # aoflagger -strategy ../yaml_configs/target/firstpass.rfis tab1_obs_16A_450T-0440-1338_256I_001F-1.227e+09-1.227e+09_100PAST_000GAST_000EAST_1SAT_0GRD_1.0e+00RFI/tab1_obs_16A_450T-0440-1338_256I_001F-1.227e+09-1.227e+09_100PAST_000GAST_000EAST_1SAT_0GRD_1.0e+00RFI.ms/
15
+
16
+
17
+ def write_perfect_flags(ms_path: str, n_sigma: float = 3.0):
18
+
19
+ xds = xds_from_ms(ms_path)[0]
20
+
21
+ if n_sigma > 0:
22
+ flags = da.abs(xds.CAL_DATA - xds.AST_MODEL_DATA) > (
23
+ n_sigma * xds.SIGMA * da.sqrt(2)
24
+ )
25
+ else:
26
+ flags = xr.zeros_like(xds.DATA).astype(bool)
27
+
28
+ xds = xds.assign(FLAG=flags)
29
+ cols = ["FLAG"]
30
+
31
+ dask.compute(xds_to_table([xds], ms_path, cols))
32
+
33
+ print()
34
+ print(f"Flag Threshold : {n_sigma: .1f} sigma")
35
+ print(f"Flag Rate : {100*flags.data.mean().compute(): .1f} %")
36
+
37
+ return flags
38
+
39
+
40
+ def write_to_aoflags(ms_path: str):
41
+
42
+ xds_ms = xds_from_ms(ms_path)[0]
43
+
44
+ xds_ms = xds_ms.assign({"AO_FLAGS": xds_ms["FLAG"]})
45
+ cols = ["AO_FLAGS"]
46
+
47
+ print("Writing AOFlagger flags to 'AO_FLAGS' column in MS file.")
48
+
49
+ dask.compute(xds_to_table([xds_ms], ms_path, cols))
50
+
51
+
52
+ def write_aoflags_to_flag(ms_path: str):
53
+
54
+ xds_ms = xds_from_ms(ms_path)[0]
55
+
56
+ xds_ms = xds_ms.assign(FLAG=xds_ms["AO_FLAGS"])
57
+ cols = ["FLAG"]
58
+
59
+ print("Writing AOFlagger flags to 'FLAG' column in MS file.")
60
+
61
+ dask.compute(xds_to_table([xds_ms], ms_path, cols))
62
+
63
+
64
+ def run_aoflagger(
65
+ ms_path: str,
66
+ data_column: str = "CAL_DATA",
67
+ strategy_paths: list = None,
68
+ sif_path: str = None,
69
+ bash_exec: str = "/bin/bash",
70
+ rerun_aoflagger: bool = False,
71
+ ):
72
+
73
+ reflagged = False
74
+
75
+ if ms_path[-1] == "/":
76
+ ms_path = ms_path[:-1]
77
+
78
+ if not rerun_aoflagger:
79
+ xds_ms = xds_from_ms(ms_path)
80
+ try:
81
+ aoflags = xds_ms["AO_FLAGS"]
82
+ write_aoflags_to_flag(ms_path)
83
+ print("Using previous AOFlagger run.")
84
+ except:
85
+ aoflags = None
86
+
87
+ if not aoflags or rerun_aoflagger:
88
+
89
+ data_dir, ms_file = os.path.split(os.path.abspath(ms_path))
90
+
91
+ docker_opts = "--rm -v /etc/group:/etc/group -v /etc/passwd:/etc/passwd -v /etc/shadow:/etc/shadow -v/etc/sudoers.d:/etc/sudoers.d -e HOME=${HOME} --user=`id -ur`"
92
+ container_cmd = f"docker run {docker_opts} -v {data_dir}:/data --workdir /data stimela/aoflagger:latest"
93
+
94
+ if sif_path is not None:
95
+ sif_path = os.path.abspath(sif_path)
96
+ container_cmd = (
97
+ f"singularity exec --bind {data_dir}:/data --pwd /data {sif_path}"
98
+ )
99
+
100
+ if strategy_paths is not None:
101
+ write_perfect_flags(ms_path, 0)
102
+ for strategy_path in strategy_paths:
103
+
104
+ strategy_path = os.path.abspath(strategy_path)
105
+ shutil.copy(strategy_path, data_dir)
106
+ strategy_file = os.path.split(strategy_path)[1]
107
+ strategy = f"-strategy /data/{strategy_file}"
108
+
109
+ aoflag_cmd = f"{container_cmd} aoflagger -column {data_column} {strategy} /data/{ms_file}"
110
+ subprocess.run(aoflag_cmd, shell=True, executable=bash_exec)
111
+
112
+ print()
113
+ print(
114
+ f"Strategies : {[os.path.split(strategy)[1] for strategy in strategy_paths]}"
115
+ )
116
+
117
+ else:
118
+ aoflag_cmd = f"{container_cmd} aoflagger /data/{ms_file}"
119
+ subprocess.run(aoflag_cmd, shell=True, executable=bash_exec)
120
+ print()
121
+
122
+ write_to_aoflags(ms_path)
123
+ reflagged = True
124
+
125
+ flags = xds_from_ms(ms_path)[0].FLAG.data
126
+ print(f"Flag Rate : {100*flags.mean().compute(): .1f} %")
127
+
128
+ return flags, reflagged
129
+
130
+
131
+ def main():
132
+
133
+ parser = argparse.ArgumentParser(
134
+ description="Flag CAL_DATA lying a certain threshold away from the AST_MODEL_DATA. Requires tabascal simulated MS files."
135
+ )
136
+ parser.add_argument(
137
+ "-m", "--ms_path", required=True, help="File path to the Measurement Set."
138
+ )
139
+ parser.add_argument(
140
+ "-s",
141
+ "--n_sigma",
142
+ default=3.0,
143
+ type=float,
144
+ help="Threshold in number of std of noise given by SIGMA column. 0 unflags everything. Default is 3.0.",
145
+ )
146
+ parser.add_argument(
147
+ "-d",
148
+ "--data_col",
149
+ default="DATA",
150
+ help="Data column to run AOFlagger on. Default is 'DATA'.",
151
+ )
152
+ parser.add_argument(
153
+ "-ao",
154
+ "--aoflagger",
155
+ default=False,
156
+ action=argparse.BooleanOptionalAction,
157
+ help="Whether to use AOFlagger to flag the data. Default is False.",
158
+ )
159
+ parser.add_argument(
160
+ "-rao",
161
+ "--rerun_aoflagger",
162
+ default=False,
163
+ action=argparse.BooleanOptionalAction,
164
+ help="Whether to rerun AOFlagger to flag the data. Default is False.",
165
+ )
166
+ parser.add_argument(
167
+ "-st",
168
+ "--strategy_paths",
169
+ default=None,
170
+ help="list of paths to AOFlagger strategies.",
171
+ )
172
+ parser.add_argument(
173
+ "-sp",
174
+ "--sif_path",
175
+ default=None,
176
+ help="Paths to AOFlagger singularity image.",
177
+ )
178
+ args = parser.parse_args()
179
+ ms_path = args.ms_path
180
+ n_sigma = args.n_sigma
181
+ strategy_paths = args.strategy_paths
182
+ data_col = args.data_col
183
+
184
+ if args.aoflagger:
185
+ if strategy_paths is not None:
186
+ strategy_paths = strategy_paths.split(",")
187
+ flags = run_aoflagger(
188
+ ms_path, data_col, strategy_paths, args.sif_path, args.rerun_aoflagger
189
+ )
190
+ else:
191
+ flags = write_perfect_flags(ms_path, n_sigma)
192
+
193
+
194
+ if __name__ == "__main__":
195
+ main()