-
Notifications
You must be signed in to change notification settings - Fork 22
/
visibilityPA.py
executable file
·434 lines (355 loc) · 14.3 KB
/
visibilityPA.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
"""Produces a graph of the visibility & accessible position angles
for a given RA & DEC, and prints out corresponding information,
including the ranges of accessible and inaccessible PAs.
Usage: python visibilityPA.py RA DEC [targetName]
if targetName is specified, then the figure is saved
-Created by David Lafreniere, March 2016
-makes use of (and hacks) several scripts created by Pierre Ferruit
that are part of the JWST Python tools JWSTpylib and JWSTpytools
"""
import datetime
import math
import pkg_resources
from astropy.table import Table
from astropy.time import Time
from bokeh.plotting import figure, ColumnDataSource
from bokeh.models import HoverTool, ranges
from bokeh.models.widgets import Panel, Tabs
import matplotlib.dates as mdates
import numpy as np
from . import ephemeris_old2x as EPH
from jwst_gtvt.find_tgt_info import get_table
from ..utils import blockPrint, enablePrint
D2R = math.pi / 180. # degrees to radians
R2D = 180. / math.pi # radians to degrees
def checkVisPA(ra, dec, targetName=None, ephFileName=None, fig=None):
"""Check the visibility at a range of position angles.
Parameters
----------
ra: str
The RA of the target in hh:mm:ss.s or dd:mm:ss.s or representing a float
dec: str
The Dec of the target in hh:mm:ss.s or dd:mm:ss.s or representing a float
targetName: str
The target name
ephFileName: str
The filename of the ephemeris file
fig: bokeh.plotting.figure
The figure to plot to
Returns
-------
paGood : float
The good position angle.
paBad : float
The bad position angle.
gd : matplotlib.dates object
The greogrian date.
fig : bokeh.plotting.figure object
The plotted figure.
"""
if ephFileName is None:
eph_file = 'data/contam_visibility/JWST_ephem_short.txt'
ephFileName = pkg_resources.resource_filename('exoctk', eph_file)
if ra.find(':') > -1: # format is hh:mm:ss.s or dd:mm:ss.s
ra = convert_ddmmss_to_float(ra) * 15. * D2R
dec = convert_ddmmss_to_float(dec) * D2R
else: # format is decimal
ra = float(ra) * D2R
dec = float(dec) * D2R
# load ephemeris
eclFlag = False
eph = EPH.Ephemeris(ephFileName, eclFlag)
# convert dates from MJD to Gregorian calendar dates
mjd = np.array(eph.datelist)
d = mdates.julian2num(mjd + 2400000.5)
gd = mdates.num2date(d)
# loop through dates and determine VIS and PAs (nominal, min, max)
vis = np.empty(mjd.size, dtype=bool)
paNom = np.empty(mjd.size)
paMin = np.empty(mjd.size)
paMax = np.empty(mjd.size)
for i in range(mjd.size):
# is it visible?
vis[i] = eph.in_FOR(mjd[i], ra, dec)
# nominal PA at this date
pa = eph.normal_pa(mjd[i], ra, dec)
# search for minimum PA allowed by roll
pa0 = pa
while eph.is_valid(mjd[i], ra, dec, pa0 - 0.002):
pa0 -= 0.002
# search for maximum PA allowed by roll
pa1 = pa
while eph.is_valid(mjd[i], ra, dec, pa1 + 0.002):
pa1 += 0.002
paNom[i] = (pa * R2D) % 360
paMin[i] = (pa0 * R2D) % 360
paMax[i] = (pa1 * R2D) % 360
# does PA go through 360 deg?
wrap = np.any(np.abs(np.diff(paNom[np.where(vis)[0]])) > 350)
# Determine good and bad PA ranges
# Good PAs
i, = np.where(vis)
pa = np.concatenate((paNom[i], paMin[i], paMax[i]))
if wrap:
pa = np.append(pa, (0., 360.))
pa.sort()
i1, = np.where(np.diff(pa) > 10)
i0 = np.insert(i1 + 1, 0, 0)
i1 = np.append(i1, -1)
paGood = np.dstack((pa[i0], pa[i1])).round(1).reshape(-1, 2).tolist()
# bad PAs (complement of the good PAs)
paBad = []
if paGood[0][0] > 0:
paBad.append([0., paGood[0][0]])
for i in range(1, len(paGood)):
paBad.append([paGood[i - 1][1], paGood[i][0]])
if paGood[-1][1] < 360.:
paBad.append([paGood[-1][1], 360.])
# Make a figure
if fig is None or fig:
tools = 'crosshair, reset, hover, save'
radec = ', '.join([str(ra), str(dec)])
fig = figure(tools=tools, plot_width=800, plot_height=400,
x_axis_type='datetime',
title=targetName or radec)
# Do all figure calculations
iBad, = np.where(vis == False)
paMasked = np.copy(paNom)
paMasked[iBad] = np.nan
gdMasked = np.copy(gd)
i = np.argmax(paNom)
if paNom[i + 1] < 10:
i += 1
paMasked = np.insert(paMasked, i, np.nan)
gdMasked = np.insert(gdMasked, i, gdMasked[i])
i = np.argmax(paMin)
goUp = paMin[i - 2] < paMin[i - 1] # PA going up at wrap point?
# Top part
i0_top = 0 if goUp else i
i1_top = i if goUp else paMin.size - 1
paMaxTmp = np.copy(paMax)
paMaxTmp[np.where(paMin > paMax)[0]] = 360
# Bottom part
i = np.argmin(paMax)
i0_bot = i if goUp else 0
i1_bot = paMin.size - 1 if goUp else i
paMinTmp = np.copy(paMin)
paMinTmp[np.where(paMin > paMax)[0]] = 0
# Convert datetime to a number for Bokeh
gdMaskednum = [datetime.date(2019, 6, 1) + datetime.timedelta(days=n)
for n, d in enumerate(gdMasked)]
color = 'green'
# Draw the curve and error
try:
fig.line(
gdMaskednum,
paMasked,
legend_label='cutoff',
line_color=color)
except AttributeError:
fig.line(gdMaskednum, paMasked, legend='cutoff', line_color=color)
# Top
terr_y = np.concatenate([paMin[i0_top:i1_top + 1],
paMaxTmp[i0_top:i1_top + 1][::-1]])
terr_x = np.concatenate([gdMaskednum[i0_top:i1_top + 1],
gdMaskednum[i0_top:i1_top + 1][::-1]])
fig.patch(terr_x, terr_y, color=color, fill_alpha=0.2, line_alpha=0)
# Bottom
berr_y = np.concatenate([paMinTmp[i0_bot:i1_bot + 1],
paMax[i0_bot:i1_bot + 1][::-1]])
berr_x = np.concatenate([gdMaskednum[i0_bot:i1_bot + 1],
gdMaskednum[i0_bot:i1_bot + 1][::-1]])
fig.patch(berr_x, berr_y, color='red', fill_alpha=0.2, line_alpha=0)
from bokeh.plotting import show
show(fig)
# Plot formatting
fig.xaxis.axis_label = 'Date'
fig.yaxis.axis_label = 'Aperture Position Angle (degrees)'
return paGood, paBad, gd, fig
def using_gtvt(ra, dec, instrument, targetName='noName', ephFileName=None, output='bokeh'):
"""Plot the visibility (at a range of position angles) against time.
Parameters
----------
ra : str
The RA of the target (in degrees) hh:mm:ss.s or dd:mm:ss.s or representing a float
dec : str
The Dec of the target (in degrees) hh:mm:ss.s or dd:mm:ss.s or representing a float
instrument : str
Name of the instrument. Can either be (case-sensitive):
'NIRISS', 'NIRCam', 'MIRI', 'FGS', or 'NIRSpec'
ephFileName : str
The filename of the ephemeris file.
output : str
Switches on plotting with Bokeh. Parameter value must be 'bokeh'.
Returns
-------
paGood : float
The good position angle.
paBad : float
The bad position angle.
gd : matplotlib.dates object
The gregorian date.
fig : bokeh.plotting.figure object
The plotted figure.
"""
# Getting calculations from GTVT (General Target Visibility Tool)
blockPrint()
tab = get_table(ra, dec)
enablePrint()
gd = tab['Date']
paMin = tab[str(instrument) + ' min']
paMax = tab[str(instrument) + ' max']
paNom = tab[str(instrument) + ' nom']
v3min = tab['V3PA min']
v3max = tab['V3PA max']
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~NOTE~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Addressing NIRSpec issue*
# *the issue that NIRSpec's angle goes beyond 360 degrees with some targs,
# thus resetting back to 0 degrees, which can make the plot look weird
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
index = np.arange(0, len(paNom), 1)
for idx in index:
try:
a1 = paNom[idx]
b1 = paNom[idx + 1]
if (np.isfinite(a1)) & (np.isfinite(b1)):
delta = np.abs(a1 - b1)
if delta > 250:
gd = np.insert(gd, idx + 1, np.nan)
paMin = np.insert(paMin, idx + 1, np.nan)
paMax = np.insert(paMax, idx + 1, np.nan)
paNom = np.insert(paNom, idx + 1, np.nan)
v3min = np.insert(v3min, idx + 1, np.nan)
v3max = np.insert(v3min, idx + 1, np.nan)
except BaseException:
pass
# Setting up HoverTool parameters & other variables
COLOR = 'green'
TOOLS = 'pan, wheel_zoom, box_zoom, reset, save'
SOURCE = ColumnDataSource(data=dict(pamin=paMin,
panom=paNom,
pamax=paMax,
date=gd))
TOOLTIPS = [('Date', '@date{%F}'),
('Maximum Aperture PA', '@pamax'),
('Nominal Aperture PA', '@panom'),
('Minimum Aperture PA', '@pamin')]
# Time to plot
if output == 'bokeh':
fig = figure(tools=TOOLS,
plot_width=800,
plot_height=400,
x_axis_type='datetime',
title='{} Visibility with {}'.format(targetName,
instrument))
# Draw the curve and PA min/max circles
try:
nom = fig.line('date', 'panom',
line_color=COLOR,
legend_label='Nominal Aperture PA',
alpha=.5,
source=SOURCE)
except AttributeError:
nom = fig.line('date', 'panom',
line_color=COLOR,
legend='Nominal Aperture PA',
alpha=.5,
source=SOURCE)
fig.circle('date', 'pamin', color=COLOR, size=1, source=SOURCE)
fig.circle('date', 'pamax', color=COLOR, size=1, source=SOURCE)
# Adding HoverTool
fig.add_tools(HoverTool(renderers=[nom],
tooltips=TOOLTIPS,
formatters={'date': 'datetime'},
mode='vline'))
# Plot formatting
fig.xaxis.axis_label = 'Date'
fig.yaxis.axis_label = 'Aperture Position Angle (degrees)'
fig.y_range = ranges.Range1d(0, 360)
# Making the output table
# Creating new lists w/o the NaN values
v3minnan, v3maxnan, paNomnan, paMinnan, paMaxnan, gdnan, mjds = \
[], [], [], [], [], [], []
for vmin, vmax, pnom, pmin, pmax, date in zip(
v3min, v3max, paNom, paMin, paMax, gd):
if np.isfinite(pmin):
v3minnan.append(vmin)
v3maxnan.append(vmax)
paNomnan.append(pnom)
paMinnan.append(pmin)
paMaxnan.append(pmax)
gdnan.append(date)
# Adding MJD column
mjdnan = []
for date in gdnan:
t = Time(str(date), format='iso')
mjd = t.mjd
mjdnan.append(mjd)
# Adding lists to a table object
table = Table([v3minnan,
v3maxnan,
paMinnan,
paMaxnan,
paNomnan,
gdnan,
mjdnan],
names=('min_V3_PA',
'max_V3_PA',
'min_Aperture_PA',
'max_Aperture_PA',
'nom_Aperture_PA',
'Gregorian',
'MJD'))
# Getting bad PAs
allPAs = np.arange(0, 360, 1)
badPAs = []
for pa in allPAs:
if (pa not in np.round(paMinnan)) & \
(pa not in np.round(paMaxnan)) & \
(pa not in np.round(paNomnan)):
badPAs.append(pa)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~NOTE~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# This addresses a bokeh shading issue that accidentally shades
# accessible PAs (e.g: trappist-1b)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
badPAs = select_badPAs_ge_paNomnan(badPAs, paNomnan)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~NOTE~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Grouping the bad PAs into lists within the badPAs list.
# This will make bad PA shading easier in the contamination Bokeh plot
# (sossContamFig.py)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
badPAs = np.sort(badPAs)
if len(badPAs > 0):
grouped_badPAs = [[badPAs[0]]]
for idx in range(1, len(badPAs)):
if ((badPAs[idx - 1] + 1) == badPAs[idx]):
grouped_badPAs[len(grouped_badPAs) - 1].append(badPAs[idx])
elif ((badPAs[idx - 1] + 1) < badPAs[idx]):
grouped_badPAs.append([badPAs[idx]])
grouped_badPAs = np.asarray(grouped_badPAs)
else: # Accounting for targets with 100% visibility
grouped_badPAs = np.asarray([])
return paMin, paMax, gd, fig, table, grouped_badPAs
def select_badPAs_ge_paNomnan(badPAs, paNomnan, threshold=7):
"""Returns the absolute difference between each badPAs and paNomnan
Should be greater than threshold (default=7)
Parameters
----------
badPAs: list
The list of bad position angles
paNomnan: list
The list of nominal PAs
Returns
-------
np.ndarray
The array of PAs
"""
# Reshaping
badPAs_array = np.array(badPAs)[np.newaxis] # (1, len(badPAs))
paNomnan_array = np.array(paNomnan)[np.newaxis].T # (len(paNomnan), 1)
# elementwise absolute difference
diff = np.abs(np.subtract(badPAs_array, paNomnan_array)) # (len(paNomnan), len(badPAs))
# boolean array above threshold
above_thresh = np.all(diff >= threshold, axis=0) # (len(badPAs),)
# index and return those that are above threshold
return badPAs_array[0, above_thresh]