From 9c0ddf2df59151f9be5ad45c588fcef89942b9b1 Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Tue, 19 Aug 2025 14:32:58 +0200 Subject: [PATCH 1/5] add fits2ms and rmtree --- src/radiotools/utils/__init__.py | 4 ++- src/radiotools/utils/utils.py | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/radiotools/utils/__init__.py b/src/radiotools/utils/__init__.py index 1f32665..cf4ab6b 100644 --- a/src/radiotools/utils/__init__.py +++ b/src/radiotools/utils/__init__.py @@ -1,7 +1,9 @@ -from .utils import get_array_names, img2jansky, rms +from .utils import get_array_names, img2jansky, rms, uvfits2ms, rmtree __all__ = [ "get_array_names", "img2jansky", "rms", + "uvfits2ms", + "rmtree", ] diff --git a/src/radiotools/utils/utils.py b/src/radiotools/utils/utils.py index 0ee4247..82e5aa5 100644 --- a/src/radiotools/utils/utils.py +++ b/src/radiotools/utils/utils.py @@ -2,9 +2,11 @@ import numpy as np import requests +import subprocess from astropy.io import fits from bs4 import BeautifulSoup from numpy.typing import ArrayLike +from pathlib import Path def get_array_names(url: str) -> list[str]: @@ -73,3 +75,48 @@ def img2jansky(image: ArrayLike, header: fits.Header): * np.power(header["CDELT1"], 2) / (np.pi * header["BMIN"] * header["BMAJ"]) ) + + +def uvfits2ms(fits_path, ms_path): + """Converts a uvfits_file into a measurement set (ms file). + + Parameters + ---------- + fits_path : str + path to fits_file + ms_path : str + path to store ms_file + """ + casa = "casa --quiet --nologger --nologfile --nogui --norc --agg -c " + arg = ( + "importuvfits(fitsfile='" + + str(fits_path) + + "'" + + ", vis=" + + "'" + + str(ms_path) + + "'" + + ")" + ) + + command = casa + '"' + arg + '"' + + subprocess.run(command, shell=True) + + +def rmtree(root: Path): + """Recursively remove directories and files + starting from root directory. + + Parameters + ---------- + root : Path + Root path of the directories you want to delete. + """ + for p in root.iterdir(): + if p.is_dir(): + rmtree(p) + else: + p.unlink() + + root.rmdir() From 40b536aa40e128b4052de3bd442a23ac5d3d3766 Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Tue, 19 Aug 2025 14:33:15 +0200 Subject: [PATCH 2/5] add command line tool for fits2ms conversion --- pyproject.toml | 1 + src/radiotools/cli_tools/fits2ms.py | 54 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 src/radiotools/cli_tools/fits2ms.py diff --git a/pyproject.toml b/pyproject.toml index 7206300..0aa42a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,6 +89,7 @@ dev = [ [project.scripts] radiotools-info = "radiotools.info:main" radiotools-vis = "radiotools.cli_tools.vis:main" +radiotools-fits2ms = "radiotools.cli_tools.fits2ms:main" [tool.hatch.version] source = "vcs" diff --git a/src/radiotools/cli_tools/fits2ms.py b/src/radiotools/cli_tools/fits2ms.py new file mode 100644 index 0000000..d9d7f8f --- /dev/null +++ b/src/radiotools/cli_tools/fits2ms.py @@ -0,0 +1,54 @@ +"""Tool to convert uvfits files into measurement sets. +Single files or all files inside a directory can be converted. +""" + +import click + +from pathlib import Path +from natsort import natsorted +import numpy as np +from radiotools.utils import uvfits2ms, rmtree +import subprocess +from tqdm.auto import tqdm + + +@click.command() +@click.argument("fits_path", type=click.Path(exists=True, dir_okay=True)) +@click.argument("ms_path", type=click.Path(exists=True, dir_okay=True)) +@click.option("--log", default=False) +def main(fits_path, ms_path, log=False): + """Convert uvfits files into measurement sets. + + Parameters + ---------- + fits_path : str + Path to fits file or directory with fits files + ms_path : str + Path to store ms file or directory for ms files + """ + fits_path = Path(fits_path) + ms_path = Path(ms_path) + + if ".ms" not in ms_path.name: + ms_path.mkdir(parents=True, exist_ok=True) + + if fits_path.is_dir(): + fits_files = natsorted(np.array([x for x in fits_path.iterdir()])) + else: + fits_files = fits_path + + for i, path in enumerate(tqdm(fits_files)): + if fits_path.is_dir(): + file_name = "vis_" + str(i) + ".ms" + out = ms_path / file_name + else: + out = ms_path.parent + "/vis_" + str(i) + ".ms" + if out.exists(): + rmtree(out) + uvfits2ms(path, out) + # if not log: + # subprocess.run("rm casa-*.log", shell=True) + + +if __name__ == "__main__": + main() From cd0fa35c1ff4923959378c211662887ba0bb3fb1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:34:16 +0000 Subject: [PATCH 3/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/radiotools/cli_tools/fits2ms.py | 10 +++++----- src/radiotools/utils/__init__.py | 2 +- src/radiotools/utils/utils.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/radiotools/cli_tools/fits2ms.py b/src/radiotools/cli_tools/fits2ms.py index d9d7f8f..641a342 100644 --- a/src/radiotools/cli_tools/fits2ms.py +++ b/src/radiotools/cli_tools/fits2ms.py @@ -2,15 +2,15 @@ Single files or all files inside a directory can be converted. """ -import click - from pathlib import Path -from natsort import natsorted + +import click import numpy as np -from radiotools.utils import uvfits2ms, rmtree -import subprocess +from natsort import natsorted from tqdm.auto import tqdm +from radiotools.utils import rmtree, uvfits2ms + @click.command() @click.argument("fits_path", type=click.Path(exists=True, dir_okay=True)) diff --git a/src/radiotools/utils/__init__.py b/src/radiotools/utils/__init__.py index cf4ab6b..805bcb3 100644 --- a/src/radiotools/utils/__init__.py +++ b/src/radiotools/utils/__init__.py @@ -1,4 +1,4 @@ -from .utils import get_array_names, img2jansky, rms, uvfits2ms, rmtree +from .utils import get_array_names, img2jansky, rms, rmtree, uvfits2ms __all__ = [ "get_array_names", diff --git a/src/radiotools/utils/utils.py b/src/radiotools/utils/utils.py index 82e5aa5..57fe9c3 100644 --- a/src/radiotools/utils/utils.py +++ b/src/radiotools/utils/utils.py @@ -1,12 +1,12 @@ import re +import subprocess +from pathlib import Path import numpy as np import requests -import subprocess from astropy.io import fits from bs4 import BeautifulSoup from numpy.typing import ArrayLike -from pathlib import Path def get_array_names(url: str) -> list[str]: From 4b9591410828ce65c5402a3c178d06a07cb204ab Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Tue, 19 Aug 2025 14:35:49 +0200 Subject: [PATCH 4/5] add changes fragment --- docs/changes/30.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/changes/30.feature.rst diff --git a/docs/changes/30.feature.rst b/docs/changes/30.feature.rst new file mode 100644 index 0000000..3e76de4 --- /dev/null +++ b/docs/changes/30.feature.rst @@ -0,0 +1 @@ +added command line tool for uvfits to measurement set conversion using casa From 46708fc3e19bffc044b3a0122202cad3c62b293c Mon Sep 17 00:00:00 2001 From: Kevin Schmidt Date: Tue, 19 Aug 2025 14:52:59 +0200 Subject: [PATCH 5/5] drop deletion of logfiles --- src/radiotools/cli_tools/fits2ms.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/radiotools/cli_tools/fits2ms.py b/src/radiotools/cli_tools/fits2ms.py index 641a342..a768bc6 100644 --- a/src/radiotools/cli_tools/fits2ms.py +++ b/src/radiotools/cli_tools/fits2ms.py @@ -46,8 +46,6 @@ def main(fits_path, ms_path, log=False): if out.exists(): rmtree(out) uvfits2ms(path, out) - # if not log: - # subprocess.run("rm casa-*.log", shell=True) if __name__ == "__main__":