Unverified Commit d6eda370 authored by scisup's avatar scisup Committed by GitHub

Merge pull request #9 from lofar-astron/AddCalibrator

Add calibrator and multi-beam calculation
parents 632e1616 e21e3f0c
......@@ -3,17 +3,17 @@
The LOFAR calculator LUCI is deployed on ASTRON's webserver dop385 and is managed using [supervisord](http://supervisord.org/). Whenever you want to make a public release,
+ On your development node, create a new tag with ```git tag -m <message> <version id>``` and ```git push --follow-tags```.
+ Log on to dop385 and go to the appropriate directory with ```cd /data/LUCI/LOFAR-calculator```.
+ Update the repository with ```git pull```. If you do not have write access to this directory, contact Reinoud Bokhorst.
+ Log on to dop385 and go to the appropriate directory with ```cd /data/LUCI/LOFAR-calculator```.
+ Update the repository with ```git pull```. If you do not have write access to this directory, contact Jasmin Klipic.
+ Restart supervisord with ```supervisorctl restart luci```.
+ Check <https://support.astron.nl/luci/> if everything is fine.
+ Check <https://support.astron.nl/luci/> if everything is fine.
# How to update the singularity image?
All the required dependencies for LUCI on dop385 is installed inside a singularity container. We do not have to update the container every time we make a public release. However, if you want to update the container, do the following:
+ The recipe for creating the container is supplied with the repository. See file ```singularity.recipe```. Make changes to the file as needed.
+ Build the container with ```sudo singularity build lofarcalc.simg singularity.recipte```. If the build is successful, you will see a new file called lofarcalc.simg.
+ Copy ```lofarcalc.simg``` to ```dop385:/data/LUCI```. If you do not have write access to this directory, contact Reinoud Bokhorst.
+ Build the container with ```sudo singularity build lofarcalc.simg singularity.recipte```. If the build is successful, you will see a new file called lofarcalc.simg.
+ Copy ```lofarcalc.simg``` to ```dop385:/data/LUCI```. If you do not have write access to this directory, contact Jasmin Klipic.
+ Restart supervisord with ```supervisorctl restart luci```.
+ Check <https://support.astron.nl/luci/> if everything is fine.
+ Check <https://support.astron.nl/luci/> if everything is fine.
......@@ -60,11 +60,12 @@ def calculate_im_noise(n_core, n_remote, n_int, hba_mode, obs_t, n_sb):
im_noise *= 1.E6 # In uJy
return '{:0.2f}'.format(im_noise)
def calculate_raw_size(obs_t, int_time, n_baselines, n_chan, n_sb):
def calculate_raw_size(obs_t, cal_t, n_cal, int_time, n_baselines, n_chan, n_sb, n_beams):
"""Compute the datasize of a raw LOFAR measurement set given the
length of the observation, correlator integration time, number
of baselines, number of channels per subband, and number of subbands"""
n_rows = int(n_baselines * (obs_t / int_time)) - n_baselines
# TODO: The below equation needs to be fixed (scale n_baselines).
n_rows = int(n_baselines * ( (obs_t*n_beams + cal_t*n_cal ) / int_time)) - n_baselines
# A single row in LofarStMan format contains
# - 32-bit sequence number (4 bytes)
# - n_chan*16-bit samples for weight and sigma calculation (2*n_chan bytes)
......@@ -83,7 +84,7 @@ def calculate_bf_size(n_sub, n_chan, n_pol, n_value, t_samp, obs_time):
size_GB = size_Gbs * obs_time / 8.
return size_GB
def calculate_proc_size(obs_t, int_time, n_baselines, n_chan, n_sb, pipe_type,
def calculate_proc_size(obs_t, cal_t, n_cal, int_time, n_baselines, n_chan, n_sb, n_beams, pipe_type,
t_avg, f_avg, dy_compress):
"""Compute the datasize of averaged LOFAR measurement set given the
length of the observation, integration time, number of baselines,
......@@ -96,7 +97,7 @@ def calculate_proc_size(obs_t, int_time, n_baselines, n_chan, n_sb, pipe_type,
n_chan //= f_avg
# Change integ_t to account for t_avg
int_time *= t_avg
n_rows = int(n_baselines * (obs_t / int_time)) - n_baselines
n_rows = int(n_baselines * ( (obs_t*n_beams + cal_t*n_cal ) / int_time)) - n_baselines
# What does a single row in an averaged MS contain?
sb_size = n_rows * ((7*8) + \
(4+(4*n_chan)) + \
......@@ -112,7 +113,7 @@ def calculate_proc_size(obs_t, int_time, n_baselines, n_chan, n_sb, pipe_type,
tot_size = tot_size/3.
return '{:0.2f}'.format(tot_size)
def calculate_pipe_time(obs_t, n_sb, array_mode, ateam_names, pipe_type):
def calculate_pipe_time(obs_t, cal_t, n_cal, n_sb, n_beams, array_mode, ateam_names, pipe_type):
"""Compute the pipeline processing time.
Inputs:
obs_t - Observation time in hours
......@@ -133,14 +134,14 @@ def calculate_pipe_time(obs_t, n_sb, array_mode, ateam_names, pipe_type):
if pipe_type == 'preprocessing':
if 'hba' in array_mode:
proc_time = hba_factor[n_ateams] * n_sb * obs_t
proc_time = hba_factor[n_ateams] * n_sb * ( obs_t * n_beams + cal_t * n_cal )
else:
proc_time = lba_factor[n_ateams] * n_sb * obs_t
proc_time = lba_factor[n_ateams] * n_sb * ( obs_t * n_beams + cal_t * n_cal )
# Convert to hours
proc_time /= 3600.
return proc_time
def validate_inputs(obs_t, n_core, n_remote, n_int, n_sb, integ_t, t_avg,
def validate_inputs(obs_t, cal_t, n_cal, n_core, n_remote, n_int, n_sb, integ_t, t_avg,
f_avg, src_name, coord, hba_mode, pipe_type, ateam_names):
"""Valid text input supplied by the user: observation time, number of
subbands, and integration time. Following checks will be performed:
......@@ -160,13 +161,29 @@ def validate_inputs(obs_t, n_core, n_remote, n_int, n_sb, integ_t, t_avg,
Return state=True/False accompanied by an error msg
Note: all input parameters are still strings."""
msg = ''
# Validate the length of the observing time
# Validate the length of the observing times
try:
float(obs_t)
if float(obs_t) <= 0:
msg += 'Observation time cannot be zero or negative.\n'
if float(obs_t) < 0:
msg += 'Observation time cannot be negative.\n'
except ValueError:
msg += 'Invalid observation time specified.\n'
try:
float(cal_t)
if float(cal_t) < 0:
msg += 'Calibrator duration cannot be negative.\n'
except ValueError:
msg += 'Invalid calibrator duration specified.\n'
try:
float(cal_t)+float(obs_t)
if float(cal_t)+float(obs_t) <= 0:
msg += 'One of Observation time and Calibration duration must be at least one.\n'
except ValueError:
msg += 'Invalid calibrator duration or observation time specified.\n'
# Validate the number of calibrators
if n_cal < 0:
msg += 'Number of calibrators cannot be negative.\n'
# Validate the number of stations
if n_core < 0 or n_core > 24:
msg += 'Number of core stations must be between 0 and 24.\n'
......
......@@ -33,7 +33,7 @@ app.title = 'LUCI - LOFAR Unified Calculator for Imaging'
##############################################
##############################################
# Show observational setup fields based on
# Show observational setup fields based on
# obsMode dropdown value
##############################################
@app.callback(
......@@ -41,15 +41,15 @@ app.title = 'LUCI - LOFAR Unified Calculator for Imaging'
Output('tabModeRowL', 'style'),
Output('tabModeRow', 'style'),
Output('tabModeRow', 'value'),
Output('stokesForm', 'style'),
Output('stokesRowL', 'style'),
Output('stokesRow', 'style'),
Output('nRingsForm', 'style'),
Output('nRingsRow', 'style'),
Output('nRingsRowL', 'style'),
Output('pipeTypeRow', 'options'),
Output('pipeTypeRow', 'value'),
......@@ -83,13 +83,13 @@ def toggle_obs_mode(obs_value):
# Show TAb stokes fields based on dropdown value
################################################
@app.callback(
[Output('stokesRow', 'options'),
[Output('stokesRow', 'options'),
Output('stokesRow', 'value'),
Output('nRemoteForm', 'style'),
Output('nRemoteRow', 'style'),
Output('nRemoteRowL', 'style'),
Output('nIntForm', 'style'),
Output('nIntRow', 'style'),
Output('nIntRowL', 'style'),
......@@ -113,7 +113,7 @@ def toggle_stokes(value):
else:
return valid_stokes, 'I', \
{'display':'none'}, {'display':'none'}, {'display':'none'}, \
{'display':'none'}, {'display':'none'}, {'display':'none'},
{'display':'none'}, {'display':'none'}, {'display':'none'},
##############################################
......@@ -184,22 +184,22 @@ def validate_t_avg(n_blur, n_clicks, value, is_open):
#######################################
@app.callback(
Output('msgboxFAvg', 'is_open'),
[Input('fAvgRow', 'n_blur'),
[Input('fAvgRow', 'value'),
Input('mbfAvgClose', 'n_clicks')
],
[State('fAvgRow', 'value'),
State('msgboxFAvg', 'is_open')
[State('msgboxFAvg', 'is_open'),
State('nChanRow','value')
]
)
def validate_t_avg(n_blur, n_clicks, value, is_open):
def validate_f_avg(value, n_clicks, is_open, channels_per_subband):
"""Validate frequency averaging factor and display error message if needed"""
if is_open is True and n_clicks is not None:
# The message box is open and the user has clicked the close
# button. Close the alert message
return False
if n_blur is None:
#if n_blur is None:
# The page is loading. Do not validate anything
return False
# return False
else:
# Text box has lost focus.
# Go ahead and validate the text in it.
......@@ -207,7 +207,71 @@ def validate_t_avg(n_blur, n_clicks, value, is_open):
int(str(value))
except ValueError:
return True
try:
assert int(str(channels_per_subband))>=int(str(value))
except:
return True
return False
#######################################
# Limit freq averaging factor
#######################################
@app.callback(
Output('fAvgRow', 'options'),
[Input('nChanRow', 'value'),
Input('mbfAvgClose', 'n_clicks')
],
[State('msgboxFAvg', 'is_open'),
]
)
def validate_f_avg(value, n_clicks, is_open):
"""Validate frequency averaging factor and display error message if needed"""
if is_open is True and n_clicks is not None:
# The message box is open and the user has clicked the close
# button. Close the alert message
return False
#if n_blur is None:
# The page is loading. Do not validate anything
# return False
else:
# Text box has lost focus.
# Go ahead and validate the text in it.
if int(str(value))==64:
return [
{'label':'1', 'value':1},
{'label':'2', 'value':2},
{'label':'4', 'value':4},
{'label':'8', 'value':8},
{'label':'16', 'value':16},
{'label':'32', 'value':32},
{'label':'64', 'value':64},
]
elif int(str(value))==128:
return [
{'label':'1', 'value':1},
{'label':'2', 'value':2},
{'label':'4', 'value':4},
{'label':'8', 'value':8},
{'label':'16', 'value':16},
{'label':'32', 'value':32},
{'label':'64', 'value':64},
{'label':'128', 'value':128},
]
# else, return default, max 256 averaging factor
return [
{'label':'1', 'value':1},
{'label':'2', 'value':2},
{'label':'4', 'value':4},
{'label':'8', 'value':8},
{'label':'16', 'value':16},
{'label':'32', 'value':32},
{'label':'64', 'value':64},
{'label':'128', 'value':128},
{'label':'256', 'value':256}
]
#######################################
# What should the resolve button do?
......@@ -253,6 +317,8 @@ def on_resolve_click(n, close_msg_box, target_name, is_open):
Input('mbGenPdfClose', 'n_clicks')
],
[State('obsTimeRow', 'value'),
State('calTimeRow', 'value'),
State('nCalRow', 'value'),
State('nCoreRow', 'value'),
State('nRemoteRow', 'value'),
State('nIntRow', 'value'),
......@@ -260,6 +326,7 @@ def on_resolve_click(n, close_msg_box, target_name, is_open):
State('nSbRow', 'value'),
State('intTimeRow', 'value'),
State('hbaDualRow', 'value'),
State('coordRow', 'value'),
State('pipeTypeRow', 'value'),
State('tAvgRow', 'value'),
......@@ -283,8 +350,8 @@ def on_resolve_click(n, close_msg_box, target_name, is_open):
State('stokesRow', 'value')
]
)
def on_genpdf_click(n_clicks, close_msg_box, obs_t, n_core, n_remote, n_int, n_chan,
n_sb, integ_t, ant_set, pipe_type, t_avg, f_avg, is_dysco,
def on_genpdf_click(n_clicks, close_msg_box, obs_t, cal_t, n_cal, n_core, n_remote, n_int, n_chan,
n_sb, integ_t, ant_set, coord, pipe_type, t_avg, f_avg, is_dysco,
im_noise_val, raw_size, proc_size, pipe_time, is_msg_box_open,
elevation_fig, distance_table, obs_date, obs_mode, tab_mode, stokes):
"""Function defines what to do when the generate pdf button is clicked"""
......@@ -306,8 +373,8 @@ def on_genpdf_click(n_clicks, close_msg_box, obs_t, n_core, n_remote, n_int, n_c
# Generate a relative and absolute filenames to the pdf file
rel_path = os.path.join(rel_path, 'summary_{}.pdf'.format(randnum))
abs_path = os.path.join(os.getcwd(), rel_path)
g.generate_pdf(rel_path, obs_t, n_core, n_remote, n_int, n_chan,
n_sb, integ_t, ant_set, pipe_type, t_avg, f_avg,
g.generate_pdf(rel_path, obs_t, cal_t, n_cal, n_core, n_remote, n_int, n_chan,
n_sb, integ_t, ant_set, coord, pipe_type, t_avg, f_avg,
is_dysco, im_noise_val, raw_size, proc_size, pipe_time,
elevation_fig, distance_table, obs_date,
obs_mode, tab_mode, stokes)
......@@ -339,6 +406,8 @@ def serve_static(resource):
Input('msgBoxClose', 'n_clicks'),
],
[State('obsTimeRow', 'value'),
State('calTimeRow', 'value'),
State('nCalRow', 'value'),
State('nCoreRow', 'value'),
State('nRemoteRow', 'value'),
State('nIntRow', 'value'),
......@@ -361,7 +430,7 @@ def serve_static(resource):
State('stokesRow', 'value')
]
)
def on_calculate_click(n, n_clicks, obs_t, n_core, n_remote, n_int, n_chan, n_sb,
def on_calculate_click(n, n_clicks, obs_t, cal_t, n_cal, n_core, n_remote, n_int, n_chan, n_sb,
integ_t, hba_mode, pipe_type, t_avg, f_avg, dy_compress,
is_open, src_name, coord, obs_date, calib_names,
ateam_names, obs_mode, tab_mode, stokes):
......@@ -390,7 +459,7 @@ def on_calculate_click(n, n_clicks, obs_t, n_core, n_remote, n_int, n_chan, n_sb
n_remote = '0'
if n_int is None:
n_int = '0'
status, msg = bk.validate_inputs(obs_t, int(n_core), int(n_remote), \
status, msg = bk.validate_inputs(obs_t, cal_t, int(n_cal), int(n_core), int(n_remote), \
int(n_int), n_sb, integ_t, t_avg, f_avg, \
src_name, coord, hba_mode, pipe_type, \
ateam_names)
......@@ -400,6 +469,13 @@ def on_calculate_click(n, n_clicks, obs_t, n_core, n_remote, n_int, n_chan, n_sb
{'display':'none'}, {}
else:
# Estimate the raw data size
if coord is not '':
coord_list = coord.split(',')
coord_input_list = coord.split(',')
n_sap=len(coord_list)
else:
n_sap=1
n_baselines = bk.compute_baselines(int(n_core), int(n_remote),
int(n_int), hba_mode)
im_noise = bk.calculate_im_noise(int(n_core), int(n_remote),
......@@ -408,8 +484,12 @@ def on_calculate_click(n, n_clicks, obs_t, n_core, n_remote, n_int, n_chan, n_sb
if obs_mode == 'Interferometric':
# Calculate interferometric raw size
raw_size = bk.calculate_raw_size(float(obs_t), float(integ_t),
n_baselines, int(n_chan), int(n_sb))
raw_size = bk.calculate_raw_size(float(obs_t), float(cal_t), int(n_cal), float(integ_t),
n_baselines, int(n_chan), int(n_sb), n_sap)
avg_size = bk.calculate_proc_size(float(obs_t), float(cal_t), int(n_cal), float(integ_t),
n_baselines, int(n_chan), int(n_sb), n_sap,
pipe_type, int(t_avg), int(f_avg),
dy_compress)
if obs_mode == 'Beamformed':
# Calculate beamformed datasize
if stokes == 'I':
......@@ -430,18 +510,15 @@ def on_calculate_click(n, n_clicks, obs_t, n_core, n_remote, n_int, n_chan, n_sb
pipe_time = None
avg_size = 0
else:
pipe_time = bk.calculate_pipe_time(float(obs_t), int(n_sb),
pipe_time = bk.calculate_pipe_time(float(obs_t), float(cal_t), int(n_cal), int(n_sb), n_sap,
hba_mode, ateam_names,
pipe_type)
avg_size = bk.calculate_proc_size(float(obs_t), float(integ_t),
n_baselines, int(n_chan), int(n_sb),
pipe_type, int(t_avg), int(f_avg),
dy_compress)
avg_size = bk.calculate_proc_size(float(obs_t), float(cal_t), int(n_cal), float(integ_t),
n_baselines, int(n_chan), int(n_sb), n_sap,
pipe_type, int(t_avg), int(f_avg),
dy_compress)
# It is useful to have coord as a list from now on
if coord is not '':
coord_list = coord.split(',')
coord_input_list = coord.split(',')
# Add calibrator names to the target list so that they can be
# plotted together. Before doing that, make a copy of the input
......@@ -478,13 +555,13 @@ def on_calculate_click(n, n_clicks, obs_t, n_core, n_remote, n_int, n_chan, n_sb
else:
# User has specified a coordinate and it has passed validation
# in the validate_inputs function.
# Check if the number of SAPs is less than 488
# Check if the number of beamlets is less than 488
n_point = len(coord_input_list)
n_sap = n_point * int(n_sb)
max_sap = 488
if n_sap > max_sap:
n_beamlet = n_point * int(n_sb)
max_beamlet = 488
if n_beamlet > max_beamlet:
msg = 'Number of targets times number of subbands cannot ' + \
'be greater than {}.'.format(max_sap)
'be greater than {}.'.format(max_beamlet)
return '', '', '', '', msg, True, \
{'display':'none'}, {}, {'display':'none'}, {}, \
{'display':'none'}, {}
......
......@@ -71,8 +71,8 @@ def make_pdf_plot(elevation_fig, outfilename):
plt.tight_layout()
plt.savefig(outfilename, dpi=100)
def generate_pdf(pdf_file, obs_t, n_core, n_remote, n_int, n_chan, n_sb, integ_t,
antenna_set, pipe_type, t_avg, f_avg, is_dysco, im_noise_val,
def generate_pdf(pdf_file, obs_t, cal_t, n_cal, n_core, n_remote, n_int, n_chan, n_sb, integ_t,
antenna_set, coord, pipe_type, t_avg, f_avg, is_dysco, im_noise_val,
raw_size, proc_size, pipe_time, elevation_fig, distance_table,
obs_date, obs_mode, tab_mode, stokes):
"""Function to generate a pdf file summarizing the content of the calculator.
......@@ -82,6 +82,13 @@ def generate_pdf(pdf_file, obs_t, n_core, n_remote, n_int, n_chan, n_sb, integ_t
pdf.add_page()
pdf.set_font('Arial', '', 16)
if coord is not '':
coord_list = coord.split(',')
coord_input_list = coord.split(',')
n_sap=len(coord_list)
else:
n_sap=None
# Generate an html string to be written to the file
string = '<table border="0" align="left" width="80%">'
string += '<thead><tr><th width="70%" align="left">Parameter</th>'
......@@ -103,6 +110,13 @@ def generate_pdf(pdf_file, obs_t, n_core, n_remote, n_int, n_chan, n_sb, integ_t
string += '<tr><td>Observation time (in seconds)</td>'
string += ' <td>{}</td></tr>'.format(obs_t)
string += '<tr><td>Calibrator duration (in seconds)</td>'
string += ' <td>{}</td></tr>'.format(cal_t)
string += '<tr><td>Number of calibrators</td>'
string += ' <td>{}</td></tr>'.format(n_cal)
if n_sap:
string += '<tr><td>Number of simultaneous targets</td>'
string += ' <td>{}</td></tr>'.format(n_sap)
string += '<tr><td>No. of stations</td>'
if obs_mode == 'Beamformed' and tab_mode == 'Coherent':
string += ' <td>({}, {}, {})</td></tr>'.format(n_core, 0, 0)
......
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_core_components as dcc
from datetime import date
from datetime import date
###############################################################################
# Define a modal to display error messages for observation time
......@@ -50,22 +50,24 @@ msgBox = dbc.Modal([
###############################################################################
defaultParams = {'obsMode':'Interferometric',
'tabMode':'Coherent',
'obsTime':'28800',
'Ncore':'24',
'obsTime':'28800',
'calTime':'600',
'Ncal': '2',
'Ncore':'24',
'Nremote':'14',
'Nint':'14',
'Nchan':'64',
'Nint':'14',
'Nchan':'64',
'Nsb':'488',
'intTime':'1',
'intTime':'1',
'hbaDual':'hbadualinner',
'Nrings':'0',
'pipeType':'none',
'tAvg':'1',
'fAvg':'4',
'pipeType':'none',
'tAvg':'1',
'fAvg':'4',
'dyCompress':'enable',
'targetName':'',
'targetName':'',
'target_coord':'',
}
......@@ -91,7 +93,7 @@ obsMode = dbc.FormGroup([
options=[
{'label':'Interferometric', 'value':'Interferometric'},
{'label':'Beamformed', 'value':'Beamformed'}
], value=defaultParams['obsMode'], searchable=False,
], value=defaultParams['obsMode'], searchable=False,
clearable=False, id='obsModeRow'
), width=dropWidth
)
......@@ -103,7 +105,7 @@ tabMode = dbc.FormGroup([
options=[
{'label':'Coherent', 'value':'Coherent'},
{'label':'Incoherent', 'value':'Incoherent'}
], value=defaultParams['tabMode'], searchable=False,
], value=defaultParams['tabMode'], searchable=False,
clearable=False, id='tabModeRow'
), width=dropWidth, id='tabModeCol'
)
......@@ -112,7 +114,7 @@ stokes = dbc.FormGroup([
dbc.Label('Stokes products to record', width=labelWidth, id='stokesRowL'),
dbc.Col(
dcc.Dropdown(
options=[], searchable=False,
options=[], searchable=False,
clearable=False, id='stokesRow'
), width=dropWidth
)
......@@ -120,16 +122,38 @@ stokes = dbc.FormGroup([
obsTime = dbc.FormGroup([
dbc.Label('Observation time (in seconds)', width=labelWidth),
dbc.Col(
dbc.Input(type='text',
id='obsTimeRow',
dbc.Input(type='text',
id='obsTimeRow',
value=defaultParams['obsTime']
), width=inpWidth
)
], row=True)
calTime = dbc.FormGroup([
dbc.Label('Calibrator duration (in seconds)', width=labelWidth),
dbc.Col(
dbc.Input(type='text',
id='calTimeRow',
value=defaultParams['calTime']
), width=inpWidth
)
], row=True)
Ncal = dbc.FormGroup([
dbc.Label('No. of calibrator observations', width=labelWidth),
dbc.Col(
dbc.Input(type='number',
id='nCalRow',
value=defaultParams['Ncal']
), width=inpWidth
)
], row=True)
Ncore = dbc.FormGroup([
dbc.Label('No. of core stations (0 - 24)', width=labelWidth),
dbc.Col(
dbc.Input(type='number',
dbc.Input(type='number',
id='nCoreRow',
value=defaultParams['Ncore']
), width=inpWidth
......@@ -140,18 +164,18 @@ Nremote = dbc.FormGroup([
id='nRemoteRowL'
),
dbc.Col(
dbc.Input(type='number',
dbc.Input(type='number',
id='nRemoteRow',
value=defaultParams['Nremote']
), width=inpWidth
)
)
], row=True, id='nRemoteForm')
Nint = dbc.FormGroup([
dbc.Label('No. of international stations (0 - 14)', width=labelWidth,
id='nIntRowL'
),
dbc.Col(
dbc.Input(type='number',
dbc.Input(type='number',
id='nIntRow',
value=defaultParams['Nint']
), width=inpWidth
......@@ -165,7 +189,7 @@ Nchan = dbc.FormGroup([
{'label':'64', 'value':'64'},
{'label':'128', 'value':'128'},
{'label':'256', 'value':'256'}
], value=defaultParams['Nchan'], searchable=False,
], value=defaultParams['Nchan'], searchable=False,
clearable=False, id='nChanRow'
), width=dropWidth
)
......@@ -173,7 +197,7 @@ Nchan = dbc.FormGroup([
Nsb = dbc.FormGroup([
dbc.Label('Number of subbands', width=labelWidth),
dbc.Col(
dbc.Input(type='number',
dbc.Input(type='number',
id='nSbRow',
value=defaultParams['Nsb']
), width=inpWidth
......@@ -182,7 +206,7 @@ Nsb = dbc.FormGroup([
intTime = dbc.FormGroup([
dbc.Label('Integration time (in seconds)', width=labelWidth),
dbc.Col(
dbc.Input(type='text',
dbc.Input(type='text',
id='intTimeRow',
value=defaultParams['intTime']
), width=inpWidth
......@@ -196,7 +220,7 @@ hbaDual = dbc.FormGroup([
{'label':'LBA Outer', 'value':'lbaouter'},
{'label':'HBA Dual', 'value':'hbadual'},
{'label':'HBA Dual Inner', 'value':'hbadualinner'}
], value=defaultParams['hbaDual'], searchable=False,
], value=defaultParams['hbaDual'], searchable=False,
clearable=False, id='hbaDualRow',
), width=dropWidth
)
......@@ -209,13 +233,14 @@ buttons = html.Div([
])
])
link = html.Div([
html.A(id='download-link',
children='Download file',
html.A(id='download-link',
children='Download file',
style={'display':'none'}
)
])
obsGUISetup = dbc.Form([obsMode, tabMode, stokes, obsTime, Ncore, Nremote, Nint, Nchan,