aboutsummaryrefslogtreecommitdiffstats
path: root/astro
diff options
context:
space:
mode:
Diffstat (limited to 'astro')
-rwxr-xr-xastro/oskar/correct-pb.py54
-rwxr-xr-xastro/oskar/runOSKAR.py598
2 files changed, 0 insertions, 652 deletions
diff --git a/astro/oskar/correct-pb.py b/astro/oskar/correct-pb.py
deleted file mode 100755
index cac20ff..0000000
--- a/astro/oskar/correct-pb.py
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (c) Weitna LI <weitian@aaronly.me>
-# MIT License
-#
-# Correct for primary beam by dividing the image by the (simulated)
-# primary beam pattern.
-#
-# 2017-06-20
-#
-
-import os
-import sys
-import argparse
-
-from astropy.io import fits
-
-
-def main():
- parser = argparse.ArgumentParser(
- description="Correct for the primary beam pattern")
- parser.add_argument("-C", "--clobber", dest="clobber",
- action="store_true",
- help="overwrite existing files")
- parser.add_argument("-p", "--primary-beam", dest="pb",
- required=True,
- help="file of the primary beam pattern")
- parser.add_argument("infile", help="input image to be corrected for")
- parser.add_argument("outfile", nargs="?",
- help="output pb-corrected image (default: add " +
- "'pbcorr.fits' suffix)")
- args = parser.parse_args()
-
- if args.outfile:
- outfile = args.outfile
- else:
- outfile = os.path.splitext(args.infile)[0] + ".pbcorr.fits"
-
- with fits.open(args.infile) as f:
- imgin = f[0].data
- header = f[0].header
- pb = fits.open(args.pb)[0].data
- imgout = imgin / pb
- header.add_history(" ".join(sys.argv))
- hdu = fits.PrimaryHDU(data=imgout, header=header)
- try:
- hdu.writeto(outfile, overwrite=args.clobber)
- except TypeError:
- hdu.writeto(outfile, clobber=args.clobber)
- print("Wrote pb-corrected image: %s" % outfile)
-
-
-if __name__ == "__main__":
- main()
diff --git a/astro/oskar/runOSKAR.py b/astro/oskar/runOSKAR.py
deleted file mode 100755
index 57d9b7f..0000000
--- a/astro/oskar/runOSKAR.py
+++ /dev/null
@@ -1,598 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (c) 2017 Weitian LI <liweitianux@live.com>
-# MIT license
-#
-# 2017-04-07
-#
-# Change logs:
-# 2017-06-08:
-# * Add more command line arguments
-# * Set "dryrun" to True
-# * Do not output OSKAR binary visibility file (output MS only)
-# 2017-05-05:
-# * Fix ConfigParser getfloat error
-# * Workaround old astropy which uses "clobber" instead of "overwrite"
-#
-
-"""
-Run OSKAR to simulate the visibilities from the sky model specified
-by a FITS image.
-
-
-Credits
--------
-[1] GitHub: OxfordSKA/OSKAR
- https://github.com/OxfordSKA/OSKAR
-[2] GitHub: OxfordSKA/EoR - Emma_files/sim_tidy.py
- https://github.com/OxfordSKA/EoR/blob/master/Emma_files/sim_tidy.py
-"""
-
-import os
-import sys
-import subprocess
-import configparser
-import argparse
-import logging
-
-import numpy as np
-import astropy.io.fits as fits
-import astropy.constants as ac
-from astropy.wcs import WCS
-
-
-logging.basicConfig(level=logging.INFO)
-logger = logging.getLogger(os.path.basename(sys.argv[0]))
-
-
-class Settings:
- """
- OSKAR settings manager.
- """
- def __init__(self, infile):
- self.infile = infile
- self.config = configparser.ConfigParser(interpolation=None)
- self.config.read(infile)
- logger.info("Read in configuration file: %s" % infile)
- self.init_oskar_settings()
- self.update_oskar_settings(self.config)
-
- @property
- def my_settings(self):
- return self.config["my"]
-
- @property
- def dryrun(self):
- return self.my_settings.getboolean("dryrun", fallback=True)
-
- @property
- def clobber(self):
- return self.my_settings.getboolean("clobber", fallback=False)
-
- @property
- def quiet(self):
- return self.my_settings.getboolean("quiet", fallback=False)
-
- @property
- def oskar_bin(self):
- oskar = self.my_settings.get("oskar_bin",
- fallback="oskar_sim_interferometer")
- return os.path.expanduser(oskar)
-
- @property
- def output_settings_fn(self):
- """
- String format pattern for the output OSKAR settings file.
- """
- default = "settings/sim_interferometer_{freq:.2f}.ini"
- return self.my_settings.get("output_settings_fn", fallback=default)
-
- @property
- def output_skymodel_fn(self):
- """
- String format pattern for the output OSKAR sky model file.
- """
- default = "skymodel/skymodel_{freq:.2f}.txt"
- return self.my_settings.get("output_skymodel_fn", fallback=default)
-
- @property
- def output_skyfits_fn(self):
- """
- String format pattern for the output FITS slice of the sky model.
- """
- default = "skymodel/skymodel_{freq:.2f}.fits"
- return self.my_settings.get("output_skyfits_fn", fallback=default)
-
- @property
- def output_ms_fn(self):
- """
- String format pattern for the output simulated visibility
- data in MeasurementSet format.
- """
- default = "visibility/visibility_{freq:.2f}.ms"
- return self.my_settings.get("output_ms_fn", fallback=default)
-
- @property
- def output_vis_fn(self):
- """
- String format pattern for the output simulated visibility
- data in OSKAR binary format.
- """
- default = "visibility/visibility_{freq:.2f}.oskar"
- return self.my_settings.get("output_vis_fn", fallback=default)
-
- @property
- def telescope_model(self):
- """
- Telescope model used for visibility simulations.
- """
- return self.my_settings["telescope_model"]
-
- @property
- def input_cube(self):
- """
- Input FITS spectral cube.
- """
- return self.my_settings["input_cube"]
-
- @property
- def image_size(self):
- """
- Width/X and height/Y of the input FITS image (unit: pixel)
- """
- size = self.my_settings["image_size"].split(",")
- return (int(size[0]), int(size[1]))
-
- @property
- def image_pixsize(self):
- """
- Pixel size of the input FITS image (unit: arcsec)
- """
- return self.my_settings.getfloat("image_pixsize")
-
- @property
- def frequency(self):
- """
- Frequency of the input image. (unit: MHz)
-
- NOTE: required if the above input FITS file is not a cube, but
- a 2D image.
- """
- return self.my_settings.getfloat("frequency")
-
- @property
- def bandwidth(self):
- """
- Bandwidth of the input image. (unit: MHz)
- """
- return self.my_settings.getfloat("bandwidth")
-
- @property
- def ra0(self):
- """
- R.A. of the center of the input sky field.
- unit: deg
- """
- return self.my_settings.getfloat("ra0", fallback=0.0)
-
- @property
- def dec0(self):
- """
- Dec. of the center of the input sky field.
- unit: deg
- """
- return self.my_settings.getfloat("dec0", fallback=-27.0)
-
- @property
- def use_gpus(self):
- """
- Whether to GPUs
- """
- return self.my_settings.getboolean("use_gpus", fallback=False)
-
- @property
- def start_time(self):
- """
- Start time of the simulating observation
- """
- # This default time keeps 'EoR0' region above horizon for 12 hours.
- # SKA EoR0 region: (ra, dec) = (0, -27) [deg]
- default = "2000-01-01T03:30:00.000"
- return self.my_settings.get("start_time", fallback=default)
-
- @property
- def obs_length(self):
- """
- Observation length of time (unit: s).
- """
- default = 12.0 * 3600 # 12 hours
- return self.my_settings.getfloat("obs_length", fallback=default)
-
- @property
- def obs_interval(self):
- """
- Observation interval providing the number of time steps in the
- output data (unit: s).
- """
- default = 10.0 # [s]
- return self.my_settings.getfloat("obs_interval", fallback=default)
-
- @property
- def time_average(self):
- """
- Correlator time-average duration to simulate time-averaging smearing
- (unit: s).
- """
- default = 10.0 # [s]
- return self.my_settings.getfloat("time_average", fallback=default)
-
- def init_oskar_settings(self):
- """
- Initialize a `ConfigParser` instance with the default settings
- for 'oskar_sim_interferometer'.
- """
- settings = configparser.ConfigParser()
- settings.read_dict({
- "General": {
- "app": "oskar_sim_interferometer",
- },
- "simulator": {
- "use_gpus": self.use_gpus,
- "max_sources_per_chunk": 65536,
- "double_precision": "true",
- "keep_log_file": "true",
- },
- "sky": {
- "advanced/apply_horizon_clip": "false",
- },
- "observation": {
- "phase_centre_ra_deg": self.ra0,
- "phase_centre_dec_deg": self.dec0,
- "start_time_utc": self.start_time,
- "length": self.obs_length,
- "num_time_steps":
- int(np.ceil(self.obs_length/self.obs_interval)),
- "num_channels": 1,
- },
- "telescope": {
- "input_directory": self.telescope_model,
- "pol_mode": "Scalar",
- "normalise_beams_at_phase_centre": "true",
- "allow_station_beam_duplication": "true",
- "aperture_array/array_pattern/enable": "true",
- "aperture_array/element_pattern/functional_type": "Dipole",
- "aperture_array/element_pattern/dipole_length": 0.5,
- "aperture_array/element_pattern/dipole_length_units":
- "Wavelengths",
- "station_type": "Aperture array",
- },
- "interferometer": {
- "channel_bandwidth_hz": self.bandwidth * 1e6,
- "time_average_sec": self.time_average,
- "uv_filter_min": "min",
- "uv_filter_max": "max",
- "uv_filter_units": "Wavelengths",
- }
- })
- self.oskar_settings = settings
- logger.info("Initialized 'oskar_settings'")
-
- def update_oskar_settings(self, config):
- """
- Update the OSKAR settings with the loaded user configurations.
- """
- for section in self.oskar_settings.sections():
- if section in config:
- for key, value in config[section].items():
- self.oskar_settings[section][key] = value
- logger.info("oskar_settings: [%s]%s = %s" % (
- section, key, value))
- logger.info("Updated 'oskar_settings'")
-
- def write_oskar_settings(self, outfile, clobber=False):
- """
- Write the settings file for 'oskar_sim_interferometer'.
- """
- if os.path.exists(outfile) and (not clobber):
- raise OSError("oskar settings file already exists: " % outfile)
- with open(outfile, "w") as fp:
- # NOTE: OSKAR do NOT like space around '='
- self.oskar_settings.write(fp, space_around_delimiters=False)
- logger.info("Wrote oskar settings file: %s" % outfile)
-
-
-class SpectralCube:
- """
- Manipulate the FITS spectral cube.
-
- NOTE: The FITS data as `numpy.ndarray` has the opposite index
- ordering, which likes the Fortran style, i.e., fastest
- changing axis last: data[frequency, y, x]
- """
- def __init__(self, infile):
- self.infile = infile
- with fits.open(infile) as hdulist:
- self.header = hdulist[0].header
- self.cube = hdulist[0].data
- self.wcs = WCS(self.header)
- logger.info("Loaded FITS spectral cube: %s" % infile)
- logger.info("Spectral cube: width=%d, height=%d" %
- (self.width, self.height))
- if not self.is_cube:
- logger.warning("NOT a spectral cube!")
- else:
- logger.info("Number of frequencies: %d" % self.nfreq)
-
- @property
- def naxis(self):
- return self.header["NAXIS"]
-
- @property
- def is_cube(self):
- return self.naxis == 3
-
- @property
- def width(self):
- """
- Width of the image, i.e., X axis.
- """
- return self.header["NAXIS1"]
-
- @property
- def height(self):
- """
- Height of the image, i.e., Y axis.
- """
- return self.header["NAXIS2"]
-
- @property
- def nfreq(self):
- return self.header["NAXIS3"]
-
- @property
- def frequencies(self):
- """
- Frequencies of this cube. (unit: MHz)
- If the input file is not a cube, then return 'None'.
- """
- if not self.is_cube:
- logger.warning("Input FITS file is not a spectral cube: %s" %
- self.infile)
- return None
-
- nfreq = self.nfreq
- pix = np.zeros(shape=(nfreq, self.naxis), dtype=np.int)
- pix[:, -1] = np.arange(nfreq)
- world = self.wcs.wcs_pix2world(pix, 0)
- freqMHz = world[:, -1] / 1e6 # Hz -> MHz
- return freqMHz
-
- def get_slice(self, nth=0):
- """
- Extract the specified nth frequency slice from the cube.
- """
- if not self.is_cube:
- logger.warning("Input FITS file is not a spectral cube: %s" %
- self.infile)
- return self.cube
- else:
- return self.cube[nth, :, :]
-
-
-class SkyModel:
- """
- OSKAR sky model.
- """
- def __init__(self, image, freq, pixsize, ra0, dec0):
- self.image = image # K (brightness temperature)
- self.freq = freq # MHz
- self.pixsize = pixsize # arcsec
- self.ra0 = ra0 # deg
- self.dec0 = dec0 # deg
- logger.info("SkyModel: Loaded image @ %.2f [MHz]" % freq)
-
- @property
- def wcs(self):
- """
- WCS for the given image assuming the 'SIN' projection.
- """
- shape = self.image.shape
- delta = self.pixsize / 3600.0 # deg
- wcs_ = WCS(naxis=2)
- wcs_.wcs.ctype = ["RA---SIN", "DEC--SIN"]
- wcs_.wcs.crval = np.array([self.ra0, self.dec0])
- wcs_.wcs.crpix = np.array([shape[1], shape[0]]) / 2.0 + 1
- wcs_.wcs.cdelt = np.array([delta, delta])
- return wcs_
-
- @property
- def fits_header(self):
- header = self.wcs.to_header()
- header["BUNIT"] = ("Jy/pixel", "Brightness unit")
- header["FREQ"] = (self.freq, "Frequency [MHz]")
- header["RA0"] = (self.ra0, "Center R.A. [deg]")
- header["DEC0"] = (self.dec0, "Center Dec. [deg]")
- return header
-
- @property
- def factor_K2JyPixel(self):
- """
- Conversion factor to convert brightness unit from 'K' to 'Jy/pixel'
-
- http://www.iram.fr/IRAMFR/IS/IS2002/html_1/node187.html
- """
- pixarea = np.deg2rad(self.pixsize/3600.0) ** 2 # [sr]
- kB = ac.k_B.si.value # Boltzmann constant [J/K]
- c0 = ac.c.si.value # speed of light in vacuum [m/s]
- freqHz = self.freq * 1e6 # [Hz]
- factor = 2*kB * 1.0e26 * pixarea * (freqHz/c0)**2
- return factor
-
- @property
- def ra_dec(self):
- """
- Calculate the (ra, dec) of each image pixel using the above WCS.
-
- NOTE: axis ordering difference between numpy array and FITS
- """
- shape = self.image.shape
- wcs = self.wcs
- x, y = np.meshgrid(np.arange(shape[1]), np.arange(shape[0]))
- pix = np.column_stack([x.flatten(), y.flatten()])
- world = wcs.wcs_pix2world(pix, 0)
- ra = world[:, 0].reshape(shape)
- dec = world[:, 1].reshape(shape)
- return (ra, dec)
-
- @property
- def sky(self):
- """
- OSKAR sky model array converted from the input image.
-
- Columns
- -------
- ra : (J2000) right ascension (deg)
- dec : (J2000) declination (deg)
- flux : source (Stokes I) flux density (Jy)
- """
- ra, dec = self.ra_dec
- ra = ra.flatten()
- dec = dec.flatten()
- flux = self.image.flatten() * self.factor_K2JyPixel
- mask = flux > 1e-40
- sky_ = np.column_stack([ra[mask], dec[mask], flux[mask]])
- return sky_
-
- def write_sky_model(self, outfile, clobber=False):
- """
- Write the converted sky model for simulation.
- """
- if os.path.exists(outfile) and (not clobber):
- raise OSError("oskar sky model file already exists: " % outfile)
- sky = self.sky
- header = ("Frequency = %.3f [MHz]\n" % self.freq +
- "Pixel size = %.2f arcsec\n" % self.pixsize +
- "RA0 = %.4f [deg]\n" % self.ra0 +
- "Dec0 = %.4f [deg]\n" % self.dec0 +
- "Number of sources = %d\n\n" % len(sky) +
- "R.A.[deg] Dec.[deg] flux[Jy]")
- np.savetxt(outfile, sky, fmt='%.10e, %.10e, %.10e', header=header)
- logger.info("Wrote oskar sky model file: %s" % outfile)
-
- def write_fits(self, outfile, oldheader=None, clobber=False):
- if os.path.exists(outfile) and (not clobber):
- raise OSError("Sky FITS already exists: " % outfile)
- if oldheader is not None:
- header = oldheader
- header.extend(self.fits_header, update=True)
- else:
- header = self.fits_header
- image = self.image * self.factor_K2JyPixel
- hdu = fits.PrimaryHDU(data=image, header=header)
- try:
- hdu.writeto(outfile, overwrite=True)
- except TypeError:
- hdu.writeto(outfile, clobber=True) # old astropy versions
- logger.info("Wrote sky FITS to file: %s" % outfile)
-
-
-class Oskar:
- """
- Run OSKAR simulations
- """
- def __init__(self, settings):
- self.settings = settings
-
- def run(self, settingsfile, dryrun=True, shfile=None):
- cmd = [self.settings.oskar_bin]
- if self.settings.quiet:
- cmd += ["--quiet"]
- cmd += [settingsfile]
- shellcmd = cmd[0]
- for arg in cmd[1:]:
- shellcmd += ' "%s"' % arg
- logger.info("Running OSKAR simulator:")
- logger.info("$ %s" % shellcmd)
- if shfile:
- open(shfile, "a").write("%s\n" % shellcmd)
- if dryrun:
- logger.info("Dry run!")
- else:
- subprocess.check_call(cmd)
-
-
-def main():
- parser = argparse.ArgumentParser(
- description="Run OSKAR to simulate visibilities")
- parser.add_argument("-C", "--clobber", dest="clobber",
- action="store_true",
- help="overwrite existing files")
- parser.add_argument("-R", "--run-oskar", dest="run",
- action="store_true",
- help="run OSKAR instead of just print commands")
- parser.add_argument("-S", "--sh-file", dest="shfile",
- help="also write OSKAR commands to this file")
- parser.add_argument("config", help="Configuration file")
- args = parser.parse_args()
-
- settings = Settings(args.config)
- clobber = args.clobber if args.clobber else settings.clobber
- dryrun = settings.dryrun
- if args.run:
- dryrun = False
- if args.shfile and os.path.exists(args.shfile):
- if clobber:
- os.remove(args.shfile)
- else:
- raise OSError("Output file already exists: %s" % args.shfile)
-
- image_cube = SpectralCube(settings.input_cube)
- frequencies = image_cube.frequencies # [MHz]
- if frequencies is None:
- frequencies = [settings.frequency]
- logger.info("Number of image slices/frequencies: %d" % len(frequencies))
-
- for nth, freq in enumerate(frequencies):
- logger.info(">>> Processing #%d/%d image slice @ %.2f [MHz] <<<" %
- (nth+1, len(frequencies), freq))
- settingsfile = settings.output_settings_fn.format(freq=freq)
- skymodelfile = settings.output_skymodel_fn.format(freq=freq)
- skyfitsfile = settings.output_skyfits_fn.format(freq=freq)
- msfile = settings.output_ms_fn.format(freq=freq)
- visfile = settings.output_vis_fn.format(freq=freq)
- for filepath in [settingsfile, skymodelfile, skyfitsfile,
- msfile, visfile]:
- dname = os.path.dirname(filepath)
- if not os.path.isdir(dname):
- os.makedirs(dname)
-
- newconfig = configparser.ConfigParser()
- newconfig.read_dict({
- "sky": {
- "oskar_sky_model/file": skymodelfile,
- },
- "observation": {
- "start_frequency_hz": freq * 1e6,
- },
- "interferometer": {
- "oskar_vis_filename": "",
- "ms_filename": msfile,
- },
- })
- settings.update_oskar_settings(newconfig)
- settings.write_oskar_settings(outfile=settingsfile, clobber=clobber)
-
- image_slice = image_cube.get_slice(nth)
- skymodel = SkyModel(image=image_slice, freq=freq,
- pixsize=settings.image_pixsize,
- ra0=settings.ra0, dec0=settings.dec0)
- skymodel.write_sky_model(skymodelfile, clobber=clobber)
- skymodel.write_fits(skyfitsfile, oldheader=image_cube.header,
- clobber=clobber)
-
- oskar = Oskar(settings)
- oskar.run(settingsfile, dryrun=dryrun, shfile=args.shfile)
-
-
-if __name__ == '__main__':
- main()