Browse Source

Bug Fix. Warning messages. Help tooltips

Benito Marcote 2 years ago
  1. 76
  2. BIN
  3. 91
  4. 3
  5. BIN
  6. BIN
  7. 13


@ -25,6 +25,7 @@ import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
import plotly.graph_objs as go
from datetime import datetime as dt
from astropy.time import Time
@ -108,9 +109,30 @@ server = app.server
# app.css.append_css({"external_url": ""})
def tooltip(message, idname, trigger='?', placement='right', **kwargs):
"""Defines a tooltip (popover) that will be shown in the rendered page.
It will place a <sup>`trigger`</sup> in the page within a span tag.
Returns the list with all html elements.
return [html.Span(children=html.Sup(trigger, className='popover-link'), id=idname),
dbc.Tooltip(message, target=idname, placement=placement,
# className='tooltip-class', #innerClassName='tooltip-class-inner',
##################### This is the webpage layout
app.layout = html.Div([
$(document).ready(function() {
placement : 'bottom',
title : '<div style="text-align:center; color:red; text-decoration:underline; font-size:14px;"> Muah ha ha</div>', //this is the top title bar of the popover. add some basic css
html: 'true',
content : '<div id="popOverBox"><img src="" width="251" height="201" /></div>' //this is the content of the html box. add the image here or anything you want really.
html.Div(id='banner', className='navbar-brand d-flex p-3 shadow-sm', children=[
html.A(className='d-inline-block mr-md-auto', href="", children=[
html.Img(height='70px', src=app.get_asset_url("logo_evn.png"),
@ -139,13 +161,14 @@ app.layout = html.Div([
'padding': '2%'}, children=[
html.Div(className='form-group', children=[
html.Label('Observing Band'),
#data-content="This popover appears on the right", data-toggle="popover"),
*tooltip(idname='popover-band', message="First select the observing band. Antenna list will be updated and only the ones that can observe at this band will be enable."),
dcc.Dropdown(id='band', persistence=True,
options=[{'label': fs.bands[b], 'value': b} for b \
in fs.bands], value='18cm'),
html.Div(className='form-group', children=[
html.Label('Select default VLBI Network(s)'),
*tooltip(idname='popover-network', message="Automatically selects the default antennas for the selected VLBI network(s)."),
dcc.Dropdown(id='array', options=[{'label': n, 'value': n} \
for n in default_arrays if n != 'e-EVN'], value=['EVN'],
@ -154,6 +177,8 @@ app.layout = html.Div([
dcc.Checklist(id='e-EVN', className='checkbox', persistence=True,
options=[{'label': ' e-EVN (real-time) mode',
'value': 'e-EVN'}], value=[]),
message="Only available for the EVN: real-time correlation mode.")
html.Div(className='form-group', children=[
html.Label('Start of observation (UTC)'),
@ -175,6 +200,8 @@ app.layout = html.Div([
html.Div(className='form-group', children=[
html.Label('Target Source Coordinates'),
message="J2000 coordinates are assumed."),
# dcc.Input(id='source', value='hh:mm:ss dd:mm:ss', type='text',
dcc.Input(id='source', value='12:29:06.7 +02:03:08.6', type='text',
className='form-control', placeholder="hh:mm:ss dd:mm:ss",
@ -184,37 +211,54 @@ app.layout = html.Div([
html.Div(className='form-group', children=[
children='Percent. of on-target time'),
children='% of on-target time'),
message="Assumes that you will only spend this amount of the total observing time on the given target source. It affects the expected sensitivity."),
dcc.Slider(id='onsourcetime', min=20, max=100, step=5, value=75,
marks= {i: str(i) for i in range(20, 101, 10)},
html.Div(className='form-group', children=[
html.Label('Datarate per station (in Mbps)'),
message=["Expected datarate for each station, assuming all of them run at the same rate.",
html.Li("The EVN can run typically at up to 2 Gbps (1 Gbps at L band), although a few antennas may observe at lower datarates."),
html.Li("The VLBA can now observe up to 4 Gbps."),
html.Li("The LBA typically observes at 512 Mbps but can run up to 1 Gbps."),
html.Li("Check the documentation from other networks to be sure about their capabilities.")])]),
dcc.Dropdown(id='datarate', placeholder="Select a datarate...",
options=[{'label': str(dr), 'value': dr} \
for dr in fs.data_rates], value=1024, persistence=True),
html.Div(className='form-group', children=[
html.Label('Number of subbands'),
message="In how many subbands the total band will be split during correlation."),
dcc.Dropdown(id='subbands', placeholder="Select no. subbands...",
options=[{'label': str(sb), 'value': sb} \
for sb in fs.subbands], value=8, persistence=True),
html.Div(className='form-group', children=[
html.Label('Number of spectral channels'),
message="How many channels per subband will be produced after correlation."),
dcc.Dropdown(id='channels', placeholder="Select no. channels...",
options=[{'label': str(ch), 'value': ch} \
for ch in fs.channels], value=32, persistence=True),
html.Div(className='form-group', children=[
html.Label('Number of polarizations'),
message="Number of polarizations to correlate. Note that VLBI observes circular polarizations. Full polarization implies the four stokes: RR, LL, RL, LR; while dual polarization implies RR and LL only."),
dcc.Dropdown(id='pols', placeholder="Select polarizations...",
options=[{'label': fs.polarizations[p], 'value': p} \
for p in fs.polarizations], value=4, persistence=True),
html.Div(className='form-group', children=[
html.Label('Integration time (s)'),
message="Integration time to compute each visibility. Note that for continuum observations values of 1-2 seconds are typical."),
dcc.Dropdown(id='inttime', placeholder="Select integration time...",
options=[{'label': fs.inttimes[it], 'value': it} \
for it in fs.inttimes], value=2, persistence=True),
@ -255,7 +299,8 @@ app.layout = html.Div([
# dcc.Markdown(id='sensitivity-output',
# children="Set the observation first.")
html.Div(className='col-12', id='sensitivity-output',
children=[html.Div(className='col-6 justify-content-center', children=[html.Br(),
children=[html.Div(className='col-6 justify-content-center',
html.P("You need to set the observation and click in the 'Compute Observation' buttom first (go to the previous tab).")])
@ -299,6 +344,7 @@ app.layout = html.Div([
def error_text(an_error):
"""Message written in a modal error window.
@ -408,7 +454,7 @@ def update_sensitivity(obs):
temp_msg += [f"The longest (projected) baseline is {optimal_units(longest_bl, [, u.m]):.5n} ({longest_bl_lambda.value:.3n} {[0]}lambda)."]
synthbeam = obs.synthesized_beam()
synthbeam_units = optimal_units(synthbeam['bmaj'], [u.arcsec, u.mas, u.uas]).unit
temp_msg += [f"The expected synthesized beam will be approx. {synthbeam['bmaj'].to(synthbeam_units).value:.2n} x {synthbeam['bmin'].to(synthbeam_units):.2n}^2, PA = {synthbeam['pa']:.2n}."]
temp_msg += [f"The expected synthesized beam will be approx. {synthbeam['bmaj'].to(synthbeam_units).value:.2n} x {synthbeam['bmin'].to(synthbeam_units):.2n}^2, PA = {synthbeam['pa']:.3n}."]
cards += create_sensitivity_card('Antennas', temp_msg)
# Frequency
@ -589,18 +635,28 @@ def compute_observation(n_clicks, band, starttime, endtime, source, onsourcetime
obs_times = time0 + np.linspace(0, (time1-time0).to(u.min).value, 50)*u.min
# obs_times = time0 + np.arange(0, (time1-time0).to(u.min).value, 15)*u.min
all_selected_antennas = list(itertools.chain.from_iterable(ants))
obs = observation.Observation(target=target_source, times=obs_times, band=band,
obs = observation.Observation(target=target_source, times=obs_times, band=band,
datarate=datarate, subbands=subbands, channels=channels,
polarizations=pols, inttime=inttime, ontarget=onsourcetime/100.0,
# except Exception as e:
# return dash.no_update, dash.no_update, dash.no_update, error_text(e)
sensitivity_results = update_sensitivity(obs)
except observation.SourceNotVisible:
return [html.Br(),dbc.Alert([html.H4("Warning!", className='alert-heading'),
html.P(["Your source cannot be observed within the arranged observation.",
"The source is not visible for any of the selected antennas " \
+ "in the given observing time."]),
html.P("Modify the observing time of select a different array to observe" \
+ " this source.")],\
color='warning', dismissable=True)], \
dash.no_update, dash.no_update, dash.no_update, dash.no_update, dash.no_update
# return update_sensitivity(obs), dash.no_update, dash.no_update
# TODO: parallelize all these functions
return 'You can check now the results in the different tabs', update_sensitivity(obs), \
return [html.Br(),
dbc.Alert("You can check now the results in the different tabs", color='info', \
dismissable=True)], sensitivity_results, \
get_fig_ant_elev(obs), get_fig_ant_up(obs), get_fig_uvplane(obs), dash.no_update


Binary file not shown.


Width:  |  Height:  |  Size: 70 KiB


Width:  |  Height:  |  Size: 67 KiB


@ -3262,13 +3262,13 @@ input[type=checkbox]:checked:after {
input[type=checkbox]:not(:disabled):checked:hover:after {
content: " ";
color: gray;
/* content: " "; */
/* color: gray; */
background-color: rgb(232, 222, 222);
content: "\2714";
font-weight: bold;
/* font-weight: bold; */
color: green;
font-size: 1.5rem;
/* font-size: 1.1rem; */
@ -3383,6 +3383,89 @@ input[type=checkbox]:disabled:after {
margin-top: -15px;
.popover-link {
color: gray;
.tooltip {
position: absolute;
/* z-index: 1070; */
display: block;
/* font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; */
font-size: 12px;
font-style: normal;
font-weight: normal;
line-height: 1.42857143;
text-align: left;
text-decoration: none;
text-shadow: none;
text-transform: none;
letter-spacing: normal;
word-break: normal;
word-spacing: normal;
word-wrap: normal;
white-space: normal;
opacity: 0;
line-break: auto;
.tooltip-inner {
max-width: 200px;
padding: 3px 8px;
color: gray;
text-align: left;
/* background-color: #f4f4ef; */
background-color: white;
border-radius: 4px;
border: 1px solid #a01d26;
} { opacity: 1;} { margin-top: -3px; padding: 5px 0;}
.tooltip-class.right { margin-left: 3px; padding: 0 5px;}
.tooltip-class.bottom { margin-top: 3px; padding: 5px 0;}
.tooltip-class.left { margin-left: -3px; padding: 0 5px;}
.tooltip-class-arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
color: #a01d26;
} .tooltip-class-arrow {
bottom: 0;
left: 50%;
margin-left: -5px;
border-width: 5px 5px 0;
border-top-color: #a01d26;
} .tooltip-class-arrow { bottom: 0; right: 5px; margin-bottom: -5px; border-width: 5px 5px 0; border-top-color: #a01d26;} .tooltip-class-arrow { bottom: 0; left: 5px; margin-bottom: -5px; border-width: 5px 5px 0; border-top-color: #a01d26;}
.tooltip-class.right .tooltip-class-arrow { top: 50%; left: 0; margin-top: -5px; border-width: 5px 5px 5px 0; border-right-color: #a01d26;}
.tooltip-class.left .tooltip-class-arrow { top: 50%; right: 0; margin-top: -5px; border-width: 5px 0 5px 5px; border-left-color: #a01d26;}
.tooltip-class.bottom .tooltip-class-arrow { top: 0; left: 50%; margin-left: -5px; border-width: 0 5px 5px; border-bottom-color: #a01d26;}
.tooltip-class.bottom-left .tooltip-class-arrow { top: 0; right: 5px; margin-top: -5px; border-width: 0 5px 5px; border-bottom-color: #a01d26;}
.tooltip-class.bottom-right .tooltip-class-arrow { top: 0; left: 5px; margin-top: -5px; border-width: 0 5px 5px; border-bottom-color: #a01d26;}
.alert-warning {
/* color:#07767a; */
color: #856404;
background-color: #fff3cd;
border-color: #ffeeba;
.alert-warning hr {
border-top-color: #ffe8a1;
.alert-warning .alert-link {
color: #533f03;
box-shadow: inset 0 2px 4px 0 hsla(0 0% 0& 0.08);


@ -65,6 +65,8 @@ Stations
- name : str
- coord : coord.SkyCord
@ -153,6 +155,7 @@ X In sensitivity: highlight antennas that cannot observe the source.
- Arecibo limits. Currently not shown.
- Add a per-baseline basis sensitivity? (maybe as a roll-over?)
- Also, something about the largest angular scale you are sensitive to.
- Add favicon.ico to the assets folder.


Binary file not shown.


Binary file not shown.


@ -13,6 +13,9 @@ from astroplan import FixedTarget
from . import stations
class SourceNotVisible(Exception):
class Source(FixedTarget):
"""Defines a source with some coordinates and a name.
@ -278,7 +281,7 @@ class Observation(object):
def datasize(self):
"""Returns the expected size for the output FITS files.
temp = len(self.stations)**2*(self.times[-1]-self.times[0])/self.inttime
temp = len(self.stations)**2*((self.times[-1]-self.times[0])/self.inttime).decompose()
temp *= self.polarizations*self.subbands*self.channels
temp *= 1.75*u.GB/(131072*3600)
@ -341,6 +344,8 @@ class Observation(object):
It returns a dictionary containing the uv values in lambda units
for each baseline as key.
Complex conjugates are not provided.
It may raise the exception SourceNotVisible if no antennas can observe the source
at all during the observation.
bl_uv_up = {}
hourangle = - self.gstimes
@ -369,7 +374,11 @@ class Observation(object):
for i,bl_name in enumerate(bl_names):
ant1, ant2 = bl_name.split('-')
bl_up = (np.array([a for a in ants_up[ant1][0] if a in ants_up[ant2][0]]), )
bl_uv_up[bl_name] = bl_uv[:,:,i][bl_up]/
if len(bl_up[0]) > 0:
bl_uv_up[bl_name] = bl_uv[:,:,i][bl_up]/
if len(bl_uv_up.keys()) == 0:
raise SourceNotVisible
return bl_uv_up