00001 import os, shutil
00002 import numpy
00003 import numpy.fft as FFT
00004 import math
00005
00006 from asap.scantable import scantable
00007 from asap.parameters import rcParams
00008 from asap.logging import asaplog, asaplog_post_dec
00009 from asap.selector import selector
00010 from asap.asapgrid import asapgrid2
00011
00012
00013 class sbseparator:
00014 """
00015 The sbseparator class is defined to separate SIGNAL and IMAGE
00016 sideband spectra observed by frequency-switching technique.
00017 It also helps supressing emmission of IMAGE sideband.
00018 *** WARNING *** THIS MODULE IS EXPERIMENTAL
00019 Known issues:
00020 - Frequencies of IMAGE sideband cannot be reconstructed from
00021 information in scantable in sideband sparation. Frequency
00022 setting of SIGNAL sideband is stored in output table for now.
00023 - Flag information (stored in FLAGTRA) is ignored.
00024
00025 Example:
00026 # Create sideband separator instance whith 3 input data
00027 sbsep = sbseparator(['test1.asap', 'test2.asap', 'test3.asap'])
00028 # Set reference IFNO and tolerance to select data
00029 sbsep.set_frequency(5, 30, frame='TOPO')
00030 # Set direction tolerance to select data in unit of radian
00031 sbsep.set_dirtol(1.e-5)
00032 # Set rejection limit of solution
00033 sbsep.set_limit(0.2)
00034 # Solve image sideband as well
00035 sbsep.set_both(True)
00036 # Invoke sideband separation
00037 sbsep.separate('testout.asap', overwrite = True)
00038 """
00039 def __init__(self, infiles):
00040 self.intables = None
00041 self.signalShift = []
00042 self.imageShift = []
00043 self.dsbmode = True
00044 self.getboth = False
00045 self.rejlimit = 0.2
00046 self.baseif = -1
00047 self.freqtol = 10.
00048 self.freqframe = ""
00049 self.solveother = False
00050 self.dirtol = [1.e-5, 1.e-5]
00051
00052 self.tables = []
00053 self.nshift = -1
00054 self.nchan = -1
00055
00056 self.set_data(infiles)
00057
00058
00059
00060 @asaplog_post_dec
00061 def set_data(self, infiles):
00062 """
00063 Set data to be processed.
00064
00065 infiles : a list of filenames or scantables
00066 """
00067 if not (type(infiles) in (list, tuple, numpy.ndarray)):
00068 infiles = [infiles]
00069 if isinstance(infiles[0], scantable):
00070
00071 for stab in infiles:
00072 if not isinstance(stab, scantable):
00073 asaplog.post()
00074 raise TypeError, "Input data is not a list of scantables."
00075
00076
00077 self._reset_data()
00078 self.intables = infiles
00079 else:
00080
00081 for name in infiles:
00082 if not os.path.exists(name):
00083 asaplog.post()
00084 raise ValueError, "Could not find input file '%s'" % name
00085
00086
00087 self._reset_data()
00088 self.intables = infiles
00089
00090 asaplog.push("%d files are set to process" % len(self.intables))
00091
00092
00093 def _reset_data(self):
00094 del self.intables
00095 self.intables = None
00096 self.signalShift = []
00097
00098 self.tables = []
00099 self.nshift = -1
00100 self.nchan = -1
00101
00102 @asaplog_post_dec
00103 def set_frequency(self, baseif, freqtol, frame=""):
00104 """
00105 Set IFNO and frequency tolerance to select data to process.
00106
00107 Parameters:
00108 - reference IFNO to process in the first table in the list
00109 - frequency tolerance from reference IF to select data
00110 frame : frequency frame to select IF
00111 """
00112 self._reset_if()
00113 self.baseif = baseif
00114 if isinstance(freqtol,dict) and freqtol["unit"] == "Hz":
00115 if freqtol['value'] > 0.:
00116 self.freqtol = freqtol
00117 else:
00118 asaplog.post()
00119 asaplog.push("Frequency tolerance should be positive value.")
00120 asaplog.post("ERROR")
00121 return
00122 else:
00123
00124 if freqtol > 0:
00125 self.freqtol = float(freqtol)
00126 else:
00127 asaplog.post()
00128 asaplog.push("Frequency tolerance should be positive value.")
00129 asaplog.post("ERROR")
00130 return
00131 self.freqframe = frame
00132
00133 def _reset_if(self):
00134 self.baseif = -1
00135 self.freqtol = 0
00136 self.freqframe = ""
00137 self.signalShift = []
00138
00139 self.tables = []
00140 self.nshift = 0
00141 self.nchan = -1
00142
00143 @asaplog_post_dec
00144 def set_dirtol(self, dirtol=[1.e-5,1.e-5]):
00145 """
00146 Set tolerance of direction to select data
00147 """
00148
00149 if not (type(dirtol) in [list, tuple, numpy.ndarray]):
00150 dirtol = [dirtol, dirtol]
00151 if len(dirtol) == 1:
00152 dirtol = [dirtol[0], dirtol[0]]
00153 if len(dirtol) > 1:
00154 self.dirtol = dirtol[0:2]
00155 else:
00156 asaplog.post()
00157 asaplog.push("Invalid direction tolerance. Should be a list of float in unit radian")
00158 asaplog.post("ERROR")
00159 return
00160 asaplog.post("Set direction tolerance [%f, %f] (rad)" % \
00161 (self.dirtol[0], self.dirtol[1]))
00162
00163 @asaplog_post_dec
00164 def set_shift(self, mode="DSB", imageshift=None):
00165 """
00166 Set shift mode and channel shift of image band.
00167
00168 mode : shift mode ['DSB'|'SSB']
00169 When mode='DSB', imageshift is assumed to be equal
00170 to the shift of signal sideband but in opposite direction.
00171 imageshift : a list of number of channel shift in image band of
00172 each scantable. valid only mode='SSB'
00173 """
00174 if mode.upper().startswith("S"):
00175 if not imageshift:
00176 raise ValueError, "Need to set shift value of image sideband"
00177 self.dsbmode = False
00178 self.imageShift = imageshift
00179 asaplog.push("Image sideband shift is set manually: %s" % str(self.imageShift))
00180 else:
00181
00182 self.dsbmode = True
00183 self.imageShift = []
00184
00185 @asaplog_post_dec
00186 def set_both(self, flag=False):
00187 """
00188 Resolve both image and signal sideband when True.
00189 """
00190 self.getboth = flag
00191 if self.getboth:
00192 asaplog.push("Both signal and image sidebands are solved and output as separate tables.")
00193 else:
00194 asaplog.push("Only signal sideband is solved and output as an table.")
00195
00196 @asaplog_post_dec
00197 def set_limit(self, threshold=0.2):
00198 """
00199 Set rejection limit of solution.
00200 """
00201
00202 self.rejlimit = threshold
00203 asaplog.push("The threshold of rejection is set to %f" % self.rejlimit)
00204
00205
00206 @asaplog_post_dec
00207 def set_solve_other(self, flag=False):
00208 """
00209 Calculate spectra by subtracting the solution of the other sideband
00210 when True.
00211 """
00212 self.solveother = flag
00213 if flag:
00214 asaplog.push("Expert mode: solution are obtained by subtraction of the other sideband.")
00215
00216
00217 @asaplog_post_dec
00218 def separate(self, outname="", overwrite=False):
00219 """
00220 Invoke sideband separation.
00221
00222 outname : a name of output scantable
00223 overwrite : overwrite existing table
00224 """
00225
00226
00227 self._setup_shift()
00228
00229
00230 nshift = len(self.tables)
00231 signaltab = self._grid_outtable(self.tables[0].copy())
00232 if self.getboth:
00233 imagetab = signaltab.copy()
00234
00235 rejrow = []
00236 for irow in xrange(signaltab.nrow()):
00237 currpol = signaltab.getpol(irow)
00238 currbeam = signaltab.getbeam(irow)
00239 currdir = signaltab.get_directionval(irow)
00240 spec_array, tabidx = self._get_specarray(polid=currpol,\
00241 beamid=currbeam,\
00242 dir=currdir)
00243
00244 if len(tabidx) == 0:
00245 asaplog.post()
00246 asaplog.push("skipping row %d" % irow)
00247 rejrow.append(irow)
00248 continue
00249 signal = self._solve_signal(spec_array, tabidx)
00250 signaltab.set_spectrum(signal, irow)
00251 if self.getboth:
00252 image = self._solve_image(spec_array, tabidx)
00253 imagetab.set_spectrum(image, irow)
00254
00255
00256 signaltab.flag_row(rejrow)
00257 if self.getboth:
00258 imagetab.flag_row(rejrow)
00259
00260 if outname == "":
00261 outname = "sbsepareted.asap"
00262 signalname = outname + ".signalband"
00263 if os.path.exists(signalname):
00264 if not overwrite:
00265 raise Exception, "Output file '%s' exists." % signalname
00266 else:
00267 shutil.rmtree(signalname)
00268 signaltab.save(signalname)
00269 if self.getboth:
00270
00271 asaplog.post()
00272 asaplog.push("Saving IMAGE sideband.")
00273 asaplog.push("Note, frequency information of IMAGE sideband cannot be properly filled so far. (future development)")
00274 asaplog.push("Storing frequency setting of SIGNAL sideband in FREQUENCIES table for now.")
00275 asaplog.post("WARN")
00276
00277 imagename = outname + ".imageband"
00278 if os.path.exists(imagename):
00279 if not overwrite:
00280 raise Exception, "Output file '%s' exists." % imagename
00281 else:
00282 shutil.rmtree(imagename)
00283 imagetab.save(imagename)
00284
00285
00286 def _solve_signal(self, data, tabidx=None):
00287 if not tabidx:
00288 tabidx = range(len(data))
00289
00290 tempshift = []
00291 dshift = []
00292 if self.solveother:
00293 for idx in tabidx:
00294 tempshift.append(-self.imageShift[idx])
00295 dshift.append(self.signalShift[idx] - self.imageShift[idx])
00296 else:
00297 for idx in tabidx:
00298 tempshift.append(-self.signalShift[idx])
00299 dshift.append(self.imageShift[idx] - self.signalShift[idx])
00300
00301 shiftdata = numpy.zeros(data.shape, numpy.float)
00302 for i in range(len(data)):
00303 shiftdata[i] = self._shiftSpectrum(data[i], tempshift[i])
00304 ifftdata = self._Deconvolution(shiftdata, dshift, self.rejlimit)
00305 result_image = self._combineResult(ifftdata)
00306 if not self.solveother:
00307 return result_image
00308 result_signal = self._subtractOtherSide(shiftdata, dshift, result_image)
00309 return result_signal
00310
00311
00312 def _solve_image(self, data, tabidx=None):
00313 if not tabidx:
00314 tabidx = range(len(data))
00315
00316 tempshift = []
00317 dshift = []
00318 if self.solveother:
00319 for idx in tabidx:
00320 tempshift.append(-self.signalShift[idx])
00321 dshift.append(self.imageShift[idx] - self.signalShift[idx])
00322 else:
00323 for idx in tabidx:
00324 tempshift.append(-self.imageShift[idx])
00325 dshift.append(self.signalShift[idx] - self.imageShift[idx])
00326
00327 shiftdata = numpy.zeros(data.shape, numpy.float)
00328 for i in range(len(data)):
00329 shiftdata[i] = self._shiftSpectrum(data[i], tempshift[i])
00330 ifftdata = self._Deconvolution(shiftdata, dshift, self.rejlimit)
00331 result_image = self._combineResult(ifftdata)
00332 if not self.solveother:
00333 return result_image
00334 result_signal = self._subtractOtherSide(shiftdata, dshift, result_image)
00335 return result_signal
00336
00337 @asaplog_post_dec
00338 def _grid_outtable(self, table):
00339
00340 gridder = asapgrid2(table)
00341 gridder.setIF(self.baseif)
00342
00343 cellx = str(self.dirtol[0])+"rad"
00344 celly = str(self.dirtol[1])+"rad"
00345 dirarr = numpy.array(table.get_directionval()).transpose()
00346 mapx = dirarr[0].max() - dirarr[0].min()
00347 mapy = dirarr[1].max() - dirarr[1].min()
00348 nx = max(1, numpy.ceil(mapx/self.dirtol[0]))
00349 ny = max(1, numpy.ceil(mapy/self.dirtol[0]))
00350
00351 asaplog.push("Regrid output scantable with cell = [%s, %s]" % \
00352 (cellx, celly))
00353 gridder.defineImage(nx=nx, ny=ny, cellx=cellx, celly=celly)
00354 gridder.setFunc(func='box', width=1)
00355 gridder.setWeight(weightType='uniform')
00356 gridder.grid()
00357 return gridder.getResult()
00358
00359
00360 @asaplog_post_dec
00361 def _get_specarray(self, polid=None, beamid=None, dir=None):
00362 ntable = len(self.tables)
00363 spec_array = numpy.zeros((ntable, self.nchan), numpy.float)
00364 nspec = 0
00365 asaplog.push("Start data selection by POL=%d, BEAM=%d, direction=[%f, %f]" % (polid, beamid, dir[0], dir[1]))
00366 tabidx = []
00367 for itab in range(ntable):
00368 tab = self.tables[itab]
00369
00370 try:
00371 tab.set_selection(pols=[polid], beams=[beamid])
00372 if tab.nrow() > 0: tabidx.append(itab)
00373 except:
00374 asaplog.post()
00375 asaplog.push("table %d - No spectrum ....skipping the table" % (itab))
00376 asaplog.post("WARN")
00377 continue
00378
00379
00380 spec = numpy.zeros(self.nchan, numpy.float)
00381 selrow = []
00382 for irow in range(tab.nrow()):
00383 currdir = tab.get_directionval(irow)
00384 if (abs(currdir[0] - dir[0]) > self.dirtol[0]) or \
00385 (abs(currdir[1] - dir[1]) > self.dirtol[1]):
00386 continue
00387 selrow.append(irow)
00388 if len(selrow) == 0:
00389 asaplog.post()
00390 asaplog.push("table %d - No spectrum ....skipping the table" % (itab))
00391 asaplog.post("WARN")
00392 continue
00393 else:
00394 seltab = tab.copy()
00395 seltab.set_selection(selector(rows=selrow))
00396
00397 if tab.nrow() > 1:
00398 asaplog.push("table %d - More than a spectrum selected. averaging rows..." % (itab))
00399 tab = seltab.average_time(scanav=False, weight="tintsys")
00400 else:
00401 tab = seltab
00402
00403 spec_array[nspec] = tab._getspectrum()
00404 nspec += 1
00405
00406 if nspec != ntable:
00407 asaplog.post()
00408
00409 asaplog.push("Could not find corresponding rows in some tables.")
00410 asaplog.push("Number of spectra selected = %d (table: %d)" % (nspec, ntable))
00411 if nspec < 2:
00412 asaplog.push("At least 2 spectra are necessary for convolution")
00413 asaplog.post("ERROR")
00414 return False, tabidx
00415
00416 return spec_array[0:nspec], tabidx
00417
00418
00419 @asaplog_post_dec
00420 def _setup_shift(self):
00421
00422 if not self.intables:
00423 asaplog.post()
00424 raise RuntimeError, "Input data is not defined."
00425
00426
00427
00428
00429 byname = False
00430
00431 if isinstance(self.intables[0], str):
00432
00433 if not os.path.exists(self.intables[0]):
00434 asaplog.post()
00435 raise RuntimeError, "Could not find '%s'" % self.intables[0]
00436
00437 stab = scantable(self.intables[0],average=False)
00438 ntab = len(self.intables)
00439 byname = True
00440 else:
00441 stab = self.intables[0]
00442 ntab = len(self.intables)
00443
00444 if len(stab.getbeamnos()) > 1:
00445 asaplog.post()
00446 asaplog.push("Mult-beam data is not supported by this module.")
00447 asaplog.post("ERROR")
00448 return
00449
00450 valid_ifs = stab.getifnos()
00451 if self.baseif < 0:
00452 self.baseif = valid_ifs[0]
00453 asaplog.post()
00454 asaplog.push("IFNO is not selected. Using the first IF in the first scantable. Reference IFNO = %d" % (self.baseif))
00455
00456 if not (self.baseif in valid_ifs):
00457 asaplog.post()
00458 errmsg = "IF%d does not exist in the first scantable" % \
00459 self.baseif
00460 raise RuntimeError, errmsg
00461
00462 asaplog.push("Start selecting tables and IFNOs to solve.")
00463 asaplog.push("Cheching frequency of the reference IF")
00464 unit_org = stab.get_unit()
00465 coord = stab._getcoordinfo()
00466 frame_org = coord[1]
00467 stab.set_unit("Hz")
00468 if len(self.freqframe) > 0:
00469 stab.set_freqframe(self.freqframe)
00470 stab.set_selection(ifs=[self.baseif])
00471 spx = stab._getabcissa()
00472 stab.set_selection()
00473 basech0 = spx[0]
00474 baseinc = spx[1]-spx[0]
00475 self.nchan = len(spx)
00476 if isinstance(self.freqtol, float):
00477 vftol = abs(baseinc * self.freqtol)
00478 self.freqtol = dict(value=vftol, unit="Hz")
00479 else:
00480 vftol = abs(self.freqtol['value'])
00481 inctol = abs(baseinc/float(self.nchan))
00482 asaplog.push("Reference frequency setup (Table = 0, IFNO = %d): nchan = %d, chan0 = %f Hz, incr = %f Hz" % (self.baseif, self.nchan, basech0, baseinc))
00483 asaplog.push("Allowed frequency tolerance = %f Hz ( %f channels)" % (vftol, vftol/baseinc))
00484 poltype0 = stab.poltype()
00485
00486 self.tables = []
00487 self.signalShift = []
00488 if self.dsbmode:
00489 self.imageShift = []
00490
00491 for itab in range(ntab):
00492 asaplog.push("Table %d:" % itab)
00493 tab_selected = False
00494 if itab > 0:
00495 if byname:
00496 stab = scantable(self.intables[itab],average=False)
00497 self.intables.append(stab)
00498 else:
00499 stab = self.intables[itab]
00500 unit_org = stab.get_unit()
00501 coord = stab._getcoordinfo()
00502 frame_org = coord[1]
00503 stab.set_unit("Hz")
00504 if len(self.freqframe) > 0:
00505 stab.set_freqframe(self.freqframe)
00506
00507
00508 if stab.poltype() != poltype0:
00509 asaplog.post()
00510 raise Exception, "POLTYPE should be equal to the first table."
00511
00512 if len(stab.getbeamnos()) > 1:
00513 asaplog.post()
00514 asaplog.push("table contains multiple beams. It may not be handled properly.")
00515 asaplog.push("WARN")
00516
00517 for ifno in stab.getifnos():
00518 stab.set_selection(ifs=[ifno])
00519 spx = stab._getabcissa()
00520 if (len(spx) != self.nchan) or \
00521 (abs(spx[0]-basech0) > vftol) or \
00522 (abs(spx[1]-spx[0]-baseinc) > inctol):
00523 continue
00524 tab_selected = True
00525 seltab = stab.copy()
00526 seltab.set_unit(unit_org)
00527 seltab.set_freqframe(frame_org)
00528 self.tables.append(seltab)
00529 self.signalShift.append((spx[0]-basech0)/baseinc)
00530 if self.dsbmode:
00531 self.imageShift.append(-self.signalShift[-1])
00532 asaplog.push("- IF%d selected: sideband shift = %16.12e channels" % (ifno, self.signalShift[-1]))
00533 stab.set_selection()
00534 stab.set_unit(unit_org)
00535 stab.set_freqframe(frame_org)
00536 if not tab_selected:
00537 asaplog.post()
00538 asaplog.push("No data selected in Table %d" % itab)
00539 asaplog.post("WARN")
00540
00541 asaplog.push("Total number of IFs selected = %d" % len(self.tables))
00542 if len(self.tables) < 2:
00543 asaplog.post()
00544 raise RuntimeError, "At least 2 IFs are necessary for convolution!"
00545
00546 if not self.dsbmode and len(self.imageShift) != len(self.signalShift):
00547 asaplog.post()
00548 errmsg = "User defined channel shift of image sideband has %d elements, while selected IFNOs are %d" % (len(self.imageShift), len(self.signalShift))
00549 raise RuntimeError, errmsg
00550
00551 self.signalShift = numpy.array(self.signalShift)
00552 self.imageShift = numpy.array(self.imageShift)
00553 self.nshift = len(self.tables)
00554
00555 @asaplog_post_dec
00556 def _preprocess_tables(self):
00557
00558
00559 for itab in range(len(self.tables)):
00560 self.tables[itab] = self.tables[itab].average_time(scanav=False, weight="tintsys")
00561
00562
00563
00564
00565
00566
00567
00568
00569
00570
00571
00572
00573
00574
00575
00576
00577
00578
00579 def _Deconvolution(self, data_array, shift, threshold=0.00000001):
00580 FObs = []
00581 Reject = 0
00582 nshift, nchan = data_array.shape
00583 nspec = nshift*(nshift-1)/2
00584 ifftObs = numpy.zeros((nspec, nchan), numpy.float)
00585 for i in range(nshift):
00586 F = FFT.fft(data_array[i])
00587 FObs.append(F)
00588 z = 0
00589 for i in range(nshift):
00590 for j in range(i+1, nshift):
00591 Fobs = (FObs[i]+FObs[j])/2.0
00592 dX = (shift[j]-shift[i])*2.0*math.pi/float(self.nchan)
00593
00594 for k in range(1,self.nchan):
00595 if math.fabs(math.sin(dX*k)) > threshold:
00596 Fobs[k] += ((FObs[i][k]-FObs[j][k])/2.0/(1.0-math.cos(dX*k))*math.sin(dX*k))*1.0j
00597 else: Reject += 1
00598 ifftObs[z] = FFT.ifft(Fobs)
00599 z += 1
00600 print 'Threshold=%s Reject=%d' % (threshold, Reject)
00601 return ifftObs
00602
00603 def _combineResult(self, ifftObs):
00604 nspec = len(ifftObs)
00605 sum = ifftObs[0]
00606 for i in range(1,nspec):
00607 sum += ifftObs[i]
00608 return(sum/float(nspec))
00609
00610 def _subtractOtherSide(self, data_array, shift, Data):
00611 sum = numpy.zeros(len(Data), numpy.float)
00612 numSP = len(data_array)
00613 for i in range(numSP):
00614 SPsub = data_array[i] - Data
00615 sum += self._shiftSpectrum(SPsub, -shift[i])
00616 return(sum/float(numSP))
00617
00618 def _shiftSpectrum(self, data, Shift):
00619 Out = numpy.zeros(self.nchan, numpy.float)
00620 w2 = Shift % 1
00621 w1 = 1.0 - w2
00622 for i in range(self.nchan):
00623 c1 = int((Shift + i) % self.nchan)
00624 c2 = (c1 + 1) % self.nchan
00625 Out[c1] += data[i] * w1
00626 Out[c2] += data[i] * w2
00627 return Out.copy()