|
| 1 | +import numpy as n |
| 2 | +import os, glob |
| 3 | +import casautil |
| 4 | + |
| 5 | +tb = casautil.tools.table() |
| 6 | + |
| 7 | +class solutions(): |
| 8 | + """ Container for CASA caltable(s). |
| 9 | + Provides tools for applying to data of shape (nints, nbl, nch, npol). |
| 10 | + Initialize class based on input file(s) and selection criteria. |
| 11 | + Optional flux scale gain file can be given. Should be gain file applied to source with setjy applied. |
| 12 | + """ |
| 13 | + |
| 14 | + def __init__(self, gainfile, flagants=True): |
| 15 | + """ Initialize with a table of CASA gain solutions. Can later add BP. |
| 16 | + """ |
| 17 | + |
| 18 | + self.parsegain(gainfile) |
| 19 | + self.flagants = flagants |
| 20 | + |
| 21 | + def parsegain(self, gainfile): |
| 22 | + """Takes .g1 CASA cal table and places values in numpy arrays. |
| 23 | + """ |
| 24 | + |
| 25 | + tb.open(gainfile) |
| 26 | + mjd = tb.getcol('TIME')/(24*3600) # mjd days, as for telcal |
| 27 | + spw = tb.getcol('SPECTRAL_WINDOW_ID') |
| 28 | + gain = tb.getcol('CPARAM') # dimensions of (npol, 1?, ntimes*nants) |
| 29 | + snr = tb.getcol('SNR') |
| 30 | + flagged = tb.getcol('FLAG') |
| 31 | + tb.close() |
| 32 | + tb.open(gainfile + '/ANTENNA') |
| 33 | + antname = tb.getcol('NAME') # ant number in order written to gain file |
| 34 | + antnum = n.array([int(antname[i][2:]) for i in range(len(antname))]) |
| 35 | + tb.close() |
| 36 | + |
| 37 | + # # need to find parent data MS to get some metadata |
| 38 | + # mslist = glob.glob(gainfile[:-3] + '*.ms') |
| 39 | + # try: |
| 40 | + # msfile = mslist[0] |
| 41 | + # print 'Found parent data MS %s' % msfile |
| 42 | + # except IndexError: |
| 43 | + # print 'Could not find parent data MS for metadata...' |
| 44 | + |
| 45 | + # tb.open(msfile + '/ANTENNA') |
| 46 | + # antname = tb.getcol('NAME') # one name per ant |
| 47 | + # tb.close() |
| 48 | + # tb.open(msfile + '/SPECTRAL_WINDOW') |
| 49 | + # reffreq = 1e-6*(tb.getcol('REF_FREQUENCY')+tb.getcol('TOTAL_BANDWIDTH')/2) # similar to telcal "skyfreq" |
| 50 | + # specname = tb.getcol('NAME') |
| 51 | + # tb.close() |
| 52 | + # tb.open(msfile + '/SOURCE') |
| 53 | + # source = [name for name in tb.getcol('NAME') if 'J' in name][0] # should return single cal name **hack** |
| 54 | + # tb.close() |
| 55 | + # nsol = len(gain[0,0]) |
| 56 | + |
| 57 | + # ifid0R = specname[0][7] + '-' + specname[0][8] # one value |
| 58 | + # ifid0L = specname[0][9] + '-' + specname[0][10] # one value |
| 59 | + # ifid1R = specname[1][7] + '-' + specname[1][8] # one value |
| 60 | + # ifid1L = specname[1][9] + '-' + specname[1][10] # one value |
| 61 | + |
| 62 | + # # paste R,L end to end, so first loop over time, then spw, then pol |
| 63 | + # mjd = n.concatenate( (time, time), axis=0) |
| 64 | + # ifid = [ifid0R]*(nsol/2) + [ifid1R]*(nsol/2) + [ifid0L]*(nsol/2) + [ifid1L]*(nsol/2) # first quarter is spw0,pol0, then spw1,pol0, ... |
| 65 | + # skyfreq = n.concatenate( (reffreq[0]*n.ones(nsol/2), reffreq[1]*n.ones(nsol/2), reffreq[0]*n.ones(nsol/2), reffreq[1]*n.ones(nsol/2)), axis=0) |
| 66 | + # gain = n.concatenate( (gain[0,0],gain[1,0]), axis=0) |
| 67 | + # amp = n.abs(gain) |
| 68 | + # phase = n.degrees(n.angle(gain)) |
| 69 | + # source = [source]*nsol*2 |
| 70 | + # flagged = n.concatenate( (flag[0,0],flag[1,0]), axis=0) |
| 71 | + |
| 72 | + nants = len(n.unique(antnum)) |
| 73 | + nspw = len(n.unique(spw)) |
| 74 | + self.spwlist = n.unique(spw) |
| 75 | + npol = len(gain) |
| 76 | + |
| 77 | + # merge times less than 2s |
| 78 | + nsol = 0 |
| 79 | + newmjd = [n.unique(mjd)[0]] |
| 80 | + skip = [] |
| 81 | + for i in range(1, len(n.unique(mjd))): |
| 82 | + if 24*3600*(n.unique(mjd)[i] - n.unique(mjd)[i-1]) < 30.: |
| 83 | + skip.append(n.unique(mjd)[i]) |
| 84 | + continue |
| 85 | + else: |
| 86 | + newmjd.append(n.unique(mjd)[i]) |
| 87 | + |
| 88 | + self.uniquemjd = n.array(newmjd) |
| 89 | + nsol = len(self.uniquemjd) |
| 90 | + |
| 91 | + print 'Parsed gain table solutions for %d solutions (skipping %d), %d ants, %d spw, and %d pols' % (nsol, len(skip), nants, nspw, npol) |
| 92 | + print 'Unique solution times', self.uniquemjd |
| 93 | + |
| 94 | + self.gain = n.zeros( (nsol, nants, nspw, npol), dtype='complex' ) |
| 95 | + flags = n.zeros( (nsol, nants, nspw, npol), dtype='complex' ) |
| 96 | + for sol in range(nsol): |
| 97 | + for ant in range(nants): |
| 98 | + for spw in range(nspw): |
| 99 | + for pol in range(npol): |
| 100 | + self.gain[sol, ant, spw, pol] = gain[pol,0,spw*nsol*nants+sol*nants+ant] |
| 101 | + flags[sol, ant, spw, pol] = flagged[pol,0,spw*nsol*nants+sol*nants+ant] |
| 102 | + self.gain = n.ma.masked_array(self.gain, flags) |
| 103 | + |
| 104 | +# gain = n.concatenate( (n.concatenate( (gain[0,0,:nants*nsol].reshape(nsol,nants,1,1), gain[1,0,:nants*nsol].reshape(nsol,nants,1,1)), axis=3), n.concatenate( (gain[0,0,nants*nsol:].reshape(nsol,nants,1,1), gain[1,0,nants*nsol:].reshape(nsol,nants,1,1)), axis=3)), axis=2) |
| 105 | +# flagged = n.concatenate( (n.concatenate( (flagged[0,0,:nants*nsol].reshape(nsol,nants,1,1), flagged[1,0,:nants*nsol].reshape(nsol,nants,1,1)), axis=3), n.concatenate( (flagged[0,0,nants*nsol:].reshape(nsol,nants,1,1), flagged[1,0,nants*nsol:].reshape(nsol,nants,1,1)), axis=3)), axis=2) |
| 106 | +# self.gain = n.ma.masked_array(gain, flagged == True) |
| 107 | + |
| 108 | + self.mjd = n.array(mjd); self.antnum = antnum |
| 109 | + |
| 110 | + # make another version of ants array |
| 111 | +# self.antnum = n.concatenate( (antnum, antnum), axis=0) |
| 112 | +# self.amp = n.array(amp); self.phase = n.array(phase) |
| 113 | +# self.antname = n.concatenate( (antname[antnum], antname[antnum]), axis=0) |
| 114 | +# self.complete = n.arange(len(self.mjd)) |
| 115 | + |
| 116 | + # for consistency with telcal |
| 117 | + #self.ifid = n.array(ifid); self.skyfreq = n.array(skyfreq); self.source = n.array(source) |
| 118 | + |
| 119 | + def parsebp(self, bpfile, debug=False): |
| 120 | + """ Takes bp CASA cal table and places values in numpy arrays. |
| 121 | + Assumes two or fewer spw. :\ |
| 122 | + Assumes one bp solution per file. |
| 123 | + """ |
| 124 | + |
| 125 | + # bandpass. taking liberally from Corder et al's analysisutilities |
| 126 | + ([polyMode, polyType, nPolyAmp, nPolyPhase, scaleFactor, nRows, nSpws, nUniqueTimesBP, uniqueTimesBP, |
| 127 | + nPolarizations, frequencyLimits, increments, frequenciesGHz, polynomialPhase, |
| 128 | + polynomialAmplitude, timesBP, antennasBP, cal_desc_idBP, spwBP]) = openBpolyFile(bpfile, debug) |
| 129 | + |
| 130 | + # index iterates over antennas, then times/sources (solution sets). each index has 2x npoly, which are 2 pols |
| 131 | + polynomialAmplitude = n.array(polynomialAmplitude); polynomialPhase = n.array(polynomialPhase) |
| 132 | + polynomialAmplitude[:,0] = 0.; polynomialAmplitude[:,nPolyAmp] = 0. |
| 133 | + polynomialPhase[:,0] = 0.; polynomialPhase[nPolyPhase] = 0. |
| 134 | + ampSolR, ampSolL = calcChebyshev(polynomialAmplitude, frequencyLimits, n.array(frequenciesGHz)*1e+9) |
| 135 | + phaseSolR, phaseSolL = calcChebyshev(polynomialPhase, frequencyLimits, n.array(frequenciesGHz)*1e+9) |
| 136 | + |
| 137 | + nants = len(n.unique(antennasBP)) |
| 138 | + self.bptimes = n.array(timesBP) |
| 139 | + ptsperspec = 1000 |
| 140 | + npol = 2 |
| 141 | + print 'Parsed bp solutions for %d solutions, %d ants, %d spw, and %d pols' % (nUniqueTimesBP, nants, nSpws, nPolarizations) |
| 142 | + self.bandpass = n.zeros( (nants, nSpws*ptsperspec, npol), dtype='complex') |
| 143 | + for spw in range(nSpws): |
| 144 | + ampSolR[spw*nants:(spw+1)*nants] += 1 - ampSolR[spw*nants:(spw+1)*nants].mean() # renormalize mean over ants (per spw) == 1 |
| 145 | + ampSolL[spw*nants:(spw+1)*nants] += 1 - ampSolL[spw*nants:(spw+1)*nants].mean() |
| 146 | + for ant in range(nants): |
| 147 | + self.bandpass[ant, spw*ptsperspec:(spw+1)*ptsperspec, 0] = ampSolR[ant+spw*nants] * n.exp(1j*phaseSolR[ant+spw*nants]) |
| 148 | + self.bandpass[ant, spw*ptsperspec:(spw+1)*ptsperspec, 1] = ampSolL[ant+spw*nants] * n.exp(1j*phaseSolL[ant+spw*nants]) |
| 149 | + |
| 150 | + self.bpfreq = n.zeros( (nSpws*ptsperspec) ) |
| 151 | + for spw in range(nSpws): |
| 152 | + self.bpfreq[spw*ptsperspec:(spw+1)*ptsperspec] = 1e9 * frequenciesGHz[nants*spw] |
| 153 | + |
| 154 | +# bpSolR0 = ampSolR[:nants] * n.exp(1j*phaseSolR[:nants]) |
| 155 | +# bpSolR1 = ampSolR[nants:] * n.exp(1j*phaseSolR[nants:]) |
| 156 | +# bpSolL0 = ampSolL[:nants] * n.exp(1j*phaseSolL[:nants]) |
| 157 | +# bpSolL1 = ampSolL[nants:] * n.exp(1j*phaseSolL[nants:]) |
| 158 | + |
| 159 | + # structure close to tpipe data structure (nant, freq, pol). note that freq is oversampled to 1000 bins. |
| 160 | +# self.bandpass = n.concatenate( (n.concatenate( (bpSolR0[:,:,None], bpSolR1[:,:,None]), axis=1), n.concatenate( (bpSolL0[:,:,None], bpSolL1[:,:,None]), axis=1)), axis=2) |
| 161 | +# self.bpfreq = 1e9*n.concatenate( (frequenciesGHz[0], frequenciesGHz[nants]), axis=0) # freq values at bp bins |
| 162 | +# print 'Parsed bp table solutions for %d solutions, %d ants, %d spw, and %d pols' % (nUniqueTimesBP, nants, nSpws, nPolarizations) |
| 163 | + |
| 164 | + def setselection(self, time, freqs, spws=[0,1], pols=[0,1], verbose=0): |
| 165 | + """ Set select parameter that defines time, spw, and pol solutions to apply. |
| 166 | + time defines the time to find solutions near in mjd. |
| 167 | + freqs defines frequencies to select bandpass solution |
| 168 | + spws is list of min/max indices to be used (e.g., [0,1]) |
| 169 | + pols is index of polarizations. |
| 170 | + pols/spws not yet implemented beyond 2sb, 2pol. |
| 171 | + """ |
| 172 | + |
| 173 | + # spw and pols selection not yet implemented beyond 2/2 |
| 174 | + self.spws = spws |
| 175 | + self.pols = pols |
| 176 | + |
| 177 | + # select by smallest time distance for source |
| 178 | + mjddist = n.abs(time - self.uniquemjd) |
| 179 | + closestgain = n.where(mjddist == mjddist.min())[0][0] |
| 180 | + |
| 181 | + print 'Using gain solution at MJD %.5f, separated by %d min ' % (self.uniquemjd[closestgain], mjddist[closestgain]*24*60) |
| 182 | + self.gain = self.gain[closestgain,:,spws[0]:spws[1]+1,pols[0]:pols[1]+1] |
| 183 | + |
| 184 | + if hasattr(self, 'bandpass'): |
| 185 | + bins = [n.where(n.min(n.abs(self.bpfreq-selfreq)) == n.abs(self.bpfreq-selfreq))[0][0] for selfreq in freqs] |
| 186 | + self.bandpass = self.bandpass[:,bins,pols[0]:pols[1]+1] |
| 187 | + self.freqs = freqs |
| 188 | + if verbose: |
| 189 | + print 'Using solution at BP bins: ', bins |
| 190 | + |
| 191 | + def calc_flag(self, sig=3.0): |
| 192 | + """ Calculates antennas to flag, based on bad gain and bp solutions. |
| 193 | + """ |
| 194 | + |
| 195 | + if len(self.gain.shape) == 4: |
| 196 | + gamp = n.abs(self.gain).mean(axis=0) # mean gain amp for each ant over time |
| 197 | + elif len(self.gain.shape) == 3: |
| 198 | + gamp = n.abs(self.gain) # gain amp for selected time |
| 199 | + |
| 200 | +# badgain = n.where(gamp < gamp.mean() - sig*gamp.std()) |
| 201 | + badgain = n.where( (gamp < n.median(gamp) - sig*gamp.std()) | gamp.mask) |
| 202 | + print 'Flagging low/bad gains for ant/spw/pol:', self.antnum[badgain[0]], badgain[1], badgain[2] |
| 203 | + |
| 204 | + badants = badgain |
| 205 | + return badants |
| 206 | + |
| 207 | + def apply(self, data, blarr): |
| 208 | + """ Applies calibration solution to data array. Assumes structure of (nint, nbl, nch, npol). |
| 209 | + blarr is array of size 2xnbl that gives pairs of antennas in each baseline (a la tpipe.blarr). |
| 210 | + """ |
| 211 | + |
| 212 | + ant1ind = [n.where(ant1 == n.unique(blarr))[0][0] for (ant1,ant2) in blarr] |
| 213 | + ant2ind = [n.where(ant2 == n.unique(blarr))[0][0] for (ant1,ant2) in blarr] |
| 214 | + |
| 215 | + # flag bad ants |
| 216 | + if self.flagants: |
| 217 | + badants = self.calc_flag() |
| 218 | + else: |
| 219 | + badants = n.array([[]]) |
| 220 | + |
| 221 | + # apply gain correction |
| 222 | + if hasattr(self, 'bandpass'): |
| 223 | + corr = n.ones_like(data) |
| 224 | + flag = n.ones_like(data).astype('int') |
| 225 | + chans_uncal = range(len(self.freqs)) |
| 226 | + for spw in range(len(self.gain[0])): |
| 227 | + chsize = n.round(self.bpfreq[1]-self.bpfreq[0], 0) |
| 228 | + ww = n.where( (self.freqs >= self.bpfreq[spw*1000]) & (self.freqs <= self.bpfreq[(spw+1)*1000-1]+chsize) )[0] |
| 229 | + if len(ww) == 0: |
| 230 | + print 'Gain solution frequencies not found in data for spw %d.' % (self.spws[spw]) |
| 231 | + firstch = ww[0] |
| 232 | + lastch = ww[-1]+1 |
| 233 | + for ch in ww: |
| 234 | + chans_uncal.remove(ch) |
| 235 | + print 'Combining gain sol from spw=%d with BW chans from %d-%d' % (self.spws[spw], firstch, lastch) |
| 236 | + for badant in n.transpose(badants): |
| 237 | + if badant[1] == spw: |
| 238 | + badbl = n.where((badant[0] == n.array(ant1ind)) | (badant[0] == n.array(ant2ind)))[0] |
| 239 | + flag[:, badbl, firstch:lastch, badant[2]] = 0 |
| 240 | + |
| 241 | + corr1 = self.gain[ant1ind, spw, :][None, :, None, :] * self.bandpass[ant1ind, firstch:lastch, :][None, :, :, :] |
| 242 | + corr2 = (self.gain[ant2ind, spw, :][None, :, None, :] * self.bandpass[ant2ind, firstch:lastch, :][None, :, :, :]).conj() |
| 243 | + |
| 244 | + corr[:, :, firstch:lastch, :] = corr1 * corr2 |
| 245 | + if len(chans_uncal): |
| 246 | + print 'Setting data without bp solution to zero for chan range %d-%d.' % (chans_uncal[0], chans_uncal[-1]) |
| 247 | + flag[:, :, chans_uncal,:] = 0 |
| 248 | + data[:] *= flag/corr |
| 249 | + else: |
| 250 | + for spw in range(len(self.gain[0,0])): |
| 251 | + pass |
| 252 | + |
| 253 | +def openBpolyFile(caltable, debug=False): |
| 254 | +# mytb = au.createCasaTool(tbtool) # from analysisutilities by corder |
| 255 | + tb.open(caltable) |
| 256 | + desc = tb.getdesc() |
| 257 | + if ('POLY_MODE' in desc): |
| 258 | + polyMode = tb.getcol('POLY_MODE') |
| 259 | + polyType = tb.getcol('POLY_TYPE') |
| 260 | + scaleFactor = tb.getcol('SCALE_FACTOR') |
| 261 | + antenna1 = tb.getcol('ANTENNA1') |
| 262 | + times = tb.getcol('TIME') |
| 263 | + cal_desc_id = tb.getcol('CAL_DESC_ID') |
| 264 | + nRows = len(polyType) |
| 265 | + for pType in polyType: |
| 266 | + if (pType != 'CHEBYSHEV'): |
| 267 | + print "I do not recognized polynomial type = %s" % (pType) |
| 268 | + return |
| 269 | + # Here we assume that all spws have been solved with the same mode |
| 270 | + uniqueTimesBP = n.unique(tb.getcol('TIME')) |
| 271 | + nUniqueTimesBP = len(uniqueTimesBP) |
| 272 | + if (nUniqueTimesBP >= 2): |
| 273 | + if debug: |
| 274 | + print "Multiple BP sols found with times differing by %s seconds. Using first." % (str(uniqueTimesBP-uniqueTimesBP[0])) |
| 275 | + nUniqueTimesBP = 1 |
| 276 | + uniqueTimesBP = uniqueTimesBP[0] |
| 277 | + mystring = '' |
| 278 | + nPolyAmp = tb.getcol('N_POLY_AMP') |
| 279 | + nPolyPhase = tb.getcol('N_POLY_PHASE') |
| 280 | + frequencyLimits = tb.getcol('VALID_DOMAIN') |
| 281 | + increments = 0.001*(frequencyLimits[1,:]-frequencyLimits[0,:]) |
| 282 | + frequenciesGHz = [] |
| 283 | + for i in range(len(frequencyLimits[0])): |
| 284 | + freqs = (1e-9)*n.arange(frequencyLimits[0,i],frequencyLimits[1,i],increments[i]) # **for some reason this is nch-1 long?** |
| 285 | + frequenciesGHz.append(freqs) |
| 286 | + polynomialAmplitude = [] |
| 287 | + polynomialPhase = [] |
| 288 | + for i in range(len(polyMode)): |
| 289 | + polynomialAmplitude.append([1]) |
| 290 | + polynomialPhase.append([0]) |
| 291 | + if (polyMode[i] == 'A&P' or polyMode[i] == 'A'): |
| 292 | + polynomialAmplitude[i] = tb.getcell('POLY_COEFF_AMP',i)[0][0][0] |
| 293 | + if (polyMode[i] == 'A&P' or polyMode[i] == 'P'): |
| 294 | + polynomialPhase[i] = tb.getcell('POLY_COEFF_PHASE',i)[0][0][0] |
| 295 | + |
| 296 | + tb.close() |
| 297 | + tb.open(caltable+'/CAL_DESC') |
| 298 | + nSpws = len(tb.getcol('NUM_SPW')) |
| 299 | + spws = tb.getcol('SPECTRAL_WINDOW_ID') |
| 300 | + spwBP = [] |
| 301 | + for c in cal_desc_id: |
| 302 | + spwBP.append(spws[0][c]) |
| 303 | + tb.close() |
| 304 | + nPolarizations = len(polynomialAmplitude[0]) / nPolyAmp[0] |
| 305 | + if debug: |
| 306 | + mystring += '%.3f, ' % (uniqueTimesBP/(24*3600)) |
| 307 | + print 'BP solution has unique time(s) %s and %d pols' % (mystring, nPolarizations) |
| 308 | + |
| 309 | + # This value is overridden by the new function doPolarizations in ValueMapping. |
| 310 | + # print "Inferring %d polarizations from size of polynomial array" % (nPolarizations) |
| 311 | + return([polyMode, polyType, nPolyAmp, nPolyPhase, scaleFactor, nRows, nSpws, nUniqueTimesBP, |
| 312 | + uniqueTimesBP, nPolarizations, frequencyLimits, increments, frequenciesGHz, |
| 313 | + polynomialPhase, polynomialAmplitude, times, antenna1, cal_desc_id, spwBP]) |
| 314 | + else: |
| 315 | + tb.close() |
| 316 | + return([]) |
| 317 | + # end of openBpolyFile() |
| 318 | + |
| 319 | +def calcChebyshev(coeffs, validDomain, freqs): |
| 320 | + """ |
| 321 | + Given a set of coefficients, |
| 322 | + this method evaluates a Chebyshev approximation. |
| 323 | + Used for CASA bandpass reading. |
| 324 | + input coeffs and freqs are numpy arrays |
| 325 | + """ |
| 326 | + |
| 327 | + domain = (validDomain[1] - validDomain[0])[0] |
| 328 | + bins = -1 + 2* n.array([ (freqs[i]-validDomain[0,i])/domain for i in range(len(freqs))]) |
| 329 | + ncoeffs = len(coeffs[0])/2 |
| 330 | + rr = n.array([n.polynomial.chebval(bins[i], coeffs[i,:ncoeffs]) for i in range(len(coeffs))]) |
| 331 | + ll = n.array([n.polynomial.chebval(bins[i], coeffs[i,ncoeffs:]) for i in range(len(coeffs))]) |
| 332 | + |
| 333 | + return rr,ll |
0 commit comments