Parses sdm file to define metadata for observation, including scan info,
    image grid parameters, pipeline memory usage, etc.
    Mirrors parsems.get_metadata().
    If paramfile defined, it will use it (filename or RT.Params instance ok).

def get_metadata(filename, scan, paramfile='', **kwargs):
    """ Parses sdm file to define metadata for observation, including scan info,
    image grid parameters, pipeline memory usage, etc.
    Mirrors parsems.get_metadata().
    If paramfile defined, it will use it (filename or RT.Params instance ok).
    """

    # create primary state dictionary
    d = {}

    # set workdir
    d['filename'] = os.path.abspath(filename)
    d['workdir'] = os.path.dirname(d['filename'])

    # define parameters of pipeline via Params object
    params = pp.Params(paramfile)
    for k in params.defined:   # fill in default params
        d[k] = params[k]

    # overload with provided kwargs
    for key in kwargs.keys():
        if key in params.defined:
            stdname = '(standard)'
        else:
            stdname = ''
        logger.info('Setting %s key %s to %s' % (stdname, key, kwargs[key]))
        d[key] = kwargs[key]

    # option of not writing log file (need to improve later)
    if d['logfile']:
        fh = logging.FileHandler(os.path.join(d['workdir'], 'rtpipe_%d.log' % int(round(time()))))
        fh.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
        logger.parent.addHandler(fh)
        if hasattr(logging, d['loglevel']):
            logger.parent.setLevel(getattr(logging, d['loglevel']))
        else:
            logger.warn('loglevel of {0} not attribute of logging'.format(d['loglevel']))

    # define scan list
#    if 'bdfdir' not in d:
#        d['bdfdir'] = ''

    scans = read_scans(d['filename'], bdfdir=d['bdfdir'])
    sources = read_sources(d['filename'])

    # define source props
    d['source'] = scans[scan]['source']
    d['radec'] = [(prop['ra'], prop['dec'])
                  for (sr, prop) in sources.iteritems()
                  if prop['source'] == d['source']][0]

    # define spectral info
    sdm = getsdm(d['filename'], bdfdir=d['bdfdir'])
    d['spw_orig'] = [int(str(row.spectralWindowId).split('_')[1])
                     for row in sdm['SpectralWindow']]
    d['spw_nchan'] = [int(row.numChan) for row in sdm['SpectralWindow']]

    try:
        d['spw_reffreq'] = [float(row.chanFreqStart)
                            for row in sdm['SpectralWindow']]   # nominal
    except:
        # GMRT uses array of all channel starts
        d['spw_reffreq'] = [float(row.chanFreqArray.strip().split(' ')[2])
                            for row in sdm['SpectralWindow']]
    try:
        d['spw_chansize'] = [float(row.chanFreqStep)
                             for row in sdm['SpectralWindow']]   # nominal
    except:
        # GMRT uses array of all channel starts
        d['spw_chansize'] = [float(row.chanWidthArray.strip().split(' ')[2])
                             for row in sdm['SpectralWindow']]

    # select spw. note that spw selection not fully supported yet.
    if not len(d['spw']):
        d['spw'] = d['spw_orig']

    spwch = []
    reffreq = d['spw_reffreq']
    spectralwindow = d['spw_orig']
    numchan = d['spw_nchan']
    chansize = d['spw_chansize']
    for freq in sorted(d['spw_reffreq']):
        ii = reffreq.index(freq)
        if spectralwindow[ii] in d['spw']:
            # spacing of channel *centers*
            spwch.extend(list(np.linspace(reffreq[ii], reffreq[ii]
                                          + (numchan[ii]-1) * chansize[ii],
                                          numchan[ii])))

    d['freq_orig'] = np.array([np.mean(spwch[i:i+d['read_fdownsample']])
                               for i in range(0, len(spwch),
                                              d['read_fdownsample'])],
                              dtype='float32') / 1e9

    # select subset of channels
    if not len(d['chans']):
        d['chans'] = range(len(d['freq_orig']))

    d['nspw'] = len(d['spw'])
    d['freq'] = d['freq_orig'][d['chans']]
    d['nchan'] = len(d['chans'])

    # define chan ranges per spw (before selecting subset)
    spw_chanr = []
    i0 = 0
    for nch in d['spw_nchan']:
        spw_chanr.append((i0, i0+nch))
        i0 = nch
    d['spw_chanr'] = spw_chanr

    # define nchan per spw after selecting subset
    d['spw_nchan_select'] = [len([ch
                                  for ch in range(d['spw_chanr'][i][0],
                                                  d['spw_chanr'][i][1])
                                  if ch in d['chans']])
                             for i in range(len(d['spw_chanr']))]
    spw_chanr_select = []
    i0 = 0
    for nch in d['spw_nchan_select']:
        spw_chanr_select.append((i0, i0+nch))
        i0 += nch
    d['spw_chanr_select'] = spw_chanr_select

    # define image params
    d['urange'] = {}
    d['vrange'] = {}
    d['scan'] = scan
    (u, v, w) = calc_uvw(d['filename'],
                         d['scan'], bdfdir=d['bdfdir'])  # default uses time at start
    u = u * d['freq_orig'][0] * (1e9/3e8) * (-1)
    v = v * d['freq_orig'][0] * (1e9/3e8) * (-1)
    d['urange'][d['scan']] = u.max() - u.min()
    d['vrange'][d['scan']] = v.max() - v.min()
    d['dishdiameter'] = float(str(sdm['Antenna'][0]
                                  .dishDiameter).strip())  # should be in meters
    # delay beam larger than VLA field of view at all freqs
    d['uvres_full'] = np.round(d['dishdiameter']
                               / (3e-1 / d['freq'].min()) / 2).astype('int')

    if not all(('npixx_full' in d, 'npixy_full' in d)):
        # uvw from get_uvw already in lambda at ch0
        urange = d['urange'][d['scan']] * (d['freq'].max()
                                           / d['freq_orig'][0])
        vrange = d['vrange'][d['scan']] * (d['freq'].max()
                                           / d['freq_orig'][0])
        # power array for 2**i * 3**j
        powers = np.fromfunction(lambda i, j: 2**i*3**j,
                                 (14, 10), dtype='int')
        rangex = np.round(d['uvoversample']*urange).astype('int')
        rangey = np.round(d['uvoversample']*vrange).astype('int')
        largerx = np.where(powers - rangex / d['uvres_full'] > 0,
                           powers, powers[-1, -1])
        p2x, p3x = np.where(largerx == largerx.min())
        largery = np.where(powers - rangey / d['uvres_full'] > 0,
                           powers, powers[-1, -1])
        p2y, p3y = np.where(largery == largery.min())
        d['npixx_full'] = (2**p2x * 3**p3x)[0]
        d['npixy_full'] = (2**p2y * 3**p3y)[0]

    # define ants/bls
    # hacking here to fit observatory-specific use of antenna names
    if 'VLA' in str(sdm['ExecBlock'][0]['telescopeName']):
