source: trunk/python/sbseparator.py@ 2708

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

New Development: Yes

JIRA Issue: Yes (CAS-4141)

Ready for Test: Yes

Interface Changes: Yes

What Interface Changed:

  • a new c++ class, STSideBandSep and its python interface.
  • a new method set_lo1 in the sbseparator module.

Test Programs: manual tests by CSV scientists.

Put in Release Notes: No

Module(s): STSidebandSep (a new class) and sbseparator

Description:

Added a new c++ class, STSideBandSep, and its python interface (python_STSideBandSep.cpp).
The class works on calculating frequency information of image side band and fill scantable.
Also added a new python method, set_lo1(double), in sbseparator module. This is to allow
user to set LO1 frequency to solve frequencies of image side band manually.


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