source: trunk/python/sbseparator.py@ 2681

Last change on this file since 2681 was 2649, checked in by Kana Sugimoto, 12 years ago

New Development: No

JIRA Issue: Yes (CAS-4141/CSV-1526)

Ready for Test: Yes (* Fix for CASA 4.0 *)

Interface Changes: No

What Interface Changed:

Test Programs:

Put in Release Notes: No

Module(s):

Description:

For got to commit build and import code of sbseparator module.
Added known issues and warning messages.

File size: 23.0 KB
Line 
1import os, shutil
2import numpy
3import numpy.fft as FFT
4import math
5
6from asap.scantable import scantable
7from asap.parameters import rcParams
8from asap.logging import asaplog, asaplog_post_dec
9from asap.selector import selector
10from asap.asapgrid import asapgrid2
11#from asap._asap import sidebandsep
12
13class sbseparator:
14 """
15 The sbseparator class is defined to separate SIGNAL and IMAGE
16 sideband spectra observed by frequency-switching technique.
17 It also helps supressing emmission of IMAGE sideband.
18 *** WARNING *** THIS MODULE IS EXPERIMENTAL
19 Known issues:
20 - Frequencies of IMAGE sideband cannot be reconstructed from
21 information in scantable in sideband sparation. Frequency
22 setting of SIGNAL sideband is stored in output table for now.
23 - Flag information (stored in FLAGTRA) is ignored.
24
25 Example:
26 # Create sideband separator instance whith 3 input data
27 sbsep = sbseparator(['test1.asap', 'test2.asap', 'test3.asap'])
28 # Set reference IFNO and tolerance to select data
29 sbsep.set_frequency(5, 30, frame='TOPO')
30 # Set direction tolerance to select data in unit of radian
31 sbsep.set_dirtol(1.e-5)
32 # Set rejection limit of solution
33 sbsep.set_limit(0.2)
34 # Solve image sideband as well
35 sbsep.set_both(True)
36 # Invoke sideband separation
37 sbsep.separate('testout.asap', overwrite = True)
38 """
39 def __init__(self, infiles):
40 self.intables = None
41 self.signalShift = []
42 self.imageShift = []
43 self.dsbmode = True
44 self.getboth = False
45 self.rejlimit = 0.2
46 self.baseif = -1
47 self.freqtol = 10.
48 self.freqframe = ""
49 self.solveother = False
50 self.dirtol = [1.e-5, 1.e-5] # direction tolerance in rad (2 arcsec)
51
52 self.tables = []
53 self.nshift = -1
54 self.nchan = -1
55
56 self.set_data(infiles)
57
58 #self.separator = sidebandsep()
59
60 @asaplog_post_dec
61 def set_data(self, infiles):
62 """
63 Set data to be processed.
64
65 infiles : a list of filenames or scantables
66 """
67 if not (type(infiles) in (list, tuple, numpy.ndarray)):
68 infiles = [infiles]
69 if isinstance(infiles[0], scantable):
70 # a list of scantable
71 for stab in infiles:
72 if not isinstance(stab, scantable):
73 asaplog.post()
74 raise TypeError, "Input data is not a list of scantables."
75
76 #self.separator._setdata(infiles)
77 self._reset_data()
78 self.intables = infiles
79 else:
80 # a list of filenames
81 for name in infiles:
82 if not os.path.exists(name):
83 asaplog.post()
84 raise ValueError, "Could not find input file '%s'" % name
85
86 #self.separator._setdataname(infiles)
87 self._reset_data()
88 self.intables = infiles
89
90 asaplog.push("%d files are set to process" % len(self.intables))
91
92
93 def _reset_data(self):
94 del self.intables
95 self.intables = None
96 self.signalShift = []
97 #self.imageShift = []
98 self.tables = []
99 self.nshift = -1
100 self.nchan = -1
101
102 @asaplog_post_dec
103 def set_frequency(self, baseif, freqtol, frame=""):
104 """
105 Set IFNO and frequency tolerance to select data to process.
106
107 Parameters:
108 - reference IFNO to process in the first table in the list
109 - frequency tolerance from reference IF to select data
110 frame : frequency frame to select IF
111 """
112 self._reset_if()
113 self.baseif = baseif
114 if isinstance(freqtol,dict) and freqtol["unit"] == "Hz":
115 if freqtol['value'] > 0.:
116 self.freqtol = freqtol
117 else:
118 asaplog.post()
119 asaplog.push("Frequency tolerance should be positive value.")
120 asaplog.post("ERROR")
121 return
122 else:
123 # torelance in channel unit
124 if freqtol > 0:
125 self.freqtol = float(freqtol)
126 else:
127 asaplog.post()
128 asaplog.push("Frequency tolerance should be positive value.")
129 asaplog.post("ERROR")
130 return
131 self.freqframe = frame
132
133 def _reset_if(self):
134 self.baseif = -1
135 self.freqtol = 0
136 self.freqframe = ""
137 self.signalShift = []
138 #self.imageShift = []
139 self.tables = []
140 self.nshift = 0
141 self.nchan = -1
142
143 @asaplog_post_dec
144 def set_dirtol(self, dirtol=[1.e-5,1.e-5]):
145 """
146 Set tolerance of direction to select data
147 """
148 # direction tolerance in rad
149 if not (type(dirtol) in [list, tuple, numpy.ndarray]):
150 dirtol = [dirtol, dirtol]
151 if len(dirtol) == 1:
152 dirtol = [dirtol[0], dirtol[0]]
153 if len(dirtol) > 1:
154 self.dirtol = dirtol[0:2]
155 else:
156 asaplog.post()
157 asaplog.push("Invalid direction tolerance. Should be a list of float in unit radian")
158 asaplog.post("ERROR")
159 return
160 asaplog.post("Set direction tolerance [%f, %f] (rad)" % \
161 (self.dirtol[0], self.dirtol[1]))
162
163 @asaplog_post_dec
164 def set_shift(self, mode="DSB", imageshift=None):
165 """
166 Set shift mode and channel shift of image band.
167
168 mode : shift mode ['DSB'|'SSB']
169 When mode='DSB', imageshift is assumed to be equal
170 to the shift of signal sideband but in opposite direction.
171 imageshift : a list of number of channel shift in image band of
172 each scantable. valid only mode='SSB'
173 """
174 if mode.upper().startswith("S"):
175 if not imageshift:
176 raise ValueError, "Need to set shift value of image sideband"
177 self.dsbmode = False
178 self.imageShift = imageshift
179 asaplog.push("Image sideband shift is set manually: %s" % str(self.imageShift))
180 else:
181 # DSB mode
182 self.dsbmode = True
183 self.imageShift = []
184
185 @asaplog_post_dec
186 def set_both(self, flag=False):
187 """
188 Resolve both image and signal sideband when True.
189 """
190 self.getboth = flag
191 if self.getboth:
192 asaplog.push("Both signal and image sidebands are solved and output as separate tables.")
193 else:
194 asaplog.push("Only signal sideband is solved and output as an table.")
195
196 @asaplog_post_dec
197 def set_limit(self, threshold=0.2):
198 """
199 Set rejection limit of solution.
200 """
201 #self.separator._setlimit(abs(threshold))
202 self.rejlimit = threshold
203 asaplog.push("The threshold of rejection is set to %f" % self.rejlimit)
204
205
206 @asaplog_post_dec
207 def set_solve_other(self, flag=False):
208 """
209 Calculate spectra by subtracting the solution of the other sideband
210 when True.
211 """
212 self.solveother = flag
213 if flag:
214 asaplog.push("Expert mode: solution are obtained by subtraction of the other sideband.")
215
216
217 @asaplog_post_dec
218 def separate(self, outname="", overwrite=False):
219 """
220 Invoke sideband separation.
221
222 outname : a name of output scantable
223 overwrite : overwrite existing table
224 """
225 # List up valid scantables and IFNOs to convolve.
226 #self.separator._separate()
227 self._setup_shift()
228 #self._preprocess_tables()
229
230 nshift = len(self.tables)
231 signaltab = self._grid_outtable(self.tables[0].copy())
232 if self.getboth:
233 imagetab = signaltab.copy()
234
235 rejrow = []
236 for irow in xrange(signaltab.nrow()):
237 currpol = signaltab.getpol(irow)
238 currbeam = signaltab.getbeam(irow)
239 currdir = signaltab.get_directionval(irow)
240 spec_array, tabidx = self._get_specarray(polid=currpol,\
241 beamid=currbeam,\
242 dir=currdir)
243 #if not spec_array:
244 if len(tabidx) == 0:
245 asaplog.post()
246 asaplog.push("skipping row %d" % irow)
247 rejrow.append(irow)
248 continue
249 signal = self._solve_signal(spec_array, tabidx)
250 signaltab.set_spectrum(signal, irow)
251 if self.getboth:
252 image = self._solve_image(spec_array, tabidx)
253 imagetab.set_spectrum(image, irow)
254
255 # TODO: Need to remove rejrow form scantables here
256 signaltab.flag_row(rejrow)
257 if self.getboth:
258 imagetab.flag_row(rejrow)
259
260 if outname == "":
261 outname = "sbsepareted.asap"
262 signalname = outname + ".signalband"
263 if os.path.exists(signalname):
264 if not overwrite:
265 raise Exception, "Output file '%s' exists." % signalname
266 else:
267 shutil.rmtree(signalname)
268 signaltab.save(signalname)
269 if self.getboth:
270 # Warnings
271 asaplog.post()
272 asaplog.push("Saving IMAGE sideband.")
273 asaplog.push("Note, frequency information of IMAGE sideband cannot be properly filled so far. (future development)")
274 asaplog.push("Storing frequency setting of SIGNAL sideband in FREQUENCIES table for now.")
275 asaplog.post("WARN")
276
277 imagename = outname + ".imageband"
278 if os.path.exists(imagename):
279 if not overwrite:
280 raise Exception, "Output file '%s' exists." % imagename
281 else:
282 shutil.rmtree(imagename)
283 imagetab.save(imagename)
284
285
286 def _solve_signal(self, data, tabidx=None):
287 if not tabidx:
288 tabidx = range(len(data))
289
290 tempshift = []
291 dshift = []
292 if self.solveother:
293 for idx in tabidx:
294 tempshift.append(-self.imageShift[idx])
295 dshift.append(self.signalShift[idx] - self.imageShift[idx])
296 else:
297 for idx in tabidx:
298 tempshift.append(-self.signalShift[idx])
299 dshift.append(self.imageShift[idx] - self.signalShift[idx])
300
301 shiftdata = numpy.zeros(data.shape, numpy.float)
302 for i in range(len(data)):
303 shiftdata[i] = self._shiftSpectrum(data[i], tempshift[i])
304 ifftdata = self._Deconvolution(shiftdata, dshift, self.rejlimit)
305 result_image = self._combineResult(ifftdata)
306 if not self.solveother:
307 return result_image
308 result_signal = self._subtractOtherSide(shiftdata, dshift, result_image)
309 return result_signal
310
311
312 def _solve_image(self, data, tabidx=None):
313 if not tabidx:
314 tabidx = range(len(data))
315
316 tempshift = []
317 dshift = []
318 if self.solveother:
319 for idx in tabidx:
320 tempshift.append(-self.signalShift[idx])
321 dshift.append(self.imageShift[idx] - self.signalShift[idx])
322 else:
323 for idx in tabidx:
324 tempshift.append(-self.imageShift[idx])
325 dshift.append(self.signalShift[idx] - self.imageShift[idx])
326
327 shiftdata = numpy.zeros(data.shape, numpy.float)
328 for i in range(len(data)):
329 shiftdata[i] = self._shiftSpectrum(data[i], tempshift[i])
330 ifftdata = self._Deconvolution(shiftdata, dshift, self.rejlimit)
331 result_image = self._combineResult(ifftdata)
332 if not self.solveother:
333 return result_image
334 result_signal = self._subtractOtherSide(shiftdata, dshift, result_image)
335 return result_signal
336
337 @asaplog_post_dec
338 def _grid_outtable(self, table):
339 # Generate gridded table for output (Just to get rows)
340 gridder = asapgrid2(table)
341 gridder.setIF(self.baseif)
342
343 cellx = str(self.dirtol[0])+"rad"
344 celly = str(self.dirtol[1])+"rad"
345 dirarr = numpy.array(table.get_directionval()).transpose()
346 mapx = dirarr[0].max() - dirarr[0].min()
347 mapy = dirarr[1].max() - dirarr[1].min()
348 nx = max(1, numpy.ceil(mapx/self.dirtol[0]))
349 ny = max(1, numpy.ceil(mapy/self.dirtol[0]))
350
351 asaplog.push("Regrid output scantable with cell = [%s, %s]" % \
352 (cellx, celly))
353 gridder.defineImage(nx=nx, ny=ny, cellx=cellx, celly=celly)
354 gridder.setFunc(func='box', width=1)
355 gridder.setWeight(weightType='uniform')
356 gridder.grid()
357 return gridder.getResult()
358
359
360 @asaplog_post_dec
361 def _get_specarray(self, polid=None, beamid=None, dir=None):
362 ntable = len(self.tables)
363 spec_array = numpy.zeros((ntable, self.nchan), numpy.float)
364 nspec = 0
365 asaplog.push("Start data selection by POL=%d, BEAM=%d, direction=[%f, %f]" % (polid, beamid, dir[0], dir[1]))
366 tabidx = []
367 for itab in range(ntable):
368 tab = self.tables[itab]
369 # Select rows by POLNO and BEAMNO
370 try:
371 tab.set_selection(pols=[polid], beams=[beamid])
372 if tab.nrow() > 0: tabidx.append(itab)
373 except: # no selection
374 asaplog.post()
375 asaplog.push("table %d - No spectrum ....skipping the table" % (itab))
376 asaplog.post("WARN")
377 continue
378
379 # Select rows by direction
380 spec = numpy.zeros(self.nchan, numpy.float)
381 selrow = []
382 for irow in range(tab.nrow()):
383 currdir = tab.get_directionval(irow)
384 if (abs(currdir[0] - dir[0]) > self.dirtol[0]) or \
385 (abs(currdir[1] - dir[1]) > self.dirtol[1]):
386 continue
387 selrow.append(irow)
388 if len(selrow) == 0:
389 asaplog.post()
390 asaplog.push("table %d - No spectrum ....skipping the table" % (itab))
391 asaplog.post("WARN")
392 continue
393 else:
394 seltab = tab.copy()
395 seltab.set_selection(selector(rows=selrow))
396
397 if tab.nrow() > 1:
398 asaplog.push("table %d - More than a spectrum selected. averaging rows..." % (itab))
399 tab = seltab.average_time(scanav=False, weight="tintsys")
400 else:
401 tab = seltab
402
403 spec_array[nspec] = tab._getspectrum()
404 nspec += 1
405
406 if nspec != ntable:
407 asaplog.post()
408 #asaplog.push("Some tables has no spectrum with POL=%d BEAM=%d. averaging rows..." % (polid, beamid))
409 asaplog.push("Could not find corresponding rows in some tables.")
410 asaplog.push("Number of spectra selected = %d (table: %d)" % (nspec, ntable))
411 if nspec < 2:
412 asaplog.push("At least 2 spectra are necessary for convolution")
413 asaplog.post("ERROR")
414 return False, tabidx
415
416 return spec_array[0:nspec], tabidx
417
418
419 @asaplog_post_dec
420 def _setup_shift(self):
421 ### define self.tables, self.signalShift, and self.imageShift
422 if not self.intables:
423 asaplog.post()
424 raise RuntimeError, "Input data is not defined."
425 #if self.baseif < 0:
426 # asaplog.post()
427 # raise RuntimeError, "Reference IFNO is not defined."
428
429 byname = False
430 #if not self.intables:
431 if isinstance(self.intables[0], str):
432 # A list of file name is given
433 if not os.path.exists(self.intables[0]):
434 asaplog.post()
435 raise RuntimeError, "Could not find '%s'" % self.intables[0]
436
437 stab = scantable(self.intables[0],average=False)
438 ntab = len(self.intables)
439 byname = True
440 else:
441 stab = self.intables[0]
442 ntab = len(self.intables)
443
444 if len(stab.getbeamnos()) > 1:
445 asaplog.post()
446 asaplog.push("Mult-beam data is not supported by this module.")
447 asaplog.post("ERROR")
448 return
449
450 valid_ifs = stab.getifnos()
451 if self.baseif < 0:
452 self.baseif = valid_ifs[0]
453 asaplog.post()
454 asaplog.push("IFNO is not selected. Using the first IF in the first scantable. Reference IFNO = %d" % (self.baseif))
455
456 if not (self.baseif in valid_ifs):
457 asaplog.post()
458 errmsg = "IF%d does not exist in the first scantable" % \
459 self.baseif
460 raise RuntimeError, errmsg
461
462 asaplog.push("Start selecting tables and IFNOs to solve.")
463 asaplog.push("Cheching frequency of the reference IF")
464 unit_org = stab.get_unit()
465 coord = stab._getcoordinfo()
466 frame_org = coord[1]
467 stab.set_unit("Hz")
468 if len(self.freqframe) > 0:
469 stab.set_freqframe(self.freqframe)
470 stab.set_selection(ifs=[self.baseif])
471 spx = stab._getabcissa()
472 stab.set_selection()
473 basech0 = spx[0]
474 baseinc = spx[1]-spx[0]
475 self.nchan = len(spx)
476 if isinstance(self.freqtol, float):
477 vftol = abs(baseinc * self.freqtol)
478 self.freqtol = dict(value=vftol, unit="Hz")
479 else:
480 vftol = abs(self.freqtol['value'])
481 inctol = abs(baseinc/float(self.nchan))
482 asaplog.push("Reference frequency setup (Table = 0, IFNO = %d): nchan = %d, chan0 = %f Hz, incr = %f Hz" % (self.baseif, self.nchan, basech0, baseinc))
483 asaplog.push("Allowed frequency tolerance = %f Hz ( %f channels)" % (vftol, vftol/baseinc))
484 poltype0 = stab.poltype()
485
486 self.tables = []
487 self.signalShift = []
488 if self.dsbmode:
489 self.imageShift = []
490
491 for itab in range(ntab):
492 asaplog.push("Table %d:" % itab)
493 tab_selected = False
494 if itab > 0:
495 if byname:
496 stab = scantable(self.intables[itab],average=False)
497 self.intables.append(stab)
498 else:
499 stab = self.intables[itab]
500 unit_org = stab.get_unit()
501 coord = stab._getcoordinfo()
502 frame_org = coord[1]
503 stab.set_unit("Hz")
504 if len(self.freqframe) > 0:
505 stab.set_freqframe(self.freqframe)
506
507 # Check POLTYPE should be equal to the first table.
508 if stab.poltype() != poltype0:
509 asaplog.post()
510 raise Exception, "POLTYPE should be equal to the first table."
511 # Multiple beam data may not handled properly
512 if len(stab.getbeamnos()) > 1:
513 asaplog.post()
514 asaplog.push("table contains multiple beams. It may not be handled properly.")
515 asaplog.push("WARN")
516
517 for ifno in stab.getifnos():
518 stab.set_selection(ifs=[ifno])
519 spx = stab._getabcissa()
520 if (len(spx) != self.nchan) or \
521 (abs(spx[0]-basech0) > vftol) or \
522 (abs(spx[1]-spx[0]-baseinc) > inctol):
523 continue
524 tab_selected = True
525 seltab = stab.copy()
526 seltab.set_unit(unit_org)
527 seltab.set_freqframe(frame_org)
528 self.tables.append(seltab)
529 self.signalShift.append((spx[0]-basech0)/baseinc)
530 if self.dsbmode:
531 self.imageShift.append(-self.signalShift[-1])
532 asaplog.push("- IF%d selected: sideband shift = %16.12e channels" % (ifno, self.signalShift[-1]))
533 stab.set_selection()
534 stab.set_unit(unit_org)
535 stab.set_freqframe(frame_org)
536 if not tab_selected:
537 asaplog.post()
538 asaplog.push("No data selected in Table %d" % itab)
539 asaplog.post("WARN")
540
541 asaplog.push("Total number of IFs selected = %d" % len(self.tables))
542 if len(self.tables) < 2:
543 asaplog.post()
544 raise RuntimeError, "At least 2 IFs are necessary for convolution!"
545
546 if not self.dsbmode and len(self.imageShift) != len(self.signalShift):
547 asaplog.post()
548 errmsg = "User defined channel shift of image sideband has %d elements, while selected IFNOs are %d" % (len(self.imageShift), len(self.signalShift))
549 raise RuntimeError, errmsg
550
551 self.signalShift = numpy.array(self.signalShift)
552 self.imageShift = numpy.array(self.imageShift)
553 self.nshift = len(self.tables)
554
555 @asaplog_post_dec
556 def _preprocess_tables(self):
557 ### temporary method to preprocess data
558 ### Do time averaging for now.
559 for itab in range(len(self.tables)):
560 self.tables[itab] = self.tables[itab].average_time(scanav=False, weight="tintsys")
561
562
563# def save(self, outfile, outform="ASAP", overwrite=False):
564# if not overwrite and os.path.exists(outfile):
565# raise RuntimeError, "Output file '%s' already exists" % outfile
566#
567# #self.separator._save(outfile, outform)
568
569# def done(self):
570# self.close()
571
572# def close(self):
573# pass
574# #del self.separator
575
576
577
578########################################################################
579 def _Deconvolution(self, data_array, shift, threshold=0.00000001):
580 FObs = []
581 Reject = 0
582 nshift, nchan = data_array.shape
583 nspec = nshift*(nshift-1)/2
584 ifftObs = numpy.zeros((nspec, nchan), numpy.float)
585 for i in range(nshift):
586 F = FFT.fft(data_array[i])
587 FObs.append(F)
588 z = 0
589 for i in range(nshift):
590 for j in range(i+1, nshift):
591 Fobs = (FObs[i]+FObs[j])/2.0
592 dX = (shift[j]-shift[i])*2.0*math.pi/float(self.nchan)
593 #print 'dX,i,j=',dX,i,j
594 for k in range(1,self.nchan):
595 if math.fabs(math.sin(dX*k)) > threshold:
596 Fobs[k] += ((FObs[i][k]-FObs[j][k])/2.0/(1.0-math.cos(dX*k))*math.sin(dX*k))*1.0j
597 else: Reject += 1
598 ifftObs[z] = FFT.ifft(Fobs)
599 z += 1
600 print 'Threshold=%s Reject=%d' % (threshold, Reject)
601 return ifftObs
602
603 def _combineResult(self, ifftObs):
604 nspec = len(ifftObs)
605 sum = ifftObs[0]
606 for i in range(1,nspec):
607 sum += ifftObs[i]
608 return(sum/float(nspec))
609
610 def _subtractOtherSide(self, data_array, shift, Data):
611 sum = numpy.zeros(len(Data), numpy.float)
612 numSP = len(data_array)
613 for i in range(numSP):
614 SPsub = data_array[i] - Data
615 sum += self._shiftSpectrum(SPsub, -shift[i])
616 return(sum/float(numSP))
617
618 def _shiftSpectrum(self, data, Shift):
619 Out = numpy.zeros(self.nchan, numpy.float)
620 w2 = Shift % 1
621 w1 = 1.0 - w2
622 for i in range(self.nchan):
623 c1 = int((Shift + i) % self.nchan)
624 c2 = (c1 + 1) % self.nchan
625 Out[c1] += data[i] * w1
626 Out[c2] += data[i] * w2
627 return Out.copy()
Note: See TracBrowser for help on using the repository browser.