# find config first, then antids, then ant names
        configid = [str(row.configDescriptionId) for row in sdm['Main']
                    if d['scan'] == int(row.scanNumber)][0]
        antids = [str(row.antennaId) for row in sdm['ConfigDescription']
                  if configid == row.configDescriptionId][0].split(' ')[2:]
        d['ants'] = [int(str(row.name).lstrip('ea'))
                     for antid in antids
                     for row in sdm['Antenna']
                     if antid == str(row.antennaId)]
# Not complete. Execblock defines ants per scan, which can change.
#        d['ants'] = [int(ant.name.lstrip('ea'))
#                     for ant in sdm['Antenna']]
    elif 'GMRT' in str(sdm['ExecBlock'][0]['telescopeName']):
        d['ants'] = [int(str(ant.antennaId).split('_')[1])
                     for ant in sdm['Antenna']]

    # remove unwanted ants
    for ant in d['excludeants']:
        d['ants'].remove(ant)
    # required to assure that added antennas don't confuse cal antenna parsing
    d['ants'].sort()

    d['nants'] = len(d['ants'])
#    d['blarr'] = np.array([[d['ants'][i],d['ants'][j]]
#                           for j in range(d['nants']) for i in range(0,j)])
    d['nbl'] = d['nants']*(d['nants']-1)/2

    # define times
    d['starttime_mjd'] = scans[d['scan']]['startmjd']

    # assume inttime same for all scans
    scan = sdm.scan(d['scan'])
    d['inttime'] = scan.bdf.get_integration(0).interval
    d['nints'] = int(scan.bdf.numIntegration)

    # define pols
    d['pols_orig'] = [pol
                      for pol in (str(sdm['Polarization'][0]
                                      .corrType).strip()
                                      .split(' '))
                      if pol in ['XX', 'YY', 'XY', 'YX',
                                 'RR', 'LL', 'RL', 'LR']]
    d['npol_orig'] = int(sdm['Polarization'][0].numCorr)

    # summarize metadata
    logger.info('\n')
    logger.info('Metadata summary:')
    logger.info('\t Working directory and data at %s, %s'
                % (d['workdir'], os.path.basename(d['filename'])))
    logger.info('\t Using scan %d, source %s'
                % (int(d['scan']), d['source']))
    logger.info('\t nants, nbl: %d, %d' % (d['nants'], d['nbl']))
    logger.info('\t Freq range (%.3f -- %.3f). %d spw with %d chans.'
                % (d['freq'].min(), d['freq'].max(), d['nspw'], d['nchan']))
    logger.info('\t Scan has %d ints (%.1f s) and inttime %.3f s'
                % (d['nints'], d['nints']*d['inttime'], d['inttime']))
    logger.info('\t %d polarizations: %s'
                % (d['npol_orig'], d['pols_orig']))
    logger.info('\t Ideal uvgrid npix=(%d,%d) and res=%d (oversample %.1f)'
                % (d['npixx_full'], d['npixy_full'], d['uvres_full'],
                   d['uvoversample']))

    return d