python-script
plotobs_cycle10.py
— 112.3 KB
File contents
"""
Version: 1.1
Author
John Carpenter (john.carpenter@alma.cl)
Maintainer
Gabriel Marinello (gabriel.marinello@alma.cl)
Change history:
September 13, 2019 : First release of script (JMC)
March 2019: Update for Cycle 8
March 2022: Update for Cycle 9
March 9, 2023: Update for Cycle 10
Purpose
plotobs_cycle10.py prints and plots the pointing positions of
ongoing Grade A projects for Cycle 9. The ALMA Archive should
be consulted for the list of completed observations over all
Cycles.
The script provides three basic function:
1) Plot observations around a specified source name or coordinate,
and overlay a proposed observation (single pointing or
rectangular mosaic) on the plot.
2) List the sources observed for a project
3) List the details (sensitivity, angular resolution, etc...)
of an observations
Support
plotobs_cycle10.py is user-contributed software that is distributed
"as-is". plotobs_cycle10.py is not an offical ALMA product and is
not supported by the ALMA Regional Centers (ARCs). Please report any
bugs to Gabriel Marinello.
Dependencies
1) The plotobs_cycle10.py script requires a data file containing
the list of ALMA observations. The root name of the file is
contained in the variable LIST_OF_OBSERVATIONS, and the data file
can be found on the ALMA Science Portal in the link
http://almascience.org/proposing/duplications.
The science portal has two versions of the data: an excel spreadsheet
(.xls extension) and a comma-separated-variable (.csv extension)
format. Both formats work with this script. To read the excel
file, the python package "xlrd" must be installed. My experience
is that this package is often not installed, and using the .csv
file is more likely to be successful.
2) Various python packages must be installed, which are listed below
under "Required software".
To run the program
# Start python with graphics enabled by the --pylab option
ipython --pylab
# Import program
import plotobs_cycle10 as po
# Main routines
po.plot(...) # Plots ALMA observations and a proposed observation
po.project(...) # Show observations for ALMA projects
po.row(...) # List observing parameters for rows in spreadsheet
Examples
# Plot scheduled observations within 100" x 100" of NGC7496 along
# with a proposed pointing at the position of the galaxy
po.plot('NGC7496', plotsize=200)
# Plot scheduled observations within 600" x 600" of NGC7496, along with a
# proposed mosaic at 345 GHz
po.plot('NGC7496', plotsize=600, length=240, width=120,
pa=60, freq=345., mosaic=True)
# Plot scheduled observations within 480" of (ra, dec) = (17h20m42s, -35d48m04s)
po.plot('17h20m42s', '-35d48m04s', plotsize=480, frame='icrs', unit='deg')
# Plot scheduled observations in a 1 deg region around ra, dec = (150, 2.2) J2000
po.plot(150, 2.2, plotsize=3600)
# Plot scheduled observations in a 20 arcmin around galactic center, but mosaics only
po.plot(0, 0, frame='galactic', plotsize=1200., mosonly=True)
# Plot scheduled observations within a 120" x 120" of HL Tau and GO Tau
po.plot('HL Tau, GO Tau', plotsize=120)
# Plot scheduled observations near coordinates in row 494 of the spreadsheet
po.plot(494)
# Plot remaining observations centered on row 494 in the spreadsheet, and only row 494
po.plot(494, include=494)
# Plot remaining observations for sources listed in a text file
# Contents of input file "input.txt":
HL Tau
GO Tau
plotsize = 480
NGC7496
frame = galactic
plotsize = 60
178.8590 -19.9724
po.plot(inputFile='input.txt')
# List scheduled observations for project 2018.1.00035.L
po.project('2018.1.00035.L')
# Print the observational parameters for row 1971 in Excel spreadsheet
po.row(1971)
Notes
1) After running "import plotobs as po", additional help can
be obtained by typing in python:
help(po)
help(po.plot)
help(po.row)
help(po.project)
2) When plotobs_cycle10 is run the first time in a python session, the
spreadsheet containing the observations is read into memory and
stored in the global variable OBSERVATIONS. The data stored in
memory is then used on subsequent calls.
3) Target of Opportunity or solar system observations will likely (but
not always) have a RA/Dec coordinate of (0 deg, 0 deg). Such
observations can be identified by running the script as:
po.plot(0, 0)
Cautions
1) The spreadsheet used by plotobs_cycle10.py contains the sensitivity
and angular resolution requested by the principal investigator.
The achieved sensitivity and angular resolution of the actual
observations will differ.
2) The sensitivity per spectral window is computed using the reference
frequency and reference bandwidth, but does not take into account
the variation in the system temperature amongst the various
spectral windows.
Software
Platforms
1) plotobs_cycle10.py program has been successfully tested on
a MacBook Pro and an iMac running OS Sierra.
The program has successfully worked with the following:
1) python 3.5
2) numpy 1.15.4
3) pandas version 0.23.4; see http://pandas.pydata.org .
4) astropy version 2.0; see http://www.astropy.org
5) If you want to read the excel (.xlsx) instead of the csv version
of the spreadsheet, the xlrd python package needs to be installed.
Internet connection
plotobs_cycle10.py does not require a internet connection to run.
plotobs_cycle10.py does use the Sesame database to resolve the
coordinates for source names. If an internet connection is
not available, the script cannot use Sesame, and will to determine
the coordinates by looking for the source name in the spreadsheet.
"""
# Load packages
from cgitb import text
import matplotlib.pylab as py
import numpy as np
import pandas as pd
import re
import os
import astropy
from astropy.coordinates import SkyCoord
import astropy.constants as G
from matplotlib.patches import Circle, Polygon
# Seaborn package is not required, but it makes the plot looks nicer
try:
import seaborn as sns
except:
pass
# Name of columns in observations
ANG_RES = 'Req. Ang. Res.'
BAND = 'Band'
DEC_DEG = 'Dec'
DEC_DEG_ORIG = 'Dec_orig'
DEC_DMS = 'Dec_DMS'
EXCEL_LINE = 'excel' # Created in readObservations
FWHM_PB = 'FWHM PB' # Created in readObservations
IS_ACA_STANDALONE = 'standAlone_ACA'
IS_LP = 'Is Large Program' # Created in readObservations
IS_MOSAIC = 'Mosaic'
IS_SOLAR = 'Solar Observing'
IS_SPECTRAL = 'isSpectralScan' # Created in readObservations
IS_SPW_SKY_FREQ = 'Is Sky Freq?'
IS_VLBI = 'VLBI'
LAS = 'Req. LAS'
LAT_OFFSET = 'Lat Offset'
LON_OFFSET = 'Long Offset'
MOS_AREA = 'mosaic area' # Created in readObservations
MOS_LENGTH = 'Mos. Length'
MOS_PA = 'Mos. PA'
MOS_WIDTH = 'Mos. Width'
MAX_SIZE = 'Max size' # Created in readObservations
MOS_SPACING = 'Mos. Spacing'
MOS_COORD = 'Mos. Coord.'
MOVING_OBJECT = 'Moving object' # Set in readObservation
PINAME = 'PI'
POLARIZATION = 'Polarization'
PROJECT = 'Project Code'
RA_DEG = 'RA'
RA_DEG_ORIG = 'RA_orig'
RA_HMS = 'RA_HMS'
REF_FREQ = 'Ref.Frequency'
REF_BW = 'Ref.Freq.Width'
REF_BW_MEASURE = 'repBandWidth_Measure'
REF_BW_UNIT = 'repBandwidthSG_unit'
REP_WIN_BW = 'bandwidth_SPW_rep'
REP_WIN_RES = 'spectralRes_SPW_rep'
REQ_SENS = 'Req.Sensitivity'
REQ_SENS_UNIT = 'sensitivity_unit'
SG_FULLNAME = 'Science Goal'
SG_NAME = 'SG_Number'
SPS_BW = 'SPS Bandwidth'
SPS_END = 'SPS End Freq.'
SPS_SPW_RES = 'SPS Spec. Res.'
SPS_START = 'SPS Start Freq.'
SPW_FREQ = {}
SPW_BW = {}
SPW_RES = {}
TARGET = 'Target Name'
TARGET_STRIPPED = 'Target Name stripped' # Add by readObservations()
TIME_TOTAL = 'estimatedTime'
TIME_12M = 'est12Time'
TIME_ACA = 'estACATime'
TIME_7M = 'est7Time'
TIME_TP = 'eTPTime'
USE_12M = 'Use 12m?' # Added by readObservations()
USE_7M = 'Use 7-m?'
USE_TPA = 'Use TP?'
VELOCITY = 'Velocity'
VELOCITY_UNIT = 'Velocity unit' # added by readObservations()
VELOCITY_REF = 'Vel. Frame'
VELOCITY_DOP = 'Vel. Convention'
# Set array keywords
NWINDOWS = 16
for win in range(1, NWINDOWS+1):
SPW_FREQ[win] = 'Freq SPW %d' % win
SPW_BW[win] = 'Bandwidth SPW %d' % win
SPW_RES[win] = 'Spec.Res. SPW %d' % win
# List of observations
LIST_OF_OBSERVATIONS = 'dup_input_cycle10'
SKIPROWS = None
# Store observations in the spreadsheet in global variable
OBSERVATIONS = None
# List of entries in the observations, their label, and unit
OBS_ITEMS = dict()
OBS_ITEMS[ANG_RES] = ['Angular resolution', 'arcsec']
OBS_ITEMS[BAND] = ['Band', None]
OBS_ITEMS[DEC_DEG] = ['Declination', 'dms']
OBS_ITEMS[FWHM_PB] = ['12m primary beam size', 'arcsec']
OBS_ITEMS[IS_MOSAIC] = ['Is mosaic?', None]
OBS_ITEMS[IS_SPECTRAL] = ['Is spectral scan?', None]
OBS_ITEMS[LAS] = ['Largest angular scale', 'arcsec']
OBS_ITEMS[LAT_OFFSET] = ['Latitude offset', 'arcsec']
OBS_ITEMS[LON_OFFSET] = ['Latitude offset', 'arcsec']
OBS_ITEMS[MAX_SIZE] = ['Maximum field of view', 'arcsec']
OBS_ITEMS[MOS_AREA] = ['Mosaic area', 'sq. arcmin']
OBS_ITEMS[MOS_COORD] = ['Coordinate system', None]
OBS_ITEMS[MOS_LENGTH] = ['Length', 'arcsec']
OBS_ITEMS[MOS_PA] = ['Position angle', 'deg']
OBS_ITEMS[MOS_SPACING] = ['Pointing spacings', 'arcsec']
OBS_ITEMS[MOS_WIDTH] = ['Width', 'arcsec']
OBS_ITEMS[POLARIZATION] = ['Polarization', None]
OBS_ITEMS[PROJECT] = ['Project code', None]
OBS_ITEMS[RA_DEG] = ['Right ascension', 'hms']
OBS_ITEMS[REF_FREQ] = ['Reference frequency', 'GHz']
OBS_ITEMS[REF_BW] = ['Reference bandwidth', 'MHz']
OBS_ITEMS[REQ_SENS] = ['Reference sensitivity', 'mJy']
OBS_ITEMS[SPS_START] = ['Spectral scan start', 'GHz']
OBS_ITEMS[SPS_END] = ['Spectral scan end', 'GHz']
OBS_ITEMS[SPS_BW] = ['Spectral scan bandwidth', 'MHz']
OBS_ITEMS[SPS_SPW_RES] = ['Spectral scan resolution', 'MHz']
OBS_ITEMS[TARGET] = ['Target name', None]
OBS_ITEMS[VELOCITY] = ['Center velocity', None]
OBS_ITEMS[VELOCITY_UNIT] = ['Unit for velocity', None]
OBS_ITEMS[VELOCITY_REF] = ['Velocity frame', None]
OBS_ITEMS[VELOCITY_DOP] = ['Velocity convention', None]
OBS_ITEMS[USE_7M] = ['Use 7m array', None]
OBS_ITEMS[USE_TPA] = ['Use Total Power Array?', None]
# Keywords for dictionary in readObservations only
DATA = 'data'
# Keywords for dictionary in getSourceCoordinates()
RA = 'ra'
DEC = 'dec'
ORIGINAL = 'original'
PLOTSIZE = 'plotsize'
ISCOORD = 'iscoord'
NOCOORD = 'nocoord'
# Miscellaneous keywords
COORDS = 'c'
MOSAIC_TYPE_RECTANGLE = 'Rectangle'
MOSAIC_TYPE_CUSTOM = 'Custom'
MOSAIC_TYPES = [MOSAIC_TYPE_RECTANGLE, MOSAIC_TYPE_CUSTOM]
# Miscellaneous parameters
EPSILON = 1e-4
# Set band frequencies
# http://www.almaobservatory.org/en/about-alma/how-does-alma-work/technology/front-end
BAND_UNKNOWN = 'unknown'
BAND_FREQ = dict()
BAND_FREQ['ALMA_RB_01'] = [31, 50]
BAND_FREQ['ALMA_RB_02'] = [67, 90]
BAND_FREQ['ALMA_RB_03'] = [84, 116]
BAND_FREQ['ALMA_RB_04'] = [125, 163]
BAND_FREQ['ALMA_RB_05'] = [158, 211]
BAND_FREQ['ALMA_RB_06'] = [211, 275]
BAND_FREQ['ALMA_RB_07'] = [275, 373]
BAND_FREQ['ALMA_RB_08'] = [385, 500]
BAND_FREQ['ALMA_RB_09'] = [602, 720]
BAND_FREQ['ALMA_RB_10'] = [787, 950]
BAND_FREQ[BAND_UNKNOWN] = [0, 0]
def getSkipRows(filename):
""" Rows to skip in LIST_OF_OBSERVATIONS file
since they contain header information.
"""
if filename.find(LIST_OF_OBSERVATIONS) == 0:
skiprows = list(range(39))
if len(skiprows) == 0:
skiprows.append(1)
else:
skiprows.append(max(skiprows)+2)
elif filename.find('archive') == 0:
skiprows = []
elif filename.find('proposals_cycle9') == 0:
skiprows = []
elif filename.find('proposals_cycle8') == 0:
skiprows = []
elif filename.find('proposals_cycle7') == 0:
skiprows = []
else:
raise Exception('Unknown spreadsheet: %s' % filename)
return skiprows
def getUsableBandwidth(bw, indx=None, throwException=True):
"""
Returns the usable bandwidth in MHz.
Inputs:
bw : nominal bandwidth in MHz.
indx: Row number in the data structure. This is used for informational
purposes only if there is an error in the input bw.
throwException : If True, then throw an exception if bandwidth is invalid.
Otherwise, return None as the usable bandwidth.
Output:
usable bandwidth in MHz
Notes:
The usable bandwidths are given in Table 5.3 of the
Cycle 3 ALMA Technical Handbook.
"""
msg = None
if abs(bw - 62.5) < EPSILON or abs(bw - 58.6) < 0.1:
use_bw = 58.6
elif abs(bw - 125.) < EPSILON or abs(bw - 117.2) < 0.1:
use_bw = 117.2
elif abs(bw - 250.) < EPSILON or abs(bw - 234.4) < 0.1:
use_bw = 234.4
elif abs(bw - 500.) < EPSILON or abs(bw - 468.8) < 0.1:
use_bw = 468.8
elif abs(bw - 1000.) < EPSILON or abs(bw - 937.5) < 0.1:
use_bw = 937.5
elif abs(bw-1875) < EPSILON or abs(bw-2000) < EPSILON or (bw > 1875 and bw < 2000):
use_bw = 1875.
else:
if throwException:
msg = 'Do not recognize bandwidth value: %.1f' % bw
if indx is not None:
msg = 'Do not recognize bandwidth value (%.1f) on row %d' % (bw, getExcelFromIndex(indx))
else:
use_bw = None
if msg is not None:
raise Exception(msg)
else:
return use_bw
def checkEphemerisNames(data):
"""
Check the names for sources that have a name "ephemeris". They
should have been replaced with the actual source names from
the proposals.
"""
# print 'WARNING: Not checking ephemeris objects'
return
# Set codes/sources that are moving objects and names need to be modified.
tuples = [ ]
# Separate tuples
found_tuples = [False] * len(tuples)
codes = []
sgnames = []
for i in range(len(tuples)):
codes.append(tuples[i][0])
sgnames.append(tuples[i][1])
codes = np.array(codes)
sgnames = np.array(sgnames)
# Loop over rows in data structure
for indx in data.index:
# Get information
code = data[PROJECT][indx]
name = str(data[TARGET][indx]) # str is needed since some source names are numeric
# Loop over cases
if name.find('Ephemeris') == 0 and \
abs(data[RA_DEG][indx]) < EPSILON and \
abs(data[DEC_DEG][indx]) < EPSILON:
# Find entry
j = np.where( (codes == code) & (sgnames == name) )[0]
if len(j) == 0:
raise Exception('Could not find project code %s and source %s: see row=%d' % (code, name, getExcelFromIndex(indx)))
elif len(j) > 1:
raise Exception('Found multiple entries for project code %s and source %s' % (code, name))
# Modify name
data.loc[indx, (TARGET)] = tuples[j][2]
found_tuples[j] = True
# All rows should have been identified
if found_tuples.count(False) > 0:
print('Did not find the following ephemeris sources:')
for j, found in enumerate(found_tuples):
if not found:
print('%s %s' % (tuples[j][0], tuples[j][1]))
raise Exception('Not all ephemeris sources were identified.')
def getIndexFromExcel(excel, check=True):
"""
Return the index number of the data structure from the excel row number.
If check = True, the resultant values is checked for errors.
"""
# Set index to account for header row and the index=1 in excel
indx = excel - 2
# Account for skipped rows. Assumes all skipped rows are before data
if SKIPROWS is not None:
indx -= len(SKIPROWS)
# Check for errors
lindex = makeList(indx)
for l in lindex:
if check and OBSERVATIONS is not None and \
(l < 0 or l > OBSERVATIONS[DATA].index.max()):
minRow = getExcelFromIndex(0, check=False)
maxRow = getExcelFromIndex(OBSERVATIONS[DATA].index.max(), check=False)
raise Exception('Excel row (%d) is out of range. Allowed range is between %d and %d' % \
(excel, minRow, maxRow))
return indx
def getExcelFromIndex(indx, check=True):
"""
Return the row number in the excel spreadsheet from the index number
in the data structure.
If check = True, the resultant valueis checked for errors.
"""
# Set index to account for header row and the index=1 in excel
excel = indx + 2
# Account for skipped rows. Assumes all skipped rows are before data
if SKIPROWS is not None:
excel += len(SKIPROWS)
# Check for errors
if check and OBSERVATIONS is not None:
minIndex = 0
maxIndex = OBSERVATIONS[DATA].index.max()
minExcel = getExcelFromIndex(minIndex, check=False)
maxExcel = getExcelFromIndex(maxIndex, check=False)
if excel < minExcel or excel > maxExcel:
raise Exception('Index (%d) is out of range. Allowed range is between %d and %d' % \
(indx, minIndex, maxIndex))
return excel
def getBandNumber(band):
"""
Returns band number from the band string in the spreadsheet.
This assumes the band format is in ALMA_RB_NN.
If there is an error, then 0 is return.
"""
try :
bn = int(band.split('_')[-1])
except:
bn = 0
return bn
def makeList(sources, parse=True, delimiter=','):
""" Convert the variable "sources" into a list.
Input : sources - a variable or list
parse - if True, parse elements with delimiter separator
delimiter - the delimiter when parsing strings
Output: Return value is a list.
If input is a single value, then output is [sources]
If input is a list, the result is copied
Examples:
(1) l = makeList('3c273')
(2) l = makeList('3c273, 3c279')
(3) l = makeList(['3c273','3c279'])
"""
# Return if sources is empty
if sources is None: return None
# Convert to arrays
l = list()
t = [type(sources)]
if str in t or str in t:
l = [sources]
else:
try:
i = len(sources)
l = np.array(sources)
except:
l = np.array([sources])
if len(l) == 0: return None
# Parse string
if parse:
t = list()
for z in l:
if str in [type(z)]:
for zz in z.split(delimiter):
t.append(re.sub(' +',' ', zz.strip()))
else:
t.append(z)
l = np.array(t[:])
return l
def convertHmsString(value, ndec=0, showSeconds=True, delimiter=':'):
"""
Converts floating point to HH:MM:[SS.S]
Inputs
value : floating point number
ndec : number of decimal points to print seconds
showSeconds : If True, show seconds, otherwise just
use HH:MM. Default:True
delimiter : the characters to be used between the numbers.
Output
a string of format hh:mm:[ss.s]
Examples:
convertHmsString(15.0)
convertHmsString(15.0, ndec=2)
convertHmsString(15.0, ndec=2, delimiter='hms')
convertHmsString(15.0, ndec=2, delimiter='dms')
"""
# Set delimiter
spacing = delimiter
if len(spacing) == 1: spacing = [spacing] * 3
# Construct string
t = value
st = str(type(value))
if st.find('int') >= 0 or st.find('float') >= 0:
x = abs(value)
h = int(x)
m = int(60 * (x - h))
sec = 3600.0 * (x - h - m/60.0)
t = str("%.2d" % h) + spacing[0] + str('%.2d' % m)
if showSeconds :
# Add seconds
t += spacing[1]
format = '%0' + str(ndec+3) + '.' + str(ndec) + 'f'
if ndec <= 0: format = '%.2d'
t += str(format % sec)
if spacing[2] not in [' ', ':']: t += spacing[2]
if value < 0.0: t = '-' + t
return t
def computeZ(data, indx):
"""
Computes redshift based on velocity for row indx in data.
"""
# Get velocity
velocity = data[VELOCITY][indx]
velocity_unit = data[VELOCITY_UNIT][indx]
doppler = data[VELOCITY_DOP][indx]
# Convert velocity to km/s
if velocity_unit == 'km/s':
v_kms = velocity
elif velocity_unit == 'm/s':
v_kms = velocity / 1e3
else:
raise Exception('Velocity unit not recognized: %s' % velocity_unit)
# Compute redshift
c_kms = G.c.value / 1e3
if doppler == 'OPTICAL':
z = v_kms / c_kms
elif doppler == 'RADIO':
z = v_kms/c_kms / (1.0 - v_kms/c_kms)
elif doppler == 'RELATIVISTIC':
z = np.sqrt( (1. + v_kms/c_kms) / (1.0 - v_kms/c_kms) ) - 1.0
else:
raise Exception('Doppler frame not recognized (%s) on row %d' % (doppler, getExcelFromIndex(indx)))
# Done
return z
def fwhmPB(freqGHz, diameter):
"""
Returns primary FWHM in arcsec
freqGHz : frequency in GHz
diameter: telescope diameter in meters
See the knowledgebase article for more information:
https://help.almascience.org/index.php?/Knowledgebase/Article/View/90/0/how-do-i-model-the-alma-primary-beam-and-how-can-i-use-that-model-to-obtain-the-sensitivity-profile-for-an-image-mosaic
"""
return 1.13 * G.c.value / (freqGHz*1e9) / diameter / np.pi * 180.0 * 3600.0
def plotMosaic(ax, corners, fc='black', ec='None',
linewidth=2, alpha=0.5, hatch=None):
"""
Plot a rectangular region for a mosaic.
alpha : transperancy of the plot rectangle
ax : pylab plot handle
corners : dictionary containing the corners of the mosaic, specific
in RA,Dec offsets in arcseconds relative to the (0,0). It
is best to use getMosaicCorners() to compute corner
positions.
ec : edge color of the plot rectangle
fc : solid face color of the rectangle
hatch : hatch pattern for the rectangle
linewidth : linewidth of the plot edge
"""
UL = corners['UL']
UR = corners['UR']
BL = corners['BL']
BR = corners['BR']
xypolygon = np.zeros( (4, 2) )
xypolygon[:,0] = np.array([UL[0], UR[0], BR[0], BL[0]])
xypolygon[:,1] = np.array([UL[1], UR[1], BR[1], BL[1]])
p = Polygon(xypolygon, closed=True, fc=fc, ec=ec, alpha=alpha, linewidth=linewidth, hatch=hatch)
result = ax.add_artist(p)
# ax.plot([UL[0], UR[0], BR[0], BL[0], UL[0]],
# [UL[1], UR[1], BR[1], BL[1], UL[1]],
# color=ec, linewidth=linewidth)
return result
def plotPrimaryBeam(ax, xy, freq, diameter, fc='black', ec='black',
alpha=1, linewidth=2):
"""
Plot the primary beam FWHM for an observation at frequency freq in GHz
for a telescope diameter in meters.
ax : pylab plot handle
xy : center of the primary beam
fc : face color of the plot circle
ec : edge color of the plot circle
alpha : transperancy of the plot circle
linewidth : linewidth of the plot edge
"""
radius = 0.5 * fwhmPB(freq, diameter)
c = Circle( xy, radius=radius, fc=fc, ec=ec, alpha=alpha, linewidth=linewidth)
result = ax.add_artist(c)
# color = 'yellow'
# e = Ellipse( (0, 0), width=2*radius, height=2*radius, angle=0,
# color=color, fc=color, ec='black')
# result = ax.add_artist(e)
return result
def getBandColor(freq):
"""
Set a plot color based on the input frequency (GHz),
which a different color is chosen per ALMA band.
"""
# Set band colors for plotting
band_colors = dict()
band_colors['ALMA_RB_01'] = 'gray'
band_colors['ALMA_RB_02'] = 'silver'
band_colors['ALMA_RB_03'] = 'blue'
band_colors['ALMA_RB_04'] = 'peru'
band_colors['ALMA_RB_05'] = 'yellow'
band_colors['ALMA_RB_06'] = 'black'
band_colors['ALMA_RB_07'] = 'green'
band_colors['ALMA_RB_08'] = 'orange'
band_colors['ALMA_RB_09'] = 'magenta'
band_colors['ALMA_RB_10'] = 'red'
band_colors[BAND_UNKNOWN] = 'tan'
# Find color
band_input = None
for key, freq_range in BAND_FREQ.items():
if freq >= freq_range[0] and freq <= freq_range[1]:
band_input = key
break
if band_input is None:
print('Warning: Frequency outside of ALMA bands.')
band_input = BAND_UNKNOWN
# Done
return band_colors[band_input]
def checkObservations(obsdata, isdata=False):
"""
Check that the user-supplied observation seems reasonable.
Only a light check is done.
obsdata is either "observations" from readObservations (isdata=False)
or observations[DATA] (if isdata=True)
"""
# Checks if isdata=False
if isdata:
# Must be panda structure
if pd.core.frame.DataFrame not in [type(obsdata)]:
raise Exception('observations must be a dictionary')
# Get keys
keys = obsdata.keys().tolist()
msg = 'data'
else:
# Must have two keywords, if observations
if dict not in [type(obsdata)]:
raise Exception('observations must be a dictionary')
# Check keys
for key in [DATA, COORDS]:
if key not in obsdata.has_key(key):
raise Exception('%s keyword not found in observations' % key)
# Get keys
keys = obsdata[DATA].keys().tolist()
msg = 'observations[%s]' % DATA
# Check entries in OBS_ITEMS
for key in OBS_ITEMS.keys():
if keys.count(key) != 1:
raise Exception('Keyword "%s" not found in %s' % (key, msg))
# Check spectral keywords
for w in SPW_FREQ.keys():
if SPW_FREQ[w] not in keys:
raise Exception('Keyword "%s" not found in %s' % (SPW_FREQ[w], msg))
if SPW_BW[w] not in keys:
raise Exception('Keyword "%s" not found in %s' % (SPW_BW[w], msg))
if SPW_RES[w] not in keys:
raise Exception('Keyword "%s" not found in %s' % (SPW_RES[w], msg))
# Done
return True
def checkData(data, verbose=True, spreadsheet=None):
"""
Run checks on the data read from the Excel spreadsheet to
catch any obvious anomalies.
"""
# Message
if verbose:
print('Running checks on data')
# Initialize
error = False
# Check keywords
if verbose:
print(' ... checking keywords in data structure')
checkObservations(data, isdata=True)
# Check RA
if verbose:
print(' ... checking right ascensions')
j = np.where( (data[RA_DEG] < 0) | (data[RA_DEG] > 360.0) )[0]
if len(j) > 0:
error = True
if verbose: print(' --- invalid right ascension in %d rows' % len(j))
# Check Declination
if verbose:
if verbose: print(' ... checking declinations')
j = np.where( (data[DEC_DEG] < -90.) | (data[DEC_DEG] > 90.0) )[0]
if len(j) > 0:
error = True
if verbose:
print(' --- invalid declination in %d rows' % len(j))
# Check mosaic offsets
if verbose:
print(' ... checking mosaic parameters are present for rectangular mosaics')
# Check mosaic system
j = (data[MOS_COORD].isnull() == True) & (data[IS_MOSAIC] == MOSAIC_TYPE_RECTANGLE)
if np.sum(j) > 0:
error = True
if verbose:
print(' --- found %d mosaics where system was not set' % len(j))
# Check PA
j = (data[MOS_PA].isnull() == True) & (data[IS_MOSAIC] == MOSAIC_TYPE_RECTANGLE)
if np.sum(j) > 0:
error = True
if verbose:
print(' --- found %d mosaics where PA was not set' % np.sum(j))
# Check length
j = (data[MOS_LENGTH].isnull() == True) & (data[IS_MOSAIC] == MOSAIC_TYPE_RECTANGLE)
if np.sum(j) > 0:
error = True
if verbose:
print(' --- found %d mosaics where length was not set' % np.sum(j))
j = (data[MOS_LENGTH] <= 0) & (data[IS_MOSAIC] == MOSAIC_TYPE_RECTANGLE)
if np.sum(j) > 0:
error = True
if verbose:
print(' --- found %d mosaics where length was zero' % np.sum(j))
# Check width
j = (data[MOS_WIDTH].isnull() == True) & (data[IS_MOSAIC] == MOSAIC_TYPE_RECTANGLE)
if np.sum(j) > 0:
error = True
if verbose:
print(' --- found %d mosaics where width was not set' % np.sum(j))
j = (data[MOS_WIDTH] <= 0) & (data[IS_MOSAIC] == MOSAIC_TYPE_RECTANGLE)
if np.sum(j) > 0:
error = True
if verbose:
print(' --- found %d mosaics where width was zero' % np.sum(j))
# Check if reference sensitivity is valid
if verbose:
print(' ... checking values of requested sensitivity are non-zero')
mask = (data[REQ_SENS] <= 0.) | (np.isnan(data[REQ_SENS]) == True)
if mask.sum() > 0:
error = True
if verbose:
print(' --- invalid sensitivity values in %d rows' % len(j))
data.loc[mask, (REQ_SENS)] = 1.0
# Check if reference bandwidth is valid
if verbose:
print(' ... checking values of reference bandwidth are non-zero')
j = (data[REF_BW] <= 0.) | (data[REF_BW].isnull() == True)
if np.sum(j) > 0:
error = True
print(data[PROJECT][j])
if verbose:
print(' --- invalid reference bandwidth values in %d rows' % np.sum(j))
# Check spectral scans
if verbose:
print(' ... checking spectral scan frequencies and bandwidths')
for keys in [ [SPS_START, 'starting frequency'],
[SPS_END, 'ending frequency'],
[SPS_BW, 'bandwidth'],
[SPS_SPW_RES, 'spectral resolution'],
]:
j = ((data[keys[0]].isnull() == True) | (data[keys[0]] <= 0)) & (data[IS_SPECTRAL] == True)
if np.sum(j) > 0:
error = True
if verbose:
print(' --- spectral scan %s is not set in %d rows' % (keys[1], np.sum(j)))
# Check that the frequencies match the bands
if verbose:
print(' ... checking values of frequencies and bandwidths')
for indx in data.index:
# Get range of frequencies defined this ALMA band
freq_range = BAND_FREQ[data[BAND][indx]]
# Check reference frequency
if data[REF_FREQ][indx] < freq_range[0] or \
data[REF_FREQ][indx] > freq_range[1]:
error = True
if verbose:
print(' --- reference frequency out of range in row %d' % getExcelFromIndex(indx, check=False))
# Check spectral scan frequencies
if data[IS_SPECTRAL][indx]:
# Starting frequency
if data[SPS_START][indx] < freq_range[0] or \
data[SPS_START][indx] > freq_range[1]:
error = True
if verbose:
print(' --- spectral scan starting frequency out of range in row %d' % getExcelFromIndex(indx, check=False))
# Ending frequency
if data[SPS_END][indx] < freq_range[0] or \
data[SPS_END][indx] > freq_range[1]:
error = True
if verbose:
print(' --- spectral scan ending frequency out of range in row %d' % getExcelFromIndex(indx))
else:
# Check each window
for w in list(SPW_FREQ.keys()):
# Check frequencies
nu = data[SPW_FREQ[w]][indx]
if np.isfinite(nu) and (nu < freq_range[0] or nu > freq_range[1]):
error = True
if verbose:
print(' --- spectral window frequency out of range in window %d on row %d' % \
(w, getExcelFromIndex(indx, check=False)))
# Both frequencies and bandwidths need to be set
bw = data[SPW_BW[w]][indx]
if (np.isfinite(nu) and np.isnan(bw)) or (np.isfinite(bw) and np.isnan(nu)):
error = True
if verbose:
print(' --- frequencies and bandwidths are not both set in window %d on row %d' % \
(w, getExcelFromIndex(indx, check=False)))
# Check usable bandwidth
if np.isfinite(nu) and getUsableBandwidth(bw, indx=indx) is None:
error = True
if verbose:
print(' --- bandwidth is not recognized in window %d on row %d' % \
(w, getExcelFromIndex(indx, check=False)))
# Exit if there were any errors
if verbose:
print(' ... done')
if error:
if spreadsheet is None:
msg = 'The spreadsheet contains unexpected errors.'
else:
msg = 'The spreadsheet %s contains unexpected errors.' % spreadsheet
if not verbose:
msg += '\nTry with verbose=True to see further information'
raise Exception(msg)
# Done
return
def setSensitivitymJy(data, indx):
""" Compute sensitivity in mJy """
# Initialize
unit = data[REQ_SENS_UNIT][indx]
value = None
# Loop over possible units
if unit == 'mJy':
value = data[REQ_SENS][indx]
elif unit == 'Jy':
value = data[REQ_SENS][indx] * 1e3
elif unit in ['mK', 'K']:
# Set scale factor to convert to mK
scale = None
if unit == 'mK':
scale = 1.
elif unit == 'K':
scale = 1e3
else:
raise Exception('Un-recognized unit (%s) on row %d' % (unit, getExcelFromIndex(indx)))
# Get values needed to convert sensitivity to mJy
tb_mK = data[REQ_SENS][indx] * scale
angres = data[ANG_RES][indx]
freq = data[REF_FREQ][indx]
# Check values
if tb_mK <= 0 or angres <= 0 or freq <= 0:
print(indx)
print(tb_mK, angres, freq)
raise Exception('Error computing sensitivity on row %s' % (getExcelFromIndex(indx)))
# Compute wavelength
value = tb_mK * mjypermk(freq, angres, freq)
else:
raise Exception('Un-recognized unit (%s) on row %d' % (unit, getExcelFromIndex(indx)))
# Check
if value is None or value <= 0 or np.isnan(value):
raise Exception('Error setting sensitivity (%s) for row %d' % (str(value), getExcelFromIndex(indx)))
# Done
return value
def getFinestResolution(data, indx):
""" Return finest resolution in the spectral setup in MHz """
if data[IS_SPECTRAL][indx]:
resolution = data[SPS_SPW_RES][indx]
else:
# Get frequencies of each window
resolution = None
for w in list(SPW_FREQ.keys()):
# Is this a valid window?
if np.isnan(data[SPW_FREQ[w]][indx]):
continue
# Set resolution
res = data[SPW_RES[w]][indx]
# Get minimum
if resolution is None or res < resolution:
resolution = res
# Check
if resolution is None or resolution <= 0 or np.isnan(resolution):
raise Exception('Error setting resolution for row %d' % getExcelFromIndex(indx))
# Done
return resolution
def getLargestBandwidth(data, indx):
""" Return largest bandwidth in the spectral setup in MHz """
if data[IS_SPECTRAL][indx]:
bandwidth = data[SPS_BW][indx]
else:
# Get frequencies of each window
bandwidth = None
for w in list(SPW_FREQ.keys()):
# Is this a valid window?
if np.isnan(data[SPW_FREQ[w]][indx]):
continue
# Set resolution
bw = data[SPW_BW[w]][indx]
# Get minimum
if bandwidth is None or bw > bandwidth:
bandwidth = bw
# Check
if bandwidth is None or bandwidth <= 0 or np.isnan(bandwidth):
raise Exception('Error setting largest bandwidth for row %d' % getExcelFromIndex(indx))
# Done
return bandwidth
def setReferenceBandwidth(data, indx):
""" Set reference bandwidth in Hz """
# Initialize
measure = data[REF_BW_MEASURE][indx]
value = data[REF_BW][indx]
# Loop over possible measures
if measure == 'AggregateBandWidth':
value = computeAggregateBandwidth(data, indx)
elif measure == 'FinestResolution':
value = getFinestResolution(data, indx)
elif measure == 'LargestWindowBandWidth':
value = getLargestBandwidth(data, indx)
elif measure == 'RepresentativeWindowBandWidth':
value = data[REP_WIN_BW][indx]
elif measure == 'RepresentativeWindowResolution':
value = data[REP_WIN_RES][indx]
elif measure == 'User':
pass
else:
raise Exception('Un-recognized bandwidth measure (%s) on row %d' % (measure, getExcelFromIndex(indx)))
# Check
if value is None or value <= 0 or np.isnan(value):
raise Exception('Error setting reference bandwidth for row %d' % getExcelFromIndex(indx))
# Done
return value
def computeCoords(data):
""" Computes astropy coordinates """
coords = SkyCoord(data[RA_DEG].values*astropy.units.degree,
data[DEC_DEG].values*astropy.units.degree, frame='icrs')
return coords
def computeMeanFrequency(data, indx):
"""
Computes mean frequency of a correlator setup.
In computing the mean frequency, the frequency is weighted by
the bandwidth.
"""
if data[IS_SPECTRAL][indx]:
meanfreq = 0.5 * (data[SPS_START][indx] + data[SPS_END][indx])
else:
# Initialize
sumf = 0.
sumbw = 0.
# Loop over spectral windows
for win, key in SPW_FREQ.items():
# Skip if not a valid window
if np.isnan(data[key][indx]):
continue
# Sum
nu = data[key][indx]
bw = data[SPW_BW[win]][indx]
sumf += nu*bw
sumbw += bw
# Compute mean frequency
if sumbw == 0:
raise Exception('Bug for indx=%d, %s, %s...' % \
(indx, data[PROJECT][indx], data[TARGET]))
meanfreq = sumf / sumbw
# Done
return meanfreq
def readObservations(input=LIST_OF_OBSERVATIONS, verbose=True,
removeSolar=True, removeVLBI=True,
onlySolar=False, correctMosaicSpacing=True,
clearSolar=False):
""" Wrapper to readObservation() """
# Initialize data structure
data = None
# Loop over input files
for fn in makeList(input):
# Read
tmp = readObservingFile(fn, verbose=verbose, removeSolar=removeSolar, removeVLBI=removeVLBI, onlySolar=onlySolar, correctMosaicSpacing=correctMosaicSpacing, clearSolar=clearSolar)
# Concatenate files
if data is None:
data = tmp
else:
data = data.append(tmp, ignore_index=True)
# Compute RA/DEC
if verbose:
print(' ... converting RA/DEC to astropy sky coordinates')
coords = computeCoords(data)
# Done
print('NOTE: This script only checks on-going observations.')
print(' Completed observations are listed in the ALMA archive.')
return {DATA: data, COORDS:coords}
def readObservingFile(input=LIST_OF_OBSERVATIONS, verbose=True,
removeSolar=True, removeVLBI=True, onlySolar=False,
correctMosaicSpacing=False, clearSolar=False):
"""
Read in observations using pandas.
A global variable called SKIPROWS is set indicating which rows
were skipped in reading the data file.
Inputs:
input : root name for the file containing the existing and
scheduled observations. The .csv or .xlsx extension can
be omitted.
correctMosaicSpacing : If True, corrects an error in Ignacio's
spreadsheet where the mosaic spacing was too fine.
clearSolar : If True, set coordinates of solar observations to (0,0) and set name to Sun.
Output:
A python dictionary with the following entries:
DATA : a panda data set containing the spreadsheet
COORDS : astropy sky coordinates for each source in DATA
"""
# Read in observations. Input list may either be an excel spreadsheet or
# a csv file. Both are tried, with the .csv first and then the .xlsx.
data = None
input_without_extension = input.replace('.xlsx','').replace('.xls', '').replace('.csv', '')
if verbose:
print('Reading observations')
possible_inputs_files = ['%s.%s' % (input_without_extension, ext) for ext in ['csv','xlsx']]
# Try reading file
for inputFile in possible_inputs_files:
if '.csv' in inputFile:
print('Using csv file...')
if os.path.exists(inputFile):
# Get number of rows to skip
skiprows = getSkipRows(inputFile)
data = pd.read_csv(inputFile, skiprows=skiprows, header=0, low_memory=False)
elif '.xls' in inputFile:
print('Using excel file...')
if os.path.exists(inputFile):
# Get number of rows to skip
skiprows = getSkipRows(inputFile)
data = pd.read_excel(inputFile, skiprows=skiprows, header=0)
else:
raise Exception('Do not recognize extension for file %s' % input)
# Did we succeed?
if data is not None:
if verbose:
print(' ... read %d rows in %s' % (data.shape[0], inputFile))
global SKIPROWS
SKIPROWS = skiprows
break
# Check if data were read
if data is None:
raise Exception('Could not read data. Check that the input file %s exists with either a .csv for .xls extension.' % input)
# Delete column containing RA/DEC in string form since RA/DEC may
# be modified below.
del data[RA_HMS]
del data[DEC_DMS]
# Copy RA/DEC to original values before apply mosaic
data[RA_DEG_ORIG] = data[RA_DEG]
data[DEC_DEG_ORIG] = data[DEC_DEG]
# Set variable indicating if observation is a spectral scan
data[IS_SPECTRAL] = (data[SPS_START].notnull() == True)
# Add flag if ACA standalone
if not IS_ACA_STANDALONE in data.columns:
print(' ... WARNING: assuming these are not ACA standalone observations')
data[IS_ACA_STANDALONE] = np.array([False] * data.shape[0])
# else:
# data[USE_12M] = np.array([True] * data.shape[0])
# Remove (or include) solar observations, if needed. If we are not removing
# them, then we need to check sensitivities and bandwidth are non-zero.
if IS_SOLAR in data.columns:
if removeSolar:
# Find how many there are
nrows = data[data[IS_SOLAR] == True].shape[0]
nprojects = data[PROJECT][data[IS_SOLAR] == True].unique().shape[0]
print(' ... removing %d solar observations in %d projects' % (nrows, nprojects))
data = data[data[IS_SOLAR] == False]
else:
# Reset if needed
if clearSolar:
# Message
print(' ... resetting coordinates and names for solar observations')
# Get solar observations
mask = (data[IS_SOLAR])
# Reset name
# data.loc[mask, TARGET] = 'Sun'
# Reset coordinates
data.loc[mask, RA_DEG] = 0
data.loc[mask, DEC_DEG] = 0
data.loc[mask, RA_DEG_ORIG] = 0
data.loc[mask, DEC_DEG_ORIG] = 0
# Check sensitivity
mask = (data[IS_SOLAR] == True) & (data[REQ_SENS] == 0)
data.loc[mask, REQ_SENS] = 0.1
# Check bandwidth
mask = (data[IS_SOLAR] == True) & (data[REF_BW]==0)
data.loc[mask, REF_BW] = 7500.0
# Keep only solar if needed
if onlySolar:
print('Only keeping solar observations')
data = data[data[IS_SOLAR] == True]
# Remove VLBI observations, if needed. If we are not removing them,
# then we need to check sensitivities and bandwidth are non-zero.
if IS_VLBI in data.columns:
if removeSolar:
# Find how many there are
nrows = data[data[IS_VLBI] == True].shape[0]
nprojects = data[PROJECT][data[IS_VLBI] == True].unique().shape[0]
print(' ... removing %d VLBI observations in %d projects' % (nrows, nprojects))
data = data[data[IS_VLBI] == False]
else:
# Check sensitivity
j = (data[IS_VLBI] == True) & (data[REQ_SENS]==0)
data.loc[j, REQ_SENS] = 0.1
# Check bandwidth
j = (data[IS_VLBI] == True) & (data[REF_BW]==0)
data.loc[j, REF_BW] = 7500.0
# Check angular resolution
j = (data[IS_VLBI] == True) & (data[ANG_RES]==0)
data.loc[j, ANG_RES] = 0.00001
# Convert sensitivity to mJy
if REQ_SENS_UNIT in data.columns:
# Initialize
sens_mjy = np.zeros(data.shape[0])
# Recompute sensitivity
for i, indx in enumerate(data.index):
sens_mjy[i] = setSensitivitymJy(data, indx)
# Save
data.loc[:, (REQ_SENS)] = sens_mjy
# Deletet unit column
del data[REQ_SENS_UNIT]
# Convert reference frequency width to MHz
if REF_BW_UNIT in data.columns:
# Only support GHz
nrows = data[data[REF_BW_UNIT] != 'GHz'].shape[0]
if nrows > 0:
raise Exception('%d values for reference frequency bandwidth are not GHz' % nrows)
# Convert to MHz
data[REF_BW] *= 1000
# Delete unit
data[REF_BW_UNIT]
# Set reference bandwidth
if REF_BW_MEASURE in data.columns:
# Initialize
ref_bw = np.array(data[REF_BW].values, dtype=float)
# Set reference bandwidth
for i, indx in enumerate(data.index):
# if ref_bw[i] <= 0:
ref_bw[i] = setReferenceBandwidth(data, indx)
# Save
data.loc[:, (REF_BW)] = ref_bw
# Deletet measure column
del data[REF_BW_MEASURE]
# Set science goal, if needed
if SG_NAME not in data.columns:
data[SG_NAME] = np.array([''] * data.shape[0], dtype='str')
if SG_FULLNAME not in data.columns:
data[SG_FULLNAME] = np.array([''] * data.shape[0], dtype='str')
# Set PI name to blank if column is not present
if PINAME not in data.columns:
data[PINAME] = np.array([''] * data.shape[0], dtype='str')
# Set moving objects
data[MOVING_OBJECT] = (data[RA_DEG_ORIG].abs() < EPSILON) & \
(data[DEC_DEG_ORIG].abs() < EPSILON)
# Set large programs
data[IS_LP] = data[PROJECT].str.endswith(".L")
# Set excel line
cnst = 2
if skiprows is not None:
cnst += len(skiprows)
data[EXCEL_LINE] = data.index + cnst
# Correct the RA/DEC for sources with an RA/DEC offset. Note the offsets
# may be given in galactic coordinates for mosaics.
if verbose:
print(' ... correcting centroid RA/DEC in mosaics for offsets')
# Assume J2000 if no coordinate system provided
j = (data[MOS_COORD].isnull() == True)
data.loc[j, MOS_COORD] = 'J2000'
# Get unique coordinate sysmtes
coord_system = data[MOS_COORD][data[MOS_COORD].notnull() == True].unique()
# Correction depends on the coordinate system
for cs in coord_system:
# Find observations with this system
j = (data[MOS_COORD] == cs) & \
(data[LAT_OFFSET].notnull() == True) & \
(data[LON_OFFSET].notnull() == True) & \
( (data[LON_OFFSET].abs() > EPSILON) |
(data[LAT_OFFSET].abs() > EPSILON))
# Get data
x = data.loc[j, RA_DEG].values
y = data.loc[j, DEC_DEG].values
dx = data.loc[j, LON_OFFSET].values
dy = data.loc[j, LAT_OFFSET].values
# Correct coordinates
if cs in ['ICRS', 'icrs', 'J2000']:
# Message
# if verbose:
# print ' --- correcting %4d positions for equatorial offsets' % len(j)
# Correct RA/Dec
new_ra = x + dx / 3600.0 / np.cos(y / 180.0 * np.pi)
new_dec = y + dy / 3600.0
pass
elif cs == 'galactic':
# Message
# if verbose:
# print ' --- correcting %4d mosaic positions for galactic offsets' % np.sum(j)
# Convert mosaic center to galactic coordinates
c = SkyCoord(ra=x, dec=y, frame='icrs', unit='degree')
glon = c.galactic.l.deg
glat = c.galactic.b.deg
# Add offset
glon += dx / 3600.0 / np.cos(glat / 180.0 * np.pi)
glat += dy / 3600.0
# Convert back to RA/DEC
c = SkyCoord(glon, glat, unit='degree', frame='galactic')
new_ra = c.icrs.ra.deg
new_dec = c.icrs.dec.deg
else:
raise Exception('Mosaic coordinate system not implemented: %s' % cs)
# Check right ascension
l = np.where(new_ra < 0)[0]
if len(l) > 0:
new_ra[l] += 360.0
l = np.where(new_ra > 360)[0]
if len(l) > 0:
new_ra[l] -= 360.0
# Check declination
if np.any(np.abs(new_dec) > 90.0):
raise Exception('Error setting mosaic declination')
# Save
data.loc[j, RA_DEG] = new_ra
data.loc[j, DEC_DEG] = new_dec
# Convert RA/DEC to degree in sky coordinates
# if verbose:
# print ' ... converting RA/DEC to astropy sky coordinates'
# coords = SkyCoord(data[RA_DEG]*astropy.units.degree,
# data[DEC_DEG]*astropy.units.degree, frame='icrs')
# Set the velocity unit.
# Until now, all SG have used km/s as the velocity unit. but I want to keep
# the flexibility in case m/s is used in the future.
if VELOCITY_UNIT in data.columns:
nrows = data[data[VELOCITY_UNIT] != 'km/s'].shape[0]
if nrows > 0:
raise Exception('%d values for velocity unit are not km/s' % nrows)
else:
data[VELOCITY_UNIT] = np.array(['km/s'] * data.shape[0])
# Compute redshift
if verbose:
print(' ... computing redshifts')
redshifts = np.zeros(data.shape[0])
for i, indx in enumerate(data.index):
redshifts[i] = computeZ(data, indx)
# Correct frequencies for spectral windows that have rest frequencies
if verbose:
print(' ... correcting rest frequencies to sky frequencies')
jndx = dict()
znu = dict()
for w in list(SPW_FREQ.keys()):
jndx[w] = []
znu[w] = []
for i, indx in enumerate(data.index):
# Determine if windows are doppler corrected or not
if data[IS_SPECTRAL][indx]:
if data[IS_SPW_SKY_FREQ][indx] == True:
pass
else:
raise Exception('Not expecting spectral scan to be rest frequencies: excel line = %d' % getExcelFromIndex(indx, check=False))
else:
# Determine if windows are sky or rest frequencies
if data[IS_SPW_SKY_FREQ][indx] not in [0, 1, False, True]:
raise Exception('Error reading IS_SPW_SKY_FREQ on row %d' % getExcelFromIndex(indx))
elif not data[IS_SPW_SKY_FREQ][indx]:
# Loop over windows and correct frequencies
for w in list(SPW_FREQ.keys()):
nu = data[SPW_FREQ[w]][indx]
if np.isfinite(nu):
jndx[w].append(indx)
znu[w].append(nu / (1.0 + redshifts[i]))
for w in list(SPW_FREQ.keys()):
if len(jndx[w]) > 0:
data.loc[jndx[w],(SPW_FREQ[w])] = znu[w]
# Since all windows are now doppler corrected, delete IS_SKY_FREQ columns
del data[IS_SPW_SKY_FREQ]
# If reference frequency is zero, then set it to the mean frequency
# This has to be done AFTER correcting the frequencies for the doppler shift
j = (data[REF_FREQ] == 0)
n = np.sum(j)
if n > 0:
# Print message
print(' ... correcting reference frequency on %d rows' % n)
# Make sure required variables are present
meanfreq = np.zeros(n)
for i, indx in enumerate(j[j==True].index):
meanfreq[i] = computeMeanFrequency(data, indx)
data.loc[j, REF_FREQ] = meanfreq
# Get FWHM primary beam size
mask = (data[REF_FREQ] < 0) | (data[REF_FREQ].isnull())
if mask.sum() > 0:
raise Exception('Invalid frequencies in spreadsheet')
diameter = np.array([12.] * data.shape[0])
j = np.where( (data[IS_ACA_STANDALONE] == True) )
diameter[j] = 7.0
data[FWHM_PB] = fwhmPB(data[REF_FREQ], diameter)
# Set largest size in arcseconds, including mosaics and FWHM of primary beam
data[MAX_SIZE] = data[ [MOS_LENGTH, MOS_WIDTH, FWHM_PB] ].max(axis=1)
# If IS_MOSAIC is all nan, then convert it to string
n = data[IS_MOSAIC][data[IS_MOSAIC].notnull()].count()
if n == 0:
data[IS_MOSAIC] = np.array(['N/A'] * data.shape[0])
# Compute mosaic area
if verbose:
print(' ... computing area of rectangular mosaics')
data[MOS_AREA] = np.zeros_like(data[MOS_LENGTH])
mask = (data[IS_MOSAIC] == MOSAIC_TYPE_RECTANGLE)
if mask.sum() > 0:
print(' ... computing mosaic area of %d mosaics' % mask.sum())
data.loc[mask, MOS_AREA] = data[MOS_LENGTH][mask] * data[MOS_WIDTH][mask] / 3600.0
# Get source name with mosaic extension for custom mosaics
names = [''] * data.shape[0]
for j, indx in enumerate(data.index):
# Get target name in spread sheet
name = str(data[TARGET][indx])
# Is this a custom mosaic?
if isObsMosaic(data, indx, mtype=MOSAIC_TYPE_CUSTOM) and name.find('None') != 0:
# Get name without the extension
i = name.rfind('-')
# if i <= 0 or not name.endswith('(cm)'):
# raise Exception('Unknown extension for custom mosaic: %s on row %d' % (name, getExcelFromIndex(indx)))
name = name[:i]
# Save
names[j] = name
data[TARGET_STRIPPED] = names
# Correct mosaic spacings.
# The ACA mosaic spacings are incorrect in Ignacio's spreadsheet. I do not
# the correct spacing, so this is potentially in error.
if correctMosaicSpacing:
# Message
print(' ... correcting ACA mosaic spacings')
# Select data
mask = (data[IS_MOSAIC] == MOSAIC_TYPE_RECTANGLE) & (data[IS_ACA_STANDALONE])
# Correct
data.loc[mask, MOS_SPACING] = 0.51093 * data[FWHM_PB][mask]
# Modify ephemeris names
if verbose:
print(' ... checking names for ephemeris sources')
checkEphemerisNames(data)
# Message
if verbose:
print(' ... done')
# Run checks on the data structure
checkData(data, verbose=verbose, spreadsheet=input)
# Done
# return {DATA: data, COORDS:coords}
return data
def computeAggregateBandwidth(data, indx):
""" Compute aggregate bandwidth in MHz for indx """
if data[IS_SPECTRAL][indx]:
sps_bw = getUsableBandwidth(data[SPS_BW][indx])
nwin = 4
aggregateBandwidth = nwin * sps_bw
else:
# Get frequencies of each window
windows = list(SPW_FREQ.keys())
nu1 = np.zeros(len(windows))
nu2 = np.zeros(len(windows))
use = np.zeros(len(windows), dtype=bool)
for i, w in enumerate(windows):
# Is this a valid window?
if np.isnan(data[SPW_FREQ[w]][indx]):
use[i] = False
continue
freq = data[SPW_FREQ[w]][indx] * 1e3
bw = getUsableBandwidth(data[SPW_BW[w]][indx])
use[i] = True
f1 = freq - 0.5 * bw
f2 = freq + 0.5 * bw
nu1[i] = min([f1, f2])
nu2[i] = max([f1, f2])
# Sort by decreasing bandwidth
j = np.where(nu1 > 0)
nu1 = nu1[j]
nu2 = nu2[j]
bw = nu2 - nu1
iarg = np.argsort(bw)[::-1]
nu1 = nu1[iarg]
nu2 = nu2[iarg]
# Compute aggregate bandwidth
j = np.where(nu1 > 0)
for iw in range(nu1.size):
# Skip if not using the window
if not use[iw]:
continue
# Check other windows
for jw in range(iw+1, nu1.size):
# Skip name windows
if not use[jw]:
continue
# Check frequency range
if nu1[jw] >= nu1[iw] and nu2[jw] <= nu2[iw]:
nu1[jw] = 0
nu2[jw] = 0
use[jw] = False
elif nu1[jw] < nu1[iw] and nu2[jw] > nu2[iw]:
raise Exception('Should not be here since I sorted by decreasing bandwidth. Indx=%d' % indx)
elif nu1[jw] >= nu2[iw] or nu2[jw] <= nu1[iw]:
pass
elif nu1[jw] >= nu1[iw] and nu1[jw] <= nu2[iw] and nu2[jw] >= nu2[iw]:
nu1[jw] = nu2[iw]
if nu1[jw] == nu2[jw]:
use[jw] = False
elif nu2[jw] < nu1[jw]:
raise Exception('Error setting bandwidth')
elif nu1[jw] <= nu1[iw] and nu2[jw] >= nu1[iw] and nu2[jw] <= nu2[iw]:
nu2[jw] = nu1[iw]
if nu1[jw] == nu2[jw]:
use[jw] = False
elif nu2[jw] < nu1[jw]:
raise Exception('Error setting bandwidth')
else:
raise Exception('Error computing aggregate bandwidth')
# Compute aggregate bandwidth in MHz
aggregateBandwidth = np.sum(nu2 - nu1)
# Done
return aggregateBandwidth
def computeContinuumSensitivity(data, indx):
"""
Compute the continuum sensitivity for a single pointing
For spectral scans, the sensitivity is computed for one tuning,
not the full spectral scan.
Inputs:
data : data structure from readObservations()
indx : Index number in data
Output:
A dictionary with the following keys:
'bw' : aggregrate bandwidth in MHz
'rmsmJy': continuum rms in mJy
'rmsmK' : continuum rms in mK for the specified angular
resolution and reference refrequencin data
"""
# Gather sensitivity information
req_sen = data[REQ_SENS][indx]
ref_freq = data[REF_FREQ][indx]
ref_bw = data[REF_BW][indx]
angres = data[ANG_RES][indx]
# Compute bandwidth if single tuning or spectral scans
aggregateBandwidth = computeAggregateBandwidth(data, indx)
# Compute continuum sensitivity
if ref_bw <= 0 or aggregateBandwidth <= 0:
rmsContinuum = np.nan
raise Exception('Error computing continuum sensitivity')
else:
rmsContinuum_mJy = req_sen * np.sqrt(ref_bw / aggregateBandwidth)
rmsContinuum_mK = rmsContinuum_mJy / mjypermk(ref_freq, angres, ref_freq)
# Done
return {'bw': aggregateBandwidth, 'rmsmJy': rmsContinuum_mJy,
'rmsmK': rmsContinuum_mK}
def printRowProject(data, indx, spaces=''):
"""
Print project information for entry indx in "data".
"""
# Functions to print data
def printEntries(obsdata, indx, items):
for key in items:
a = OBS_ITEMS[key]
printEntry(obsdata, indx, key, a[0], a[1])
def printEntry(obsdata, indx, key, label, unit):
# Set unit
sunit = unit
if sunit is None: sunit = ''
# Set value - there must be a better way!
if key in [FWHM_PB, REF_FREQ, REF_BW]:
svalue = '%.1f' % obsdata[key][indx]
elif key in [MOS_LENGTH, MOS_WIDTH]:
svalue = '%.1f' % obsdata[key][indx]
elif key in [MOS_AREA]:
svalue = '%.3f' % obsdata[key][indx]
elif key in [REQ_SENS, ANG_RES]:
svalue = '%.3f' % obsdata[key][indx]
elif key in [RA_DEG]:
svalue = convertHmsString(obsdata[key][indx] / 15.0, delimiter=':', ndec=2)
elif key in [DEC_DEG]:
svalue = convertHmsString(obsdata[key][indx], delimiter=':', ndec=2)
elif key == BAND:
svalue = '%d' % getBandNumber(obsdata[key][indx])
elif key in [POLARIZATION]:
svalue = obsdata[key][indx].lower()
elif key in [MOS_SPACING]:
svalue = '%.1f' % obsdata[key][indx]
elif key == IS_MOSAIC:
if isObsMosaic(obsdata, indx, mtype=MOSAIC_TYPE_RECTANGLE):
svalue = 'Rectangular mosaic'
elif isObsMosaic(obsdata, indx, mtype=MOSAIC_TYPE_CUSTOM):
svalue = 'Custom mosaic'
elif obsdata[IS_MOSAIC][indx] == 'N/A' or np.isnan(obsdata[IS_MOSAIC][indx]):
svalue = 'False'
else:
raise Exception('Unknown type of mosaic: %s' % data[IS_MOSAIC][indx])
else:
svalue = '%s' % obsdata[key][indx]
# Print item
print('%s%-22s %-13s %s' % (spaces, label, svalue, sunit))
# Add some space
print('')
print('')
# Print header
lineExcel = getExcelFromIndex(indx)
print('%sSource information for spreadsheet line %d (project = %s)' % \
(spaces, lineExcel, data[PROJECT][indx]))
# Print source information
items = [TARGET,
RA_DEG,
DEC_DEG,
]
printEntries(data, indx, items)
# Print observing parameters
print('')
print('%sObserving parameters' % spaces)
items = [BAND,
FWHM_PB,
ANG_RES,
LAS,
]
printEntries(data, indx, items)
# Print observing modes
print('')
print('%sObserving modes' % spaces)
items = [POLARIZATION, USE_7M, USE_TPA, IS_SPECTRAL]
printEntries(data, indx, items)
# Print mosaic information
print('')
print('%sMosaic information' % spaces)
items = [IS_MOSAIC]
if isObsMosaic(data, indx, mtype=MOSAIC_TYPE_RECTANGLE):
items.extend([MOS_AREA, MOS_LENGTH, MOS_WIDTH, MOS_PA, MOS_SPACING, MOS_COORD])
printEntries(data, indx, items)
# Print continuum sensitivity if not a spectral scan
if not data[IS_SPECTRAL][indx]:
print('')
print('%sEstimated continuum sensitivity' % spaces)
result = computeContinuumSensitivity(data, indx)
print('%s%-22s %-13.0f %s' % \
(spaces, 'Aggregate bandwidth', result['bw'], 'MHz'))
if np.isnan(result['rmsmJy']):
print('%s%-22s %-13s %s' % \
(spaces, 'Continuum RMS', 'N/A', ''))
else:
print('%s%-22s %-13.3f %s' % \
(spaces, 'Continuum RMS', result['rmsmJy'], 'mJy'))
print('%s%-22s %-13.1f %s' % \
(spaces, 'Continuum RMS', result['rmsmK'], 'mK'))
def mjypermk(freq_ghz, angres_ref, freq_ref_ghz):
"""
Returns the conversion factor to convert mK to mJy.
"""
omega_ref = np.pi / 4.0 / np.log(2.0) * (angres_ref / 3600.0 / 180.0 * np.pi)**2
omega = omega_ref * (freq_ref_ghz / freq_ghz)**2
wavelength = G.c.value / (freq_ghz * 1e9)
constant = 2.0 * G.k_B.value * 1e-3 / wavelength**2 * omega * 1e29
return constant
def printRowCorrelator(data, indx, spaces=''):
"""
Print correlator configuration for a row entry observations.
The output will be different for spectral scans and single tunings.
Inputs:
data : Contains data from readObservations() in the DATA key
indx : Entry number in observations
spaces : Spaces to format output
"""
if data[IS_SPECTRAL][indx]:
printRowCorrelatorSpectralScan(data, indx, spaces=spaces)
else:
printRowCorrelatorSingle(data, indx, spaces=spaces)
def printRowCorrelatorSingle(data, indx, spaces=''):
"""
Print project information for entry indx in "observations" if it
is a single tuning.
Inputs:
data : Contains data from readObservations() in the DATA key
indx : Entry number in observations
spaces : Spaces to format output
"""
# Add some space
print('')
# Print header
lineExcel = getExcelFromIndex(indx)
print('%sCorrelator setup' % spaces)
# Print header
print('%s%3s %8s %17s %19s %17s %17s' % \
(spaces, 'Win', 'Sky Freq', 'Usable Bandwidth', 'Spectral resolution',
'RMS/bandwidth', 'RMS/resolution'))
print('%s%3s %8s %17s %19s %17s %17s' % \
(spaces, '---', '--------', '-----------------', '-------------------',
'----------------', '-----------------'))
print('%s%3s %8s %8s %8s %9s %9s %8s %8s %8s %8s' % \
(spaces, '', '(GHz)', '(MHz)', '(km/s)', '(MHz)', '(km/s)',
'mJy', 'mK', 'mJy', 'K'
))
# Gather sensitivity information
req_sen = data[REQ_SENS][indx]
ref_freq = data[REF_FREQ][indx]
ref_bw = data[REF_BW][indx]
# Get frequencies
windows = list(SPW_FREQ.keys())
winfreq = []
winnumber = []
for w in windows:
if np.isfinite(data[SPW_FREQ[w]][indx]):
winnumber.append(w)
winfreq.append(data[SPW_FREQ[w]][indx])
# Sort by frequency
iarg = np.argsort(winfreq)
winfreq = np.array(winfreq)[iarg]
winnumber = np.array(winnumber)[iarg]
# Loop over windows
for w in winnumber:
# Is this a valid window?
if np.isnan(data[SPW_FREQ[w]][indx]):
continue
# Gather spectral window information
freq = data[SPW_FREQ[w]][indx]
bw = getUsableBandwidth(data[SPW_BW[w]][indx])
res = data[SPW_RES[w]][indx]
# Print window
print('%s%3d' % (spaces, w), end=' ')
# Frequency
print('%8.3f' % freq, end=' ')
# Bandwidth in MHz
print(' %8.1f' % bw, end=' ')
# Bandwidth in km/s
bw_kms = bw * 1e-3 / freq * G.c.value / 1e3
print('%8.1f' % bw_kms, end=' ')
# Channel resolution in MHz
print(' %9.3f' % res, end=' ')
# Channel resolution in km/s
res_kms = res * 1e-3 / freq * G.c.value / 1e3
print('%9.3f' % res_kms, end=' ')
# Sensitivity per bandwidth and channel
if ref_bw == 0:
srms_bw_mjy = '%8s' % 'N/A'
srms_bw_mk = '%8s' % 'N/A'
srms_res_mjy = '%8s' % 'N/A'
srms_res_k = '%8s' % 'N/A'
else:
# In mJy
rms_bandwidth_mJy = req_sen * np.sqrt(ref_bw / bw)
rms_resolution_mJy = req_sen * np.sqrt(ref_bw / res)
# In mK
angres = data[ANG_RES][indx]
rms_bandwidth_mK = rms_bandwidth_mJy / mjypermk(freq, angres, ref_freq)
rms_resolution_K = rms_resolution_mJy / mjypermk(freq, angres, ref_freq) / 1e3
# Save as strings
srms_bw_mjy = '%8.3f' % rms_bandwidth_mJy
srms_bw_mk = '%8.3f' % rms_bandwidth_mK
srms_res_mjy = '%8.3f' % rms_resolution_mJy
srms_res_k = '%8.3f' % rms_resolution_K
print(' %s' % srms_bw_mjy, end=' ')
print('%s' % srms_bw_mk, end=' ')
print(' %s' % srms_res_mjy, end=' ')
print('%s' % srms_res_k, end=' ')
# Done width entry
print('')
def printRowCorrelatorSpectralScan(data, indx, spaces=''):
"""
Print project information for entry indx in "observations"
that is a spectral scan.
Inputs:
data : Contains data from readObservations() in the DATA key
indx : Entry number in data
spaces : Spaces to format output
"""
# Add some space
print('')
# Print header
lineExcel = getExcelFromIndex(indx)
print('%sCorrelator information for spectral scan' % spaces)
# Gather sensitivity information
req_sen = data[REQ_SENS][indx]
ref_freq = data[REF_FREQ][indx]
ref_bw = data[REF_BW][indx]
# Get spectral window information
sps_start = data[SPS_START][indx]
sps_end = data[SPS_END][indx]
sps_bw = getUsableBandwidth(data[SPS_BW][indx])
sps_res = data[SPS_SPW_RES][indx]
# Compute quantities
sps_res_kms = sps_res/1e3 / sps_start * G.c.value / 1e3
# Sensitivity per bandwidth and resolution element
if ref_bw == 0:
srms_bw_mjy = '%8s' % 'N/A'
srms_bw_mk = '%8s' % 'N/A'
srms_res_mjy = '%8s' % 'N/A'
srms_res_k = '%8s' % 'N/A'
else:
# In mJy
rms_bandwidth_mJy = req_sen * np.sqrt(ref_bw / sps_bw)
rms_resolution_mJy = req_sen * np.sqrt(ref_bw / sps_res)
# In mK
angres = data[ANG_RES][indx]
rms_bandwidth_mK = rms_bandwidth_mJy / mjypermk(sps_start, angres, ref_freq)
rms_resolution_K = rms_resolution_mJy / mjypermk(sps_start, angres, ref_freq) / 1e3
# Save as strings
srms_bw_mjy = '%8.2f' % rms_bandwidth_mJy
srms_bw_mk = '%8.2f' % rms_bandwidth_mK
srms_res_mjy = '%8.3f' % rms_resolution_mJy
srms_res_k = '%8.3f' % rms_resolution_K
# Print spectral scale information
print('%sStarting frequency : %8.3f GHz' % (spaces, sps_start))
print('%sEnding frequency : %8.3f GHz' % (spaces, sps_end))
print('%sUsable bandwidth per window : %8.3f MHz' % (spaces, sps_bw))
print('%sSpectral resolution : %8.3f MHz' % (spaces, sps_res))
print('%sSpectral resolution : %8.3f km/s @ %.1f GHz' % (spaces, sps_res_kms, sps_start))
print('%sRMS per spectral resolution : %s mJy' % (spaces, srms_res_mjy))
print('%sRMS per spectral resolution : %s K ' % (spaces, srms_res_k))
def isObsMosaic(data, indx, mtype=MOSAIC_TYPES):
"""
Returns True or False if an observation is a mosaic of a
type listed in MOSAIC_TYPES.
data : Contains data from readObservations() in the DATA key
indx : Row number within data
mtype : A list of mosaic types
The type of mosaics are currently MOSAIC_TYPE_CUSTOM and
MOSAIC_TYPE_RECTANGLE.
"""
ismosaic = False
if data[IS_MOSAIC][indx] in MOSAIC_TYPES:
ismosaic = data[IS_MOSAIC][indx] in makeList(mtype)
elif data[IS_MOSAIC][indx] == 'N/A' or np.isnan(data[IS_MOSAIC][indx]):
ismosaic = False
else:
raise Exception('Unexpected value of IS_MOSAIC on row %d' % getExcelFromIndex(indx))
return ismosaic
def printSummaryHeader(label, spaces=''):
"""
Print header for summary table.
Inputs:
label : label to print in the header row
spaces : spaces used for formatting
"""
print('')
print('Summary information for %s' % label)
print('%s%6s %6s %-14s %-23s %12s %12s %8s %8s %8s %7s %7s %4s %4s %5s' % \
(spaces, 'N', 'Excel', 'Project code', 'Target name', 'RA', 'Dec', 'Sky Freq', 'Ang.Res.', 'L.A.S.',
'Polar-', 'MosArea', ' 7m?', 'TPA?', 'Spec.'))
print('%s%6s %6s %-14s %-23s %12s %12s %8s %8s %8s %7s %7s %4s %4s %5s' % \
(spaces, '', 'row', '', '', 'J2000', 'J2000', '(GHz)', '(arcsec)', '(arcsec)', 'ization', 'amin^2', '', '', 'Scan?'))
def printSummarySource(number, observations, indx, spaces=''):
"""
Print summary information for sources with indx of the observations.
Inputs:
number : Index to keep track of number of sources
observations : The observations data from readObservations()
indx : The index number if observations
Output:
A printed summary of the sources
"""
project = observations[DATA][PROJECT][indx]
target = observations[DATA][TARGET][indx]
sra = convertHmsString(float(observations[DATA][RA_DEG][indx]/15.0), ndec=1, delimiter='hms')
sdec = convertHmsString(float(observations[DATA][DEC_DEG][indx]), ndec=1, delimiter='dms')
# scoords = observations[COORDS][indx].to_string(style='hmsdms', precision=1).split()
# sra = scoords[0]
# sdec = scoords[1]
sfreq = '%.1f' % observations[DATA][REF_FREQ][indx]
sangular = '%.2f' % observations[DATA][ANG_RES][indx]
slas = '%.1f' % observations[DATA][LAS][indx]
smosaic = '-'
saca = '-'
saca = '-'
stpa = '-'
sspectral = '-'
spol = observations[DATA][POLARIZATION][indx].lower()
if observations[DATA][IS_SPECTRAL][indx]:
sspectral = 'Yes'
if isObsMosaic(observations[DATA], indx):
if isObsMosaic(observations[DATA], indx, mtype=MOSAIC_TYPE_RECTANGLE):
smosaic = '%.1f' % observations[DATA][MOS_AREA][indx]
else:
smosaic = 'custom'
if observations[DATA][USE_7M][indx] == True:
saca = 'Yes'
if observations[DATA][USE_TPA][indx] == True:
sata = 'Yes'
print('%s%6d %6d %-14s %-23s %12s %12s %8s %8s %8s %7s %7s %4s %4s %5s' % \
(spaces, number, getExcelFromIndex(indx), project, target, sra, sdec,
sfreq, sangular, slas, spol, smosaic, saca, stpa, sspectral))
def getMosaicCorners(ra_mosaic, dec_mosaic, width, height, angle, frame,
center=None, indx=None):
"""
Returns the corners of the mosaic in RA/Dec offsets from the mosaic
center. The function takes into account if the mosaic is specified
in galactic coordinates.
Inputs
angle : position angle of the rectangle in degrees.
center : The center of the plot in RA/Dec coordinates in degrees.
Enter as a tuple. e.g., center=[180.0, -10.0]
dec_mosaic : Declination center of the mosaic
frame : coordinate frame of the mosaic (J2000, icrs, or galactic)
heighto : height of the rectangle in arcseconds. Note this
corresponds to "width" in the spreadsheet.
ra_mosaic : RA center of the mosaic
widtho : width of the rectangle in arcseconds. Note this
corresponds to "height" in the spreadsheet.
Output:
A dictionary containing the RA/Dec offsets of the corners of the
mosaic relative to coords. If center, is none, then the mosaic
center is used. The dictionary keywords are UL, UR, BL, and BR.
"""
# Get the center of the mosaic in the native frame
if frame in ['J2000', 'icrs', 'ICRS']:
xcen_mosaic = ra_mosaic
ycen_mosaic = dec_mosaic
elif frame.lower() == 'galactic':
# Convert to ra/dec
c = SkyCoord(ra=ra_mosaic, dec=dec_mosaic, frame='icrs', unit='degree')
xcen_mosaic = c.galactic.l.deg
ycen_mosaic = c.galactic.b.deg
else:
msg = 'Unknown mosaic frame (%s)' % (frame)
if indx is not None:
msg += ' on row %d' % (getExcelFromIndex(indx))
raise Exception(msg)
# Compute vertices relative to mosaic center
x = 0
y = 0
A = -angle / 180.0 * np.pi
results = dict()
results['UL'] = (x + ( width / 2. ) * np.cos(A) - ( height / 2. ) * np.sin(A), y + ( height / 2. ) * np.cos(A) + ( width / 2. ) * np.sin(A))
results['UR'] = (x - ( width / 2. ) * np.cos(A) - ( height / 2. ) * np.sin(A), y + ( height / 2. ) * np.cos(A) - ( width / 2. ) * np.sin(A))
results['BL'] = (x + ( width / 2. ) * np.cos(A) + ( height / 2. ) * np.sin(A), y - ( height / 2. ) * np.cos(A) + ( width / 2. ) * np.sin(A))
results['BR'] = (x - ( width / 2. ) * np.cos(A)+ ( height / 2. ) * np.sin(A), y - ( height / 2. ) * np.cos(A) - ( width / 2. ) * np.sin(A))
# Set plot center.
# The plot center is either specified in center, or if center=None,
# the plot center is the mosaic itself.
ra_center = ra_mosaic
dec_center = dec_mosaic
if center is not None:
ra_center = center[0]
dec_center = center[1]
# Set mosaic offsets in arcseconds relative to center
corners = dict()
for key, c in results.items():
# Add offsets to central coordinates in native frame of the mosaic
xcorner = xcen_mosaic + c[0] / 3600.0 / np.cos(ycen_mosaic / 180.0 * np.pi)
ycorner = ycen_mosaic + c[1] / 3600.0
# Check limits on x
if xcorner < 0:
xcorner += 360.
if xcorner > 360:
xcorner -= 360.
# Check limits on y
if abs(ycorner) > 90.0:
msg = 'Error setting y-axis mosaic'
if indx is not None:
msg += ' on row %d' % getExcelFromIndex(indx)
raise Exception(msg)
# Convert corner coordinates to ra/dec, if needed
if frame.lower() == 'galactic':
c = SkyCoord(xcorner, ycorner, frame=frame, unit='degree')
xcorner = c.icrs.ra.deg
ycorner = c.icrs.dec.deg
# Compute offsets relative to mosaic center
dra = (xcorner - ra_center)
ddec = (ycorner - dec_center) * 3600.0
if abs(dra) > 180.0:
# we are on either side of RA=0
if dra > 0:
dra -= 360.0
else:
dra += 360.0
dalp = dra * 3600.0 * np.cos(dec_center / 180. * np.pi)
# Save corner
corners[key] = (dalp, ddec)
# Done
return corners
def plotSources(coords, observations, diameter=12,
include=None, exclude=None,
mosaic=False, width=60, length=60, pa=0.,
mframe='icrs', freq=345., mosonly=False,
plotroot='source', plotsizeo=120., plottype='pdf'):
"""
Plot observations that are nearby the specified coordinates.
Inputs:
coords : proposed coordinates from the user set by the
function getSourceCoordinates()
diameter : diameter of the telescope in meters
exclude : if set, it contains a list of spreadsheet row numbers
that should not be plotted.
freq : proposed observing frequency in GHz
include : if set, it contains a list of spreadsheet row
numbers that can be plotted if all other criteria
are set.
length : length of the mosaic in arcseconds
mframe : coordinate system of the mosaic (icrs or galactic)
mosaic : if True, the proposed observations are a mosaic
mosonly : if True, plot/print mosaic observations only
observations : existing observations from readObservations()
pa : position angle of the mosaic in degrees
plotroot : root name for the file containing the plot. The root
name will be appended with a plot number, which is
useful when plotting.
multiple sources) and the plottype.
plotsizeo : plot size in arcseconds
plottype : Type of plot to generate. The plot type must be
supported by your version of python. "pdf" and "png"
are usually safe. If plottype=None, then no plot is
saved.
Default is "pdf".
width : width of the mosaic in arcseconds
"""
# Set spacing for formatting purposes
spaces = ''
# Make a plot for each source
for i in range(coords[ORIGINAL].size):
# Initialize plot
py.figure(i+1, figsize=(9,9))
py.clf()
ax = py.subplot(1, 1, 1, aspect='equal')
# Set plot width
plotsize = coords[PLOTSIZE][i]
if plotsize is None:
plotsize = plotsizeo
# Find sources that overlap
if coords[NOCOORD][i]:
result = observations[DATA][TARGET][observations[DATA][TARGET].str.lower() == coords[ORIGINAL][i].lower()]
else:
# Compute separation in degrees
sep = coords[COORDS][i].separation(observations[COORDS])
sepArcsec = sep.arcsec
# Find entries within plot size
separationArcsec = sepArcsec - 0.5*observations[DATA][MAX_SIZE]
result = separationArcsec[separationArcsec <= (0.5*plotsize)]
jindices = result.index
# Print summary of observation
if len(jindices) == 0:
sra = convertHmsString(float(coords[COORDS][i].ra.deg/15.0), ndec=1, delimiter='hms')
sdec = convertHmsString(float(coords[COORDS][i].dec.deg), ndec=1, delimiter='dms')
print('')
print('Summary information for %s' % coords[ORIGINAL][i])
print(' No observations found within %g x %g arcsec region centered around (ra, dec) = (%s, %s) J2000' % \
(plotsize, plotsize, sra, sdec))
else:
printSummaryHeader('%g x %g arcsec region around %s' % \
(plotsize, plotsize, coords[ORIGINAL][i]), spaces=spaces)
# Set limits based on plotsize
ax.set_xlim( 0.5*plotsize, -0.5*plotsize)
ax.set_ylim(-0.5*plotsize, 0.5*plotsize)
ax.set_xlabel('$\\Delta\\alpha\ \ \\mathrm{(arcsec)}$')
ax.set_ylabel('$\\Delta\\delta\ \ \\mathrm{(arcsec)}$')
# Get row lists to include/exclude
rows_include = makeList(include)
rows_exclude = makeList(exclude)
# Set plot center.
# Sources will be plotted in offsets relative to this coordinate.
ra_center = coords[COORDS][i].ra.deg
dec_center = coords[COORDS][i].dec.deg
# Loop over observations which overlap the field
legend_symbols = []
legend_labels = []
number = 0
for indx in jindices:
# Get excel line
excelRow = getExcelFromIndex(indx)
if rows_include is not None and excelRow not in rows_include:
continue
if rows_exclude is not None and excelRow in rows_exclude:
continue
# If not mosaic and mosonly=True, then skip
if not isObsMosaic(observations[DATA], indx) and mosonly:
continue
# Get ra and dec offset from nominal position in arcseconds
if coords[NOCOORD][i]:
dalp = 0
ddec = 0
else:
# Compute offset
ddec = (observations[DATA][DEC_DEG][indx] - dec_center) * 3600.0
dra = (observations[DATA][RA_DEG][indx] - ra_center)
if abs(dra) > 180.0:
# we are on either side of RA=0
if dra > 0:
dra -= 360.0
else:
dra += 360.0
dalp = dra * 3600.0 * np.cos(dec_center / 180.0 * np.pi)
# Set center as offset coordinates
xy = (dalp, ddec)
# Print summary of observation
number += 1
printSummarySource(number, observations, indx, spaces=spaces)
# Set plot color based on band
color = getBandColor(observations[DATA][REF_FREQ][indx])
# Plot mosaic or single pointing
label = 'N = %d' % number
if isObsMosaic(observations[DATA], indx, mtype=MOSAIC_TYPE_RECTANGLE):
# Get the coordinates of the rectangle in RA/DEC offset units
mosaicCorners = getMosaicCorners(\
observations[DATA][RA_DEG][indx],
observations[DATA][DEC_DEG][indx],
observations[DATA][MOS_LENGTH][indx],
observations[DATA][MOS_WIDTH][indx],
observations[DATA][MOS_PA][indx],
observations[DATA][MOS_COORD][indx],
center=[ra_center, dec_center])
result = plotMosaic(ax, mosaicCorners, fc=color, ec=color , hatch=None, alpha=0.1)
else:
result = plotPrimaryBeam(ax, xy, observations[DATA][REF_FREQ][indx], diameter,
fc='None', ec=color)
legend_symbols.append(result)
legend_labels.append(label)
# Loop over observations which overlap the field
# color = getBandColor(freq)
color = 'tan'
if mosaic:
corners = getMosaicCorners(ra_center, dec_center, length, width, pa, mframe)
result = plotMosaic(ax, corners, fc=color, ec='None', alpha=0.5, linewidth=3)
else:
result = plotPrimaryBeam(ax, (0,0), freq, diameter, fc=color, ec='None', alpha=0.5)
legend_symbols.append(result)
legend_labels.append('Proposed')
# Plot title with original entry and translation
sra = convertHmsString(float(coords[COORDS][i].ra.deg/15.0), ndec=1, delimiter='hms')
sdec = convertHmsString(float(coords[COORDS][i].dec.deg), ndec=1, delimiter='dms')
if coords[NOCOORD][i]:
labelTranslated = ''
else:
labelTranslated = '%s, %s' % (sra, sdec)
labelOriginal = '%s' % coords[ORIGINAL][i]
fs = 14
# ax.set_title(labelOriginal, loc='left', fontsize=fs)
# ax.set_title(labelTranslated, loc='right', fontsize=fs)
ax.annotate(text='Entered:', xy=(0,1.05), xycoords='axes fraction', size=fs)
ax.annotate(text=labelOriginal, xy=(0.2,1.05), xycoords='axes fraction', size=fs)
ax.annotate(text='Translated:', xy=(0,1.01), xycoords='axes fraction', size=fs)
ax.annotate(text=labelTranslated, xy=(0.2,1.01), xycoords='axes fraction', size=fs)
# py.legend(legend_symbols, legend_labels)
# Save plot to a file
py.show()
if plottype is not None:
# Set name
root = plotroot
if root is None:
root = 'source'
# Try creating plot
try:
plotfile = '%s%d.%s' % (root, i+1, plottype)
py.savefig(plotfile)
print(' Plot saved to %s' % plotfile)
except:
print(' Warning: Could not create plot %s.' % plotfile)
print(' Is that plot type supported by your python extension?')
def isMovingObject(data, indx):
"""
Returns True/False that object is a moving object.
"""
return (data[RA_DEG][indx]==0 and data[DEC_DEG][indx]==0)
def getCoordinatesFromName(name, data=None, lookup=True):
"""
Try to get coordinates for the source name.
First, it tries using Sesame. If that fails, then we try using
finding the source in the observations spreadsheet.
name : Name of the source
data : Contains data from readObservations() in the DATA key
"""
# Try resolving source name
found = False
if lookup:
result = astropy.coordinates.name_resolve.get_icrs_coordinates(name)
try:
result = astropy.coordinates.name_resolve.get_icrs_coordinates(name)
found = True
except:
pass
if not found:
# Initialize
result = None
# Is source in observation list?
if data is not None:
j = np.where(data[TARGET].str.lower() == name.lower())[0]
if len(j) > 0 and not isMovingObject(data, j[0]):
result = SkyCoord(data[RA_DEG][j[0]], data[DEC_DEG][j[0]], unit='deg', frame='icrs')
# Done
return result
def getSourceCoordinates(longitude, latitude, sources, inputFile, rows,
unit='deg', frame='icrs', data=None,
spreadsheet=None, lookup=True):
"""
Get list of coordinates to search.
longitude : longitude (equatorial or galactic coordinates. Examples:
longitude=180.0
longitude='180.0d'
longitude='15h00m00s'
longitude=['10h12m13s', '180.0d', 180.0]
latitude : longitude (equatorial or galactic coordinates. Examples:
latitude=-4.0
latitude='-4.0d'
latitude=['-21d11m12s', '-4d', -4.0]
sources : List of source names. Examples:
sources='DG Tau'
sources='DG Tau, HL Tau, DM Tau'
sources=['DG Tau', 'HL Tau', 'DM Tau']
inputFile : Name of text file containing sources to search. In addition
to source names, the file may contain commands to change
plot sizes. The pound symbol (#) is used as a comment marker.
Example input for the data file.
# Specify sources to sea
HL Tau
GO Tau
# Change plot size from default for remaining sources
plotsize = 480
# Additional source name
NGC7496
# Change coordinate frames and plot size
frame = galactic
plotsize = 60
# Search by coordinate
178.8590 -19.9724
rows : List of row numbers in excel spreadsheet
rows=1800
rows=[1800, 1850]
unit : Unit for longitude/latitude if they are entered as floating
points. Most likely values are
a) unit='deg' , which means both longitude and latitude
are in degrees.
b) unit='hour,deg', which means longitude is in
hours and latitude is in degrees.
frame : Coordinate frame for longitude/latitude. Accepted values
are 'icrs' for equatorial or 'galactic'.
data : Contains data from readObservations() in the DATA key.
This is used to search for individual sources if
a source name is not name resolved.
lookup : If True, resolve the source name using Sesame.
lookup=False can be useful if the name listed in the
spreadsheet is desired.
spreadsheet: Name of the spreadsheet containing the observations.
This parameter is optional, and only used to
customize the output.
There are four option to search:
1) Enter list of numpy array of RA/DEC.
The format is flexible in that RA/Dec be in string, hex, or
decimal format. If the units are not specified, the default
units are given by the 'unit' variable.
For example:
longitude=['01h05m11s', 1.32]
latitude=['-10d05m11s', -20.0]
2) Enter of a list of source names that will be name resolved
3) Enter a file with coordinates/names, one entry per line
The comment symbol per line is the pound symbol.
The entry will name resolved first, and if that is not successful,
it will be coordinate resolved. If that is not successful, then
the source is skipped.
4) Enter list of row numbers
"""
# Initialize ra/dec list
ra = []
dec = []
original = []
iscoord = []
nocoord = []
plotsize = []
# Add coordinates
def addCoordinates(c, psize=None):
# Save in degrees
if c is None:
ra.append(0)
dec.append(0)
nocoord.append(True)
plotsize.append(psize)
else:
if type(c.icrs.ra.deg) in [list, np.ndarray]:
zra = np.array(c.icrs.ra.deg)
else:
zra = np.array([c.icrs.ra.deg])
if type(c.icrs.dec.deg) in [list, np.ndarray]:
zdec = np.array(c.icrs.dec.deg)
else:
zdec = np.array([c.icrs.dec.deg])
ra.extend(zra)
dec.extend(zdec)
nocoord.extend([False] * zra.size)
plotsize.extend([psize] * zra.size)
# Process RA/Dec
if longitude is not None or latitude is not None:
# Both must be set
if longitude is None or latitude is None:
raise Exception('Both longitude/latitude must be set if you are entering coordinates')
# Convert entries into list so we can loop over them
llon = makeList(longitude)
llat = makeList(latitude)
# Must have same size
if llon.size != llat.size:
raise Exception('longitude/latitude must have the same size')
if llon.ndim != 1:
raise Exception('Only 1D arrays can be processed for longitude')
if llat.ndim != 1:
raise Exception('Only 1D arrays can be processed for latitude')
# Get coordinates
c = SkyCoord(llon, llat, unit=unit, frame=frame)
# Add coordinates
addCoordinates(c)
# Save original entries
iscoord.extend([True] * c.icrs.ra.size)
s = []
for t in zip(llon, llat):
s.append('%s, %s %s' % (t[0], t[1], frame))
original.extend(s)
# Process row numbers
if rows is not None:
# Convert to list
lrows = getIndexFromExcel(makeList(rows))
# Get coordinates
c = SkyCoord(data[RA_DEG][lrows], data[DEC_DEG][lrows], unit='deg', frame='icrs')
# Add coordinates
addCoordinates(c)
# Save original entries
iscoord.extend([True] * c.icrs.ra.size)
s = []
for t in lrows:
ss = 'Row %d (%s)' % (getExcelFromIndex(t), str(data[TARGET][t]))
if spreadsheet is not None:
ss += ' in %s' % spreadsheet
s.append(ss)
original.extend(s)
# Process source names
if sources is not None:
# Convert into list
names = makeList(sources)
# Loop over names
for name in names:
# Get coordinates
c = getCoordinatesFromName(name, data=data, lookup=lookup)
# Add to coordinate list
if c is None:
print('Warning: Could not find coordinates for %s. Searching by object name.' % name)
addCoordinates(c)
iscoord.append(False)
original.append(name)
# Process input file
psize_new = None
unit_new = unit
frame_new = frame
if inputFile is not None:
# Read file one line at a time
finp = open(inputFile, 'r')
# Process one line at a time
nlines = 0
for line in finp:
# Strip leading and trailing spaces, as well as multiple spaces
line = re.sub(' +',' ', line.strip())
# Remove comment portion of line
j = line.find('#')
if j >= 0: line = line[0:j]
# Skip blank lines
if line.strip() == '':
continue
# Is this the plot size?
if line.lower().find('plotsize') == 0:
value = line.split('=')[1]
try:
psize_new = float(value)
except:
pass
continue
# Is this the unit?
if line.lower().find('unit') == 0:
value = line.split('=')[1]
try:
unit_new = value.strip()
except:
pass
continue
# Is this the frame?
if line.lower().find('frame') == 0:
value = line.split('=')[1]
try:
frame_new = value.strip()
except:
pass
continue
# First, try if parse line as coordinates
try:
nlines += 1
c = SkyCoord(line, unit=unit_new, frame=frame_new)
addCoordinates(c, psize=psize_new)
iscoord.extend([True] * c.icrs.ra.size)
original.extend(['%s %s' % (line, frame_new)])
except:
# Coordinates was not successful. Try resolving it as a name
c = getCoordinatesFromName(line, data=data)
if c is not None:
nlines += 1
addCoordinates(c, psize=psize_new)
iscoord.append(False)
original.append(line)
else:
print('Warning: Could not find coordinates for %s. Searching by object name.' % line)
addCoordinates(None, psize=psize_new)
iscoord.append(False)
original.append(line)
# Message
print('Read in %d sources from %s' % (nlines, inputFile))
# Prepare results
results = dict()
results[COORDS] = SkyCoord(ra, dec, unit='deg')
results[RA] = makeList(ra)
results[DEC] = makeList(dec)
results[ORIGINAL] = makeList(original)
results[ISCOORD] = makeList(iscoord)
results[NOCOORD] = makeList(nocoord)
results[PLOTSIZE] = makeList(plotsize)
# Check that all elements have the same size
keys = list(results.keys())
for i, k in enumerate(keys):
if len(results[k]) != len(results[keys[0]]):
raise Exception('Arrays do not have the same size: %s and %s' % (keys[0], k))
# Done
return results
def row(rows, showProject=True, showCorrelator=True,
inputObs=LIST_OF_OBSERVATIONS, verbose=True, refresh=False):
"""
Print detailed information about the project and correlator for a
a line in the spreadsheet.
Inputs:
rows : The row numbers in the excel spreadsheet that
should be printed. An integer or a list can be
entered.
showProject : If True, then print the correlator information for
each line
showCorrelator: If True, then print the project information for
each line
inputObs : The name of the excel spreadsheet containing the
observations.
verbose : If True, print out messages while reading spreadsheet
refresh : If True, re-read the spreadsheet
Output:
A text listing of the project and correlator information.
Example usage:
import plotobs as po
po.row(507)
po.row([507, 508])
"""
# Read list of previous or scheduled observations
global OBSERVATIONS
if OBSERVATIONS is None or refresh:
OBSERVATIONS = readObservations(inputObs, verbose=verbose)
observations = OBSERVATIONS
# Loop over input excel sheet spread lines
for excel in makeList(rows):
# Convert to index number if observation list
indx = getIndexFromExcel(excel)
# Print
if showProject: printRowProject(observations[DATA], indx)
if showCorrelator: printRowCorrelator(observations[DATA], indx)
def project(projects, inputObs=LIST_OF_OBSERVATIONS, verbose=True, refresh=False):
"""
Print information about a project in the spreadsheet.
Inputs:
projects : A string or list containing the projects to list.
inputObs : The name of the excel spreadsheet or csv file
containing the observations.
verbose : If True, print out messages while reading spreadsheet
refresh : If True, re-read the spreadsheet
Output:
A text listing of the project and correlator information.
Example usage:
import plotobs as po
po.project('2018.1.00035.L')
po.project(['2018.1.00035.L', '2018.1.00566.S'])
"""
# Read list of previous or scheduled observations
global OBSERVATIONS
if OBSERVATIONS is None or refresh:
OBSERVATIONS = readObservations(inputObs, verbose=verbose)
observations = OBSERVATIONS
# Loop over projects
for p in makeList(projects):
# Print header
printSummaryHeader(p)
# Find indices
mask = observations[DATA][PROJECT].str.lower() == p.lower()
indices = observations[DATA][PROJECT][mask].index
# Print indices
if len(indices) == 0:
print(' No observations found for project %s' % p)
else:
for n, indx in enumerate(indices):
printSummarySource(n+1, observations, indx)
def test(projects=True, rows=True, plots=True, refresh=True,
inputObs=LIST_OF_OBSERVATIONS, verbose=True):
"""
Run through all sources and through the projects to test the scripts
to make all sources can be processed without run-time errors.
projects: If True, process all projects
rows : If True, process all rows
plots : Run the plot() command on each row (very slow)
refresh : Reload spreadsheet instead of using data in memory
"""
# Load data
global OBSERVATIONS
if OBSERVATIONS is None or refresh:
OBSERVATIONS = readObservations(inputObs, verbose=verbose)
observations = OBSERVATIONS
# Check projects
if projects:
# Get unique projects
projectList = np.unique(observations[DATA][PROJECT])
# Run each project
project(projectList)
# Check rows
if rows:
for indx in observations[DATA].index:
row(getExcelFromIndex(indx))
# Plots
if plots:
for indx in observations[DATA].index:
plot(rows=getExcelFromIndex(indx))
def plot(
longitude=None,
latitude=None,
frame='icrs',
unit='deg',
sources=None,
lookup=True,
inputFile=None,
rows=None,
include=None,
exclude=None,
freq=345.0,
width=30.0,
length=60,
pa=0.0,
mframe='icrs',
mosaic=False,
refresh=False,
verbose=True,
inputObs=LIST_OF_OBSERVATIONS,
mosonly=False,
plotroot='source',
plotsize=120.0,
plottype="pdf"):
"""
Purpose:
plot() lists and displays existing observations from cycles 8 and 9
with grade A.
Each observation is plotted as a single pointing with a circle the
size of the primary beam, or a rectangle for rectangular mosaics. For
custom mosaics, each individual pointing is plotted.
One can specify an observing frequency and mosaic parameters
(optional), which will also be plotted with a filled circle or
rectangle.
Usage:
1) The user enters coordinates or source names, the observed
frequency, the proposed mosaic parameters, and the size of the
plot region (PLOTSIZE)
2) plot() will list all observation within PLOTSIZE size, as well
as plot the observations.
How to input source name or coordinates:
Parameters:
exclude : If set, it contains a list of spreadsheet row numbers
that should not be plotted.
frame : reference frame for longitude, either "icrs"
(i.e., equatorial) or "galactic"
include : If set, it contains a list of spreadsheet row numbers
that can be plotted if all other criteria are set
inputFile : Input list sources or positions
inputObs : Input excel file containing previous observations
and scheduled observations
latitude : Latitude coordinate with reference frame specified
by "frame". Examples:
latitude=-20.0, unit='deg'
latitude='-20 deg'
latitude=['-20 deg', '20d00m00s']
longitude : Longitude coordinate with reference frame specified
by "frame". Examples:
longitude=100.0, unit='deg'
longitude='100 deg'
longitude=['100 deg', '17h15m:10s']
lookup : If True, resolve the source name using Sesame.
lookup=False can be useful if the name listed in the
spreadsheet is desired.
mosonly : If True, print/display only mosaic observations
refresh : If True, re-read excel spreadsheet into memory
rows : List of row numbers in excel spreadsheet
sources : List of source names. The coordinates of the source
names are obtain from the Sesame database, and if the
name is not resolved, the name is searched
(case-insensitive) in the input spreadsheet. If
lookup=False, the search in the Sesame database is
skipped.
unit : units of longitude and latitude if not specified by
in longitude/latitude. Examples:
unit='deg' implies longitude/latitude are in degrees.
unit='hour,deg' implies longitude is in hours
and latitude is in degrees.
verbose : If True, print out messages when reading the excel
spreadsheet
Plot parameters
plotroot : Root name for the file containing the plot. The root
name will be appended with a plot number (useful when
plotting multiple sources) and the plottype.
plotsize : Size of the square region to plot in arcseconds
plottype : Type of plot to generate. The plot type must be
supported by your version of python. "pdf" and "png"
are usually safe. Default is "pdf". Set plottype=None
to not save the plots to a file.
Proposed observed parameters
freq : Proposed observing frequency in GHz
length : Proposed length of the mosaic in arcseconds
mframe : Coordinate system of the mosaic (icrs or galactic)
mosaic : If True, the proposed observations are a mosaic
pa : Proposed position angle of the mosaic in degrees
width : Proposed width of the mosaic in arcseconds
Usage:
There are three options to search for source names:
1) Enter list of coordinates.
The format is flexible in that coordinates may be a string,
hex, or decimal format. If the units are not specified, the
default units are given by the 'unit' variable.
For example:
longitude=['01h05m11s', 1.32]
latitude=['-10d05m11s', -20.0]
2) Enter of a list of source names that will be name resolved
by querying the Sesame database. An internet connection
must be available for the coordinate lookup
3) Enter a file with coordinates/names, one entry per line
The comment symbol per line is the pound symbol.
The entry will be name resolved first, and if that is not
successful, it will be coordinate resolved. If that is not
successful, then the source is skipped.
4) The source name will be matched to the OBSERVATION list.
The search will be case insensitive.
Examples
# Plot scheduled observations within 100" x 100" of NGC4298 along
# with a proposed pointing at the position of the galaxy
po.plot('NGC7496', plotsize=200)
# Plot scheduled observations within 300" x 300" of NGC4298,
# along with a proposed mosaic at 345 GHz
po.plot('NGC7496', plotsize=300, length=240, width=120,
pa=60, freq=345., mosaic=True)
# Plot remaining observations within 480" of (ra, dec) = (17h20m42s, -35d48m04s)
po.plot('17h20m42s', '-35d48m04s', plotsize=480, frame='icrs', unit='deg')
# Plot scheduled observations in a 1 deg region around ra, dec = (150, 2.2) J2000
po.plot(150, 2.2, plotsize=3600)
# Plot scheduled observations in a 1 deg around galactic center, but mosaics only
po.plot(0, 0, frame='galactic', plotsize=3600., mosonly=True)
# Plot scheduled observations within a 120" x 120" of HL Tau and GO Tau
po.plot('HL Tau, GO Tau', plotsize=120)
# Plot scheduled observations near coordinates in row 494 of the spreadsheet
po.plot(494)
# Plot scheduled observations centered on row 494 in the spreadsheet, and only row 494
po.plot(494, include=494)
# Plot remaining observations for sources listed in a text file
# Contents of input file "input.txt":
HL Tau
GO Tau
plotsize = 480
NGC7496
frame = galactic
plotsize = 60
178.8590 -19.9724
po.plot(inputFile='input.txt')
"""
# If longitude is set but no other source parameters are set, then
# assume the following:
# 1) If longitude is a string, then assume it is a source name
# 2) If it is an integer, then it is a row number
if longitude is not None and latitude is None and \
rows is None and sources is None and inputFile is None:
if str in [type(longitude)]:
sources = longitude
longitude = None
elif int in [type(longitude)]:
rows = longitude
longitude = None
# Check inputs
error = False
# Longitude/latitude must both be set or not at all
if (longitude is not None and latitude is None) or \
(longitude is None and latitude is not None):
error = True
print('Error: Longitude and latitude must both be set to enter coordinates')
# Some coordinates must be set
if (longitude is None or latitude is None) and \
rows is None and \
inputFile is None and \
sources is None:
error = True
print('Error: longitude/latitude, sources, rows, or inputFile must be set')
# Check frame of coordinates
if frame.lower() not in ['icrs', 'galactic']:
error = True
print('Error: frame must be set to "icrs" or "galactic"')
# Check plotsize
if plotsize <= 0:
error = True
print('Error: plotsize must be > 0')
# Check frequency
if freq <= 0:
error = True
print('Error: frequency must be > 0')
# Check mosaic parameters, if mosaic is set
if mosaic:
if length <= 0:
error = True
print('Error: length must be > 0')
if width <= 0:
error = True
print('Error: width must be > 0')
if mframe.lower() not in ['icrs', 'galactic']:
error = True
print('Error: mframe must be set to "icrs" or "galactic"')
if error:
print('')
print('Error starting plotobs_cycle10')
return
# Read list of previous or scheduled observations, if not entered
# on command line
global OBSERVATIONS
if OBSERVATIONS is None or refresh:
OBSERVATIONS = readObservations(input=inputObs, verbose=verbose)
observations = OBSERVATIONS
# Get coordinates of source(s)
coords = getSourceCoordinates(longitude, latitude, sources, inputFile, rows,
unit=unit, frame=frame, data=observations[DATA],
spreadsheet=inputObs, lookup=lookup)
# Plot results
plotSources(coords, observations,
include=include, exclude=exclude,
freq=freq, mosonly=mosonly,
mosaic=mosaic, mframe=mframe, width=width, length=length, pa=pa,
plotroot=plotroot, plottype=plottype, plotsizeo=plotsize)
