cal-cabb 0.1.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.
- cal_cabb-0.1.0/PKG-INFO +11 -0
- cal_cabb-0.1.0/README.md +0 -0
- cal_cabb-0.1.0/cal_cabb/__init__.py +9 -0
- cal_cabb-0.1.0/cal_cabb/casa.py +16 -0
- cal_cabb-0.1.0/cal_cabb/cli/atca_cal.py +218 -0
- cal_cabb-0.1.0/cal_cabb/logger.py +207 -0
- cal_cabb-0.1.0/cal_cabb/miriad.py +516 -0
- cal_cabb-0.1.0/cal_cabb.egg-info/PKG-INFO +11 -0
- cal_cabb-0.1.0/cal_cabb.egg-info/SOURCES.txt +13 -0
- cal_cabb-0.1.0/cal_cabb.egg-info/dependency_links.txt +1 -0
- cal_cabb-0.1.0/cal_cabb.egg-info/entry_points.txt +2 -0
- cal_cabb-0.1.0/cal_cabb.egg-info/requires.txt +5 -0
- cal_cabb-0.1.0/cal_cabb.egg-info/top_level.txt +1 -0
- cal_cabb-0.1.0/pyproject.toml +28 -0
- cal_cabb-0.1.0/setup.cfg +4 -0
cal_cabb-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cal-cabb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: ATCA CABB continuum calibration pipeline.
|
|
5
|
+
Requires-Python: <3.12,>=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: astropy>=6.1.7
|
|
8
|
+
Requires-Dist: click>=8.1.8
|
|
9
|
+
Requires-Dist: colorlog>=6.9.0
|
|
10
|
+
Requires-Dist: pandas>=2.2.3
|
|
11
|
+
Requires-Dist: pre-commit>=4.1.0
|
cal_cabb-0.1.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import casatasks
|
|
2
|
+
|
|
3
|
+
from cal_cabb.logger import filter_stdout
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@filter_stdout(
|
|
7
|
+
"XYZHAND keyword not found in AN table.",
|
|
8
|
+
"No systemic velocity",
|
|
9
|
+
"No rest frequency",
|
|
10
|
+
)
|
|
11
|
+
def importuvfits(*args, **kwargs):
|
|
12
|
+
return casatasks.importuvfits(*args, **kwargs)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def listobs(*args, **kwargs):
|
|
16
|
+
return casatasks.listobs(*args, **kwargs)
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from cal_cabb.logger import setupLogger
|
|
8
|
+
from cal_cabb.miriad import BANDS, CABBContinuumPipeline, MiriadWrapper
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command(context_settings={"show_default": True})
|
|
14
|
+
@click.option("-B", "--band", type=str, default="L")
|
|
15
|
+
@click.option("-p", "--primary-cal", type=str, default="1934-638")
|
|
16
|
+
@click.option("-s", "--gain-cal", type=str, default=None)
|
|
17
|
+
@click.option("-t", "--target", type=str, default=None)
|
|
18
|
+
@click.option("-l", "--leakage-cal", type=str, default=None)
|
|
19
|
+
@click.option(
|
|
20
|
+
"-m",
|
|
21
|
+
"--mfinterval",
|
|
22
|
+
default="1.0",
|
|
23
|
+
type=str,
|
|
24
|
+
help="Time interval to solve for antenna gains in bandpass calibration.",
|
|
25
|
+
)
|
|
26
|
+
@click.option(
|
|
27
|
+
"-b",
|
|
28
|
+
"--bpinterval",
|
|
29
|
+
default="1.0",
|
|
30
|
+
type=str,
|
|
31
|
+
help="Time interval to solve for bandpass in bandpass calibration.",
|
|
32
|
+
)
|
|
33
|
+
@click.option(
|
|
34
|
+
"-g",
|
|
35
|
+
"--gpinterval",
|
|
36
|
+
default="0.1",
|
|
37
|
+
type=str,
|
|
38
|
+
help="Time interval to solve for antenna gains in gain calibration.",
|
|
39
|
+
)
|
|
40
|
+
@click.option(
|
|
41
|
+
"-f",
|
|
42
|
+
"--nfbin",
|
|
43
|
+
default="4",
|
|
44
|
+
type=str,
|
|
45
|
+
help="Number of frequency subbands in which to solve for gains/leakage.",
|
|
46
|
+
)
|
|
47
|
+
@click.option(
|
|
48
|
+
"-n",
|
|
49
|
+
"--num-flag-rounds",
|
|
50
|
+
default=1,
|
|
51
|
+
type=int,
|
|
52
|
+
help="Number of rounds in each autoflagging / calibration loop.",
|
|
53
|
+
)
|
|
54
|
+
@click.option(
|
|
55
|
+
"-r",
|
|
56
|
+
"--refant",
|
|
57
|
+
type=click.Choice(["1", "2", "3", "4", "5", "6"]),
|
|
58
|
+
default="3",
|
|
59
|
+
help="Reference antenna.",
|
|
60
|
+
)
|
|
61
|
+
@click.option(
|
|
62
|
+
"--int-freq",
|
|
63
|
+
type=str,
|
|
64
|
+
default=None,
|
|
65
|
+
help="Intermediate Frequency (IF) to select (only valid for L-band)",
|
|
66
|
+
)
|
|
67
|
+
@click.option(
|
|
68
|
+
"--shiftra",
|
|
69
|
+
type=str,
|
|
70
|
+
default="0",
|
|
71
|
+
help="Offset between pointing and phase centre right ascension in arcsec.",
|
|
72
|
+
)
|
|
73
|
+
@click.option(
|
|
74
|
+
"--shiftdec",
|
|
75
|
+
type=str,
|
|
76
|
+
default="0",
|
|
77
|
+
help="Offset between pointing and phase centre declination in arcsec.",
|
|
78
|
+
)
|
|
79
|
+
@click.option(
|
|
80
|
+
"-P",
|
|
81
|
+
"--strong-pol",
|
|
82
|
+
is_flag=True,
|
|
83
|
+
default=False,
|
|
84
|
+
help="Solve for absolute XY phase and leakage (requires good leakage calibrator)",
|
|
85
|
+
)
|
|
86
|
+
@click.option(
|
|
87
|
+
"-F",
|
|
88
|
+
"--noflag",
|
|
89
|
+
is_flag=True,
|
|
90
|
+
default=False,
|
|
91
|
+
help="Disable birdie and rfiflag options in atlod and avoid target flagging.",
|
|
92
|
+
)
|
|
93
|
+
@click.option(
|
|
94
|
+
"-I",
|
|
95
|
+
"--interactive",
|
|
96
|
+
is_flag=True,
|
|
97
|
+
default=False,
|
|
98
|
+
help="Run calibration pipeline interactively with manual flagging.",
|
|
99
|
+
)
|
|
100
|
+
@click.option(
|
|
101
|
+
"-o",
|
|
102
|
+
"--out-dir",
|
|
103
|
+
type=Path,
|
|
104
|
+
default=Path("."),
|
|
105
|
+
help="Path to store calibrated MeasurementSet.",
|
|
106
|
+
)
|
|
107
|
+
@click.option(
|
|
108
|
+
"-S",
|
|
109
|
+
"--skip-pipeline",
|
|
110
|
+
is_flag=True,
|
|
111
|
+
default=False,
|
|
112
|
+
help="Skip execution of flagging/calibration pipeline.",
|
|
113
|
+
)
|
|
114
|
+
@click.option(
|
|
115
|
+
"-L",
|
|
116
|
+
"--savelogs",
|
|
117
|
+
is_flag=True,
|
|
118
|
+
default=False,
|
|
119
|
+
help="Store processing logs.",
|
|
120
|
+
)
|
|
121
|
+
@click.option(
|
|
122
|
+
"-d",
|
|
123
|
+
"--diagnostics",
|
|
124
|
+
is_flag=True,
|
|
125
|
+
default=False,
|
|
126
|
+
help="Generate diagnostic plots.",
|
|
127
|
+
)
|
|
128
|
+
@click.option(
|
|
129
|
+
"-k",
|
|
130
|
+
"--keep-intermediate",
|
|
131
|
+
is_flag=True,
|
|
132
|
+
default=False,
|
|
133
|
+
help="Store intermediate files produced by miriad.",
|
|
134
|
+
)
|
|
135
|
+
@click.option("-v", "--verbose", is_flag=True, default=False)
|
|
136
|
+
@click.argument("data_dir")
|
|
137
|
+
@click.argument("project_code")
|
|
138
|
+
def main(
|
|
139
|
+
data_dir,
|
|
140
|
+
project_code,
|
|
141
|
+
band,
|
|
142
|
+
primary_cal,
|
|
143
|
+
gain_cal,
|
|
144
|
+
target,
|
|
145
|
+
leakage_cal,
|
|
146
|
+
strong_pol,
|
|
147
|
+
mfinterval,
|
|
148
|
+
bpinterval,
|
|
149
|
+
gpinterval,
|
|
150
|
+
nfbin,
|
|
151
|
+
num_flag_rounds,
|
|
152
|
+
refant,
|
|
153
|
+
int_freq,
|
|
154
|
+
shiftra,
|
|
155
|
+
shiftdec,
|
|
156
|
+
noflag,
|
|
157
|
+
interactive,
|
|
158
|
+
out_dir,
|
|
159
|
+
skip_pipeline,
|
|
160
|
+
savelogs,
|
|
161
|
+
diagnostics,
|
|
162
|
+
keep_intermediate,
|
|
163
|
+
verbose,
|
|
164
|
+
):
|
|
165
|
+
os.system(f"mkdir -p {out_dir}")
|
|
166
|
+
|
|
167
|
+
logfile = out_dir / "atca-cal.log" if savelogs else None
|
|
168
|
+
setupLogger(verbose=verbose, filename=logfile)
|
|
169
|
+
|
|
170
|
+
miriad = MiriadWrapper(
|
|
171
|
+
data_dir=Path(data_dir),
|
|
172
|
+
band=BANDS.get(band),
|
|
173
|
+
project_code=project_code,
|
|
174
|
+
out_dir=out_dir,
|
|
175
|
+
strong_pol=strong_pol,
|
|
176
|
+
mfinterval=mfinterval,
|
|
177
|
+
bpinterval=bpinterval,
|
|
178
|
+
gpinterval=gpinterval,
|
|
179
|
+
nfbin=nfbin,
|
|
180
|
+
refant=refant,
|
|
181
|
+
IF=int_freq,
|
|
182
|
+
noflag=noflag,
|
|
183
|
+
verbose=verbose,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
pipeline = CABBContinuumPipeline(
|
|
187
|
+
miriad=miriad,
|
|
188
|
+
shiftra=shiftra,
|
|
189
|
+
shiftdec=shiftdec,
|
|
190
|
+
num_flag_rounds=num_flag_rounds,
|
|
191
|
+
interactive=interactive,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
pipeline.miriad.set_targets(
|
|
196
|
+
primary_cal=primary_cal,
|
|
197
|
+
leakage_cal=leakage_cal,
|
|
198
|
+
gain_cal=gain_cal,
|
|
199
|
+
target=target,
|
|
200
|
+
)
|
|
201
|
+
except ValueError as e:
|
|
202
|
+
logger.error(e)
|
|
203
|
+
exit(1)
|
|
204
|
+
|
|
205
|
+
if not skip_pipeline:
|
|
206
|
+
pipeline.run()
|
|
207
|
+
|
|
208
|
+
if diagnostics:
|
|
209
|
+
pipeline.make_diagnostics()
|
|
210
|
+
|
|
211
|
+
if not keep_intermediate:
|
|
212
|
+
miriad.cleanup()
|
|
213
|
+
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if __name__ == "__main__":
|
|
218
|
+
main()
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import selectors
|
|
4
|
+
import threading
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from functools import wraps
|
|
7
|
+
from typing import Callable, Iterable, Optional
|
|
8
|
+
|
|
9
|
+
import colorlog
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def setupLogger(verbose: bool, filename: Optional[str] = None) -> None:
|
|
13
|
+
level = logging.DEBUG if verbose else logging.INFO
|
|
14
|
+
|
|
15
|
+
# Get root logger disable any existing handlers, and set level
|
|
16
|
+
root_logger = logging.getLogger()
|
|
17
|
+
root_logger.setLevel(level)
|
|
18
|
+
root_logger.handlers = []
|
|
19
|
+
|
|
20
|
+
# Turn off some bothersome verbose logging modules
|
|
21
|
+
logging.getLogger("matplotlib").setLevel(logging.WARNING)
|
|
22
|
+
logging.getLogger("PIL").setLevel(logging.WARNING)
|
|
23
|
+
logging.getLogger("urllib").setLevel(logging.WARNING)
|
|
24
|
+
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
|
25
|
+
logging.getLogger("h5py").setLevel(logging.INFO)
|
|
26
|
+
|
|
27
|
+
if filename:
|
|
28
|
+
formatter = logging.Formatter(
|
|
29
|
+
"%(levelname)-8s %(asctime)s - %(name)s - %(message)s",
|
|
30
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
31
|
+
)
|
|
32
|
+
file_handler = logging.FileHandler(filename)
|
|
33
|
+
file_handler.setFormatter(formatter)
|
|
34
|
+
file_handler.setLevel(logging.DEBUG)
|
|
35
|
+
|
|
36
|
+
root_logger.addHandler(file_handler)
|
|
37
|
+
|
|
38
|
+
colorformatter = colorlog.ColoredFormatter(
|
|
39
|
+
"%(log_color)s%(levelname)-8s%(reset)s %(asctime)s - %(name)s - %(message)s",
|
|
40
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
41
|
+
reset=True,
|
|
42
|
+
log_colors={
|
|
43
|
+
"DEBUG": "cyan",
|
|
44
|
+
"INFO": "green",
|
|
45
|
+
"WARNING": "yellow",
|
|
46
|
+
"ERROR": "red",
|
|
47
|
+
"CRITICAL": "red,bg_white",
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
stream_handler = colorlog.StreamHandler()
|
|
52
|
+
stream_handler.setFormatter(colorformatter)
|
|
53
|
+
stream_handler.setLevel(level)
|
|
54
|
+
|
|
55
|
+
root_logger.addHandler(stream_handler)
|
|
56
|
+
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def parse_stdout_stderr(process, logger, print_stdout: bool = False):
|
|
61
|
+
"""Parse STDOUT and STDERR from a subprocess and redirect to logger."""
|
|
62
|
+
|
|
63
|
+
sel = selectors.DefaultSelector()
|
|
64
|
+
sel.register(process.stdout, selectors.EVENT_READ)
|
|
65
|
+
sel.register(process.stderr, selectors.EVENT_READ)
|
|
66
|
+
|
|
67
|
+
# Filter uninteresting warnings and set to DEBUG level
|
|
68
|
+
debug_lines = [
|
|
69
|
+
"### Warning: Using post-Aug94 ATCA flux scale for 1934-638",
|
|
70
|
+
"### Warning: Correlations flagged or edge-rejected:",
|
|
71
|
+
"PGPLOT /png: writing new file as",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
lines_to_parse = True
|
|
75
|
+
while lines_to_parse:
|
|
76
|
+
for key, val in sel.select():
|
|
77
|
+
line = key.fileobj.readline()
|
|
78
|
+
if not line:
|
|
79
|
+
lines_to_parse = False
|
|
80
|
+
break
|
|
81
|
+
|
|
82
|
+
line = line.decode().rstrip()
|
|
83
|
+
debug_line = any(debug_str in line for debug_str in debug_lines)
|
|
84
|
+
|
|
85
|
+
if print_stdout:
|
|
86
|
+
print(line)
|
|
87
|
+
elif debug_line or key.fileobj is process.stdout:
|
|
88
|
+
logger.debug(line)
|
|
89
|
+
else:
|
|
90
|
+
logger.warning(line.replace("### Warning: ", ""))
|
|
91
|
+
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
logger = logging.getLogger(__name__)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def filter_pipe_output(
|
|
99
|
+
pipe_r: int,
|
|
100
|
+
substrings: Iterable[str],
|
|
101
|
+
original_stream_fd: int,
|
|
102
|
+
stream: str,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Read lines from a pipe file descriptor and filter to debug/warning."""
|
|
105
|
+
|
|
106
|
+
with os.fdopen(pipe_r) as pipe:
|
|
107
|
+
for line in pipe:
|
|
108
|
+
if any(substr in line for substr in substrings):
|
|
109
|
+
continue
|
|
110
|
+
else:
|
|
111
|
+
os.write(original_stream_fd, line.encode())
|
|
112
|
+
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@contextmanager
|
|
117
|
+
def redirect_c_output(
|
|
118
|
+
substrings: Iterable[str],
|
|
119
|
+
filter_stdout: bool = True,
|
|
120
|
+
filter_stderr: bool = True,
|
|
121
|
+
):
|
|
122
|
+
"""A context manager to redirect STDOUT / STDERR for both python and C level output."""
|
|
123
|
+
|
|
124
|
+
# Save original file descriptors
|
|
125
|
+
original_stdout_fd = os.dup(1) if filter_stdout else None
|
|
126
|
+
original_stderr_fd = os.dup(2) if filter_stderr else None
|
|
127
|
+
|
|
128
|
+
# Create pipes for capturing output
|
|
129
|
+
stdout_pipe_r, stdout_pipe_w = os.pipe() if filter_stdout else (None, None)
|
|
130
|
+
stderr_pipe_r, stderr_pipe_w = os.pipe() if filter_stderr else (None, None)
|
|
131
|
+
|
|
132
|
+
# Start threads to read and filter the output
|
|
133
|
+
stdout_thread = None
|
|
134
|
+
if filter_stdout:
|
|
135
|
+
stdout_thread = threading.Thread(
|
|
136
|
+
target=filter_pipe_output,
|
|
137
|
+
args=(stdout_pipe_r, substrings, original_stdout_fd, "STDOUT"),
|
|
138
|
+
daemon=True,
|
|
139
|
+
)
|
|
140
|
+
stdout_thread.start()
|
|
141
|
+
|
|
142
|
+
stderr_thread = None
|
|
143
|
+
if filter_stderr:
|
|
144
|
+
stderr_thread = threading.Thread(
|
|
145
|
+
target=filter_pipe_output,
|
|
146
|
+
args=(stderr_pipe_r, substrings, original_stderr_fd, "STDERR"),
|
|
147
|
+
daemon=True,
|
|
148
|
+
)
|
|
149
|
+
stderr_thread.start()
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
# Redirect STDOUT / STDERR to pipe for filtering
|
|
153
|
+
if filter_stdout:
|
|
154
|
+
os.dup2(stdout_pipe_w, 1)
|
|
155
|
+
if filter_stderr:
|
|
156
|
+
os.dup2(stderr_pipe_w, 2)
|
|
157
|
+
|
|
158
|
+
yield
|
|
159
|
+
|
|
160
|
+
finally:
|
|
161
|
+
# Restore original file descriptors
|
|
162
|
+
if filter_stdout:
|
|
163
|
+
os.dup2(original_stdout_fd, 1)
|
|
164
|
+
if filter_stderr:
|
|
165
|
+
os.dup2(original_stderr_fd, 2)
|
|
166
|
+
|
|
167
|
+
# Close the write ends of the pipes to signal EOF to the threads
|
|
168
|
+
if filter_stdout:
|
|
169
|
+
os.close(stdout_pipe_w)
|
|
170
|
+
if filter_stderr:
|
|
171
|
+
os.close(stderr_pipe_w)
|
|
172
|
+
|
|
173
|
+
# Wait for threads to finish
|
|
174
|
+
if stdout_thread:
|
|
175
|
+
stdout_thread.join()
|
|
176
|
+
if stderr_thread:
|
|
177
|
+
stderr_thread.join()
|
|
178
|
+
|
|
179
|
+
# Close the original file descriptors and pipes
|
|
180
|
+
if filter_stdout:
|
|
181
|
+
os.close(original_stdout_fd)
|
|
182
|
+
if filter_stderr:
|
|
183
|
+
os.close(original_stderr_fd)
|
|
184
|
+
|
|
185
|
+
return
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def filter_stdout(
|
|
189
|
+
*substrings: str,
|
|
190
|
+
filter_stdout: bool = True,
|
|
191
|
+
filter_stderr: bool = True,
|
|
192
|
+
):
|
|
193
|
+
"""A decorator to filter C-level STDOUT/STDERR from within CASA function calls."""
|
|
194
|
+
|
|
195
|
+
def decorator(func: Callable):
|
|
196
|
+
@wraps(func)
|
|
197
|
+
def wrapper(*args, **kwargs):
|
|
198
|
+
with redirect_c_output(
|
|
199
|
+
substrings,
|
|
200
|
+
filter_stdout=filter_stdout,
|
|
201
|
+
filter_stderr=filter_stderr,
|
|
202
|
+
):
|
|
203
|
+
return func(*args, **kwargs)
|
|
204
|
+
|
|
205
|
+
return wrapper
|
|
206
|
+
|
|
207
|
+
return decorator
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import astropy.units as u
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
import cal_cabb
|
|
13
|
+
from cal_cabb.casa import importuvfits, listobs
|
|
14
|
+
from cal_cabb.logger import parse_stdout_stderr
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
package_root = cal_cabb.__path__[0]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def prompt(msg, bypass=False, bypass_msg=None, default_response=True):
|
|
22
|
+
if bypass:
|
|
23
|
+
if bypass_msg is not None:
|
|
24
|
+
logger.warning(bypass_msg)
|
|
25
|
+
return default_response
|
|
26
|
+
|
|
27
|
+
msg = f"{msg} (y/n)\n"
|
|
28
|
+
|
|
29
|
+
resp = input(msg)
|
|
30
|
+
if resp not in ["y", "n"]:
|
|
31
|
+
resp = input(msg)
|
|
32
|
+
|
|
33
|
+
return True if resp == "y" else False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Target:
|
|
38
|
+
name: str
|
|
39
|
+
path: Path
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class Band:
|
|
44
|
+
freq: str
|
|
45
|
+
spec: str
|
|
46
|
+
IF: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
BANDS = {
|
|
50
|
+
"L": Band(freq="2100", spec="2.1", IF="1"),
|
|
51
|
+
"C": Band(freq="5500", spec="5.5", IF="1"),
|
|
52
|
+
"X": Band(freq="9000", spec="9.0", IF="2"),
|
|
53
|
+
"K": Band(freq="12000", spec="12.0", IF="1"),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class MiriadWrapper:
|
|
59
|
+
data_dir: Path
|
|
60
|
+
band: Band
|
|
61
|
+
project_code: str
|
|
62
|
+
mfinterval: float = 1.0
|
|
63
|
+
bpinterval: float = 1.0
|
|
64
|
+
gpinterval: float = 0.1
|
|
65
|
+
nfbin: int = 4
|
|
66
|
+
refant: int = 3
|
|
67
|
+
IF: str = None
|
|
68
|
+
noflag: bool = False
|
|
69
|
+
strong_pol: bool = False
|
|
70
|
+
out_dir: Path = Path(".")
|
|
71
|
+
verbose: bool = False
|
|
72
|
+
|
|
73
|
+
def __post_init__(self):
|
|
74
|
+
# Handle selection of IF in L-band
|
|
75
|
+
if self.IF is not None and self.band.freq == "2100":
|
|
76
|
+
self.band.IF = self.IF
|
|
77
|
+
|
|
78
|
+
self.IF = self.band.IF
|
|
79
|
+
|
|
80
|
+
self.uvfile = self.out_dir / "miriad" / f"{self.project_code}.uv"
|
|
81
|
+
self.opts = {
|
|
82
|
+
"mfinterval": self.mfinterval,
|
|
83
|
+
"bpinterval": self.bpinterval,
|
|
84
|
+
"gpinterval": self.gpinterval,
|
|
85
|
+
"nfbin": self.nfbin,
|
|
86
|
+
"ifsel": self.IF,
|
|
87
|
+
"spec": self.band.spec,
|
|
88
|
+
"refant": self.refant,
|
|
89
|
+
}
|
|
90
|
+
logger.debug("Miriad options set to:")
|
|
91
|
+
for k, v in self.opts.items():
|
|
92
|
+
logger.debug(f"{k}={v}")
|
|
93
|
+
|
|
94
|
+
def run_command(self, command, args=None, print_stdout=False):
|
|
95
|
+
if args is not None:
|
|
96
|
+
args = " ".join([f"{arg}" for arg in args])
|
|
97
|
+
else:
|
|
98
|
+
args = ""
|
|
99
|
+
|
|
100
|
+
exports = " ".join(f"export {opt}='{val}';" for opt, val in self.opts.items())
|
|
101
|
+
|
|
102
|
+
p = subprocess.Popen(
|
|
103
|
+
f"source {package_root}/functions.sh; {exports} {command} {args}",
|
|
104
|
+
stdout=subprocess.PIPE,
|
|
105
|
+
stderr=subprocess.PIPE,
|
|
106
|
+
shell=True,
|
|
107
|
+
executable="/bin/bash",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
parse_stdout_stderr(p, logger, print_stdout)
|
|
111
|
+
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
def load_data(self, shiftra: str, shiftdec: str):
|
|
115
|
+
logger.info(f"Loading RPFITS files from {self.data_dir}")
|
|
116
|
+
self.run_command(
|
|
117
|
+
"load_data",
|
|
118
|
+
args=[
|
|
119
|
+
str(self.out_dir.absolute()),
|
|
120
|
+
str(self.data_dir),
|
|
121
|
+
"true" if self.noflag else "false",
|
|
122
|
+
str(self.project_code),
|
|
123
|
+
shiftra,
|
|
124
|
+
shiftdec,
|
|
125
|
+
],
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
def path(self, target: str):
|
|
131
|
+
if target is None:
|
|
132
|
+
return Target(name=None, path=None)
|
|
133
|
+
|
|
134
|
+
path = Path(self.out_dir / "miriad" / f"{target}.{self.band.freq}")
|
|
135
|
+
return Target(name=target, path=path)
|
|
136
|
+
|
|
137
|
+
def set_targets(
|
|
138
|
+
self,
|
|
139
|
+
primary_cal: str = "1934-638",
|
|
140
|
+
gain_cal: Optional[str] = None,
|
|
141
|
+
target: Optional[str] = None,
|
|
142
|
+
leakage_cal: Optional[str] = None,
|
|
143
|
+
):
|
|
144
|
+
ms = self.generate_ms(self.uvfile)
|
|
145
|
+
|
|
146
|
+
# Read and store scan summary
|
|
147
|
+
header = listobs(vis=ms)
|
|
148
|
+
scan_keys = [key for key in header.keys() if "scan" in key]
|
|
149
|
+
df = pd.DataFrame({scan: header[scan]["0"] for scan in scan_keys}).T
|
|
150
|
+
df["ScanTime"] = (df.EndTime - df.BeginTime) * u.day.to(u.min)
|
|
151
|
+
self.scans = df.sort_values("scanId").reset_index()
|
|
152
|
+
|
|
153
|
+
fields = [
|
|
154
|
+
str(Path(p).name).replace(f".{self.band.freq}", "")
|
|
155
|
+
for p in glob.glob(f"{self.out_dir}/miriad/*.{self.band.freq}")
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
# Clean up intermediate files
|
|
159
|
+
os.system(f"rm -r {ms}")
|
|
160
|
+
|
|
161
|
+
if primary_cal not in fields:
|
|
162
|
+
raise ValueError(f"Could not locate {primary_cal} field in {self.uvfile}")
|
|
163
|
+
|
|
164
|
+
# Assume target and gain calibrator are two objects with most observing time
|
|
165
|
+
total_times = (
|
|
166
|
+
df.groupby("FieldName").ScanTime.sum().reset_index().sort_values("ScanTime")
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
if gain_cal is None:
|
|
170
|
+
gain_cal = total_times.iloc[-2].FieldName
|
|
171
|
+
logger.info(f"No gain calibrator specified, defaulting to {gain_cal}")
|
|
172
|
+
if target is None:
|
|
173
|
+
target = total_times.iloc[-1].FieldName
|
|
174
|
+
logger.info(f"No science target specified, defaulting to {target}")
|
|
175
|
+
|
|
176
|
+
self.target_paths = {
|
|
177
|
+
"primary_cal": self.path(primary_cal),
|
|
178
|
+
"gain_cal": self.path(gain_cal),
|
|
179
|
+
"leakage_cal": self.path(leakage_cal),
|
|
180
|
+
"target": self.path(target),
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return
|
|
184
|
+
|
|
185
|
+
def blflag(self, vis, x, y, options):
|
|
186
|
+
per_bl = "" if "nobase" in options else "per baseline"
|
|
187
|
+
logger.info(f"Manually flagging {vis} in {y} vs {x} {per_bl}")
|
|
188
|
+
self.run_command(
|
|
189
|
+
"manflag",
|
|
190
|
+
args=[vis, x, y, options],
|
|
191
|
+
print_stdout=True,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
def autoflag(self, vis):
|
|
195
|
+
logger.info(f"Autoflagging {vis}")
|
|
196
|
+
self.run_command("autoflag", args=[vis])
|
|
197
|
+
|
|
198
|
+
def flag_times(self, vis, start_time, end_time):
|
|
199
|
+
logger.info(f"Flagging {vis} between {start_time}-{end_time}")
|
|
200
|
+
self.run_command("flag_timerange", args=[vis, start_time, end_time])
|
|
201
|
+
|
|
202
|
+
def bandpass(self, vis):
|
|
203
|
+
logger.info(f"Running bandpass calibration on {vis}")
|
|
204
|
+
interpolate = "true" if self.noflag else "false"
|
|
205
|
+
self.run_command("cal_bandpass", args=[vis, interpolate])
|
|
206
|
+
|
|
207
|
+
def bootstrap(self, vis1, vis2):
|
|
208
|
+
if vis1 != vis2:
|
|
209
|
+
logger.info(f"Bootstrapping flux scale from {vis2.name} to {vis1.name}")
|
|
210
|
+
self.run_command("bootstrap", args=[vis1, vis2])
|
|
211
|
+
|
|
212
|
+
def gaincal(self, vis, options):
|
|
213
|
+
logger.info(f"Running gain/leakage calibration on {vis}")
|
|
214
|
+
self.run_command("cal_gains", args=[vis, options])
|
|
215
|
+
|
|
216
|
+
def copycal(self, vis1, vis2):
|
|
217
|
+
logger.info(f"Copying calibration tables from {vis1.name} to {vis2.name}")
|
|
218
|
+
self.run_command("copy_cal", args=[vis1, vis2])
|
|
219
|
+
|
|
220
|
+
def gpaver(self, vis):
|
|
221
|
+
logger.info(f"Averaging calibration solutions for {vis}")
|
|
222
|
+
self.run_command("average_gains", args=[vis])
|
|
223
|
+
|
|
224
|
+
def uvaver(self, vis):
|
|
225
|
+
logger.info(f"Applying calibration solutions to {vis}")
|
|
226
|
+
self.run_command("apply_gains", args=[vis])
|
|
227
|
+
|
|
228
|
+
def generate_ms(self, uv):
|
|
229
|
+
fitsfile = f"{uv}.fits"
|
|
230
|
+
ms = f"{uv}.ms"
|
|
231
|
+
|
|
232
|
+
self.run_command("uvtofits", args=[uv, fitsfile])
|
|
233
|
+
|
|
234
|
+
importuvfits(
|
|
235
|
+
fitsfile=fitsfile,
|
|
236
|
+
vis=ms,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
os.system(f"rm -r {fitsfile}")
|
|
240
|
+
|
|
241
|
+
return ms
|
|
242
|
+
|
|
243
|
+
def cleanup(self):
|
|
244
|
+
os.system(f"rm -r {self.out_dir / 'miriad'}")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@dataclass
|
|
248
|
+
class CABBContinuumPipeline:
|
|
249
|
+
miriad: MiriadWrapper
|
|
250
|
+
shiftra: float
|
|
251
|
+
shiftdec: float
|
|
252
|
+
num_flag_rounds: int
|
|
253
|
+
interactive: bool
|
|
254
|
+
|
|
255
|
+
def __post_init__(self):
|
|
256
|
+
if os.path.exists(self.miriad.uvfile):
|
|
257
|
+
logger.warning(f"{self.miriad.uvfile} already exists, will not overwrite")
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
self.miriad.load_data(self.shiftra, self.shiftdec)
|
|
261
|
+
|
|
262
|
+
def flag_sequence(self, target):
|
|
263
|
+
self.miriad.blflag(
|
|
264
|
+
target,
|
|
265
|
+
x="time",
|
|
266
|
+
y="amp",
|
|
267
|
+
options="nofqav,nobase",
|
|
268
|
+
)
|
|
269
|
+
self.miriad.blflag(
|
|
270
|
+
target,
|
|
271
|
+
x="chan",
|
|
272
|
+
y="amp",
|
|
273
|
+
options="nofqav,nobase",
|
|
274
|
+
)
|
|
275
|
+
self.miriad.blflag(
|
|
276
|
+
target,
|
|
277
|
+
x="chan",
|
|
278
|
+
y="amp",
|
|
279
|
+
options="nofqav",
|
|
280
|
+
)
|
|
281
|
+
self.miriad.autoflag(target)
|
|
282
|
+
self.miriad.blflag(
|
|
283
|
+
target,
|
|
284
|
+
x="real",
|
|
285
|
+
y="imag",
|
|
286
|
+
options="nofqav,nobase",
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
def make_diagnostics(self):
|
|
292
|
+
logger.info("Generating calibration diagnostic plots")
|
|
293
|
+
|
|
294
|
+
cwd = Path(".").absolute()
|
|
295
|
+
os.system(f"mkdir -p {self.miriad.out_dir}/diagnostics")
|
|
296
|
+
os.chdir(self.miriad.out_dir)
|
|
297
|
+
|
|
298
|
+
pcal = self.miriad.target_paths.get("primary_cal").path.relative_to(
|
|
299
|
+
self.miriad.out_dir
|
|
300
|
+
)
|
|
301
|
+
scal = self.miriad.target_paths.get("gain_cal").path.relative_to(
|
|
302
|
+
self.miriad.out_dir
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
calibrators = set([pcal, scal])
|
|
306
|
+
|
|
307
|
+
# Primary / secondary plots
|
|
308
|
+
for vis in calibrators:
|
|
309
|
+
self.miriad.run_command(
|
|
310
|
+
f"uvfmeas vis={vis} \
|
|
311
|
+
stokes=i \
|
|
312
|
+
device=diagnostics/{vis.name}_spectrum.png/PNG"
|
|
313
|
+
)
|
|
314
|
+
self.miriad.run_command(
|
|
315
|
+
f"uvplt vis={vis} \
|
|
316
|
+
stokes=i \
|
|
317
|
+
axis=re,im \
|
|
318
|
+
options=nofqav,nobase \
|
|
319
|
+
device=diagnostics/{vis.name}_real_imag.png/PNG"
|
|
320
|
+
)
|
|
321
|
+
self.miriad.run_command(
|
|
322
|
+
f"uvplt vis={vis} \
|
|
323
|
+
stokes=i \
|
|
324
|
+
axis=freq,phase \
|
|
325
|
+
nxy=4 \
|
|
326
|
+
options=nofqav \
|
|
327
|
+
device=diagnostics/{vis.name}_phase_spectrum.png/PNG"
|
|
328
|
+
)
|
|
329
|
+
self.miriad.run_command(
|
|
330
|
+
f"gpplt vis={vis} \
|
|
331
|
+
options=gains \
|
|
332
|
+
yaxis=amp,phase \
|
|
333
|
+
log=diagnostics/{vis.name}_gains.txt"
|
|
334
|
+
)
|
|
335
|
+
self.miriad.run_command(
|
|
336
|
+
f"gpplt vis={vis} \
|
|
337
|
+
options=polarization \
|
|
338
|
+
yaxis=amp,phase \
|
|
339
|
+
log=diagnostics/{vis.name}_leakages.txt"
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Primary plots
|
|
343
|
+
self.miriad.run_command(
|
|
344
|
+
f"gpplt vis={pcal} \
|
|
345
|
+
options=dots,bandpass \
|
|
346
|
+
log=diagnostics/{vis.name}_bandpass.txt \
|
|
347
|
+
device=diagnostics/{pcal.name}_bandpass.png/PNG"
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
# Secondary plots
|
|
351
|
+
self.miriad.run_command(
|
|
352
|
+
f"uvplt vis={scal} \
|
|
353
|
+
stokes=i \
|
|
354
|
+
axis=time,amp \
|
|
355
|
+
options=nofqav,nobase \
|
|
356
|
+
device=diagnostics/{scal.name}_amp_time.png/PNG"
|
|
357
|
+
)
|
|
358
|
+
self.miriad.run_command(
|
|
359
|
+
f"uvplt vis={scal} \
|
|
360
|
+
stokes=xx,yy \
|
|
361
|
+
axis=time,phase \
|
|
362
|
+
options=nofqav,nobase \
|
|
363
|
+
device=diagnostics/{scal.name}_phase_time.png/PNG"
|
|
364
|
+
)
|
|
365
|
+
self.miriad.run_command(
|
|
366
|
+
f"uvplt vis={scal} \
|
|
367
|
+
stokes=i \
|
|
368
|
+
axis=uc,vc \
|
|
369
|
+
options=nofqav,nobase \
|
|
370
|
+
device=diagnostics/{scal.name}_uv_coverage.png/PNG"
|
|
371
|
+
)
|
|
372
|
+
self.miriad.run_command(
|
|
373
|
+
f"uvplt vis={scal} \
|
|
374
|
+
stokes=i \
|
|
375
|
+
axis=time,parang \
|
|
376
|
+
options=nofqav,nobase \
|
|
377
|
+
device=diagnostics/{scal.name}_parang_coverage.png/PNG"
|
|
378
|
+
)
|
|
379
|
+
self.miriad.run_command(
|
|
380
|
+
f"gpplt vis={scal} \
|
|
381
|
+
options=dots,wrap \
|
|
382
|
+
yaxis=phase \
|
|
383
|
+
yrange=-180,180 \
|
|
384
|
+
device=diagnostics/phase_solutions.png/PNG"
|
|
385
|
+
)
|
|
386
|
+
self.miriad.run_command(
|
|
387
|
+
f"gpplt vis={scal} \
|
|
388
|
+
options=dots,xygains,wrap \
|
|
389
|
+
yaxis=phase \
|
|
390
|
+
yrange=-10,10 \
|
|
391
|
+
device=diagnostics/xy-phase_solutions.png/PNG"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Rename bandpass plots for XX and YY polarisations
|
|
395
|
+
self.miriad.run_command(
|
|
396
|
+
f"mv \
|
|
397
|
+
diagnostics/{pcal.name}_bandpass.png \
|
|
398
|
+
diagnostics/{pcal.name}_bandpass_xx.png"
|
|
399
|
+
)
|
|
400
|
+
self.miriad.run_command(
|
|
401
|
+
f"mv \
|
|
402
|
+
diagnostics/{pcal.name}_bandpass.png_2 \
|
|
403
|
+
diagnostics/{pcal.name}_bandpass_yy.png"
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
# Remove all sub-band phase/xy-phase solution plots
|
|
407
|
+
for plot in glob.glob("diagnostics/*png_*"):
|
|
408
|
+
self.miriad.run_command(f"rm {plot}")
|
|
409
|
+
|
|
410
|
+
os.chdir(cwd)
|
|
411
|
+
|
|
412
|
+
return
|
|
413
|
+
|
|
414
|
+
def run(self):
|
|
415
|
+
primary_cal = self.miriad.target_paths.get("primary_cal").path
|
|
416
|
+
gain_cal = self.miriad.target_paths.get("gain_cal").path
|
|
417
|
+
target = self.miriad.target_paths.get("target").path
|
|
418
|
+
leakage_cal = self.miriad.target_paths.get("leakage_cal").path
|
|
419
|
+
|
|
420
|
+
if os.path.exists(f"{self.miriad.out_dir}/{target.name}.ms"):
|
|
421
|
+
logger.warning(
|
|
422
|
+
f"{self.miriad.out_dir}/{target.name}.ms already exists, will not overwrite"
|
|
423
|
+
)
|
|
424
|
+
return
|
|
425
|
+
|
|
426
|
+
# Primary bandpass / flux calibrator
|
|
427
|
+
# ---------------------------------
|
|
428
|
+
self.miriad.bandpass(primary_cal)
|
|
429
|
+
|
|
430
|
+
# Flag and solve for bandpass on primary calibrator
|
|
431
|
+
if self.interactive:
|
|
432
|
+
while prompt(f"Start interactive flagging of {primary_cal.name}?"):
|
|
433
|
+
self.flag_sequence(primary_cal)
|
|
434
|
+
self.miriad.bandpass(primary_cal)
|
|
435
|
+
else:
|
|
436
|
+
for _ in range(self.num_flag_rounds):
|
|
437
|
+
self.miriad.autoflag(primary_cal)
|
|
438
|
+
self.miriad.bandpass(primary_cal)
|
|
439
|
+
|
|
440
|
+
# Solve for primary calibrator gains / leakage
|
|
441
|
+
self.miriad.gaincal(primary_cal, options="xyvary")
|
|
442
|
+
|
|
443
|
+
# Set options to work with strong or weakly polarised calibrator
|
|
444
|
+
if self.miriad.strong_pol:
|
|
445
|
+
gp_options = "xyvary,qusolve,vsolve,xyref,polref"
|
|
446
|
+
|
|
447
|
+
else:
|
|
448
|
+
gp_options = "xyvary,qusolve"
|
|
449
|
+
|
|
450
|
+
# TODO:
|
|
451
|
+
# add flexibility for low parang coverage in which we switch off
|
|
452
|
+
# xy phase and pol corrections
|
|
453
|
+
|
|
454
|
+
# Leakage calibrator
|
|
455
|
+
# ------------------
|
|
456
|
+
if leakage_cal is not None:
|
|
457
|
+
self.miriad.copycal(primary_cal, leakage_cal)
|
|
458
|
+
|
|
459
|
+
# Flag and solve for gains / leakages / xy-phase on leakage calibrator
|
|
460
|
+
if self.interactive:
|
|
461
|
+
while prompt(f"Start interactive flagging of {leakage_cal.name}?"):
|
|
462
|
+
self.flag_sequence(leakage_cal)
|
|
463
|
+
self.miriad.gaincal(leakage_cal, options=gp_options)
|
|
464
|
+
else:
|
|
465
|
+
for _ in range(self.num_flag_rounds):
|
|
466
|
+
self.miriad.autoflag(leakage_cal)
|
|
467
|
+
self.miriad.gaincal(leakage_cal, options=gp_options)
|
|
468
|
+
|
|
469
|
+
# To avoid corruption of Stokes V zero-point, we copy solutions
|
|
470
|
+
# back to primary calibrator and repeat the sequence
|
|
471
|
+
self.miriad.copycal(leakage_cal, primary_cal)
|
|
472
|
+
self.miriad.gaincal(primary_cal, options="noxy")
|
|
473
|
+
self.miriad.copycal(primary_cal, leakage_cal)
|
|
474
|
+
self.miriad.gaincal(leakage_cal, options=gp_options)
|
|
475
|
+
|
|
476
|
+
# Now we turn off xy-phase / leakage calibration for subsequent gain calibration
|
|
477
|
+
self.miriad.copycal(leakage_cal, gain_cal)
|
|
478
|
+
gp_options = "qusolve,noxy,nopol"
|
|
479
|
+
else:
|
|
480
|
+
if primary_cal != gain_cal:
|
|
481
|
+
self.miriad.copycal(primary_cal, gain_cal)
|
|
482
|
+
|
|
483
|
+
# Secondary gain calibrator
|
|
484
|
+
# -------------------------
|
|
485
|
+
if primary_cal != gain_cal:
|
|
486
|
+
if self.interactive:
|
|
487
|
+
while prompt(f"Start interactive flagging of {gain_cal.name}?"):
|
|
488
|
+
self.flag_sequence(gain_cal)
|
|
489
|
+
self.miriad.gaincal(gain_cal, options=gp_options)
|
|
490
|
+
else:
|
|
491
|
+
for _ in range(self.num_flag_rounds):
|
|
492
|
+
self.miriad.autoflag(gain_cal)
|
|
493
|
+
self.miriad.gaincal(gain_cal, options=gp_options)
|
|
494
|
+
|
|
495
|
+
self.miriad.bootstrap(gain_cal, primary_cal)
|
|
496
|
+
|
|
497
|
+
# Science target
|
|
498
|
+
# --------------
|
|
499
|
+
self.miriad.copycal(gain_cal, target)
|
|
500
|
+
|
|
501
|
+
if not self.miriad.noflag:
|
|
502
|
+
self.miriad.autoflag(target)
|
|
503
|
+
|
|
504
|
+
# Average solutions and apply
|
|
505
|
+
# --------------------------
|
|
506
|
+
self.miriad.gpaver(target)
|
|
507
|
+
self.miriad.uvaver(gain_cal)
|
|
508
|
+
self.miriad.uvaver(target)
|
|
509
|
+
|
|
510
|
+
self.miriad.generate_ms(f"{target}.cal")
|
|
511
|
+
self.miriad.generate_ms(f"{gain_cal}.cal")
|
|
512
|
+
self.miriad.run_command(
|
|
513
|
+
f"mv {target}.cal.ms {self.miriad.out_dir}/{target.name}.ms"
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
return
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cal-cabb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: ATCA CABB continuum calibration pipeline.
|
|
5
|
+
Requires-Python: <3.12,>=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: astropy>=6.1.7
|
|
8
|
+
Requires-Dist: click>=8.1.8
|
|
9
|
+
Requires-Dist: colorlog>=6.9.0
|
|
10
|
+
Requires-Dist: pandas>=2.2.3
|
|
11
|
+
Requires-Dist: pre-commit>=4.1.0
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
cal_cabb/__init__.py
|
|
4
|
+
cal_cabb/casa.py
|
|
5
|
+
cal_cabb/logger.py
|
|
6
|
+
cal_cabb/miriad.py
|
|
7
|
+
cal_cabb.egg-info/PKG-INFO
|
|
8
|
+
cal_cabb.egg-info/SOURCES.txt
|
|
9
|
+
cal_cabb.egg-info/dependency_links.txt
|
|
10
|
+
cal_cabb.egg-info/entry_points.txt
|
|
11
|
+
cal_cabb.egg-info/requires.txt
|
|
12
|
+
cal_cabb.egg-info/top_level.txt
|
|
13
|
+
cal_cabb/cli/atca_cal.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cal_cabb
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cal-cabb"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "ATCA CABB continuum calibration pipeline."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11,<3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"astropy>=6.1.7",
|
|
9
|
+
"click>=8.1.8",
|
|
10
|
+
"colorlog>=6.9.0",
|
|
11
|
+
"pandas>=2.2.3",
|
|
12
|
+
"pre-commit>=4.1.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
atca-cal = "cal_cabb.cli.atca_cal:main"
|
|
17
|
+
|
|
18
|
+
[dependency-groups]
|
|
19
|
+
dev = [
|
|
20
|
+
"isort>=6.0.0",
|
|
21
|
+
"mypy>=1.15.0",
|
|
22
|
+
"pre-commit>=4.1.0",
|
|
23
|
+
"ruff>=0.9.7",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
[tool.ruff]
|
|
28
|
+
lint.ignore = ["E741"]
|
cal_cabb-0.1.0/setup.cfg
ADDED